Структура приложения Laravel 5.0

Я создаю RESTful API, используя Laravel 5.

Попытка держать контроллеры Http как можно более минимальными, поэтому я использую сервисный уровень (и репозитории) для обработки большей части логики.

Поскольку большинство контроллеров имеют схожие методы (например, show, index, update), я написал некоторые черты, которые обрабатывают каждый. Поскольку они говорят напрямую со службой, я могу использовать их для каждого контроллера.

Например:

<?php namespace API\Http\Controllers\Restful;

trait UpdateTrait
{
    protected $updater;

    public function update($itemID)
    {
        if (!$this->updater->authorize($itemID)) {
            return response(null, 401);
        }

        if (!$this->updater->exists($itemID)) {
            return response(null, 404);
        }

        $data = $this->request->all();
        $validator = $this->updater->validator($data);

        if ($validator->fails()) {
            return response($validator->messages(), 422);
        }

        $this->updater->update($itemID, $data);

        return response(null, 204);
    }
}

Поскольку все контроллеры имеют одни и те же черты, все они могут зависеть от одного интерфейса.

Например:

<?php namespace API\Services\Interfaces;

interface UpdaterServiceInterface
{
    public function validator(array $data);
    public function exists($itemID);
    public function update($itemID, array $data);
    public function authorize($itemID);
}

Однако это вызывает несколько проблем с автоматической инъекцией зависимостей.

1) Я должен использовать привязку к контексту:

$this->app->when("API\Http\Controllers\ThingController")
          ->needs("API\Services\Interfaces\UpdateServiceInterface")
          ->give("API\Services\Things\ThingUpdateServiceInterface")

Это само по себе не проблема, хотя это и приводит к довольно большому коду поставщика услуг, что не является идеальным. Тем не менее, это означает, что я не могу использовать метод инъекции, так как разрешение автоматической зависимости не работает для методов контроллера при использовании связывания контекста: я просто возвращаю сообщение could not instantiate API\Services\Interfaces\UpdateServiceInterface.

Это означает, что конструктор контроллера должен обрабатывать всю инъекцию зависимостей, которая становится довольно грязной:

class ThingsController extends Controller
{
    use Restful\IndexTrait,
        Restful\ShowTrait,
        Restful\UpdateTrait,
        Restful\PatchTrait,
        Restful\StoreTrait,
        Restful\DestroyTrait;

    public function __construct(
        Interfaces\CollectionServiceInterface $collection,
        Interfaces\ItemServiceInterface $item,
        Interfaces\CreatorServiceInterface $creator,
        Interfaces\UpdaterServiceInterface $updater,
        Interfaces\PatcherServiceInterface $patcher,
        Interfaces\DestroyerServiceInterface $destroyer
    ) {
        $this->collection = $collection;
        $this->item = $item;
        $this->creator = $creator;
        $this->updater = $updater;
        $this->patcher = $patcher;
        $this->destroyer = $destroyer;
    }
}

Это нехорошо - трудно тестировать, и все эти зависимости должны быть созданы, даже если используется только один из них.

Но я не могу придумать, как лучше обойти его.

Я мог бы использовать более конкретный интерфейс, например. ThingUpdateServiceInterface, (тогда мне не понадобилось бы контекстное связывание и могло бы непосредственно вставляться в черты), но тогда у меня было бы много интерфейсов, которые отличаются только по имени. Что кажется глупым.

Другая альтернатива, о которой я думал, заключалась в использовании множества меньших контроллеров, поэтому Things\UpdateController и Things\ShowController - по крайней мере, так ненужные зависимости не будут возникать каждый раз.

Или, может быть, попытка абстрагироваться от использования черт - это неправильный способ сделать что-то. Черты иногда кажутся похожими на потенциально анти-шаблон.

Любые советы будут оценены.

Ответ 1

Ваш код выглядит великолепно и хорошо продуман к обобщению.

Я хотел бы предложить эту опцию для использования Factory для создания ваших сервисов и не предопределять все инъекции зависимостей.

interface UpdateServiceInterface {
    public function action($itemID);
    //....
}

class ThingUpdateServiceInterface implements UpdateServiceInterface {
    public function action($itemID)
    {
        // TODO: Implement exists() method.
    }
}

class ApiServiceFactory {

    protected $app;

    public function __construct(Application $app) {
        $this->app = $app;
    }

    public function getUpdateService($type) {
        if ($type == 'things')
            return $this->app->make('ThingUpdateServiceInterface');
    }

   public function getCreateService($type) {
        if ($type == 'things') {
            //same idea here and for the rest
        }
    }

}




class ApiController {

    protected $factory;
    protected $model;

    public function __construct(ApiServiceFactory $factory) {
        $this->factory = $factory;
    }

    public function update($data) {
        $updater = $this->factory->getUpdateService($this->model);
        $updater->action($data);
    }
}

class ThingsController extends ApiController {
    protected $model = 'App\Thing';
}

Какова же идея всего этого:

  • базовый ApiController, который содержит все методы: обновление, вставка, удаление...
  • каждый другой контроллер для конкретного объекта расширяет контроллер api и переопределяет модель $с полным именем модели.
  • в действии контроллера он использует Factory для создания службы для этого конкретного объекта.
  • после создания сервиса он использует его для желаемого действия.
  • вы можете создать общее действие для действия, например

    DeleteServiceInterface
    

будет реализован

    class GeneralDeleteService implements DeleteServiceInterface {
           public function delete($id){......}
    }

и в Factory, когда вы запрашиваете этот DeleteService, если имя не передано, поэтому вы вернете сервис по умолчанию

надеюсь, что этот пост не будет "TL;DR"