Форум программистов, компьютерный форум, киберфорум
PHP: Laravel
Войти
Регистрация
Восстановить пароль
Блоги Сообщество Поиск Заказать работу  
 
Рейтинг 4.55/11: Рейтинг темы: голосов - 11, средняя оценка - 4.55
1 / 1 / 0
Регистрация: 14.03.2016
Сообщений: 54

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

06.08.2017, 16:50. Показов 2181. Ответов 12
Метки нет (Все метки)

Студворк — интернет-сервис помощи студентам
Добрый день! Учусь ларавель, делаем тестовый проект типа облачного хостинга файлов, который возможно станет реальным.

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

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

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

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

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

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

Роуты

PHP
1
2
3
4
5
    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');
Методы контроллера запроса на смену и смены по ссылке из письма


PHP
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
 
 
     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!');
        }
Сервис

PHP
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
    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 методы.


PHP
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
    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;
        }
    }
Модель таблицы
PHP
1
2
3
4
5
6
7
8
9
10
11
12
13
14
    namespace App\Models;
    
    use Illuminate\Database\Eloquent\Model;
    
    class EmailChange extends Model
    {
        protected $table = 'email_change';
        
        public function user()
        {
            return $this->belongsTo(User::class);
        }
    
    }
Моделька юзера. Метод отправки письма, связан не с данным, функционалом, а со сменой пароля, которая встроенная используется.

PHP
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
    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));
        }
    }
Сервис провайдеры для сервиса и репозитория
PHP
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
    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()
        {
         
            
        }
    }
PHP
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
    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, то нужны ли для этих репозиториев контракты и сервис провайдеры, учитывая что подменять реализацию я ведь вряд ли буду....
0
cpp_developer
Эксперт
20123 / 5690 / 1417
Регистрация: 09.04.2010
Сообщений: 22,546
Блог
06.08.2017, 16:50
Ответы с готовыми решениями:

Может, кто-нибудь привести пример простого приложения на фреймворке Slim или Laravel? PHP
Я просто сама не могу разобраться и написать своё что-то

Посоветуйте по структуре БД
Добрый вечер! Прошу помощи в организации БД. Всю голову уже сломал, кучу вариантов переделал. Суть: Поставили задачу организовать...

Посоветуйте пожалуйста книги по алгоритмизации и структуре данных.
Посоветуйте пожалуйста книги по алгоритмизации и структуре данных.

12
 Аватар для tarasalk
1992 / 1216 / 440
Регистрация: 13.06.2013
Сообщений: 4,115
07.08.2017, 12:50
Цитата Сообщение от htchtc052 Посмотреть сообщение
то нужны ли для этих репозиториев контракты и сервис провайдеры, учитывая что подменять реализацию я ведь вряд ли буду....
Я вам больше скажу, вам и репозитории то не нужны. В текущем виде они не выполняют свою функцию, т.к. eloquent модели протекают в сервисный слой. Если в будущем вам захочется перейти допустим на чистый sql, то вам придется переделывать не только репозитории, но и сервисы.
Цитата Сообщение от htchtc052 Посмотреть сообщение
Тогда получится для каждой таблицы свой репозиторий. В общем то логично.
Не логично. Репозитории работают с сущностями. Как хранятся эти сущности - это детали реализации. Там может вообще не быть таблиц.


В общем я бы сделал так:
1) убрал репозитории, контракты, сервис провайдеры
2) сервисы разделил на части. Одно действие, один класс.

А вот это полная жесть. Надо выносить в сервис.
PHP
1
2
3
4
private function getToken()
        {
            return hash_hmac('sha256', str_random(40), config('app.key'));
        }
1
1 / 1 / 0
Регистрация: 14.03.2016
Сообщений: 54
07.08.2017, 13:22  [ТС]
>2) сервисы разделил на части. Одно действие, один класс

