Как использовать репозитории и сервисы в Laravel?
Хотелось бы узнать как правильно использовать репозитории и сервисы в Laravel и когда их вообще нужно использовать, поскольку в Laravel есть Eloquent модели.
К примеру есть сайт на тему продажи авто.
Подскажите, насколько правильно или нет будет использовать такой вариант:
создаётся интерфейс, который используется как для выборки авто, так и для выборки из базы данных всего остального,
к примеру категорий, меню:
namespace App\Repositories\Contracts;
interface SiteRepositoryInterface
{
public function all();
public function paginate(array $params);
public function find(int $id);
public function findByAlias(string $alias);
}
к примеру выборка авто
namespace App\Repositories;
class CarRepository implements SiteRepositoryInterface
{
public function all()
{
return Car::whereStatus(1)->get();
}
public function paginate(array $params)
{
$perPage = $params['per_page'] ?? Setting::value('cars_per_page');
...
return $builder->paginate($perPage);
}
public function find(int $id)
{
return Car::find($id);
}
public function findByAlias(string $alias)
{
return Car::whereStatus(1)->where('alias', $alias)->first();
}
}
выборка категорий
namespace App\Repositories;
class CategoryRepository implements SiteRepositoryInterface
{
public function all()
{
return Category::whereStatus(1)->get();
}
public function paginate(array $params)
{
return [];
}
public function find(int $id)
{
return Category::find($id);
}
public function findByAlias(string $alias)
{
return Category::whereStatus(1)->where('alias', $alias)->first();
}
}
namespace App\Services\Contracts;
interface GeneralAppLayerInterface
{
public function getData(array $params = null);
}
interface IndexAppLayerInterface extends GeneralAppLayerInterface {}
namespace App\Services;
class GeneralService implements GeneralAppLayerInterface
{
public $path;
private $route;
private $settings;
private $menu;
private $modelRepo;
public function __construct(
SiteRepositoryInterface $settingRepo,
SiteRepositoryInterface $menuRepo
)
{
$this->route = Route::currentRouteName();
$this->settings = $settingRepo;
$this->menu = $menuRepo;
$this->path = config('app.public_images');
}
public function getData(array $params = null):array
{
$settings = $this->settings->find(1)->toArray();
$menu = $this->menu->all();
$categories = Category::with('images', 'cars.mark', 'marks.models', 'marks.cars')
->whereStatus(1)->where('parent_id', null)->get();
return [
'route' => $this->route,
'title' => $page->name ?? '',
'publicPath' => $this->path,
'menu' => $menu,
'categories' => $categories,
];
}
}
namespace App\Services;
class CarService implements IndexAppLayerInterface
{
public $path;
private $repo;
public function __construct(SiteRepositoryInterface $carrepo)
{
$this->repo = $carrepo;
}
public function getData(array $params = [])
{
$cars = $this->repo->paginate($params);
$marks = $cars->loadMissing('mark')->pluck('mark')->unique()->sort();
$category = Category::with('marks')->where('alias', $params['category_parent'])->first();
...
return [
'cars' => $cars,
'marks' => $marks,
'category' => $category,
...
'years' => $years,
'min_price' => $priceMin,
'max_price' => $priceMax,
];
}
}
namespace App\Providers;
class AppServiceProvider extends ServiceProvider
{
public function register()
{
$this->app->bind(GeneralAppLayerInterface::class, GeneralService::class);
$this->app->singleton(GeneralService::class, function($app) {
return new GeneralService(new SettingRepository(), new MenuRepository());
});
$this->app->when(CarController::class)
->needs(GeneralAppLayerInterface::class)->give(function($app) {
return $app->make(GeneralService::class);
});
$this->app->when(CarController::class)
->needs(IndexAppLayerInterface::class)->give(function($app) {
return $app->make(CarService::class);
});
$this->app->when(CarService::class)
->needs(SiteRepositoryInterface::class)->give(function($app) {
return $app->make(CarRepository::class);
});
}
}
namespace App\Http\Controllers;
class CarController extends Controller
{
public function __construct(GeneralAppLayerInterface $general, IndexAppLayerInterface $carService)
{
$this->general = $general;
$this->service = $carService;
}
public function index()
{
return view('site.index')->with($this->general->getData())->with($this->service->getData());
}
}
Как это сделать правильно? Лишние ли здесь репозитории и сервисы?
Ответы (1 шт):
Репозитории
Работают только с данными из базы. Однако, я встречал, по меньшей мере, 3 мнения на счет как именно их использовать:
Отделение от фреймворка. По сути, этот тот подход, который указан у вас - вы заранее определяете методы для работы с базой, используете их в сервисах. И если захотите сменить ORM, чтобы проще было сохранить работоспособность приложения, замените логику получения данных только в методах репозиториев, а не будете бегать по всему проекту и искать, где используются эти методы. Наверное, такой подход более каноничен и используется в больших высоконагруженных приложениях.
Отделение работы с БД от бизнес логики. Но тут, все равно, репозитории используются только для получения данных. Например, у вас есть модель Car, вы создали для нее репозиторий. У вас есть страница с автомобилями, а также есть страница с покупкой запчастей, где есть select, в котором нужно выбрать модель автомобиля. Так вот у вас в репозитории будет, по меньшей мере, 2 метода -
getCars()иgetCarsForSelectField(). В первом вы сделаете большую выборку с кучей полей, отношений и т.д., во втором, возможно пару полей и все. Такой подход повысит читаемость и организованность кода.Отделение работы с БД от бизнес логики, но с использованием всех CRUD операций, включая create, update и т.д.
На мой взгляд, репозитории были придуманы для более глобальных целей (вариант 1), но никто вас не ограничивает, если вы сможете с использованием репозиториев придумать более элегантный подход к организации приложений и не выстрелите себе в ногу, то почему бы и нет!
Сервисы
Нужны для бизнес логики. Все задачи решаем в сервисах. Но не забываем про Single Responsibility - разбиваем сервис на более мелкие сервисы под конкретную обособленную задачу, чтобы не было божественных классов.
В зависимости от подхода по репозиториям, в сервисах вы будете использовать:
- $data = $this->carsRepository->all(); (Вариант 1)(Как у вас)
- $data = $this->carsRepository->getCars(); $data = $this->carsRepository->getCarsForAnotherPage(); (Вариант 2)
- $data = $this->carsRepository->getCars(); $result= $this->carsRepository->create($createData); (Вариант 3)