Repository Trong Laravel


Laravel đã hỗ trợ mạnh mẽ trong việc truy vấn đến csdl một cách dễ dàng thông qua Model, nhưng nó có một số khuyết điểm đáng để quan tâm. Và những điều đó là lí do ta nên sử dụng Repository.

Design Pattern là gì?

  • Design Pattern là một kỹ thuật trong lập trình hướng đối tượng, được các lập trình viên, các nhà nghiên cứu đúc kết các thiết kế chuẩn cho việc lập trình có khuôn mẫu, kết cấu chung.
  • Design Pattern không phải là một ngôn ngữ cụ thể nào cả, tất cả các ngôn ngữ lập trình hay Framework của nó đều có thể áp dụng Design Pattern.
  • Design Pattern giúp các dự án dễ mở rộng, hạn chế bug và maintain được hiệu quả.
  • Design Pattern giúp thống nhất cách code chung nên các lập trình viên sẽ làm việc nhóm được hiệu quả tránh tình trạng mỗi người code 1 kiểu.

Repository

Đây là một mẫu thiết kế nâng cao mà các bạn mới tiếp xúc lập trình có lẽ cũng không để ý về nó lắm. Đối với các bạn đã có kinh nghiệm thực tập hay làm việc ở các công ti – chắc hẳn cũng đã được nghe các mentor của mình nói về nó.

  • Repository là phần trung gian, ở giữa phần dữ liệu và phần xử lý logic hay còn gọi là ModelController.
  • Giao tiếp giữa Business Login và DB sẽ được thực hiện thông quá các Interface.
  • Repository là nơi lưu trữ logic truy vấn dữ liệu. Các lệnh truy vẫn dữ liệu vốn được viết trực tiếp ở controller sẽ được đưa vào Repository. Khi đó, Controller sẽ dùng Repository để tương tác với dữ liệu thay vì sử dụng trực tiếp. Việc truy cập dữ liệu được giấu kín trong Repository.
  • Thông thường trong Laravel, các phương thức như find, create, update, delete,… được viết khá giống nhau, chỉ khác nhau ở tên Model (đối tượng) cần tương tác. Vì vậy, các đoạn code này nên đưa ra Repository để tiết kiệm việc viết code.

Lợi ích của Repository

  • Code dễ đọc, dễ phát triển sản phẩm trong làm việc nhóm
  • Giảm thay đổi code khi phần mềm có thay đổi cấu trúc dữ liệu
  • Tránh việc lặp code
  • Hạn chế lỗi trong truy vấn
  • Dễ dàng thực hiện test

Bất cập

  • Viết nhiều, viết mệt, luôn nghĩ đến việc viết như thế nào cho tối ưu, tối giản code.
  • Dự án nhỏ, mì ăn liền thì không cần xài cũng được.

Triển khai

Ở đây mình ví dụ cho model Product, mình chỉ hướng dẫn cách khai báo cũng như sử dụng chúng. Chứ KHÔNG tập trung vào việc Product sẽ chứa gì, hay những logic phức tạp …

1. Khởi tạo

Trong thư mục app, tạo 1 thư mục có tên là Repositories. Thư mục này dùng để lưu toàn bộ các Interface. Tiếp tục tạo folder Eloquent để chứa các Implementation của Interface.

Trong folder Repositories sẽ chứa:

  • BaseRepositoryInterface: interface khuôn mẫu, khai báo các phương thức chung cho các Models như: create, update, find, delete, …
  • ProductRepositoryInterface: interface chứa các phương thức riêng của Model Product.

Trong folder Eloquent sẽ chứa:

  • BaseRepository: implements Base Repository Interface, triển khai các phương thức chung cho các Model.
  • ProductRepository: implements ProductRepositoryInterface

Trong **ProductController, thay vì viết trực tiếp truy vấn, chúng ta sẽ khởi tạo controller có thuộc tính $productRepository hay bất kì thuộc tính nào mà các bạn thấy phù hợp để gán repo interface vào nó, từ đó truy vấn dẽ liệu bằng thuộc tính đó.

Có nhiều cách để khai báo các folder chứa các file này, ví dụ như mỗi model ta tạo 1 folder chứa 2 file interface và implementation. Không nhất thiết các bạn phải theo mình, nhưng các bạn nên khai báo folder Viết hoa chữ cái đầu. Viết thường, chạy ở máy không sao, lúc deploy thì lỗi đó ạ.

2. Đăng ký trong app/Providers/AppServiceProvider.php

Khi khởi tạo controller, chúng ta sẽ gán thuộc tính $productRepository có kiểu là ProductRepositoryInterface. Tại sao không phải là ProductRepository?

Mục đích là để khi các logic làm việc với dữ liệu thay đổi hay để dễ hiểu, các bạn nghĩ đến khi database thay đổi (ví dụ chuyển từ MySQL sang Redis), các lệnh truy vấn dữ liệu sẽ thay đổi. Khi đó, ta sẽ phải tạo thêm 1 class ProductRepository khác (ví dụ RedisProductRepository). Để dễ dàng cho các thay đổi như vậy, không cần sửa lại các controller đang dùng repository cũ, ta đăng kí và sau đó chỉ cần thay đổi repository ở file app/Providers/AppServiceProvider.php.

public function register()
{
    $this->app->singleton(ProductRepositoryInterface::class, function () {
        return new ProductRepository(new Product);
    });
}

