Посоветуйте по структуре приложения на php laravel 5

alonecat
Дата: 06.08.2017 16:48:19
Добрый день! Учусь ларавель, делаем тестовый проект типа облачного хостинга файлов, который возможно станет реальным.

На данный момент все в стадии развернули ларавель, система авторизации (ларавельная) с активацией и сменой мейла (своими), также загрузка и вывод аватара (свои на основе Mediable). Далее в планах подключения биллинга и работа с файлами пользователей.

Все, что сейчас сделано работает. Но хотелось бы советов, по оптимальному проектированию классов.

Для начала примерно показываю что сделал.

Для авторизации и смены мейла написал похожие друг на друга сервисы. И сделал для них интерфейсы и зарегил сервис провайдер. Они включают в себя методы для обработки как запроса на отсылку письма с подтвержением регистрации/нового мейла, так и обработку клика по ссылке из данных писем.

Токены подтвержения регистрации и смены мейла и сам новый мейл, храню в двух отдельных табличках, не в users. И для каждой этой табличке по модели элоквент и по репозиторию.

Привожу код для случая смены мейла:

Роуты

    Route::get('home/account/email', ['middleware' => ['auth', 'isVerified'], 'uses' => 'Auth\ChangeEmailController@showForm'])->name('home.account.email');
    
    Route::post('home/account/email_save', ['middleware' => ['auth', 'isVerified'], 'uses' => 'Auth\ChangeEmailController@saveForm'])->name('home.account.email_save');
    
    Route::get('home/account/email_set/{token}', ['middleware' => ['auth', 'isVerified'], 'uses' => 'Auth\ChangeEmailController@emailSet'])->name('home.account.email_set');


Методы контроллера запроса на смену и смены по ссылке из письма


   

     public function saveForm(Request $request, ChangeEmailContract $changeEmailService)
        {
            $user = Auth::user();
            
            $rules = [
                'email' => 'required|email|unique:users',
                'password' => 'required|checkpassword:'.$user->email,
            ];
            
            $messages = [
                'email.required' => 'Please enter an email address',
                'email.email' => 'Please enter a valid email address',
                'email.unique' => 'This e-mail is already taken. ',
                'password.required' => 'Please enter your password',
                'password.checkpassword' => 'Your enter wrong password',
                
            ];
            
            
            Validator::make($request::all(), $rules, $messages)->validate();
            
            $changeEmailService->sendChangeEmailMail($user, Request::get('email'));
            
            return redirect()->route('home')->with('status', "Confirmation change E-mail link send to ".Request::get('email'));
        }
        
        public function emailSet($token, ChangeEmailContract $changeEmailService)
        {
            $email = Request::get('email');
            
            try {
               $user = $changeEmailService->setEmail($token, $email);
            }
            catch (\App\Exceptions\ChangeEmailNotFoundException $e) {
                return redirect()->route('home')
                    ->with('status', $e->getMessage());
            }
            
            Auth::login($user);
           
            return redirect()->route('home')
                ->with('status', 'You successfully activated your new email!');
        }


Сервис
    namespace App\Services\Auth;
    
    use \App\Models\User;
    use \App\Contracts\Auth\ChangeEmailContract;
    use \App\Contracts\Auth\ChangeEmailRepositoryContract;
    use \App\Exceptions\ChangeEmailNotFoundException;
    use Illuminate\Mail\Mailer;
    use Illuminate\Mail\Message;
    
    class ChangeEmailService implements ChangeEmailContract
    {
    
        protected $mailer;
        
        protected $changeEmailRepo;
        
        public function __construct(ChangeEmailRepositoryContract $changeEmailRepo)
        {
           $this->changeEmailRepo = $changeEmailRepo;
        }
      
        public function sendChangeEmailMail($user, $email)
        {
            $token = $this->changeEmailRepo->createEmailChange($user, $email);
            
            \Mail::to($email)->send(
                new \App\Mail\ChangeEmail(array(
                    'email' => $email,
                    'token' => $token,
                ))
            );
        }
        
        public function setEmail($token, $email)
        {
            $changeEmail = $this->changeEmailRepo->getChangeEmailByTokenAndEmail($token, $email);
            
            if ($changeEmail === null) {
                throw new ChangeEmailNotFoundException();
            }
            
            $user = User::find($changeEmail->user_id);
            
            if (!$user) {
                throw new ChangeEmailNotFoundException();
            }
    
            $user->email = $email;
    
            $user->save();
    
            $this->changeEmailRepo->deleteChangeEmail($token);
    
            return $user;
    
        }
        
    }


