这样注册到容器是不是错了? Laravel 5.3

Is it wrong to register to the container in this way? Laravel 5.3

我想知道这种处理依赖注入的方法是否有问题。

post 的最后才是真正的问题。不过在此之前,我需要解释一下这个问题是从哪里来的。

我有一个 ValidationContract 接口,它为所有验证器提供指令(我正在努力尽可能地遵守 SOLID)

ValidatorContract.php

<?php
namespace Gms\Contracts;

interface ValidatorContract
{
    /**
     * @param array $item
     *
     * @return mixed
     * @internal param array $branch
     *
     */
    public function validateCreation(array $item);

    /**
     * @param array $item
     * @param $id
     *
     * @return mixed
     */
    public function validateUpdate(array $item, $id);
} 

然后我有几个特定的​​验证器:

这是验证器的示例 class:

<?php
/**
 * Created by PhpStorm.
 * User: caiuscitiriga
 * Date: 31/08/16
 * Time: 16:20
 */
namespace Gms\Validators;

use Gms\Repositories\BankRepository;
use Illuminate\Support\Facades\DB;
use Gms\Contracts\ValidatorContract;

/**
 * Class BankValidator
 * @package Gms\Validators
 */
class BankValidator implements ValidatorContract
{

    /**
     * @var BankRepository
     */
    private $bankRepository;

    public function __construct(BankRepository $bankRepository)
    {
        $this->bankRepository = $bankRepository;
    }
    /**
     * @param array $bank
     *
     * @return mixed
     */
    public function validateCreation(array $bank)
    {
        if ($this->missingRagioneSociale($bank)) {
            return [FALSE, 'missing ragione sociale'];
        }
        if ($this->ragioneSocialeAsEmptyString($bank)) {
            return [FALSE, 'can\'t leave ragione_sociale empty'];
        }
        if ($this->missingAbi($bank)) {
            return [FALSE, 'missing abi'];
        }
        if ($this->invalidAbiForCreation($bank)) {
            return [FALSE, 'invalid abi, already taken'];
        }

        return [TRUE, ''];
    }

    /**
     * @param array $bank
     * @param $id
     *
     * @return mixed
     */
    public function validateUpdate(array $bank, $id)
    {
        if ($this->isUpdatingAbi($bank)) {
            if (!$this->hasValidAbiForUpdate($bank['abi'], $id)) {
                return [FALSE, 'invalid abi, already taken'];
            }
        }
        if ($this->isUpdatingRagioneSociale($bank)) {
            if ($this->ragioneSocialeAsEmptyString($bank)) {
                return [FALSE, 'can\'t leave ragione_sociale empty'];
            }
        }

        return [TRUE, ''];
    }

    /**
     * @param $abi
     *
     * @return bool
     */
    protected function hasValidAbiForCreation($abi)
    {
        $results = $this->bankRepository->getBankByAbi($abi);
        if (!$results) {
            return TRUE;
        }

        return FALSE;
    }

    /**
     * @param $abi
     * @param $id
     *
     * @return bool
     */
    protected function hasValidAbiForUpdate($abi, $id)
    {
        $results = $this->bankRepository->getBankByAbiSkippingGivenID($abi, $id);
        if (!count($results)) {
            return TRUE;
        }

        return FALSE;
    }

    /**
     * @param array $bank
     *
     * @return bool
     */
    private function missingRagioneSociale(array $bank)
    {
        return !array_has($bank, 'ragione_sociale');
    }

    /**
     * @param array $bank
     *
     * @return bool
     */
    private function missingAbi(array $bank)
    {
        return !$this->isUpdatingAbi($bank);
    }

    /**
     * @param array $bank
     *
     * @return bool
     */
    private function invalidAbiForCreation(array $bank)
    {
        return !$this->hasValidAbiForCreation($bank['abi']);
    }

    /**
     * @param array $bank
     *
     * @return bool
     */
    private function ragioneSocialeAsEmptyString(array $bank)
    {
        return $bank["ragione_sociale"] == "";
    }

    /**
     * @param array $bank
     *
     * @return bool
     */
    private function isUpdatingAbi(array $bank)
    {
        return array_has($bank, 'abi');
    }