Например, для отсылки уведомления на смену мейла один класс сервиса, а для установке его по ссылке из письма другой класс сервиса??

Убрать провайдеры контракты итд. Но хотелось бы сервис внедрять как зависимость в контроллере, а не new ActivateService() каждый раз делать.

Как быть, для этого? Видимо в AppService прибиндивать просто сервисы свои причем без соответствия контракту?

>А вот это полная жесть. Надо выносить в сервис.


Понятно, тк это не обращение к БД и не место в репо.


Кроме того. Если я откажусь от репо и оставлю только сервисы. То обращения к моделям будут разбросаны по сервисам, что и сейчас отчасти так, учитывая что к юзеру обращаемся не через Repo. Это нормально? А если при этом все же будут запросы raw и через билдер?? А они будут при развитии данного кода...

Не выходит ли, что стоит оставить и Repo и сервисы?

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

При этом сервисы прибиндим в AppService, да и UserRepository пожалуй тоже... Что бы не плодить провайдеры и контракты. Так вернее будет??

Заранее благодарен за ответ.
0
 Аватар для tarasalk
1992 / 1216 / 440
Регистрация: 13.06.2013
Сообщений: 4,115
07.08.2017, 14:01
Цитата Сообщение от htchtc052 Посмотреть сообщение
Например, для отсылки уведомления на смену мейла один класс сервиса, а для установке его по ссылке из письма другой класс сервиса??
Да. С ростом сложности проекта это будет весьма кстати. Вначале конечно можно не заморачиваться.
Цитата Сообщение от htchtc052 Посмотреть сообщение
Но хотелось бы сервис внедрять как зависимость в контроллере, а не new ActivateService() каждый раз делать.
Он и так внедрится. Ларавел сам создаст объект со всеми зависимостями и даже прибиндивать не придется.
Цитата Сообщение от htchtc052 Посмотреть сообщение
если при этом все же будут запросы raw и через билдер??
Такое как правило требуется при выводе данных, а не в бизнес логике. Тут уже можно напрямую из контроллера дергать модельки. Что то можно вынести в scope, что то в отдельные классы.
У eloquent модели можно получить query и передать его в другой класс, где этот query будет обрастать дополнительной логикой. У меня так фильтры построены, они зачастую повторяются.

Цитата Сообщение от htchtc052 Посмотреть сообщение
Не выходит ли, что стоит оставить и Repo и сервисы?
Как по мне это вообще не репозиторий. С таким же успехом можно весь код репозитория прямо в модель запихать.
1
1 / 1 / 0
Регистрация: 14.03.2016
Сообщений: 54
07.08.2017, 14:19  [ТС]
Цитата Сообщение от tarasalk Посмотреть сообщение
Сообщение от htchtc052
Но хотелось бы сервис внедрять как зависимость в контроллере, а не new ActivateService() каждый раз делать.
Он и так внедрится. Ларавел сам создаст объект со всеми зависимостями и даже прибиндивать не придется.
То есть просто наверху use данный класс. А в методе иньекцию зависимости... Да вроде должно и так прокатить? А не просветишь в каком случае лучше так, а в каком app->bind все же? Про синглтон то понятно для чего он нужен. А в чем разница просто внедрения или если при этом бинд есть к контейнеру? Не столько разница в теории и механизме действия, сколько в лучших практиков того и другого??


Цитата Сообщение от tarasalk Посмотреть сообщение
Такое как правило требуется при выводе данных, а не в бизнес логике. Тут уже можно напрямую из контроллера дергать модельки. Что то можно вынести в scope, что то в отдельные классы.
У eloquent модели можно получить query и передать его в другой класс, где этот query будет обрастать дополнительной логикой. У меня так фильтры построены, они зачастую повторяются.

