Русское сообщество разработки на PHP-фреймворке Laravel.
Ты не вошёл. Вход тут.
Где то предлагаешь бизнес логику для юзера писать прямо в модельке элокьент? В классе Models\User?
Мне кажется это неверно… Это же класс именно Eloquent зачем я буду писать в него скажем метод отдающий на выходе скажем коллекцию юзеров… Это же совершенно не логично. Класс описывает одного юзера причем ради использования его в контексте запросов элокьент, а я возвращаю коллекцию…
Или ты об том, что бы вообще никуда не собирать бизнес-логику и запросы для юзера. А прямо в контроллере писать Eloquent запросы? Но не хочется это по контроллерам разбрасывать…
Я говорю про хранение запросов в классах моделей. Лучшие проекты, которые я видел, хранили запросы там и обращались к методом через:
User::getTopUsers() // Через фасад.
или
$this->user->getTopUsers() // После инжекта модели.
С таким кодом приятно работать. Проекты, где запросы хранятся в контроллерах, работать невозможно вообще, если проект более менее крупный. Репозитории видел только у новичков в купе с простынями неструктурированного говнокода.
Изменено AlexeyMezenin (06.08.2017 19:46:40)
Не в сети
Если я все таки уверен, что кроме элоквент моделей и контроллеров, нужен еще слой. То скажи пожалуйста лучше, что бы это было
-Репо
-Сервисный слой
— И то и другое по надобности.
Для крупнейших таблиц и основной функционала UserRepo UserFilesRepo UserSubscriptionsRepo
Или вообще для каждого функционала и почти каждой таблицы UserRepo UserChangeEmailRepo UserActivationRepo итд? Мне кажется скорее для крупнейших…
Не в сети
Не в сети
Не в сети
Не в сети
Не в сети
А методы работы с будующим функционалом связанным с юзером, например файлы юзера (не авы), подписки и платежи юзера?
Зависит от функционала. С файлами работать можно в контроллере, если это 1-2 строки. Если что-то серьезное, тогда двигаешь логику в отдельный класс. Подписки, платежи можно лепить в контроллере, если это вызов какого-либо функционала из стороннего пакета в пару строк. Если тебе необходимо проверить оплату, сохранить данные, послать уведомление, все это можно сделать вызовом соответствующих методов в модели, пакете (или твоем классе) и пр. В таких случаях события удобно использовать.
Если под платежами ты подразумеваешь работу с API напрямую с кучей логики, тогда всю эту логику переносишь в отдельные классы (по сути, делаешь свой пакет для работы с платежной системой).
Основная идея здесь - это хранить логику работы с данными в моделях, логику работы валидации в Request файлах, работу с емэйлами в Notification классах и т.д. А кастомную логику уже пишешь в свои классы.
Изменено AlexeyMezenin (06.08.2017 20:06:19)
Не в сети
- $this->user->getTopUsers() // После инжекта модели.
Инжект модели в контроллер в данном случае как я понимаю. И конечно это должно быть НЕ тоже самое что $user = Auth::user(); те это текущий юзер и его элокьент модель. Ну если сделать так:
$users = $user->getTopUsers();
$other_user = $user::find($other_user_id);
То кажется это ужас жуткий. И лучше уж репо. Но если есть другое решение предлагай… Но я не хочу наследовать свой модель от Eloquent
Не в сети
«С файлами работать можно в контроллере, если это 1-2 строки. Если что-то серьезное, тогда двигаешь логику в отдельный класс.»
Уверен, что сложнее. Про отдельные готовые пакеты пока не уверен. Для файлов скорее нет. Для подписок и оплаты возможно.
Опять же отдельный класс… Но надеюсь не наследованный от Eloquent…
Изменено htclog81 (06.08.2017 19:56:22)
Не в сети
Не в сети
Теперь другой вопрос. Вот эти модели или уж репо. Для Юзера, для Юзерский файлов в общем для крупных кусков бизнес логике. Как правильней привязать к контейнеру?
В AppService или каждый в своем провайдере? Нужны ли контракты/фасады все же для каждого такого класса.
И еще ты не ответил стоит ли методы активации и смены мейла пихать в модель(репо) юзера, или это отдельные модели(репо) должны быть…
Вот это ответь и отстану… И возможно через пару дней покажу переписанный код…
Изменено htclog81 (06.08.2017 19:58:56)
Не в сети
И все же совсем конкретно:
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();
}
}
Не в сети
А сервисы
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;
}
}
Что просто вынести в контроллеры??
Не в сети
То, что отсылку письма можно вынести в нотификацию я понимаю. Оставил это на потом. КРоме того в случае смены мейла, емейл нужно выслать на новый именно мейл, а не тот который в user->email потому я не очень с этим справился в нотификации и оставил просто Mail:: и думаю оно пока ладно.
Но вот вопросы выше особенно куда девать содержимое class ActivateEmailRepository и class ChangeEmailRepository ответь пожалуйста.
Не в сети
То кажется это ужас жуткий.
Ты видимо чего-то не понимаешь. Если ты в репозитории используешь запросы в Eloquent - это то же самое, что ты в классе модели используешь те же запросы. Разницы здесь нет кроме той, что при этом у тебя есть лишняя абстракция и лишний пакет.
Теперь другой вопрос. Вот эти модели или уж репо. Для Юзера, для Юзерский файлов в общем для крупных кусков бизнес логике. Как правильней привязать к контейнеру?
Они автоматически привязываются, в доках это описано. Привязывать в сервис провайдерах нужно класс к интерфейсу.
В AppService или каждый в своем провайдере?
Как хочешь. Если у тебя пара привязок на приложение, делай в AppServiceProvider.
Нужны ли контракты/фасады все же для каждого такого класса.
Фасады нужны для удобства, мне больше нравится использовать контейнер. Контракты - полностью зависит от твоей архитектуры.
И еще ты не ответил стоит ли методы активации и смены мейла пихать в модель(репо) юзера, или это отдельные модели(репо) должны быть…
Вроде ответил. Смена емэйла в методе контроллера, при этом запросы в модели. В контроллере будет пара строк, а в модели пара методов. Сейчас у тебя в сервисе куча лишнего кода, который можно выкинуть.
Не в сети
"классе модели используешь те же запросы"
Но класс модели то наследует от use Illuminate\Database\Eloquent\Model; вот в чем дело... а репо нет..
Не в сети
"Они автоматически привязываются, в доках это описано. Привязывать в сервис провайдерах нужно класс к интерфейсу."
Покажи пример автоматической привязки и так что бы потом это можно было инжектить в контроллере. Разве для этого app bind не нужен? Пусть в AppService ?
Не в сети
"Нужны ли контракты/фасады все же для каждого такого класса.
Фасады нужны для удобства, мне больше нравится использовать контейнер. Контракты - полностью зависит от твоей архитектуры."
Да я тоже за просто привязку классу к контейнеру и за его инжект в контроллере. Видимо для этого нужен просто app bind и все.
С этим ладно. Интереснее вопросы про Репо, про то что не хочу описывать бизнес логику там где наследуется от Eloquent и про то куда девать бизнес логику активации и смены мейла, в UserRepo/UserModel или куда еще?
Не в сети
" Смена емэйла в методе контроллера, при этом запросы в модели" в какой модели? UserModel или ChangeEmailModel ???
Не в сети
"Если ты в репозитории используешь запросы в Eloquent - это то же самое, что ты в классе модели используешь те же запросы"
Экземляр репо не может относится к конкретному объекту из таблицы, а экземпляр модели только этим и является. Сравни $item = Item::find(2); тут мы имеем item c id =2 и выполнять скажем $item->getSomeItems(); просто глупо. Получается вещь с ид 2, и обращаемся к ней почему, за несколькими другими вещями.. Что кажется совершенно не логично. В случае с repo не так. тк он не относится к конкретной вещи, а работает с наборами вещей, тк не наследует от Eloquent.
Может тупой пример, но понятный или нет?
Изменено htclog81 (06.08.2017 20:38:10)
Не в сети
Оставшиеся наиболее актуальные вопросы:
1) Как быть если я хочу использовать модель или репо, но не наследованные от Eloquent значит создаю, что то типа class App\MyModels\User в нем нужные методы типа User->getById($id){return $user} User->getTopUsers() {return $users} User->SetNewEmail($email). Далее бинд этого к контейнеру и инжект в контроллеры. Верно ли? Есть ли какая четкая алтернатива. Но что бы конкретный экземпляр $user не отдавал никаким уж методом нескольких юзеров, или другого юзера
2) Работа с табличками activation и change_email записать токен, обновить токен проверить токен, это в модели/репо юзера, или в отдельных моделях и репо как у меня сейчас лучше?
3) Сервисы убью, валидацию вынесу тут все ясно.
Изменено htclog81 (06.08.2017 20:57:38)
Не в сети
Сравни $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();
}
Не в сети
"Простой пример методов в модели:"
А можешь показать, примерно весь класс модели в таком случае для понимания?
Изменено htclog81 (06.08.2017 21:29:19)
Не в сети
"Я же тебе говорю про:
$item = $this->item->getByIdWithUsers(2);"
А после данной строчки будет работать в этом случае $item->getByIdWithUsers(3); и $item->getTopItems();
Хотелось бы, что бы все таки нет. Понимаешь меня?
Изменено htclog81 (06.08.2017 21:31:07)
Не в сети
Оставшиеся наиболее актуальные вопросы
Про 1) не понял. Если в вопросе 2) ты спрашиваешь про эти методы:
public function getChangeEmailByTokenAndEmail($token, $email)
{
return EmailChange::where(array('token' => $token, 'email' => $email))->first();
}
То их перемещаешь в модель.
Не в сети