    /**
     * @param array $bank
     *
     * @return bool
     */
    private function isUpdatingRagioneSociale(array $bank)
    {
        return array_has($bank, "ragione_sociale");
    }
}

从构造函数中我们可以看出,这个class需要一个依赖,就是BankRepository。我正在使用存储库,因为我读到不建议直接使用 Eloquent 模型。

存储库也有自己的合同。

这是 RepositoryContract:

<?php
namespace Gms\Contracts;

interface RepositoryContract
{
    /**
     * @return mixed
     */
    public function getAll();

    /**
     * @param $id
     *
     * @return mixed
     */
    public function find($id);

    /**
     * @param $id
     *
     * @return mixed
     */
    public function destroy($id);

    /**
     * @param $id
     * @param $data
     *
     * @return mixed
     */
    public function update($id, array $data);

    /**
     * @param array $array The data to use for the creation
     *
     * @return mixed
     */
    public function create(array $array);
}

然后 BankRepository 通过此实现遵守此合同

银行资料库:

namespace Gms\Repositories;

use App\Gms\Models\Bank;
use Gms\Contracts\RepositoryContract;

class BankRepository implements RepositoryContract
{

    public function getAll()
    {
        return Bank::all();
    }

    public function find($id)
    {
        return Bank::find($id);
    }

    public function destroy($id)
    {
        return Bank::destroy($id);
    }

    public function update($id, array $data)
    {
        return Bank::where('id', $id)->update($data);
    }

    public function create(array $data)
    {
        return Bank::create($data);
    }

    public function getBankByAbi($abi)
    {
        return Bank::where('abi', $abi)->get()->first();
    }

    public function getBankByAbiSkippingGivenID($abi, $id)
    {
        return Bank::where('abi', $abi)
            ->where('id', '!=', $id)
            ->get();
    }
}

回到我们的 BankValidator,我需要一种快速的方法来管理它的依赖关系,所以我使用了一个 ServiceProvider,它将 BankValidator 注册到服务容器。

这里是 BankValidatorSP(服务提供商)

<?php

namespace Gms\Providers\Validators;

use Gms\Repositories\BankRepository;
use Gms\Validators\BankValidator;
use Illuminate\Support\ServiceProvider;

class BankValidatorSP extends ServiceProvider
{
    /**
     * Bootstrap the application services.
     *
     * @return void
     */
    public function boot()
    {
        //
    }

    /**
     * Register the application services.
     *
     * @return void
     */
    public function register()
    {
        $this->app->bind('Gms\Validators\BankValidator', function(){
            return new BankValidator(new BankRepository());
        });
    }
}

然后,由于我有多个控制器,无论如何在构造函数中都需要在 ValidatorContract 上执行某些操作,因此我需要为每个控制器注册适当的 Validator。 例如,BankController 它将使用 BankValidator。但它的构造函数只需要合约。我在 laracasts.com

上看过这个

这是 BankController 构造函数:

class BankController extends Controller
{
    /**
     * @var BankConverter
     */
    protected $converter;
    /**
     * @var BankValidator
     */
    protected $validator;
    /**
     * @var Repository
     */
    private $repository;