Да я и про вывод. Понял что оборачивать репо все это не нужно. Тк дергать можно и с сервиса и из контроллера, что модельки что raw запросы. Нет я то это и так понимал что дергать можно... Просто казалось собрать все обращения к БД в отдельный слой хорошая практика? Ну а если я захочу поменять Eloquent на Doctrine или кеши внедрять для запросов... Вроде бы удобно когда они в одном месте?


Цитата Сообщение от tarasalk Посмотреть сообщение
Не выходит ли, что стоит оставить и Repo и сервисы?
Как по мне это вообще не репозиторий. С таким же успехом можно весь код репозитория прямо в модель запихать.
По сути то так. Это лишь обертка над моделью. В Eloquent модель все эти getByIdAndToken($id, $token) не хочется запихивать и засорять ее.. Мне кажется чисто субъективно, что Eloqent модель стоит оставить чистой и пусть служит лишь для вызова методов Eloquent.


Итого: Мы пришли к выводу, что репо не нужен, но где то методы содержащие лишь Eloquent запрос к БД хранить надо. те бывает случай Model::Find($id) да его можно вызывать из сервиса или из контроллера и он уже есть. А бывает getByIdAndToken($id, $token) или getTopModelsItems(); вот где такое хранить? Если в Eloqent модели не хочется? В сервисе, все же??
0
 Аватар для tarasalk
1992 / 1216 / 440
Регистрация: 13.06.2013
Сообщений: 4,115
07.08.2017, 14:38
Цитата Сообщение от htchtc052 Посмотреть сообщение
А в чем разница просто внедрения или если при этом бинд есть к контейнеру?
При биндинге это происходит явно, вот и вся разница. Можно указать какие то особые условия создания объекта.
Цитата Сообщение от htchtc052 Посмотреть сообщение
Ну а если я захочу поменять Eloquent на Doctrine или кеши внедрять для запросов... Вроде бы удобно когда они в одном месте?
Тогда ваши репозитории должны работать с чистыми сущностями, а не eloquent моделями. Я сначала грезил таким, но когда увидел затраты на реализацию как то приуныл. Если подобный переход на доктрину и состоится, мне лично проще переписать, чем поддерживать то, что может и не понадобится.
Цитата Сообщение от htchtc052 Посмотреть сообщение
А бывает getByIdAndToken($id, $token) или getTopModelsItems(); вот где такое хранить?
В scope,
1
1 / 1 / 0
Регистрация: 14.03.2016
Сообщений: 54
07.08.2017, 15:39  [ТС]
Тогда ваши репозитории должны работать с чистыми сущностями, а не eloquent моделями. Я сначала грезил таким, но когда увидел затраты на реализацию как то приуныл. Если подобный переход на доктрину и состоится, мне лично проще переписать, чем поддерживать то, что может и не понадобится.

Те с db:raw и чистым sql должны работать? Вообще то у меня опыт оч. большой на самописи я то привык к чистому sql. Но конечно там где возможно с ORM буду в ларе работать. Про доктрину я согласен что проще взять и переписать, чем подменять реализацию контракта репозитория. Да и может этого и не будет.

Ну, а если считать что Репо нужен лишь просто как удобный слой для сбора всех обращений к БД? Вот не важно каким образом.. Просто как контейнер для запросов... Может это и не очень стильно. Но в этом есть профит? Или скорее это еще более все запутает? Еще раз уточняю что пока код тестовый, но он может быть применен как основа для сложных и не стандартных проектов, в основном для файло-хостинга, муз.хостинга уже существующего.. То есть мне прямо простое каноническое решение основанное на только ORM может быть и тесно.
Стоит ли заложить UserRepo на будущее из этих соображений? Или все равно не надо...

Честно scope использовать не хочется пока, что то это весьма спефичное и чисто ORM-ное.

А если не в scope то куда такое девать? Или в сервис или в репо все же?


Кстати а репо без сервиса рекомендуешь? Если уж совсем не хочу от репо отказываться? Дергать UserRepo прямо из контроллеров?

