Laravel по-русски

Русское сообщество разработки на PHP-фреймворке Laravel.

Ты не вошёл. Вход тут.

#26 06.08.2017 19:45:29

Re: Нужна помощь в компоновке классов и паттернах сервис и репозиторий

Где то предлагаешь бизнес логику для юзера писать прямо в модельке элокьент? В классе Models\User?
Мне кажется это неверно… Это же класс именно Eloquent зачем я буду писать в него скажем метод отдающий на выходе скажем коллекцию юзеров… Это же совершенно не логично. Класс описывает одного юзера причем ради использования его в контексте запросов элокьент, а я возвращаю коллекцию…
Или ты об том, что бы вообще никуда не собирать бизнес-логику и запросы для юзера. А прямо в контроллере писать Eloquent запросы? Но не хочется это по контроллерам разбрасывать…

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

User::getTopUsers() // Через фасад.
или
$this->user->getTopUsers() // После инжекта модели.

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

Изменено AlexeyMezenin (06.08.2017 19:46:40)

Не в сети

#27 06.08.2017 19:46:37

htclog81
Откуда: Москва
Сообщений: 192
Сайт

Re: Нужна помощь в компоновке классов и паттернах сервис и репозиторий

Если я все таки уверен, что кроме элоквент моделей и контроллеров, нужен еще слой. То скажи пожалуйста лучше, что бы это было

-Репо
-Сервисный слой
— И то и другое по надобности.

Репо если будет, то:

Для крупнейших таблиц и основной функционала UserRepo UserFilesRepo UserSubscriptionsRepo

Или вообще для каждого функционала и почти каждой таблицы UserRepo UserChangeEmailRepo UserActivationRepo итд? Мне кажется скорее для крупнейших…

Не в сети

#28 06.08.2017 19:48:03

htclog81
Откуда: Москва
Сообщений: 192
Сайт

Re: Нужна помощь в компоновке классов и паттернах сервис и репозиторий

"Я говорю про хранение запросов в классах моделей. "

Хорошо как отделить именно классы моделей от классов элокьент моделей…

Не в сети

#29 06.08.2017 19:49:02

htclog81
Откуда: Москва
Сообщений: 192
Сайт

Re: Нужна помощь в компоновке классов и паттернах сервис и репозиторий

Может быть тупо создать UserModel класс, куда иьекцию сделать нужных элокьент моделей типа User

Не в сети

#30 06.08.2017 19:49:50

htclog81
Откуда: Москва
Сообщений: 192
Сайт

Re: Нужна помощь в компоновке классов и паттернах сервис и репозиторий

Но мне кажется не должна тянутся цепочка наследования от моей модели к Illuminate\Database\Eloquent\Model; ну вот не вижу в этом смысла…

Не в сети

#31 06.08.2017 19:50:16

htclog81
Откуда: Москва
Сообщений: 192
Сайт

Re: Нужна помощь в компоновке классов и паттернах сервис и репозиторий

И в пакетах что видел, тоже такого нет…

Не в сети

#32 06.08.2017 19:53:22

Re: Нужна помощь в компоновке классов и паттернах сервис и репозиторий

А методы работы с будующим функционалом связанным с юзером, например файлы юзера (не авы), подписки и платежи юзера?

Зависит от функционала. С файлами работать можно в контроллере, если это 1-2 строки. Если что-то серьезное, тогда двигаешь логику в отдельный класс. Подписки, платежи можно лепить в контроллере, если это вызов какого-либо функционала из стороннего пакета в пару строк. Если тебе необходимо проверить оплату, сохранить данные, послать уведомление, все это можно сделать вызовом соответствующих методов в модели, пакете (или твоем классе) и пр. В таких случаях события удобно использовать.

Если под платежами ты подразумеваешь работу с API напрямую с кучей логики, тогда всю эту логику переносишь в отдельные классы (по сути, делаешь свой пакет для работы с платежной системой).

Основная идея здесь - это хранить логику работы с данными в моделях, логику работы валидации в Request файлах, работу с емэйлами в Notification классах и т.д. А кастомную логику уже пишешь в свои классы.

Изменено AlexeyMezenin (06.08.2017 20:06:19)

Не в сети

#33 06.08.2017 19:53:25

htclog81
Откуда: Москва
Сообщений: 192
Сайт

Re: Нужна помощь в компоновке классов и паттернах сервис и репозиторий

  1. $this->user->getTopUsers() // После инжекта модели.

