Может войдёшь?
Черновики Написать статью Профиль

Шаблон репозитория с возможностью повторного использования

перевод

Мне нравится шаблон репозитория, но во многих случаях он слишком утомителен. Мы используем этот шаблон, потому что он дает нам возможность разорвать зависимость между нашими контроллерами и/или кодом служебного слоя и ORM (обычно Eloquent). К сожалению, это обычно приводит к написанию большого количества лишнего кода.

Во время работы над новым проектом я решил поработать над (частичным) решением моих бед с шаблоном репозитория. Я использовал комбинацию полиморфизма, наследования и динамического программирования, чтобы задействовать полезные части шаблона, ограничивая при этом шаблонные.

Обзор

Основная идея шаблона заключается в создании репозиториев, которые реализуют стандартный интерфейс PHPRepositoryInterface, который охватывает стандартные методы Eloquent нестатическим бесфасадным способом. Это значит, что мы можем использовать инверсию зависимостей, увеличивая тестируемость нашего приложения. Кроме того, поскольку наш код слоя данных будет скрыт за репозиториями, мы можем изолировать изменения запросов и/или бизнес-логики в этом слое.

Но реализации только функций Eloquent недостаточно. Мне также хотелось бы добавить для репозиториев возможность реализации собственных функций модели. Чтобы сделать это, мы предоставляем каждому репозиторию интерфейс модели, в который мы можем вставить функции модели.

Наконец, вся шаблонная часть скрыта за классом PHPAbstractRepository, от которого наследуют все репозитории. Мы ограничиваем шаблонную часть, используя динамическое программирование для определения модели Eloquent, которую необходимо вызвать на этапе выполнения.

Реализация

RepositoryInterface

В интерфейсе репозитория мы определяем все стандартные методы Eloquent. Сигнатуры соответствующих методов в интерфейсе и в Illuminate\Database\Eloquent\Model должны точно совпадать.

Примечание: некоторые методы Eloquent были удалены для простоты. Чтобы вернуть их, просто используйте описанный ниже шаблон.

PHP
<?php namespace Acme\Repositories;

/**
 * RepositoryInterface обеспечивает стандартные функции ЛЮБОГО
 * репозитория.
 */
interface RepositoryInterface {

  public function 
create(array $attributes);

  public function 
all($columns = array('*'));

  public function 
find($id$columns = array('*'));

  public function 
destroy($ids);

}

UserRepositoryInterface

Здесь мы определяем все методы, специфичные для User-версии репозитория. Обратите внимание, что мы наследуем от PHPRepositoryInterface, поэтому любой класс, реализующий этот интерфейс, должен также предоставлять реализацию методов PHPRepositoryInterface.

PHP
<?php namespace Acme\Repositories;

/**
 * UserRepositoryInterface содержит ТОЛЬКО сигнатуры тех методов, которые
 * относятся к объекту User.
 *
 * Обратите внимание, что мы наследуем от RepositoryInterface, поэтому любой класс,
 * реализующий этот интерфейс, должен также предоставлять реализацию всех
 * стандартных методов eloquent (find, all, и т.д.)
 */

interface UserRepositoryInterface extends RepositoryInterface {

  public function 
findUserByUsername($username);

}

UserRepository

Конкретная реализация PHPUserRepository. Мы наследуем от PHPAbstractRepository, чтобы получить реализацию методов PHPRepositoryInterface, а затем реализуем здесь любые методы определенные в PHPUserRepositoryInterface.

Обратите внимание на переменную PHP$modelClassName. Она важна, потому что мы используем её в PHPAbstractRepository для динамического вызова методов соответствующей модели Eloquent.

PHP
<?php namespace Acme\Repositories;

use 
Acme\Abstracts\Repository as AbstractRepository;

class 
UserRepository extends AbstractRepository implements UserRepositoryInterface
{
  
// Вот где происходит "магия":
  
protected $modelClassName 'User';

  
// Этот класс только реализует методы, специфичные для UserRepository
  
public function findByUserName($username)
  {
    
$where call_user_func_array("{$this->modelClassName}::where", array($username));
    return 
$where->get();
  }
}

AbstractRepository

Наконец, PHPAbstractRepository выполняет динамическую реализацию различных методов Eloquent.

PHP
<?php namespace Acme\Abstracts;

use 
Acme\Repositories\RepositoryInterface;

/**
 * AbstractRepository обеспечивает стандартную реализацию методов, определенных
 * в базовом интерфейсе репозитория. Он просто делегирует статические вызовы функций
 * нужной модели eloquent на основе $modelClassName.
 */

abstract class Repository implements RepositoryInterface {

  protected 
$modelClassName;

  public function 
create(array $attributes)
  {
    return 
call_user_func_array("{$this->modelClassName}::create", array($attributes));
  }

  public function 
all($columns = array('*'))
  {
    return 
call_user_func_array("{$this->modelClassName}::all", array($columns));
  }

  public function 
find($id$columns = array('*'))
  {
    return 
call_user_func_array("{$this->modelClassName}::find", array($id$columns));
  }

  public function 
destroy($ids)
  {
    return 
call_user_func_array("{$this->modelClassName}::destroy", array($ids));
  }
}

Пример использования

Ниже приведены два примера использования. В первом используется метод Eloquent PHPfind для получения одной записи. Во втором используется наш метод PHPfindByUsername для поиска пользователя.

PHP
<?php

$repo 
= new Acme\Repositories\UserRepository;

// вывод имени пользователя с id = 1
echo $repo->find(1)->name;

// вывод количества пользователей, у которых username = johnsmith
echo count($repo->findByUsername('johnsmith'));

Заключение

Такой подход позволяет получить преимущества использования слоя репозитория, не требуя написания большого количества шаблонного кода.

Стандартный процесс добавления новых репозиториев прост.

  • Если нам нужны только стандартные функции Eloquent, мы создаем пустой интерфейс (PHPSomethingRepositoryInterface), который наследует от PHPRepositoryInterface. Затем мы создаем класс PHPSomethingRepository, реализуем PHPSomethingRepositoryInterface, наследуем от PHPAbstractRepository и добавляем свойство PHP$modelClassName.
  • Если нам надо добавить методы, специфичные для модели, мы следуем алгоритму из предыдущего шага, а затем добавляем сигнатуры пользовательских методов в PHPSomethingRepositoryInterface, и конкретные реализации этих методов в PHPSomethingRepository.

Как вы считаете, полезен ли этот материал? Да Нет

Написать комментарий

Разметка: ? ?

Авторизуйся, чтобы прокомментировать.