И еще ты не ответил если делать репозиторий для функционала регистрации юзера, активации и смены мейла, то все же один UserRepo так как имеем одну сущность User по сути, или UserRepo, ActivationRepo, ChangeEmailRepo ??

Я не ищу прямо идеально простой и изящный способ. Скорее универсальный и без грубых ошибок проектирования.

Добавлено через 42 минуты
А что если с другой стороны все запросы писать в сераисах в том числе db::raw ну а то, что явно использует Eloquent то писать в модельках и вызывать как метод модели из сервиса? Тогда уж без repo
0
 Аватар для tarasalk
1992 / 1216 / 440
Регистрация: 13.06.2013
Сообщений: 4,115
07.08.2017, 16:12
Короче, почитайте это. Там на yii, но суть та же.
В соседних статьях есть реализации на active record и doctrine. Все 3 реализации легко взаимозаменяются, в этом суть репозитория.
Потом рекомендую подумать как будете решать такие вопросы
Цитата Сообщение от htchtc052 Посмотреть сообщение
А если не в scope то куда такое девать? Или в сервис или в репо все же?
Что это за велосипед и какую проблему он решает я хз, но это точно не сервис и не репо.
Если не хотите зависеть от ORM, не позволяйте eloquent моделям гулять по всему проекту, изолируйте их в репозиториях.
Цитата Сообщение от htchtc052 Посмотреть сообщение
Кстати а репо без сервиса рекомендуешь? Если уж совсем не хочу от репо отказываться? Дергать UserRepo прямо из контроллеров?
Я репо вообще не рекомендую. Можно и без сервиса дергать.
Цитата Сообщение от htchtc052 Посмотреть сообщение
то все же один UserRepo так как имеем одну сущность User по сути, или UserRepo, ActivationRepo, ChangeEmailRepo ??
Я бы в одну репу все пилил. Если репа станет большая, я бы группировал методы в трейтах, подключенных к основной репе.
1
1 / 1 / 0
Регистрация: 14.03.2016
Сообщений: 54
07.08.2017, 22:58  [ТС]
Спасибо!

Насчет первой статьи, там много всего что возможно мне в будующем может пригодится, а может и нет.. Doctrine, разные хитрости с БД, NoSQl. Но сейчас нет. Пока, что достаточно или ORM или чистого sql в каком то совсем нестандартном случае. Ну а на существующем тяжелом проекте где и правда большой объем данных и выборки скорее в будущем применим NoSql и уж как это будет дергаться из Laravel отнюдь не вопрос сегодняшнего дня. И именно ради этого Repo делать не нужно.


Насчет http://blog.byndyu.ru/2011/08/repository.html да тут все в точку, примерно об этих вопросах и думал.

И что делать? Отказываться от репо и дергать модели из сервисов и контроллеров? Валить все в один большой юзер-репо, как советуете? И создание юзера и аватар и отсылку и подтверждении активации и смену мейла...

Про убрать в трейты, туже активацию и смену мейла прямо мои мысли прочитали Но все равно по сути это God класс будет... Может учитывая, что нечто принципиально уже другое, ну например работа с подпиской и оплатой юзера будет в другом уже большом репо.

Хорошо, что не будет работа с БД везде разбросана. Но плохо, что по сути это громадные классы и по сути наборы статик методов выходят и лишний слой и лишний код.

К тому же, разбивая скажем на два больших репо UserRepo и UserPaymentRepo мы как будто работу с юзером искусственно делим на 2 части. А что если как и написано в статье придется из одного репо дергать другой... В общем что то тут интуитивно не то.

Хотя смириться наверное и можно с недостатками подобной схемы.

Хорошо если говорить о варианте без репозитория, то просто дергаем модели и orm запросы через них. Понемногу наверное можно Eloquent модель дополнять методами своими...

Скажем модель User дополнить методом, проверить в связке с таблицей активаций - токен активации для данного юзер? Что то типа $user -> CheckUserActivactionByToken($token)