Инжект модели в контроллер в данном случае как я понимаю. И конечно это должно быть НЕ тоже самое что $user = Auth::user(); те это текущий юзер и его элокьент модель. Ну если сделать так:

$user = Auth::user();

и далее

$users = $user->getTopUsers();

и

$other_user = $user::find($other_user_id);

То кажется это ужас жуткий. И лучше уж репо. Но если есть другое решение предлагай… Но я не хочу наследовать свой модель от Eloquent

Не в сети

#34 06.08.2017 19:55:37

htclog81
Откуда: Москва
Сообщений: 192
Сайт

Re: Нужна помощь в компоновке классов и паттернах сервис и репозиторий

«С файлами работать можно в контроллере, если это 1-2 строки. Если что-то серьезное, тогда двигаешь логику в отдельный класс.»

Уверен, что сложнее. Про отдельные готовые пакеты пока не уверен. Для файлов скорее нет. Для подписок и оплаты возможно.

Опять же отдельный класс… Но надеюсь не наследованный от Eloquent…

Изменено htclog81 (06.08.2017 19:56:22)

Не в сети

#35 06.08.2017 19:56:04

htclog81
Откуда: Москва
Сообщений: 192
Сайт

Re: Нужна помощь в компоновке классов и паттернах сервис и репозиторий

Про события и про Request ясно. Но поднятую проблему про модель/репо это не решает.

Не в сети

#36 06.08.2017 19:58:33

htclog81
Откуда: Москва
Сообщений: 192
Сайт

Re: Нужна помощь в компоновке классов и паттернах сервис и репозиторий

Теперь другой вопрос. Вот эти модели или уж репо. Для Юзера, для Юзерский файлов в общем для крупных кусков бизнес логике. Как правильней привязать к контейнеру?

В AppService или каждый в своем провайдере? Нужны ли контракты/фасады все же для каждого такого класса.

И еще ты не ответил стоит ли методы активации и смены мейла пихать в модель(репо) юзера, или это отдельные модели(репо) должны быть…

Вот это ответь и отстану… И возможно через пару дней покажу переписанный код…

Изменено htclog81 (06.08.2017 19:58:56)

Не в сети

#37 06.08.2017 20:16:42

htclog81
Откуда: Москва
Сообщений: 192
Сайт

Re: Нужна помощь в компоновке классов и паттернах сервис и репозиторий

И все же совсем конкретно:

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

namespace App\Repositories\Auth;

use \App\Contracts\Auth\ActivateEmailRepositoryContract;
use \App\Models\Activation;
use Carbon\Carbon;
use Illuminate\Database\Connection;
use Illuminate\Support\Facades\DB;


class ActivateEmailRepository implements \App\Contracts\Auth\ActivateEmailRepositoryContract
{
  public function createActivation($user)
    {

        $activation = $this->getActivation($user);

        if (!$activation) {
            return $this->createToken($user);
        }
        return $this->regenerateToken($user);

    }

    private function getToken()
    {
        return hash_hmac('sha256', str_random(40), config('app.key'));
    }
    
    private function regenerateToken($user)
    {
        $token = $this->getToken();
        
        Activation::where('user_id', $user->id)->update([
            'token' => $token,
            'created_at' => new Carbon()
        ]);
        
        return $token;
    }

    private function createToken($user)
    {
        $token = $this->getToken();
        
        Activation::insert([
            'user_id' => $user->id,
            'token' => $token,
            'created_at' => new Carbon()
        ]);
        
        return $token;
    }

    public function getActivation($user)
    {
        return Activation::where('user_id', $user->id)->first();
    }


    public function getActivationByToken($token)
    {
        return Activation::where('token', $token)->first();
    }

    public function deleteActivation($token)
    {
        Activation::where('token', $token)->delete();
    }
}

Не в сети

#38 06.08.2017 20:18:25

htclog81
Откуда: Москва
Сообщений: 192
Сайт

Re: Нужна помощь в компоновке классов и паттернах сервис и репозиторий

А сервисы


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;

    }
    
}
class ActivateEmailService implements ActivateEmailContract
{

    protected $mailer;
    
    protected $activationEmailRepo;
    
    public function __construct(ActivateEmailRepositoryContract $activationEmailRepo)
    {
       $this->activationEmailRepo = $activationEmailRepo;
    }
    
