Ứng dụng Repository vào Laravel

Giới thiệu

Hôm nay tôi cùng các bạn đi tìm hiểu về viết Repository trong 1 dự án sử dụng Laravel Framework. Về định nghĩa và khái niệm chúng ta có thể xem qua ở đây . Trước đầu tiên ta cần thống nhất về cách thức viết Repository đã nhé. Theo 1 lời của 1 GL của Framgia thì trình tự để tạo Repository như sau:

  1. Tạo folder app/Repositories
  2. Tạo interface RepositoryInterface
  3. Tạo abstract class EloquentRepository implements RepositoryInterface, trong này viết hết tất cả các hàm xử lý làm việc với DB
  4. Tạo từng class repository extends EloquentRepository trong controller sử dụng repository thay vì gọi trực tiếp model. Khi làm việc với Repository các bạn luôn nhớ điều này. và quan trọng nhất là tất cả những gì làm việc với DB chỉ trong class EloquentRepository. Điều quan trọng nữa không thể thiếu trong Repository đó là khi sử dụng với Repository phải có sự tương đồng với Model của Laravel. Ai đã từng làm việc với Laravel có lẽ không hề có phàn nàn nào với Model của Laravel cả. Vậy nên chúng ta nên bảo toàn điều này khi dùng Repository. Do vậy đầu tiên chúng ta sẽ tìm hiểu Model trong Laravel hoạt động ra sao nhé.

Model trong Laravel

Về các hàm làm việc database ta có thể xem trực tiếp trên document của Laravel. Và trong bài này ta chủ yếu làm với các hàm cơ bản và dùng nhiều trong Model đó là:

  1. find($id)
  2. create(array $attributes = array())
  3. update(array $values)
  4. where(string|Closure $column, string $operator = null, mixed $value = null)
  5. orWhere(string|Closure $column, string $operator = null, mixed $value = null)
  6. get($columns = ['*'])
  7. list($column, $key = null)
  8. count()
  9. paginate($limit = null)
  10. delete($id)
  11. deleteAll()

Ngoài ra 1 điểm nữa khi sử dụng trong Model đó là ta sử dụng các hàm liên tiếp nhau. Ví dụ như là: Tìm các người dùng có email là framgia.com và đang kích hoạt ta dùng như sau.

$users = User::where('status', true)->where('email', 'like', '%framgia.com%')->get();

Tạo interface RepositoryInterface

Ở đây bạn cần tạo 1 interface cho Repository, ở đây sẽ list các function cơ bản làm việc trực tiếp với Model. Trong bài này sẽ là 9 function mà đã liệt kê ở trên:

<?php

namespace App\Repositories\Contracts;

interface RepositoryInterface
{
     public function where($conditions, $operator = null, $value = null);

    public function orWhere($conditions, $operator = null, $value = null);

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

    public function lists($column, $key = null);

    public function paginate($perPage = 20, $columns = ['*']);

    public function find($id, $columns = ['*']);

    public function create(array $data);

    public function update(array $data, $id, $attribute = 'id', $withSoftDeletes = false);

    public function delete($id);

    public function deleteAll();
}

Tạo Eloquent Repository

Tại đây sẽ là các function hoạt động trực tiếp lên Model, đây là nơi duy nhất được làm việc trực tiếp đến Model, còn các Repository khác không làm việc trực tiếp Model mà phải gọi các function tại đây, nếu thiếu các hàm nào thì bổ sung tại đây. Đầu tiên ta cần khởi tạo EloquentRepository

<?php

namespace App\Repositories;

use App\Repositories\Contracts\RepositoryInterface;
use Illuminate\Database\Eloquent\Model;
use Illuminate\Support\Collection;
use Carbon\Carbon;
use Illuminate\Container\Container as App;

abstract class EloquentRepository implements RepositoryInterface
{
    protected $model;
    public function __construct()
    {
        $this->app = new App();
        $this->makeModel();
    }
    
    abstract public function model();
    public function makeModel()
    {
        $model = $this->app->make($this->model());
        if (!$model instanceof Model) {
            throw new Exception("Class {$this->model()} must be an instance of Illuminate\\Database\\Eloquent\\Model");
        }

        return $this->model = $model;
    }

Và ta thấy khi khởi tạo sẽ dùng đến hàm makeModel() tại đây chúng ta sẽ gọi đến Model được khai báo trong các class Repository khác sau đó gán $model bằng giá trị đó.

Tạo các Repository của các Model

Với mỗi model ta đều nên tạo 1 Repository rieeng của nó. Vậy để tạo các Repository này ta làm như. Ví dụ ta sẽ tạo UserRepository dành cho model User thì sẽ như sau:

   <?php

namespace App\Repositories;

use App\Repositories\EloquentRepository;

class UserRepository extends EloquentRepository
{
    public function model()
   {
       return \App\User::class;
   }
}

Trong trường hợp nếu như Repository mà ta cần dùng đến các Repository khác hay lấy từ model khác thì ta chỉ cần gọi các Repository tương ứng. Ví dụ trong UserRepository ta muốn lấy thêm GroupRepository chẳng hạn, với điều kiện ta đã tạo GroupRepository rồi.

   <?php

namespace App\Repositories;

use App\Repositories\EloquentRepository;
use App\Repositories\GroupRepository

class UserRepository extends EloquentRepository
{
    private $groupRepository;
    public function __construct(
       GroupRepository $groupRepository
   ) {
       parent::__construct();
       $this->groupRepository = $groupRepository;
   }
    