Вот такого типа методы писать это хорошая практика?

А еще, что меня все время волновало... А где если нет репо правильно писать запрос получающий коллекцию... Почему вообще в ларавель как то не видел в практиках и сайтах, классов коллекций? Допустим мне нужно получить список юзеров по некоторым критериям. Понятно я могу в сервисе или контроллере написать orm запрос к User::where итд. И будем мне мои $users. Ну а если я хочу данное получение юзеров повторно использовать и два раза такой запрос не писать? Где будет уместным держать метод getUsersByCritery(); возвращающий $users???

Если в модельку Users это записать то работать то будет... Но получится что то вроде $user=User::find(2); Имеем юзера с id 2, а далее $users=$user->getUsersByCritery() да это сработает, но это же некрасиво, получать от экземпляра конкретного юзера, список юзеров совершенно с ним не связанный..

Вот даже нашел прямо то, что меня волнует:

https://www.reddit.com/r/larav... ctions_in/

"Хороший статический метод, не связанный с одним экземпляром User:
public static function getAllActive()
{
return self::where('active', '=', true)->get();
}"

"Хороший регулярный метод, предназначенный для работы с экземпляром User(т.е. одна запись пользователя):
public function getEmailAddress()
{
return $this->email;
}"

То есть если нужно в класс User запихнуть метод который отдает коллекцию юзеров и результат работы которого не относиться к данному конкретному юзеру, то метод должен быть статистическим. И вызывать его как $user->getUsersByCritery() не нужно, а только как User::getUsersByCritery()

Это более менее понятно. Так будет верно?

В целом я уже согласен отказываться от Repo дергать модельки и писать код в них. А сервисы конечно стоит оставить и дробить.

В общем ты очень помог не знаю сколько бы еще это все искал и сопоставлял. Прокомментируй пожалуйста конкретику в этом посте.. И буду рефакторить код...

Добавлено через 5 часов 16 минут
Зарефакторил Смену мейла на такое


PHP
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
Контроллер
 
 
namespace App\Http\Controllers\Auth;
 
use App\Http\Controllers\Controller;
use \App\Http\Requests\ChangeEmail;
use App\Contracts\Auth\ChangeEmailContract;
use Illuminate\Support\Facades\Request;
use Illuminate\Support\Facades\Auth;
 
 
class ChangeEmailController extends Controller
{
    public function showForm(Request $request)
    {
 
        return view('home.account.email')->with([
                'title' => 'Change email',
                'pageclass' => 'home_account_email',
            ]
        );
    }
 
