【问题标题】:Laravel 5.0 Application StructureLaravel 5.0 应用结构
【发布时间】:2015-05-27 08:54:41
【问题描述】:

我正在使用 Laravel 5 构建一个 RESTful API。

尽量减少 Http 控制器,因此我使用服务层(和存储库)来处理大部分逻辑。

由于大多数控制器都有类似的方法(例如showindexupdate),我编写了一些处理每一个的特征。因为这些直接与服务对话,所以我可以为每个控制器重用这些。

例如:

<?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\UpdateControllerThings\ShowController - 至少这样就不会每次都实例化不必要的依赖项。

或者也许试图抽象到使用特征是错误的做事方式。 trait 有时确实看起来像是一种潜在的反模式。

任何建议将不胜感激。

【问题讨论】:

  • 感谢您提出详细的问题。如何使用 UpdateTrait 中的代码创建 UpdateService 并在 RESTful 更新方法中输入提示,例如 public function update($itemID, UpdateService) 我还没有测试过这个面团,但我认为它可以不测试。如果您继续目前的测试,您的测试可能会遇到问题
  • 在L5中你可以做所谓的方法注入参见here更多,我会创建授权中间件+“存在”中间件,并尽可能使用请求类。 Traits 是很好的代码抽象,但后来你会遇到问题,我认为仅仅是因为到目前为止应用程序“并不复杂”,随着时间的推移,应用程序变得越来越复杂,所以你将需要“独特”的更新程序、修补程序也许是驱逐舰(假设你做了一些 M:N 关系)。创建 RestfullController 怎么样?并做一些扩展?
  • - 当您使用上下文绑定时,方法注入不起作用,或者至少我无法做到。 - 我不想创建单个 RestfulController 并进行扩展,因为其中有不少不会使用大多数方法(例如,有些只有 indexshow)。 - 我已经在使用 auth 中间件,但我认为在服务中使用它仍然是有意义的(例如,如果将服务用于控制台而不是 HTTP)
  • 似乎有几个issues关于上下文绑定和方法注入打开,但直到现在还没有解决方案。这是一个错误,我希望它会很快得到修复。您可以在 git 问题跟踪中打开新问题,希望我们能引起 Taylor 的注意。
  • 我认为如果上下文绑定在方法上起作用,那么一切都会很整洁。也许这就是一切。

标签: laravel interface architecture laravel-5


【解决方案1】:

您的代码看起来很棒,并且经过深思熟虑的概括。

我想推荐这个选项来使用工厂来创建你的服务,而不是预定义所有的依赖注入。

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 控制器并使用模型全名覆盖 $model。
  • 在控制器的操作中,它使用工厂为特定对象创建服务。
  • 在创建服务后,它将用于所需的操作。
  • 您可以为操作创建一般操作,例如

    DeleteServiceInterface
    

将由

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

如果没有传递名称,则在工厂中当您请求该 DeleteService 时,您将返回默认服务

希望这篇文章不会是“TLDR”

【讨论】:

    猜你喜欢
    • 2015-04-14
    • 2013-06-26
    • 2015-01-12
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 2017-08-20
    • 1970-01-01
    • 2013-02-07
    相关资源
    最近更新 更多