Репо вокруг таблицы где токены и новые мейлы. Понимаю что модель нужно было включить иньекцией, но я предпочел просто обращаться к ней через ORM методы.

    namespace App\Repositories\Auth;
    
    use \App\Contracts\Auth\ChangeEmailRepositoryContract;
    use \App\Models\EmailChange;
    use Carbon\Carbon;
    use Illuminate\Database\Connection;
    use Illuminate\Support\Facades\DB;
    
    
    class ChangeEmailRepository implements ChangeEmailRepositoryContract
    {
        public function createEmailChange($user, $email)
        {
            $email_change = $this->getEmailChange($user);
            
            if (!$email_change) {
                return $this->createEmailChangeRecord($user, $email);
            }
            
            return $this->updateEmailChangeRecord($user, $email);
            
        }
        
        public function getEmailChange($user)
        {
            return EmailChange::where('user_id', $user->id)->first();
        }
        
        public function getChangeEmailByTokenAndEmail($token, $email)
        {
            return EmailChange::where(array('token' => $token, 'email' => $email))->first();
        }
        
        public function deleteChangeEmail($token)
        {
            EmailChange::where('token', $token)->delete();
        }
        
        private function updateEmailChangeRecord($user, $email)
        {
            $token = $this->getToken();
            
            EmailChange::where('user_id', $user->id)->update([
                'token' => $token,
                'email' => $email,
                'created_at' => new Carbon()
            ]);
            
            return $token;
        }
        
        private function getToken()
        {
            return hash_hmac('sha256', str_random(40), config('app.key'));
        }
        
        private function createEmailChangeRecord($user, $email)
        {
            $token = $this->getToken();
            
            EmailChange::insert([
                'user_id' => $user->id,
                'token' => $token,
                'email' => $email,
                'created_at' => new Carbon()
            ]);
            
            return $token;
        }
    }

Модель таблицы
    namespace App\Models;
    
    use Illuminate\Database\Eloquent\Model;
    
    class EmailChange extends Model
    {
        protected $table = 'email_change';
        
        public function user()
        {
            return $this->belongsTo(User::class);
        }
    
    }


Моделька юзера. Метод отправки письма, связан не с данным, функционалом, а со сменой пароля, которая встроенная используется.

    namespace App\Models;
    
    use Illuminate\Notifications\Notifiable;
    use App\Notifications\CustomResetPassword;
    use Illuminate\Foundation\Auth\User as Authenticatable;
    use \Plunk\Mediable;
    class User extends Authenticatable
    {
        use Notifiable;
        use \Plank\Mediable\Mediable;
    
        protected $fillable = [
            'name', 'email', 'password',
        ];
    
        protected $hidden = [
            'password', 'remember_token',
        ];
    
        public function sendPasswordResetNotification($token)
        {
    
            $this->notify(new CustomResetPassword($token));
        }
    }

Сервис провайдеры для сервиса и репозитория
    namespace App\Providers\Auth;
    
    use Illuminate\Support\ServiceProvider;
    
    class ChangeEmailProvider extends ServiceProvider
    {
        protected $defer = false;
    
        public function register()
        {
           
            $this->app->bind('App\Contracts\Auth\ChangeEmailContract', function ($app) {
              
                return new \App\Services\Auth\ChangeEmailService(
                   $app -> make("\App\Contracts\Auth\ChangeEmailRepositoryContract")
                );
            });
            
        }
    
        public function provides()
        {
            return ['\App\Contracts\Auth\ChangeEmailContract'];
            
        }
        
        public function boot()
        {
         
            
        }
    }
    

    namespace App\Providers\Auth;
    
    use Illuminate\Support\ServiceProvider;
    
    class ChangeEmailRepositoryProvider extends ServiceProvider
    {
        protected $defer = false;
    
        public function register()
        {
            $this->app->bind('\App\Contracts\Auth\ChangeEmailRepositoryContract', function ($app) {
                return new \App\Repositories\Auth\ChangeEmailRepository();
            });
        }
    
        public function provides()
        {
            return ['\App\Contracts\Auth\ChangeEmailRepositoryContract'];
            
        }
        
        public function boot()
        {
              
        }
    }



Как я хочу все это отрефакторить?

Я думаю нужно создать UserRepository, его явно не хватает. В него конечно нужно будет добавить саму регистрацию, смену аватара, а из данного функционала, пожалуй те небольшие процедуры которые относятся все же именно к таблице users например постановка, статус активирован, установка нового мейла...

Тогда получится для каждой таблицы свой репозиторий. В общем то логично. Но кажется более практично было бы, так как активация и смена мейла, в общем то также тесно связанные именно с юзеров вещи и все обращения к таблицам ChangeEmail и Activation а так же генерацию и проверку токена перенести в UserRepository. Да он будет толще зато смогу убрать два репозитория ChangeEmailRepository и ActivateEmailRepository и также два контракта и два сервис провайдера..

Тогда получится юзеру и связанным тесно с ними табличкам общий репозиторий, а вот сервисы отдельные для смены емайла, активации и аватара оставить.

Как считаете, так было бы оптимальнее?

Так же прошу подсказать есть в моем уже существующем и приведенном коде какие то явные ошибки?

И еще вопрос. А даже если ChangeEmailRepository и ActivateEmailRepository оставить отдельными и не переносить их содержимое в UserRepository, то нужны ли для этих репозиториев контракты и сервис провайдеры, учитывая что подменять реализацию я ведь вряд ли буду....