Repository Pattern và Service Pattern trong Laravel thông qua ứng dụng Todo
Để áp dụng Repository Pattern một cách chuyên nghiệp và tuân thủ nguyên tắc Dependency Injection, chúng ta cần tạo interface cho Repository và đăng ký nó trong Service Provider của Laravel. Việc này giúp cho code dễ bảo trì hơn, cũng như tăng tính linh hoạt khi muốn thay đổi cách xử lý của Repository (ví dụ, chuyển sang dùng một repository khác mà không cần thay đổi service hoặc controller).
1. Tạo Interface cho TodoRepository
Tạo một interface để định nghĩa các phương thức mà TodoRepository
cần triển khai. Interface này giúp tách biệt lớp Repository thực thi và dịch vụ sử dụng nó, tăng tính linh hoạt cho ứng dụng.
Tạo file app/Repositories/TodoRepositoryInterface.php
:
<?php
namespace App\Repositories;
interface TodoRepositoryInterface
{
public function getAll();
public function create(array $data);
public function find($id);
public function update($id, array $data);
public function delete($id);
}
PHP2. Triển khai Interface trong TodoRepository
Viết code cho file TodoRepository
để implement interface TodoRepositoryInterface
. Điều này đảm bảo rằng TodoRepository
phải tuân thủ các phương thức được định nghĩa trong interface.
Tạo file app/Repositories/TodoRepository.php
:
<?php
namespace App\Repositories;
use App\Models\Todo;
class TodoRepository implements TodoRepositoryInterface
{
public function getAll()
{
return Todo::all();
}
public function create(array $data)
{
return Todo::create($data);
}
public function find($id)
{
return Todo::findOrFail($id);
}
public function update($id, array $data)
{
$todo = Todo::findOrFail($id);
$todo->update($data);
return $todo;
}
public function delete($id)
{
return Todo::destroy($id);
}
}
PHP3. Đăng ký Binding trong Service Provider
Chúng ta cần đăng ký interface TodoRepositoryInterface
với lớp triển khai TodoRepository
để Laravel biết khi nào cần sử dụng TodoRepository
thông qua interface.
Tạo Service Provider bằng lệnh sau nếu chưa có file RepositoryServiceProvider
:
php artisan make:provider RepositoryServiceProvider
BashMở file app/Providers/RepositoryServiceProvider.php
và thêm binding cho interface và lớp triển khai:
<?php
namespace App\Providers;
use Illuminate\Support\ServiceProvider;
use App\Repositories\TodoRepositoryInterface;
use App\Repositories\TodoRepository;
class RepositoryServiceProvider extends ServiceProvider
{
/**
* Register services.
*
* @return void
*/
public function register()
{
$this->app->bind(TodoRepositoryInterface::class, TodoRepository::class);
}
/**
* Bootstrap services.
*
* @return void
*/
public function boot()
{
//
}
}
PHP4. Đăng ký RepositoryServiceProvider trong config/app.php
Để Laravel nhận diện Service Provider này, bạn cần đăng ký nó trong mảng providers
ở file config/app.php
:
'providers' => [
// Các provider khác
App\Providers\RepositoryServiceProvider::class,
],
PHP5. Sử dụng Interface trong Service
Bây giờ, trong TodoService
, bạn có thể sử dụng TodoRepositoryInterface
để tăng tính linh hoạt và dễ bảo trì. Điều này cũng giúp bạn dễ dàng thay đổi sang các repository khác mà không phải sửa đổi nhiều mã nguồn.
Tạo file app/Services/TodoService.php
như sau:
<?php
namespace App\Services;
use App\Repositories\TodoRepositoryInterface;
use Illuminate\Support\Facades\Storage;
class TodoService
{
protected $todoRepository;
public function __construct(TodoRepositoryInterface $todoRepository)
{
$this->todoRepository = $todoRepository;
}
public function getAllTodos()
{
return $this->todoRepository->getAll();
}
public function createTodo(array $data)
{
if (isset($data['image'])) {
$data['image_path'] = $data['image']->store('images', 'public');
}
return $this->todoRepository->create($data);
}
public function updateTodo($id, array $data)
{
if (isset($data['image'])) {
$todo = $this->todoRepository->find($id);
if ($todo->image_path) {
Storage::disk('public')->delete($todo->image_path);
}
$data['image_path'] = $data['image']->store('images', 'public');
}
return $this->todoRepository->update($id, $data);
}
public function deleteTodo($id)
{
$todo = $this->todoRepository->find($id);
if ($todo->image_path) {
Storage::disk('public')->delete($todo->image_path);
}
return $this->todoRepository->delete($id);
}
}
PHP6. Controller
Tạo app/Http/Controllers/TodoController.php
để xử lý các request từ người dùng.
<?php
namespace App\Http\Controllers;
use App\Services\TodoService;
use Illuminate\Http\Request;
class TodoController extends Controller
{
protected $todoService;
public function __construct(TodoService $todoService)
{
$this->todoService = $todoService;
}
public function index()
{
$todos = $this->todoService->getAllTodos();
return response()->json($todos);
}
public function store(Request $request)
{
$data = $request->validate([
'title' => 'required|string|max:255',
'description' => 'nullable|string',
'image' => 'nullable|image|mimes:jpeg,png,jpg,gif,svg|max:2048',
]);
$todo = $this->todoService->createTodo($data);
return response()->json($todo, 201);
}
public function update(Request $request, $id)
{
$data = $request->validate([
'title' => 'required|string|max:255',
'description' => 'nullable|string',
'image' => 'nullable|image|mimes:jpeg,png,jpg,gif,svg|max:2048',
'status' => 'boolean',
]);
$todo = $this->todoService->updateTodo($id, $data);
return response()->json($todo);
}
public function destroy($id)
{
$this->todoService->deleteTodo($id);
return response()->json(['message' => 'Todo deleted successfully']);
}
}
PHP7. Route
Thêm route vào file routes/api.php
:
use App\Http\Controllers\TodoController;
Route::apiResource('todos', TodoController::class);
PHP8. Feedback và Giải thích
- Repository Pattern: Giúp dễ dàng thay đổi cách dữ liệu được lưu trữ hoặc truy xuất mà không cần thay đổi logic nghiệp vụ trong các service hoặc controller.
- Service Pattern: Tách biệt logic nghiệp vụ khỏi controller, giúp dễ dàng quản lý và mở rộng các tính năng.
- Xử lý hình ảnh: Lưu trữ hình ảnh trong thư mục
public/images
với cấu hình driver lưu trữpublic
.
Lời Kết
Việc sử dụng interface giúp chúng ta dễ dàng thay đổi implementation của TodoRepository
trong tương lai mà không phải thay đổi toàn bộ các phần khác trong ứng dụng. Bằng cách này, bạn có thể tuân thủ nguyên tắc Dependency Inversion trong SOLID, giúp ứng dụng dễ bảo trì và mở rộng hơn.