    public function saveForm(ChangeEmail $request, ChangeEmailContract $changeEmailService)
    {
        $user = Auth::user();
 
        $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\Http\Requests;
 
use Illuminate\Foundation\Http\FormRequest;
use Illuminate\Support\Facades\Auth;
 
class ChangeEmail extends FormRequest
{
    /**
     * Determine if the user is authorized to make this request.
     *
     * @return bool
     */
    public function authorize()
    {
        return true;
    }
 
    /**
     * Get the validation rules that apply to the request.
     *
     * @return array
     */
    public function rules()
    {
        return [
           'email' => 'required|email|unique:users',
           'password' => 'required|checkpassword',
        ];
    }
 
    public function messages()
    {
        return [
            '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',
        ];
    }
}
 
Сервис
 
 
 
namespace App\Services\Auth;
 
use \App\Models\User;
use \App\Contracts\Auth\ChangeEmailContract;
use \App\Exceptions\ChangeEmailNotFoundException;
use Illuminate\Mail\Mailer;
use Illuminate\Mail\Message;
use \App\Models\EmailChange;
use Carbon\Carbon;
 
class ChangeEmailService implements ChangeEmailContract
{
 
    protected $mailer;
 
    protected $changeEmailRepo;
 
    public function __construct()
    {
 
    }
 
    public function sendChangeEmailMail($user, $email)
    {
        $token = $this->createChangeEmail($user, $email);
 
        \Mail::to($email)->send(
            new \App\Mail\ChangeEmail(array(
                'email' => $email,
                'token' => $token,
            ))
        );
    }
 
    public function setEmail($token, $email)
    {
        $changeEmail = EmailChange::where(array('token' => $token, 'email' => $email))->first();
 
        if ($changeEmail === null) {
            throw new ChangeEmailNotFoundException();
        }
 
        $user = User::find($changeEmail->user_id);
 
        if (!$user) {
            throw new ChangeEmailNotFoundException();
        }
 
        $user->email = $email;
 
        $user->save();
 
        EmailChange::where('token', $token)->delete();
 
        return $user;
 
    }
 
    private function createChangeEmail($user, $email)
    {
        $email_change = EmailChange::where('user_id', $user->id)->first();
 
        $token = $this->getToken();
 
        if (!$email_change) {
            return EmailChange::insert([
                'user_id' => $user->id,
                'token' => $token,
                'email' => $email,
                'created_at' => new Carbon()
            ]);
        }
 
        return EmailChange::where('user_id', $user->id)->update([
                    'token' => $token,
                    'email' => $email,
                    'created_at' => new Carbon()
                ]);
 
    }
 
    private function getToken()
    {
        return hash_hmac('sha256', str_random(40), config('app.key'));
    }
}
 
Провайдер
 
 
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();
        });
 
    }
 
    public function provides()
    {
        return ['\App\Contracts\Auth\ChangeEmailContract'];
    }
 
    public function boot()
    {
 
    }
}
 
 
Контракт
 
//очевиден и как был

В общем то все стало много компактенее без Репо и тем более его правайдера и контракта.

На две части сервис пока не делю... Кажется пока излишне и так даже нагляднее.

В тоже время, контракт и провайдер не стал убирать... Ведь плюс в том, что для данного сервиса можно включить $defer=true те отложенную загрузку. Он ведь нужен то лишь в двух формах, ну может когда еще в одной гипотетически понадобится. Так зачем грузить его по всему сайту.

Прошу сказать, если что то еще можно упростить. Может убрать контракт убрать, а оставить провайдер? Хотя с контрактом как то аккуратнее..
0
 Аватар для tarasalk
1992 / 1216 / 440
Регистрация: 13.06.2013
Сообщений: 4,115
07.08.2017, 23:26
Ох, ну и стена. Все норм)
Цитата Сообщение от htchtc052 Посмотреть сообщение
Почему вообще в ларавель как то не видел в практиках и сайтах, классов коллекций?
Так вот же они.
Цитата Сообщение от htchtc052 Посмотреть сообщение
далее $users=$user->getUsersByCritery() да это сработает, но это же некрасиво, получать от экземпляра конкретного юзера, список юзеров совершенно с ним не связанный..
Конечно не красиво. Поэтому используют scope. И код становится таким.
PHP
1
$users = App\User::popular()->active()->orderBy('created_at')->get();
Мне кажется вы раньше не работали с active record. Почитайте, это та еще сборная солянка.
1
1 / 1 / 0
Регистрация: 14.03.2016
Сообщений: 54
08.08.2017, 00:04  [ТС]
"Ох, ну и стена. Все норм)"

Спасибо. Тогда приведу к похожему активацию.

Кстати, а как заменить

PHP
1
2
3
4
5
6
7
8
9
10
11
    public function sendChangeEmailMail($user, $email)
    {
        $token = $this->createChangeEmail($user, $email);
 
        \Mail::to($email)->send(
            new \App\Mail\ChangeEmail(array(
                'email' => $email,
                'token' => $token,
            ))
        );
    }
Где $user передается как текущий юзер, а $email именно новый мейл из формы, на использование нотификации ларавелевской для юзера?? $user->email то ведь еще старый, а не тот что из формы.