    public function sendActivationMail($user)
    {
        if (!$user || $user->verified) {
            throw new ActivateUserNotFoundException();
        }
        
        $token = $this->activationEmailRepo->createActivation($user);
        
        \Mail::to($user->email)->send(
            new \App\Mail\ActivateEmail(array(
                'email' => $user->email,
                'token' => $token,
            ))
        );
        
    }
    
    public function activateUser($token, $email)
    {
        $activation = $this->activationEmailRepo->getActivationByToken($token);
         
        if ($activation === null) {
            throw new ActivateUserNotFoundException();
        }
        $user = User::find($activation->user_id);
        
        if (!$user) {
            throw new ActivateUserNotFoundException();
        }

        $user->verified = true;

        $user->save();

        $this->activationEmailRepo->deleteActivation($token);

        return $user;

    }
  
}

Что просто вынести в контроллеры??

Не в сети

#39 06.08.2017 20:22:07

htclog81
Откуда: Москва
Сообщений: 192
Сайт

Re: Нужна помощь в компоновке классов и паттернах сервис и репозиторий

То, что отсылку письма можно вынести в нотификацию я понимаю. Оставил это на потом. КРоме того в случае смены мейла, емейл нужно выслать на новый именно мейл, а не тот который в user->email потому я не очень с этим справился в нотификации и оставил просто Mail:: и думаю оно пока ладно.

Но вот вопросы выше особенно куда девать содержимое  class ActivateEmailRepository и class ChangeEmailRepository ответь пожалуйста.

Не в сети

#40 06.08.2017 20:22:40

Re: Нужна помощь в компоновке классов и паттернах сервис и репозиторий

То кажется это ужас жуткий.

Ты видимо чего-то не понимаешь. Если ты в репозитории используешь запросы в Eloquent - это то же самое, что ты в классе модели используешь те же запросы. Разницы здесь нет кроме той, что при этом у тебя есть лишняя абстракция и лишний пакет.

Теперь другой вопрос. Вот эти модели или уж репо. Для Юзера, для Юзерский файлов в общем для крупных кусков бизнес логике. Как правильней привязать к контейнеру?

Они автоматически привязываются, в доках это описано. Привязывать в сервис провайдерах нужно класс к интерфейсу.

В AppService или каждый в своем провайдере?

Как хочешь. Если у тебя пара привязок на приложение, делай в AppServiceProvider.

Нужны ли контракты/фасады все же для каждого такого класса.

Фасады нужны для удобства, мне больше нравится использовать контейнер. Контракты - полностью зависит от твоей архитектуры.

И еще ты не ответил стоит ли методы активации и смены мейла пихать в модель(репо) юзера, или это отдельные модели(репо) должны быть…

Вроде ответил. Смена емэйла в методе контроллера, при этом запросы в модели. В контроллере будет пара строк, а в модели пара методов. Сейчас у тебя в сервисе куча лишнего кода, который можно выкинуть.

Не в сети

#41 06.08.2017 20:25:04

htclog81
Откуда: Москва
Сообщений: 192
Сайт

Re: Нужна помощь в компоновке классов и паттернах сервис и репозиторий

"классе модели используешь те же запросы"

Но класс модели то наследует от  use Illuminate\Database\Eloquent\Model; вот в чем дело... а репо нет..

Не в сети

#42 06.08.2017 20:26:41

htclog81
Откуда: Москва
Сообщений: 192
Сайт

Re: Нужна помощь в компоновке классов и паттернах сервис и репозиторий

"Они автоматически привязываются, в доках это описано. Привязывать в сервис провайдерах нужно класс к интерфейсу."

Покажи пример автоматической привязки и так что бы потом это можно было инжектить в контроллере. Разве для этого app bind не нужен? Пусть в AppService ?

Не в сети

#43 06.08.2017 20:29:21

htclog81
Откуда: Москва
Сообщений: 192
Сайт

Re: Нужна помощь в компоновке классов и паттернах сервис и репозиторий

"Нужны ли контракты/фасады все же для каждого такого класса.
Фасады нужны для удобства, мне больше нравится использовать контейнер. Контракты - полностью зависит от твоей архитектуры."

Да я тоже за  просто привязку классу к контейнеру и за его инжект в контроллере. Видимо для этого нужен просто app bind и все.

С этим ладно. Интереснее вопросы про Репо, про то что не хочу описывать бизнес логику там где наследуется от Eloquent и про то куда девать бизнес логику активации и смены мейла, в UserRepo/UserModel или куда еще?

Не в сети

