Мне любопытно, как другие разработчики работают с фреймворком Laravel. Я видел выступление Adam Wathan о написании кода контроллера ресурсов и о том, насколько просто / чисто он выглядит.
Я хотел бы поделиться с сообществом тем, как они работают с Laravel. Мне бы хотелось узнать что-то новое и посмотреть, что я могу улучшить с помощью моих шаблонов проектирования.
В моем коде прямо сейчас я использую следующий подход:
Там, где это возможно, я стараюсь следовать принципам SOLID в качестве общего руководства. Итак, без дальнейших вступлений, перейдем к коду.
Мне нравится использовать Laravel Resource Controllers. В качестве примера давайте создадим страницу со списком пицц (index). Я также добавил два примера, чтобы показать вложенную страницу заказа относящуюся только к пицце. (страница для создания, а затем, наконец, страница для сохранения заказа).
Итоговые маршруты:
Вы заметите, что я определяю свои пространства имен на основе URL. Причина, по которой я это делаю, заключается в том, что мне легко понять, где отлаживать, в случае сбоя. Плюс, на мой взгляд, это разделяет задачи и позволяет мне контролировать мои контроллеры только 7 действиями, которые обрабатывает контроллер ресурсов.
Как вы можете увидеть выше, я стараюсь использовать только 7 методов, как это предлагается в документации Laravel для Resource Controllers:
Я признаю, что бывают моменты когда я могу использовать дополнительные методы внутри определенного класса контроллера, если я чувствую, что это имеет смысл, но я стараюсь делать это редко.
Мои методы контроллера будут использовать автоматическую инъекцию для загрузки класса Service. Итак, для нашей страницы списка пицц мы х��тим использовать PizzaService, чтобы получить всю пиццу из базы данных.
Примечание: view также соответствуют тому же шаблону папок, что и пространства имен.
Мне нравится использовать Сервисы для обработки логики в моих приложениях. Сервис для меня может быть концепцией Domain Driven или 1-к-1 с помощью модели (таблицы базы данных). У меня есть абстрактный класс, который обрабатывает общие методы, которые я много использую в моих Сервисах. (Примечание: комментарии / dockblock удалены в примерах кода)
Поэтому мой Domain / Model based Service выглядит так:
В этом PizzaService я могу добавить свои собственные методы, специфичные для логики, которую я пытаюсь реализовать. В продолжении страницы списка пицц
Репозитории в моем коде — это в основном методы, которые используют Eloquent для получения или записи данных в БД. Только Сервис может вызывать уровень репозитория. (Я сомневался в этом подходе, но сейчас я всегда стараюсь следовать ему).
Итак, PizzaRepository, который загружается PizzaService, выглядит так:
Следует также отметить, что в моих Сервисах и Репозиториях, если мне нужно, я могу перезаписать методы по умолчанию, чтобы использовать мою собственную реализацию. Вы помните ранее в нашем примере списка пицц, BaseService вызывал метод all() в репозитории. Теперь, поскольку PizzaRepositoryis не перезаписывает BaseRepository, он использует метод all() в BaseRepository, чтобы вернуть список всех пицц из базы данных.
В качестве примера при перезаписи метода BaseRepository один из моих методов использует хранимые процедуры для вставки данных, поэтому я мог бы перезаписать метод create из BaseRepository, например так:
Это простой пример, но теперь я возвращаю гидратированный результат из моей хранимой процедуры.
Я просто ввел идею Трейтов в мой код. Это произошло, когда я обнаружил, что некоторые из моих слоев репозитория нуждаются в возможности настроить сортировку (вы заметите, что у моего BaseRepository есть два свойства sortBy и sortOrder. Поэтому я создал признак Sortable. Теперь я могу сортировать страницу с списком пицц этими свойствами.
Итак, теперь в моем Сервисе, где я применил Трейт, я могу установить сортировку. (Пример ниже устанавливает порядок в конструкторе, но вы также можете запустить этот метод в своих настраиваемых методах Сервиса.)
У меня также были некоторые трудности с попыткой выяснить, как справиться с жадной загрузкой. Мне не понравилась идея вернуть данные моему контроллеру, а затем использовать ленивую жадную загрузку. Это было не слишком удобно для оптимизации запросов к базе данных. Как только я сделал Sortable Трейт, я решил сделать подобный трейт Relationable.
Затем я добавил метод with () в мои методы BaseRepository:
Через мой сервис я могу добавить следующий код к любому методу (orders — это метод отношений модели).
Я беспокоюсь, что с течением времени может быть легко усложнить мое приложение со слишком многими Трейтами, но сейчас оно работает очень хорошо.
Я хотел бы поделиться с сообществом тем, как они работают с Laravel. Мне бы хотелось узнать что-то новое и посмотреть, что я могу улучшить с помощью моих шаблонов проектирования.
В моем коде прямо сейчас я использую следующий подход:
Controller -> Service -> Repository -> ModelТам, где это возможно, я стараюсь следовать принципам SOLID в качестве общего руководства. Итак, без дальнейших вступлений, перейдем к коду.
Routes
Мне нравится использовать Laravel Resource Controllers. В качестве примера давайте создадим страницу со списком пицц (index). Я также добавил два примера, чтобы показать вложенную страницу заказа относящуюся только к пицце. (страница для создания, а затем, наконец, страница для сохранения заказа).
Route::resource('/pizzas', 'PizzaController', ['only' => [
'index',
]]);
Route::group(['prefix' => 'pizzas'], function() {
Route::resource('/orders', 'Pizza\OrderController', ['only' => [
'create', 'store',
]]);
});
Итоговые маршруты:
GET /pizzas
App\Http\Controllers\PizzaController@index
GET /pizzas/orders/create
App\Http\Controllers\Pizza\OrderController@create
POST /pizzas
App\Http\Controllers\Pizza\OrderController@store
Вы заметите, что я определяю свои пространства имен на основе URL. Причина, по которой я это делаю, заключается в том, что мне легко понять, где отлаживать, в случае сбоя. Плюс, на мой взгляд, это разделяет задачи и позволяет мне контролировать мои контроллеры только 7 действиями, которые обрабатывает контроллер ресурсов.
- Pizza\OrderController — несет ответственность только за обработку заказов пиццы
- PizzaController — несет ответственность за обработку деталей для пиццы
Controllers
Как вы можете увидеть выше, я стараюсь использовать только 7 методов, как это предлагается в документации Laravel для Resource Controllers:
- index()
- create()
- store()
- show()
- edit()
- update()
- destroy()
Я признаю, что бывают моменты когда я могу использовать дополнительные методы внутри определенного класса контроллера, если я чувствую, что это имеет смысл, но я стараюсь делать это редко.
Мои методы контроллера будут использовать автоматическую инъекцию для загрузки класса Service. Итак, для нашей страницы списка пицц мы х��тим использовать PizzaService, чтобы получить всю пиццу из базы данных.
public function index(PizzaService $pizzaService)
{
return view('pizza.index', [
'pizzas' => $pizzaService->all(),
]);
}
Примечание: view также соответствуют тому же шаблону папок, что и пространства имен.
Services
Мне нравится использовать Сервисы для обработки логики в моих приложениях. Сервис для меня может быть концепцией Domain Driven или 1-к-1 с помощью модели (таблицы базы данных). У меня есть абстрактный класс, который обрабатывает общие методы, которые я много использую в моих Сервисах. (Примечание: комментарии / dockblock удалены в примерах кода)
<?php
namespace App\Services;
abstract class BaseService
{
public $repo;
public function all()
{
return $this->repo->all();
}
public function paginated()
{
return $this->repo->paginated(config('paginate'));
}
public function create(array $input)
{
return $this->repo->create($input);
}
public function find($id)
{
return $this->repo->find($id);
}
public function update($id, array $input)
{
return $this->repo->update($id, $input);
}
public function destroy($id)
{
return $this->repo->destroy($id);
}
}
Поэтому мой Domain / Model based Service выглядит так:
<?php
namespace App\Services;
use App\Repositories\PizzaRepository;
class PizzaService extends BaseService
{
private $pizzaRepository;
public function __construct(PizzaRepository $pizzaRepository)
{
$this->pizzaRepository = $pizzaRepository;
}
}
В этом PizzaService я могу добавить свои собственные методы, специфичные для логики, которую я пытаюсь реализовать. В продолжении страницы списка пицц
$pizzaService->all() вызывает метод all() в BaseRepository, поскольку мы не перезаписываем его.Repositories
Репозитории в моем коде — это в основном методы, которые используют Eloquent для получения или записи данных в БД. Только Сервис может вызывать уровень репозитория. (Я сомневался в этом подходе, но сейчас я всегда стараюсь следовать ему).
<?php
namespace App\Repositories;
use Illuminate\Database\Eloquent\Model;
abstract class BaseRepository
{
public $sortBy = 'created_at';
public $sortOrder = 'asc';
public function all()
{
return $this->model
->orderBy($this->sortBy, $this->sortOrder)
->get();
}
public function paginated($paginate)
{
return $this
->model
->orderBy($this->sortBy, $this->sortOrder)
->paginate($paginate);
}
public function create($input)
{
$model = $this->model;
$model->fill($input);
$model->save();
return $model;
}
public function find($id)
{
return $this->model->where('id', $id)->first();
}
public function destroy($id)
{
return $this->find($id)->delete();
}
public function update($id, array $input)
{
$model = $this->find($id);
$model->fill($input);
$model->save();
return $model;
}
}
Итак, PizzaRepository, который загружается PizzaService, выглядит так:
<?php
namespace App\Repositories;
use App\Models\Pizza;
class PizzaRepository extends BaseRepository
{
protected $model;
public function __construct(Pizza $pizza)
{
$this->model = $pizza;
}
}
Следует также отметить, что в моих Сервисах и Репозиториях, если мне нужно, я могу перезаписать методы по умолчанию, чтобы использовать мою собственную реализацию. Вы помните ранее в нашем примере списка пицц, BaseService вызывал метод all() в репозитории. Теперь, поскольку PizzaRepositoryis не перезаписывает BaseRepository, он использует метод all() в BaseRepository, чтобы вернуть список всех пицц из базы данных.
В качестве примера при перезаписи метода BaseRepository один из моих методов использует хранимые процедуры для вставки данных, поэтому я мог бы перезаписать метод create из BaseRepository, например так:
<?php
namespace App\Repositories;
use App\Models\Pizza;
class PizzaRepository extends BaseRepository
{
protected $model;
public function __construct(Pizza $pizza)
{
$this->model = $pizza;
}
public function create(array $input)
{
return $this->model->hydrate(
DB::select(
'CALL create_pizza(?, ?)',
[
$name,
$hasCheese,
]
)
);
}
}
Это простой пример, но теперь я возвращаю гидратированный результат из моей хранимой процедуры.
Traits
Я просто ввел идею Трейтов в мой код. Это произошло, когда я обнаружил, что некоторые из моих слоев репозитория нуждаются в возможности настроить сортировку (вы заметите, что у моего BaseRepository есть два свойства sortBy и sortOrder. Поэтому я создал признак Sortable. Теперь я могу сортировать страницу с списком пицц этими свойствами.
<?php
namespace App\Repositories\Traits;
trait Sortable
{
public $sortBy = 'created_at';
public $sortOrder = 'asc';
public function setSortBy($sortBy = 'created_at')
{
$this->sortBy = $sortBy;
}
public function setSortOrder($sortOrder = 'desc')
{
$this->sortOrder = $sortOrder;
}
}
Итак, теперь в моем Сервисе, где я применил Трейт, я могу установить сортировку. (Пример ниже устанавливает порядок в конструкторе, но вы также можете запустить этот метод в своих настраиваемых методах Сервиса.)
<?php
namespace App\Services;
use App\Repositories\PizzaRepository;
class PizzaService extends BaseService
{
private $pizzaRepository;
public function __construct(PizzaRepository $pizzaRepository)
{
$this->pizzaRepository = $pizzaRepository;
$this->pizzaRepository->setSortBy('sort_order');
}
}
У меня также были некоторые трудности с попыткой выяснить, как справиться с жадной загрузкой. Мне не понравилась идея вернуть данные моему контроллеру, а затем использовать ленивую жадную загрузку. Это было не слишком удобно для оптимизации запросов к базе данных. Как только я сделал Sortable Трейт, я решил сделать подобный трейт Relationable.
<?php
namespace App\Repositories\Traits;
trait Relationable
{
public $relations = [];
public function setRelations($relations = null)
{
$this->relations = $relations;
}
}
Затем я добавил метод with () в мои методы BaseRepository:
public function all()
{
return $this->model
->with($this->relations)
->orderBy($this->sortBy, $this->sortOrder)
->get();
}
Через мой сервис я могу добавить следующий код к любому методу (orders — это метод отношений модели).
$this->repo->setRelations([‘orders’]);Я беспокоюсь, что с течением времени может быть легко усложнить мое приложение со слишком многими Трейтами, но сейчас оно работает очень хорошо.
