Các thủ thuật viết Repositories trong Laravel

Muốn viết code tốt hơn bạn phải thực hành nhiều hơn. Nhưng đôi khi bạn cảm thấy nhàm chán khi phải viết đi viết lai những đoạn code tương tự nhau.

Trong một ứng dụng, bạn có thể có nhiều Repositories để làm việc với hệ thống lưu trữ của mình. Khi sử dụng Laravel, phần lớn thời gian bạn sẽ sử dụng Eloquent. Tuy nhiên, khi bạn có rất nhiều Repositories, bạn sẽ nhanh chóng cảm thấy mệt mỏi và khó chịu bới vì có quá nhiều method giống nhau mà bạn phải viết đi viết lại nhiều lần.

Để tránh nhưng thao tác phải lặp lại nhiều lần. chúng ta sẽ xem qúa trình trừu tượng hóa chúng trong một Repositories.

Trên lý thuyết, nếu bạn sử dụng mô hình Repository thì bạn có thể thao tác trên bất kỳ dữ liệu nào bạn muốn.

Cấu trúc của một Repository

Trong Laravel, cấu trúc mô hình Repository điển hình là một Interface và một Class thực thi Interface đó.

Ví dụ, bạn có một Userrepository Interface:

interface UserRepository {}

Và một EloquentUserRepository:

class EloquentUserRepository implements UserRepository {}

Việc thực thi sẽ được giới hạn trong Interface và được giải quyết từ Laravel's IoC container sử dụng Service Provider:

/**
 * Register
 */
public function register()
{
    $this->app->bind('Cribbb\Repositories\User\UserRepository', function($app)
    {
        return new EloquentUserRepository( new User );
    });
}

Trong EloquentUserRepository chúng ta có thể chích dẫn một model như một đặc tính của class:

class EloquentUserRepository implements UserRepository {

  /**
   * @var Model
   */
  protected $model;

  /**
   * Constructor
   */
  public function __construct(User $model)
  {
        $this->model = $model;
  }

}

Bây giờ chúng ta có thể thực hiện truy vấn trên Model để trả về dữ liệu:

/**
 * Return all users
 *
 * @return Illuminate\Database\Eloquent\Collection
 */
public function all()
{
  return $this->model->all();
}

$this->model->all() sẽ tương đương với User::all() vì chúng ta đã chích đối tượng User vào contructor của EloquentUserRepository lên chúng ta có thể sử dụng được tất cả các method của lớp User bằng cách gọi $this->model->Ten_Phuong_Thuc

Abstracting repeating logic

Khi làm việc với Repositories, bạn muốn kết thúc việc lặp lại nhiều lần các method giống nhau trên mỗi Repository? Hãy trừu tượng chúng và đưa chúng vào một abstract class.

abstract class AbstractEloquentRepository {

  /**
   * Return all users
   *
   * @return Illuminate\Database\Eloquent\Collection
   */
  public function all()
  {
     $this->model->all();
  }

}

Chúng ta có thể kế thừa từ class trừu tượng để dùng lại được các method chung cho tất cả các Repositories.

class EloquentUserRepository extends AbstractEloquentRepository implements UserRepository {}

Giờ đây, bạn không cần viết method all() trên EloquentUserRepository vì nó sẽ tự động kế thừa từ class AbstractEloquentRepository.

Creating an Eager Loading blueprint

Eager Loading cực kỳ hữu ích khi bạn muốn load các relationship của một đối tượng để giảm số lượng các truy vấn được thực hiện trê database. Eager Loading, về cơ bản là một cách để tối ưu hóa các truy vấn bằng cách xác định những dữ liệu nào bạn muốn tìm kiếm trước.

Một method phổ biến trên Repository là tìm kiếm một đối tượng bằng id, ví dụ:

/**
 * Find an entity by id
 *
 * @param int $id
 * @return Illuminate\Database\Eloquent\Model
 */
public function getById($id)
{
  return $this->model->find($id);
}

Việc này sẽ phù hợp nếu bạn chỉ muốn trả về các đối tượng, nhưng nếu bạn cũng muốn trả về những mối quan hệ của nó qua Eager Loading? Cách duy nhất để thực hiện là định nghĩa qua một method khác.

/**
 * Find an entity by id and include the posts
 *
 * @param int $id
 * @return Illuminate\Database\Eloquent\Model
 */