Chỗ này cũng có nhiều cách khai báo, nhưng mình thích như vậy, nên mình hướng dẫn các bạn như vậy nhang.

3. Tạo các Interface và các Implementation

BaseRepositoryInterface

<?php

namespace App\Repositories;

use Illuminate\Database\Eloquent\Collection;

interface BaseRepositoryInterface
{
    public function paginate($itemOnPage);

    /**
     * @return Collection|mixed
     */
    public function getAll();

    public function all($columns = ['*']);

    /**
     * Find data by id
     *
     * @param $id
     * @param  array  $columns
     * @return \Eloquent|mixed
     */
    public function find($id, $columns = ['*']);

    /**
     * Find multiple models by their primary keys.
     *
     * @param $ids
     * @param  string[]  $columns
     * @return mixed
     */
    public function findMany($ids, $columns = ['*']);

    public function getById($id);

    public function destroy($value);

    /**
     * Delete a entity in repository by id
     *
     * @param $id
     * @return mixed
     */
    public function delete($id);

    /**
     * @param  array  $data
     * @return mixed
     */
    public function create(array $data);

    /**
     * @param  array  $condition
     * @param  array  $data
     * @return mixed
     */
    public function update(array $condition, array $data);

    /**
     * Update a entity in repository by id
     *
     * @param  array  $data
     * @param $id
     * @return mixed
     */
    public function updateById(array $data, $id);

    /**
     * @param  array  $attributes
     * @param  array  $value
     * @return mixed
     */
    public function updateOrCreate(array $attributes, $value = []);
}

BaseRepository

<?php

namespace App\Repositories\Eloquent;

use Illuminate\Database\Eloquent\Model;
use App\Repositories\BaseRepositoryInterface;
// Các class cùng folder, không cần khai báo use ...

class BaseRepository implements BaseRepositoryInterface
{
    /** @var Model|\Eloquent $model */
    protected $model;

    public function __construct(Model $model)
    {
        $this->model = $model;
    }

    public function paginate($itemOnPage)
    {
        return $this->model->paginate($itemOnPage);
    }

    public function getAll()
    {
        return $this->model->all();
    }

    public function all($columns = ['*'])
    {
        return $this->model->all($columns);
    }

    public function find($id, $columns = ['*'])
    {
        return $this->model->findOrFail($id, $columns);
    }

    public function findMany($ids, $columns = ['*'])
    {
        return $this->model->findMany($ids, $columns);
    }

    public function getById($id)
    {
        return $this->model->findOrFail($id);
    }

    public function destroy($value)
    {
        return $this->model->destroy($value);
    }

    public function delete($id)
    {
        $model = $this->find($id);

        return $model->delete();
    }

    public function create(array $data)
    {
        return $this->model->create($data);
    }

    public function update(array $condition, array $data)
    {
        return $this->model->where($condition)->update($data);
    }

    public function updateById(array $data, $id)
    {
        $model = $this->find($id);

        $model->update($data);

        return $model;
    }

    public function updateOrCreate(array $attributes, $value = [])
    {
        return $this->model->updateOrCreate($attributes, $value);
    }
}

4. Khai báo repository cho model tương ứng (Product)

ProductRepositoryInterface

<?php
namespace App\Repositories;

use App\Repositories\BaseRepositoryInterface;

interface ProductRepositoryInterface extends BaseRepositoryInterface
{
    //ví dụ: lấy 5 sản phầm đầu tiên
    public function getProduct();
}

ProductRepository

<?php
namespace App\Repositories\Eloquen;

use App\Repositories\Eloquent\BaseRepository;

class ProductRepository extends BaseRepository implements ProductRepositoryInterface
{
    public function getProduct()
    {
        return $this->model->take(5)->get();
    }
}

5. Tạo ProductController

<?php

namespace App\Http\Controllers;

use Illuminate\Http\Request;
use App\Repositories\ProductRepositoryInterface;

class ProductController extends Controller
{
    /**
     * @var ProductRepositoryInterface
     */
    protected $productRepository;

    public function __construct(ProductRepositoryInterface $productRepository)
    {
        $this->productRepository = $productRepository;
    }

    public function index()
    {
        $products = $this->productRepository->getAll();

        return view('home.products', ['products' => $products]);
    }

    public function show($id)
    {
        $product = $this->productRepository->find($id);

        return view('home.product', ['product' => $product]);
    }

    public function store(Request $request)
    {
        $data = $request->all();

        //... Validation here

        $product = $this->productRepository->create($data);

        return view('home.products');
    }

    public function update(Request $request, $id)
    {
        $data = $request->all();

        //... Validation here

        $product = $this->productRepository->update($id, $data);

        return view('home.products');
    }

    public function destroy($id)
    {
        $this->productRepository->delete($id);
        
        return view('home.products');
    }
}

Kết luận

Thông qua bài viết này mình giới thiệu đến các bạn cách để chúng ta làm việc với các câu query hiệu quả và an toàn hơn. Có nhiều bài viết với những cách khai báo khác nhau, nhưng cuối cùng cũng cùng một mục đích.

Nguồn tham khảo:

Rất mong được sự ủng hộ của mọi người để mình có động lực ra những bài viết tiếp theo.
{\__/}
( ~.~ )
/ > ♥️ I LOVE YOU 3000

JUST DO IT!


Reprint policy: All articles in this blog are used except for special statements CC BY 4.0 reprint policy. If reproduced, please indicate source Nguyễn Quang Đạt !
Comments
  TOC