Добавлено через 3 минуты
PHP
1
$users = App\User::popular()->active()->orderBy('created_at')->get();
Понимаю, что главный смысл, что бы этих популярных явно получать прямо в сервисе и контроллере с помощью ORM и scope, а не писать какой то сложный запрос внутри метода модели App\User ?

Да я в ORM глубоко не разбирался в деталях. Мне сейчас важнее общая структура приложения. Понадобится, что то посложнее от ORM запроса буду решать...

Добавлено через 7 минут
Еще момент. А в таких штуках

PHP
1
2
3
4
5
$user->email = $email;
 
        $user->save();
 
        EmailChange::where('token', $token)->delete();
Ларавель и ORM как то сама обеспечивает целостность транзакции. Или нужно бы если это критично, а не как тут удаление из таблички активации, то DB::beginTransaction();

Добавлено через 3 минуты
Кстати я смотрю свою тему за прошлый год. https://www.cyberforum.ru/php-... 93104.html там мне советуют репозиторий...

На самом деле тот функционал на laravel с DB::Raw и попыткой сделать свой биллинг я тогда отладил и все работало. Но заковыристый код вышел...

Мы тогда решили забить, тк не хватает опыта и год приводили в порядок свою самопись. А затем владелец понял, что она устарела безнадежна и решил опять попробовал, что бы я с laravel тогда максимально стараясь использовать общепринятые практики и возможности фреймворка..

А биллинг скорее всего прикрутим готовый... Кстати какой посоветуете? Ну а затем видно будет. Если все же код хороший выйдет то потащим его в старый проект... Если справлюсь.
0
 Аватар для tarasalk
1992 / 1216 / 440
Регистрация: 13.06.2013
Сообщений: 4,115
08.08.2017, 09:40

Не по теме:

Народ, спасайте :)


Цитата Сообщение от htchtc052 Посмотреть сообщение
Понимаю, что главный смысл, что бы этих популярных явно получать прямо в сервисе и контроллере с помощью ORM и scope, а не писать какой то сложный запрос внутри метода модели App\User ?
Да нет, смысл как раз в том чтобы один раз написать этот сложный (но не обязательно) запрос внутри модели, чтобы не дублировать по всему проекту. В отличии от вашего варианта, это встроенное решение с кучей плюшек.
1
1 / 1 / 0
Регистрация: 14.03.2016
Сообщений: 54
08.08.2017, 19:44  [ТС]
PHP
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
namespace App\Providers;
 
use Illuminate\Support\ServiceProvider;
use Illuminate\Support\Facades\Validator;
use Illuminate\Support\Facades\Auth;
 
class CheckPasswordValidator extends ServiceProvider
{
    /**
     * Bootstrap the application services.
     *
     * @return void
     */
    public function boot()
    {
        Validator::extend('checkpassword', function ($attribute, $value, $parameters, $validator) {
 
        $email = Auth::user()->email;
 
        if (Auth::attempt(array('email' => $email, 'password' => $value))){
                return true;
            }else{
                return false;
            }
        });
    }
 
    /**
     * Register the application services.
     *
     * @return void
     */
    public function register()
    {
        //
    }
}

Еще вопрос про валидатор. Он применяется, что бы проверить верно ли введен пароль при запросе на смену мейла.

Можно ли как то по изящней сделать? Видел в сети много вариантов, как можно определить свой валидатор...
0
Надоела реклама? Зарегистрируйтесь и она исчезнет полностью.
raxper
Эксперт
30234 / 6612 / 1498
Регистрация: 28.12.2010
Сообщений: 21,154
Блог
08.08.2017, 19:44
Помогаю со студенческими работами здесь

Ищим содействия в разработки приложения на платформе Laravel
Уважаемые форумчане! Доброе время суток! Ищу разработчика (-ков), в один интересный перспективный онлайн проект, который готов оказать...

