Найкращий спосіб реалізації патерну репозиторію в Laravel

Вступ

  • Коротко ознайомимося з концепцією патерну репозиторію та його призначенням.
  • Пояснимо, чому використання репозиторіїв допомагає в абстрагуванні логіки доступу до даних і робить застосунок більш підтримуваним, тестованим і масштабованим.

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

Leave a Reply

Your email address will not be published. Required fields are marked *