    public function model()
   {
       return \App\User::class;
   }
}

Như vậy nếu ta lấy các query liên quan đến Group thì dùng qua GroupRepository thông qua biến $groupRepository.

Các function trong EloquentRepository

Để ta có thể thực hiện nhiều điều kiện như Model thì ta cần phân biệt chức năng của function. Các function về điều kiện và function làm việc trực tiếp trên DB. Ví dụ các function như find, get, paginate, lists, count, create, update là các hàm thực thi đến db, còn các function như where, orWhere là các function liên quan đến điều kiện tìm kiếm. Với các function thực thi DB thì ta trả về giá trị Model thực thi, còn những điều kiện thì ta cho các điều kiện này vào 1 mảng và trả về chính nó. Như vậy sẽ giải quyết vấn đề dùng các hàm liên tục với nhau.

Cacs function điều kiện

Như đã nói ở trên thì các điều kiện này ta cần đưa vào 1 mảng.

    private $where;
    private $orWhere;

Và khi gọi đến where ta đưa các param vào array

    public function where($conditions, $operator = null, $value = null)
    {
        if (func_num_args() == 2) {
            list($value, $operator) = [$operator, '='];
        }

        $this->where[] = [$conditions, $operator, $value];

        return $this;
    }

    public function orWhere($conditions, $operator = null, $value = null)
    {
        if (func_num_args() == 2) {
            list($value, $operator) = [$operator, '='];
        }

        $this->orWhere[] = [$conditions, $operator, $value];

        return $this;
    }

Như vậy ta cần 1 hàm để thực thi các array điều kiện này. Trong bài viết này tôi sử dụng hàm loadWhere()

    private function loadWhere()
    {
         if (count($this->where)) {
            foreach ($this->where as $where) {
                if (is_array($where[0])) {
                    $this->model->where($where[0]);
                } else {
                    if (count($where) == 3) {
                        $this->model->where($where[0], $where[1], $where[2]);
                    } else {
                        $this->model->where($where[0], '=', $where[1]);
                    }
                }
            }
        }
        if (count($this->orWhere)) {
            foreach ($this->orWhere as $orWhere) {
                if (is_array($orWhere[0])) {
                    $this->model->orWhere($orWhere[0]);
                } else {
                    if (count($orWhere) == 3) {
                        $this->model->orWhere($orWhere[0], $orWhere[1], $orWhere[2]);
                    } else {
                        $this->model->orWhere($orWhere[0], '=', $orWhere[1]);
                    }
                }
            }
        }
    }
    

Các function thực thi DB

Với các function thực thi thì với các function như find, create, update, delete thì chúng ta không cần gọi đến loadWhere ở trên.

    public function find($id, $columns = ['*'])
    {
        $this->makeModel();

        return $this->model->find($id, $columns);
    }
    public function create(array $data)
    {
        $this->makeModel();

        return $this->model->create($data);
    }
    
    public function update(array $data, $id, $attribute = 'id', $withSoftDeletes = false)
    {
        if ($withSoftDeletes) {
            $this->newQuery()->eagerLoadTrashed();
        }

        $this->makeModel();
        $this->model->where($attribute, '=', $id)->update($data);

        return $this->findBy($attribute, $id);
    }
    public function delete($id)
    {
        $this->makeModel();

        return $this->model->destroy($id);
    }

Còn với các function còn lại ta sử dụng loadWhere để lọc các điều kiện và thực thi câu lệnh liên quan DB

    public function get($columns = ['*'])
    {
        $this->newQuery()         
            ->loadWhere()

        return $this->model->get($columns);
    }
    public function count()
    {
        $this->newQuery()         
            ->loadWhere()

        return $this->model->count();
    }
    public function lists($column, $key = null)
    {
        $this->newQuery()         
            ->loadWhere()

        return $this->model->lists($column, $key = null);
    }
     public function paginate($limit = null)
    {
        $this->newQuery()         
            ->loadWhere()

        return $this->model->paginate($limit);
    }

Kết luận

Trong bài viết này tôi chỉ nêu qua các sử dụng cơ bản và các hàm cơ bản, còn nhiều cái khác nữa các bạn tự phát triển thêm như các hàm order, with, ... Có người từng hỏi, tại sao lại dùng Repository trong khi bản thân Model nó đã ưu việt rồi. Ngoài những ưu điểm ở bài viết thì tôi cũng bổ sung cái lợi thế thực tế trong dự án:

  • Nếu như trong dự có nhiều kiểu DB, thì ta chỉ cần tạo 1 class khác và vẫn giữ nguyên các hàm, do vậy sẽ có sự đồng nhất trong toàn bộ dự án.
  • Nếu như có sự nâng cấp version của framework. Ví dụ như từ 5.1 lên 5.2 thì việc hàm lists bị thay bằng fluck thì ta chỉ cần thay đổi lại các gọi trong EloquentRepository là xong, không phải tìm toàn bộ project để thay thế.
  • Hay như Laravel 5.1 không có hàm withCount() như 5.2, ta có thể tự bổ sung vào. Tuy có ưu điểm vậy cũng có nhược của nó đấy là không gọi trực tiếp Model như vậy cũng ảnh hưởng phần nào đó đến thơi gian thực thi, nhưng cái này chủ yêu là do các viết code thôi phải không các bạn. Bài viết bàn quyền từ Framgia Việt Nam Tài liệu: https://laravel.com/ https://viblo.asia