Класс php в laravel
Здравствуйте! Подскажите пожалуйста возможно ли подключить как я понимаю стандартный класс php SoapClient() в Ларавель? Если...

Laravel php обфускатор
Нужно усложнить чтение и модификацию PHP кода. Решения уровня IonCube не подходят. Может есть какая-нибудь обфускация для laravel?...

Адаптация Laravel 5.3 и PHP WEBSocket
Доброго времени суток. У меня есть проект на Laravel 5.3 и чат на PHP WebSocket. На данном этапе чат работает так: Один из клиентов...

Laravel 5.3 и добавления слов в php файл
Доброго времени суток. Пишу приложение на Laravel 5.3. Есть локализация с двумя языками - англ и рус. В каждых файлах переводы слов по...


Искать еще темы с ответами

Или воспользуйтесь поиском по форуму:
13
Ответ Создать тему
Новые блоги и статьи
PhpStorm 2025.3: WSL Terminal всегда стартует в ~
and_y87 14.12.2025
PhpStorm 2025. 3: WSL Terminal всегда стартует в ~ (home), игнорируя директорию проекта Симптом: После обновления до PhpStorm 2025. 3 встроенный терминал WSL открывается в домашней директории. . .
Как объединить две одинаковые БД Access с разными данными
VikBal 11.12.2025
Помогите пожалуйста !! Как объединить 2 одинаковые БД Access с разными данными.
Новый ноутбук
volvo 07.12.2025
Всем привет. По скидке в "черную пятницу" взял себе новый ноутбук Lenovo ThinkBook 16 G7 на Амазоне: Ryzen 5 7533HS 64 Gb DDR5 1Tb NVMe 16" Full HD Display Win11 Pro
Музыка, написанная Искусственным Интеллектом
volvo 04.12.2025
Всем привет. Некоторое время назад меня заинтересовало, что уже умеет ИИ в плане написания музыки для песен, и, собственно, исполнения этих самых песен. Стихов у нас много, уже вышли 4 книги, еще 3. . .
От async/await к виртуальным потокам в Python
IndentationError 23.11.2025
Армин Ронахер поставил под сомнение async/ await. Создатель Flask заявляет: цветные функции - провал, виртуальные потоки - решение. Не threading-динозавры, а новое поколение лёгких потоков. Откат?. . .
Поиск "дружественных имён" СОМ портов
Argus19 22.11.2025
Поиск "дружественных имён" СОМ портов На странице: https:/ / norseev. ru/ 2018/ 01/ 04/ comportlist_windows/ нашёл схожую тему. Там приведён код на С++, который показывает только имена СОМ портов, типа,. . .
Сколько Государство потратило денег на меня, обеспечивая инсулином.
Programma_Boinc 20.11.2025
Сколько Государство потратило денег на меня, обеспечивая инсулином. Вот решила сделать интересный приблизительный подсчет, сколько государство потратило на меня денег на покупку инсулинов. . . .
Ломающие изменения в C#.NStar Alpha
Etyuhibosecyu 20.11.2025
Уже можно не только тестировать, но и пользоваться C#. NStar - писать оконные приложения, содержащие надписи, кнопки, текстовые поля и даже изображения, например, моя игра "Три в ряд" написана на этом. . .
Мысли в слух
kumehtar 18.11.2025
Кстати, совсем недавно имел разговор на тему медитаций с людьми. И обнаружил, что они вообще не понимают что такое медитация и зачем она нужна. Самые базовые вещи. Для них это - когда просто люди. . .
Создание Single Page Application на фреймах
krapotkin 16.11.2025
Статья исключительно для начинающих. Подходы оригинальностью не блещут. В век Веб все очень привыкли к дизайну Single-Page-Application . Быстренько разберем подход "на фреймах". Мы делаем одну. . .
КиберФорум - форум программистов, компьютерный форум, программирование
Powered by vBulletin
Copyright ©2000 - 2025, CyberForum.ru