    /**
     * BankController constructor.
     *
     * @param ConverterContract|BankConverter $converter
     * @param ValidatorContract|BankValidator $validator
     * @param RepositoryContract|BankRepository $repository
     * @param BankResponse|ResponseContract $response
     */
    public function __construct(
        ConverterContract $converter,
        ValidatorContract $validator,
        RepositoryContract $repository,
        ResponseContract $response
    )
    {
        $this->converter = $converter;
        $this->validator = $validator;
        $this->repository = $repository;
        $this->response = $response;
    }

如您所见,构造函数甚至需要其他合约,例如 ConverterResponseRepository 。 由于每个控制器都使用相同的 "structure",我发现为每个合同创建一个服务提供者很有用。

所以我有一个 ValidatorServiceProvider、一个 ConverterServiceProvider、一个 ResponseServiceProvider,以及一个 RepositoryServiceProvider.

为了与验证器保持一致,我在下面列出了 ValidatorServiceProvider 的实现。您会发现一些 returns 新的 class 注入依赖项的片段和一些从服务容器解析 classes 的片段。这是因为当我重构所有这些东西时,这个问题突然出现在我的脑海中。将依赖项的每个 "manual" 注入移动到单个服务提供者中,然后使用它。防止代码复制。

验证器服务提供商:

<?php
namespace Gms\Providers;

use App\Http\Controllers\BankController;
use App\Http\Controllers\BranchController;
use App\Http\Controllers\ClauseController;
use App\Http\Controllers\ContractController;
use App\Http\Controllers\CustomerController;
use App\Http\Controllers\ServiceController;
use App\Http\Controllers\SupplierController;
use App\Http\Controllers\TagController;
use Gms\Contracts\ValidatorContract;
use Gms\Repositories\BankRepository;
use Gms\Repositories\BranchRepository;
use Gms\Repositories\ClauseRepository;
use Gms\Repositories\ContractRepository;
use Gms\Repositories\CustomerRepository;
use Gms\Repositories\TagRepository;
use Gms\Validators\BankValidator;
use Gms\Validators\BranchValidator;
use Gms\Validators\ClauseValidator;
use Gms\Validators\ContractValidator;
use Gms\Validators\CustomerValidator;
use Gms\Validators\ServiceValidator;
use Gms\Validators\SupplierValidator;
use Gms\Validators\TagValidator;
use Illuminate\Support\ServiceProvider;

class ValidatorServiceProvider extends ServiceProvider
{
    /**
     * Bootstrap the application services.
     *
     * @return void
     */
    public function boot()
    {
        //
    }

    /**
     * Register the application services.
     *
     * @return void
     */
    public function register()
    {
        //  Register the RESPONSE CLASS for the Bank controller
        $this->app->when(BankController::class)
            ->needs(ValidatorContract::class)
            ->give(function () {
                return $this->app->make(BankValidator::class);
            });
        //  Register the RESPONSE CLASS for the Branch controller
        $this->app->when(BranchController::class)
            ->needs(ValidatorContract::class)
            ->give(function () {
                return new BranchValidator(new BankRepository(), new BranchRepository());
            });
        //  Register the RESPONSE CLASS for the Customer controller
        $this->app->when(CustomerController::class)
            ->needs(ValidatorContract::class)
            ->give(function () {
                return new CustomerValidator(new BranchRepository());
            });
        //  Register the RESPONSE CLASS for the Contract controller
        $this->app->when(ContractController::class)
            ->needs(ValidatorContract::class)
            ->give(function () {
                return new ContractValidator(new CustomerRepository(), new ContractRepository());
            });
        //  Register the RESPONSE CLASS for the Supplier controller
        $this->app->when(SupplierController::class)
            ->needs(ValidatorContract::class)
            ->give(function () {
                return new SupplierValidator(new BranchRepository());
            });
        //  Register the RESPONSE CLASS for the Tag controller
        $this->app->when(TagController::class)
            ->needs(ValidatorContract::class)
            ->give(function () {
                return $this->app->make(TagValidator::class);
            });
        //  Register the RESPONSE CLASS for the Service controller
        $this->app->when(ServiceController::class)
            ->needs(ValidatorContract::class)
            ->give(function () {
                return new ServiceValidator();
            });
        //  Register the RESPONSE CLASS for the Clause controller
        $this->app->when(ClauseController::class)
            ->needs(ValidatorContract::class)
            ->give(function () {
                return $this->app->make(ClauseValidator::class);
            });
    }
}

最后我们来到 config/app.php 文件中的注册本身。

数组的有趣索引"providers":

     //some other code before..

    /*
     |----------------------------------
     | GMS Service Providers
     |----------------------------------
    */
    //  Load first the validators
    Gms\Providers\Validators\ClauseValidatorSP::class,
    Gms\Providers\Validators\BankValidatorSP::class,

    //  Load the services dispatchers
    Gms\Providers\ResponseServiceProvider::class,
    Gms\Providers\ConverterServiceProvider::class,
    Gms\Providers\ValidatorServiceProvider::class,
    Gms\Providers\RepositoryServiceProvider::class,
    //  CORS
    Barryvdh\Cors\ServiceProvider::class,

],

所以这是我的问题:

这是一个好习惯吗?

