Вступ
- Коротко ознайомимося з концепцією патерну репозиторію та його призначенням.
- Пояснимо, чому використання репозиторіїв допомагає в абстрагуванні логіки доступу до даних і робить застосунок більш підтримуваним, тестованим і масштабованим.
1. Створення базового репозиторію та інтерфейсу IBaseRepository
У вас вже є клас BaseRepository
. Обговоримо, як він реалізує загальні методи для операцій CRUD, а також пояснимо призначення інтерфейсу IBaseRepository
.
- Методи, які включені:
create
,findById
,findFirstByConditions
,findAll
,findAllWithPagination
тощо. - Переваги: Забезпечує дотримання принципу DRY (Don’t Repeat Yourself), централізуючи загальні операції з базою даних.
model = $model;
}
/**
* Створити новий ресурс.
*
* @param array $attributes
* @return Model
*/
public function create(array $attributes): Model
{
return $this->model->create($attributes);
}
/**
* Знайти ресурс за його ID.
*
* @param int|string $id
* @param array $columns
* @return Model|null
* @throws \Illuminate\Database\Eloquent\ModelNotFoundException
*/
public function findById(int|string $id, array $columns = ['*']): ?Model
{
return $this->model->findOrFail($id, $columns);
}
/**
* Знайти ресурс за його умовами.
*
* @param array $conditions
* @param array $columns
* @return Model|null
* @throws \Illuminate\Database\Eloquent\ModelNotFoundException
*/
public function findFirstByConditions(array $conditions, array $columns = ['*']): ?Model
{
return $this->model->where($conditions)->select($columns)->first();
}
/**
* Отримати всі ресурси з необов’язковими фільтрами.
*
* @param array $conditions
* @param array $columns
* @return Collection|array
*/
public function findAll(array $conditions = [], array $columns = ['*']): Collection|array
{
return $this->model->where($conditions)->get($columns);
}
/**
* Отримати ресурси з пагінацією на основі фільтрів.
*
* @param int $limit
* @param array $conditions
* @param array $columns
* @return LengthAwarePaginator
*/
public function findAllWithPagination(array $conditions = [], array $columns = ['*'], int $limit): LengthAwarePaginator
{
return $this->model->where($conditions)->paginate($limit);
}
/**
* Отримати обмежену кількість ресурсів з необов’язковими фільтрами.
*
* @param int $limit
* @param array $conditions
* @param array $columns
* @return Collection
*/
public function findByLimit(int $limit, array $conditions = [], array $columns = ['*']): Collection
{
return $this->model->where($conditions)->limit($limit)->get($columns);
}
/**
* Оновити ресурс за його ID.
*
* @param array $conditions
* @param array $attributes
* @return int
*/
public function update(array $conditions, array $attributes): int
{
return $this->model->where($conditions)->update($attributes);
}
/**
* Оновити або створити ресурс.
*
* @param array $conditions
* @param array $attributes
* @return Model
*/
public function updateOrCreate(array $conditions, array $attributes): Model
{
return $this->model->updateOrCreate($conditions, $attributes);
}
/**
* Видалити ресурс за його ID.
*
* @param int|string $id
* @return int
*/
public function deleteById(int|string $id): int
{
return $this->model->destroy($id);
}
/**
* Видалити кілька ресурсів за умовами.
*
* @param array $conditions
* @return int Кількість видалених записів.
*/
public function deleteAll(array $conditions): int
{
return $this->model->where($conditions)->delete();
}
}
2. Створення репозиторію для конкретних моделей
- Пояснимо, як розширити
BaseRepository
для конкретних моделей, таких якUserRepository
або будь-яка інша модель. -
Покажемо приклади кастомних методів, таких як отримання користувачів з певними ролями або з іншими зв'язками, якщо це необхідно.
-
- @param array $conditions
- @param array $columns
- @return Collection|array
/
public function getUserWithRole(array $conditions = [], array $columns = ['']): Collection|array
{
return $this->model
->with(['role'])
->where($conditions)->get($columns);
}
}
```
3. Шар сервісів для бізнес-логіки
- Введемо шар сервісів для подальшого абстрагування бізнес-логіки від контролерів. Це гарантує, що контролер залишатиметься "тонким".
- Показуємо, як сервіс-клас взаємодіє з репозиторієм для виконання бізнес-логіки.
modelRepository = $modelRepository;
}
/**
* Створити новий ресурс.
*
* @param array $attributes
* @return mixed
*/
public function create(array $attributes): mixed
{
return $this->modelRepository->create($attributes);
}
/**
* Знайти ресурс за його ID.
*
* @param int|string $id
* @param string[] $columns
* @return mixed
*/
public function findById(int|string $id, $columns = ['*']): mixed
{
return $this->modelRepository->findById($id, $columns);
}
/**
* Знайти ресурс за його умовами.
*
* @param array $conditions
* @param string[] $columns
* @return mixed
*/
public function findFirstByConditions(array $conditions, array $columns = ['*']): mixed
{
return $this->modelRepository->findFirstByConditions($conditions, $columns);
}
/**
* Отримати всі ресурси з необов'язковими фільтрами.
*
* @param array $conditions
* @param string[] $columns
* @return Collection|array
*/
public function findAll(array $conditions = [], array $columns = ['*']): Collection|array
{
return $this->modelRepository->findAll($conditions, $columns);
}
/**
* Отримати ресурси з пагінацією на основі фільтрів.
*
* @param int $limit
* @param array $conditions
* @param string[] $columns
* @return mixed
*/
public function findAllWithPagination(array $conditions = [], array $columns = ['*'], int $limit): mixed
{
return $this->modelRepository->findAllWithPagination($conditions, $columns, $limit);
}
/**
* Отримати обмежену кількість ресурсів з необов'язковими фільтрами.
*
* @param int $limit
* @param array $conditions
* @param string[] $columns
* @return mixed
*/
public function findByLimit(int $limit, array $conditions = [], array $columns = ['*']): mixed
{
return $this->modelRepository->findByLimit($limit, $conditions, $columns);
}
/**
* Оновити ресурс за його ID.
*
* @param array $conditions
* @param array $attributes
* @return mixed
*/
public function update(array $conditions, array $attributes): mixed
{
return $this->modelRepository->update($conditions, $attributes);
}
/**
* Оновити або створити ресурс.
*
* @param array $conditions
* @param array $attributes
* @return mixed
*/
public function updateOrCreate(array $conditions, array $attributes): mixed
{
return $this->modelRepository->updateOrCreate($conditions, $attributes);
}
/**
* Видалити ресурс за його ID.
*
* @param int|string $id
* @return mixed
*/
public function deleteById(int|string $id): mixed
{
return $this->modelRepository->deleteById($id);
}
/**
* Видалити кілька ресурсів на основі умов.
*
* @param array $conditions
* @return int Кількість видалених записів.
*/
public function deleteAll(array $conditions): int
{
return $this->modelRepository->deleteAll($conditions);
}
/**
* Отримати список ресурсів для DataTables.
*
* @param Request $request
* @return JsonResponse|array
*/
public function dataTableList(Request $request): JsonResponse|array
{
return []; // Реалізуйте логіку DataTables тут.
}
}
*
* @return \Illuminate\Http\JsonResponse
*/
public function getUserData(): JsonResponse
{
try {
$data = $this->userRepository->getUserWithRole([], ['id', 'name', 'email', 'created_at', 'role_id']);
return DataTables::of($data)
->addColumn('role_name', function($data) {
return $data->role->name;
})
->addColumn('action', function($data){
return $data->id;
})->toJson();
} catch (Exception $e) {
return response()->json([
'success' => false,
'message' => 'Не вдалося отримати дані. Будь ласка, спробуйте пізніше.' . $e->getMessage(),
]);
}
}
}
4. Оптимізоване зв'язування репозиторіїв та сервісів у провайдері
UserRepository::class,
];
$services = [
IUserService::class => UserService::class,
];
$bindings = array_merge($repositories, $services);
$this->bindServiceRepositories($bindings);
}
/**
* Допоміжна функція для зв'язування інтерфейсів репозиторіїв з їхніми реалізаціями
*/
protected function bindServiceRepositories(array $repositories): void
{
foreach ($repositories as $interface => $implementation) {
$this->app->bind($interface, $implementation);
}
}
/**
* Ініціалізація сервісів.
*/
public function boot(): void
{
//
}
}
5. Використання сервісу в контролері
with([]);
}
/**
* Отримати дані користувачів для DataTables.
*
* @param \Illuminate\Http\Request $request
* @return \Illuminate\Http\JsonResponse
*/
public function getDatatables(Request $request): JsonResponse
{
if ($request->ajax()) {
return $this->userService->getUserData();
}
return response()->json([
'success' => false,
'message' => 'Невірний запит.',
]);
}
/**
* Показати форму для створення нового ресурсу.
*
* @return View
*/
public function create(): View
{
return view('admin.user.create')->with([]);
}
/**
* Зберегти новостворений ресурс у сховищі.
*
* @param UserRequest $request
* @return RedirectResponse
*/
public function store(CreateUserRequest $request): RedirectResponse
{
DB::beginTransaction();
try {
$response = $this->userService->create($request->all());
DB::commit();
return redirect()->back()->with('success', __('user_module.create_list_edit.user') . __('standard_curd_common_label.success'));
} catch (Exception $e) {
DB::rollBack();
return redirect()->back()->with('error', __('standard_curd_common_label.error'));
}
return redirect()->back()->with('error', __('standard_curd_common_label.error'));
}
/**
* Показати вказаний ресурс.
*
* @param string $id
* @return View
*/
public function show(string $id) // : View
{
// Ви можете додати логіку для отримання та повернення даних для конкретного ресурсу тут.
}
/**
* Показати форму для редагування вказаного ресурсу.
*
* @param string $id
* @return View
*/
public function edit(string $id): View
{
try {
$response = $this->userService->findById($id);
return view('admin.user.edit')->with([
'data' => $response,
]);
} catch (Exception $e) {
return redirect()->back()->with('error', __('standard_curd_common_label.error'));
}
}
/**
* Оновити вказаний ресурс у сховищі.
*
* @param UpdateUserRequest $request
* @param string $id
* @return RedirectResponse
*/
public function update(UpdateUserRequest $request, string $id): RedirectResponse
{
try {
$data = $request->except(['_token', '_method']);
if (!empty($data['password'])) {
$data['password'] = bcrypt($data['password']);
} else {
unset($data['password']);
}
$this->userService->update(['id' => $id], $data);
return redirect()->back()->with('success', __('user_module.create_list_edit.user') . __('standard_curd_common_label.update_success'));
} catch (Exception $e) {
return redirect()->back()->with('error', __('standard_curd_common_label.error'));
}
}
/**
* Видалити вказаний ресурс зі сховища.
*
* @param string $id
* @return JsonResponse
*/
public function destroy(string $id): JsonResponse
{
try {
$data = $this->userService->deleteById($id);
if ($data) {
return response()->json([
'message' => __('user_module.create_list_edit.user') . __('standard_curd_common_label.delete'),
'status_code' => ResponseAlias::HTTP_OK,
'data' => []
], ResponseAlias::HTTP_OK);
}
return response()->json([
'message' => __('user_module.create_list_edit.user') . __('standard_curd_common_label.delete_is_not'),
'status_code' => ResponseAlias::HTTP_BAD_REQUEST,
'data' => []
], ResponseAlias::HTTP_BAD_REQUEST);
} catch (Exception $e) {
return response()->json([
'message' => __('standard_curd_common_label.error'),
'status_code' => ResponseAlias::HTTP_INTERNAL_SERVER_ERROR,
'data' => []
], ResponseAlias::HTTP_INTERNAL_SERVER_ERROR);
}
}
}
Кращі практики
- Використовуйте впровадження залежностей: Уникайте прямого створення екземплярів репозиторіїв всередині контролерів. Впроваджуйте їх через інжекцію конструктора.
- Розподіл обов'язків: Залишайте репозиторії для обробки даних, а сервіси — для бізнес-логіки.
- Використання інтерфейсів: Завжди визначайте інтерфейси для репозиторіїв, щоб покращити гнучкість і зробити ваш код легшим для тестування.
- Тестований код: Використовуйте PHPUnit для тестування репозиторіїв шляхом заміни запитів до бази даних.
Висновок
Підсумуйте переваги патерну репозиторіїв:
- Масштабованість: З розвитком вашого застосунку ви зможете легко додавати нові методи до репозиторіїв, не впливаючи на інші частини коду.
- Якість коду: Використання патернів проектування, таких як патерн репозиторію, призводить до чистішого та більш підтримуваного коду.
- Інтеграція з Eloquent Laravel: Ви можете використовувати Eloquent ORM Laravel у своєму репозиторії для обробки доступу до даних, зберігаючи структуру вашого коду.
Також можна зазначити, як патерн репозиторію добре поєднується з іншими патернами проектування, такими як патерн сервісів і фабрик.
Перекладено з: Best Way to Implement Repository Pattern in Laravel