public function getByIdWithPosts($id)
{
    return $this->model->find($id)->with(array('posts'))->first();
}

Nhưng đến đây, chúng ta đang lặp lại cùng một logic để thực hiện tất cả các relationships. Thay vì làm điều này nhiều lần, chúng ta chỉ cần tạo ra một query để xác định mối quan hệ.

/**
 * Make a new instance of the entity to query on
 *
 * @param array $with
 */
public function make(array $with = array())
{
  return $this->model->with($with);
}

trong method getById chúng ta có thể bỏ qua tùy chọn với tham số $with, vì thế chúng ta có thể xác định được mối quan hệ nào được load khi gọi method này.

/**
 * Find an entity by id
 *
 * @param int $id
 * @param array $with
 * @return Illuminate\Database\Eloquent\Model
 */
public function getById($id, array $with = array())
{
  $query = $this->make($with);

  return $query->find($id);
}

Bạn có thể thêm tham số $with ở bất kỳ method nào thuộc Repository của mình và tạo ra các truy vấn nhánh sử dụng make method, bạn có thể truy cập vào Eager loading bất cứ khi nào bạn cần mà không lặp lại cấu trúc logic nào.

Get by key value

Tập hợp các method để tìm kiếm dữ liệu theo key và value. Ví dụ, bạn muốn tìm một user theo địa chỉ email của họ

/**
 * Find a user by their email address
 *
 * @param string $email
 * @return Illuminate\Database\Eloquent\Model
 */
public function getByEmail($email)
{
  return $this->model->where('email', '=', $email)->first();
}

Tuy nhiên nếu bạn cần tìm các đối tượng trên Repository sử dụng nhiều key và value trong việc tìm kiếm, sẽ có nhiều cấu trúc logic bị lặp đi lặp lại.

Thay vào đó bạn chỉ cần bỏ qua key và value trong một method chung.

/**
 * Find a single entity by key value
 *
 * @param string $key
 * @param string $value
 * @param array $with
 */
public function getFirstBy($key, $value, array $with = array())
{
  $this->make($with)->where($key, '=', $value)->first();
}

/**
 * Find many entities by key value
 *
 * @param string $key
 * @param string $value
 * @param array $with
 */
public function getManyBy($key, $value, array $with = array())
{
  $this->make($with)->where($key, '=', $value)->get();
}

Bây giờ bạn có thể sử dụng cả 2 method bất cứ khi nào bạn muốn tìm một đối tượng theo key và value.

Pagination

Pagination là một thứ phổ biến mà có thể bạn sẽ cần trên nhiều Repositories. Trong bài này, tôi sẽ chỉ cách tạo thủ công một method Pagination trong Laravel.

/**
 * Get Results by Page
 *
 * @param int $page
 * @param int $limit
 * @param array $with
 * @return StdClass Object with $items and $totalItems for pagination
 */
public function getByPage($page = 1, $limit = 10, $with = array())
{
  $result             = new StdClass;
  $result->page       = $page;
  $result->limit      = $limit;
  $result->totalItems = 0;
  $result->items      = array();

  $query = $this->make($with);

  $model = $query->skip($limit * ($page - 1))
                 ->take($limit)
                 ->get();

  $result->totalItems = $this->model->count();
  $result->items      = $model->all();

  return $result;
}

Kết luận

Sau bài viết này, bạn có thể tránh việc lặp đi lặp lại cùng một đoạn code trọng một project lớn. Bất cứ khi nào bạn cảm thấy điều gì đó đang được lại đi lặp lại nhiều lần thì đó là lúc thích hợp để trừu tượng hóa. Bằng các tạo ra các methods chung phù hợp với nhiều mục đích, chúng ta có thể làm giảm đi rất nhiều sự phức tạp của repositories và làm giảm số lượng code và logic được lặp lại.

Bất cứ khi nào bạn quyết định trừu tượng hóa một cái gì đó, bạn cần phải đảm bảo rằng điều đó là cấn thiết. Thông thường, việc trừu tượng hóa sẽ làm cho code của bạn phức tạp hơn.

Trên đây là một số kinh nghiệm khi viết Repositories trong Laravel. Chúc các bạn thành công khi làm việc với chúng nhé. ^^


All Rights Reserved