这个问题显然会引出这些其他问题:

这种方法有没有潜在的风险?

会不会掉进一个服务还没注册就用的坑?

"structure" 是不是 "fractionated" 太多了?

这感觉很像我早期 Laravel 项目之一的设置方式。过了一会儿,我意识到我正在做的是让我的应用程序在不需要灵活的地方变得灵活,代价是让它不必要地变得复杂。对不起,如果这看起来真的很挑剔,我认为更多的细节比更少的细节更好。

存储库

你的存储库感觉它只是在你的控制器和你的模型之间添加了另一个层(这实际上是一件好事)但同时没有提供额外的功能(这使它成为一件坏事)使事情变得不必要的复杂).我不知道不应该直接调用模型的建议背后的上下文,但我觉得它被误解了。

我看到 Laravel 中使用的存储库模式通常被称为服务层。这是所有业务逻辑所在的地方。比方说,当创建一个新用户时,您需要向该用户发送一个激活码,并为该用户附加一个角色。您不想在控制器中处理所有这些,因为拥有灯光控制器是一种很好的做法。因此,您要做的是创建一个存储库来处理该逻辑。在该存储库中,您将注入 User 模型然后 $this->user->create([$userParams]) (因为我认为这就是不直接使用该模型的意思,您应该注入它),并且您还可能注入您编写的任何短信实现,并用它来发短信。然后将您的角色模型注入该存储库并使用它为您的用户获取角色并将其附加到您的新用户。你最终得到的是一个实际做事的存储库(而不是充当控制器和模型之间的无用层),你不是在编写一堆在你的模型上调用相同函数名称的函数并且你的控制器保持轻量.

你的验证者

在 Laravel 的范围之外,我认为这是一个很好的方法。但是,如果我们使用 Laravel,我们应该利用它,因为我认为它提供了一些专门的工具,可以处理您正在尝试做的事情。

首先,我认为使用 Laravel 的验证器 class 和表单请求对象会大大受益。

第二个,也许这个可以被忽略,因为我不确定他们为什么这样设置的原因,但感觉每个验证器 class 都被迫为了实现而被执行作为一个实现。感觉它们彼此之间有些无关,而且我看不到它们会相互交换,因为它们在不同的实体上工作。我的意思是使用最后一个例子的短信服务。您将拥有一个短信界面,其中包含您的存储库在该服务上调用的方法。然后您将对该接口有不同的实现,例如可能是 Twilio 实现。然后,如果你需要使用除 Twilio 之外的东西,你只需编写一个新的 class 来实现你的短信界面,并告诉 Laravel 在需要时获取 class 而不是 Twilio 那个需要一个短信实现,你就完成了。您无需更改任何其他代码。

不必要地使用合同

我已经谈到了这一点,但我认为它需要进一步澄清。

看起来您在使用 ValidatorContract 来获得灵活性的地方,实际上是在使您的应用程序更加严格。例如,在你的服务提供者中,你说你的 BankController 哪里需要 ValidatorContract,然后给它 BankValidator。你应该问问自己"Will BankController ever need anything besides BankValidator for validation? Will I ever need to inject CustomerValidator into BankController?"。如果答案是否定的,那么我认为您是在浪费时间来担心服务提供商并确保他们在这种情况下都共享相同的界面。将 BankValidator 注入 BankController,将 CustomerValidator 注入 CustomerController 等并没有什么错......别误会我的意思,它们共享相同的方法真的很神奇,因为这让开发人员很容易,因为你没有记住每个验证器的不同方法名称是什么。然而,这种事情不需要通过使用接口来强制执行。

我不知道拥有银行验证器的原因是什么,但这是我认为更好地使用合约的示例。假设不同的银行需要不同的验证,如果 BankValidator 实际上是一个包含 BankController 试图调用它的方法的合约,那将更有意义。然后你可以有不同的实现,例如 ComericaBankValidator implements BankValidator。然后,当来自 comerica 的人登录时,您将把 ComericaBankValidator 绑定到 BankValidator。我敢肯定这个例子在你正在做的事情的上下文中是没有意义的,但希望它在上下文之外仍然有意义,你可以看到你的应用程序可以通过这个小的改变变得更加灵活。