#44 06.08.2017 20:30:28

htclog81
Откуда: Москва
Сообщений: 192
Сайт

Re: Нужна помощь в компоновке классов и паттернах сервис и репозиторий

" Смена емэйла в методе контроллера, при этом запросы в модели" в какой модели? UserModel или ChangeEmailModel ???

Не в сети

#45 06.08.2017 20:36:59

htclog81
Откуда: Москва
Сообщений: 192
Сайт

Re: Нужна помощь в компоновке классов и паттернах сервис и репозиторий

"Если ты в репозитории используешь запросы в Eloquent - это то же самое, что ты в классе модели используешь те же запросы"

Экземляр репо не может относится к конкретному объекту из таблицы, а экземпляр модели только этим и является. Сравни $item = Item::find(2); тут мы имеем item c id =2 и выполнять скажем $item->getSomeItems(); просто глупо. Получается вещь с ид 2, и обращаемся к ней почему, за несколькими другими вещями.. Что кажется совершенно не логично. В случае с repo не так. тк он не относится к конкретной вещи, а работает с наборами вещей, тк не наследует от Eloquent.

Может тупой пример, но понятный или нет?

Изменено htclog81 (06.08.2017 20:38:10)

Не в сети

#46 06.08.2017 20:52:50

htclog81
Откуда: Москва
Сообщений: 192
Сайт

Re: Нужна помощь в компоновке классов и паттернах сервис и репозиторий

Оставшиеся наиболее актуальные вопросы:

1) Как быть если я хочу использовать модель или репо, но не наследованные от Eloquent значит создаю, что то типа class App\MyModels\User в нем нужные методы типа User->getById($id){return $user} User->getTopUsers() {return $users} User->SetNewEmail($email). Далее бинд этого к контейнеру и инжект в контроллеры.  Верно ли? Есть ли какая четкая алтернатива. Но что бы конкретный экземпляр $user не отдавал никаким уж методом нескольких юзеров, или другого юзера wink

2) Работа с табличками activation и change_email записать токен, обновить токен проверить токен, это в модели/репо юзера, или в отдельных моделях и репо как у меня сейчас лучше?

3) Сервисы убью, валидацию вынесу тут все ясно.

Изменено htclog81 (06.08.2017 20:57:38)

Не в сети

#47 06.08.2017 21:24:48

Re: Нужна помощь в компоновке классов и паттернах сервис и репозиторий

Сравни $item = Item::find(2); тут мы имеем item c id =2 и выполнять скажем $item->getSomeItems();

Ты не так понимаешь. В своем примере ты делаешь:

$item = Item::find(2)->getSomeItems()

Я же тебе говорю про:

$item = $this->item->getByIdWithUsers(2);
$topItems = $this->item->getTopItems()

Простой пример методов в модели:

public function getByIdWithUsers($id)
{
    return $this->with(['clients', 'buyers' => function ($q) {
        $q->where('status', 1);
    }])
    ->find($id);
}

public function getTopItems()
{
    return $this->latest('rating')->take(10)->verified()->get();
}

Не в сети

#48 06.08.2017 21:29:02

htclog81
Откуда: Москва
Сообщений: 192
Сайт

Re: Нужна помощь в компоновке классов и паттернах сервис и репозиторий

"Простой пример методов в модели:"

А можешь показать, примерно весь класс модели в таком случае для понимания?

Изменено htclog81 (06.08.2017 21:29:19)

Не в сети

#49 06.08.2017 21:30:46

htclog81
Откуда: Москва
Сообщений: 192
Сайт

Re: Нужна помощь в компоновке классов и паттернах сервис и репозиторий

"Я же тебе говорю про:
$item = $this->item->getByIdWithUsers(2);"

А после данной строчки будет работать в этом случае $item->getByIdWithUsers(3); и $item->getTopItems();

Хотелось бы, что бы все таки нет. Понимаешь меня?

Изменено htclog81 (06.08.2017 21:31:07)

Не в сети

#50 06.08.2017 21:31:20

Re: Нужна помощь в компоновке классов и паттернах сервис и репозиторий

Оставшиеся наиболее актуальные вопросы

Про 1) не понял. Если в вопросе 2) ты спрашиваешь про эти методы:

public function getChangeEmailByTokenAndEmail($token, $email)
{
    return EmailChange::where(array('token' => $token, 'email' => $email))->first();
}

То их перемещаешь в модель.

Не в сети

Подвал раздела