web-dev-qa-db-ja.com

laravelファサードに依存性注入を使用する

私は、laravelファサードは便宜上最終的に存在し、これらのクラスは代わりに 注入 して疎結合を許可する必要があることを示唆する多数のソースを読みました。 Taylor Otwellにも、これを行う方法を説明する投稿 がありますが、 これに疑問を抱くのは私だけではないようです

use Redirect;

class Example class
{
    public function example()
    {
         return Redirect::route("route.name");
    }
}

なるだろう

use Illuminate\Routing\Redirector as Redirect;

class Example class
{
    protected $redirect;

    public function __constructor(Redirect $redirect)
    {
        $this->redirect = $redirect
    }

    public function example()
    {
         return $this->redirect->route("route.name");
    }
}

一部のコンストラクターとメソッドが4つ以上のパラメーターを取り始めていることに気づき始めたことを除いて、これは問題ありません。

Laravel IoC seemsはクラスコンストラクターと特定のメソッド(コントローラー)にのみ注入するため、かなり無駄のない関数とクラスがある場合でも、そのコンストラクターを見つけていますクラスは必要なクラスでパックされ、必要なメソッドに注入されます。

今、私はこのアプローチを続行する場合、独自のIoCコンテナーが必要になることを発見しています。これは、laravelのようなフレームワークを使用している場合、ホイールを再発明するようなものですか?

たとえば、私はサービスを使用して、コントローラーを処理するのではなく、ビジネス/ビューロジックを制御します。これらは単にビューをルーティングするだけです。したがって、コントローラーは最初に対応するserviceを取得し、次にurlのparameterを取得します。 1つのサービス関数もフォームの値をチェックする必要があるため、RequestValidatorが必要です。そのように、4つのパラメーターがあります。

// MyServiceInterface is binded using the laravel container
use Interfaces\MyServiceInterface;
use Illuminate\Http\Request;
use Illuminate\Validation\Factory as Validator;

...

public function exampleController(MyServiceInterface $my_service, Request $request, Validator $validator, $user_id) 
{ 
    // Call some method in the service to do complex validation
    $validation = $my_service->doValidation($request, $validator);

    // Also return the view information
    $viewinfo = $my_service->getViewInfo($user_id);

    if ($validation === 'ok') {
        return view("some_view", ['view_info'=>$viewinfo]);
    } else {
        return view("another_view", ['view_info'=>$viewinfo]);
    }
}

これは1つの例です。実際には、私のコンストラクタの多くはすでに複数のクラス(モデル、サービス、パラメータ、ファサード)が注入されています。コンストラクターインジェクション(該当する場合)をメソッドインジェクションに「オフロード」し始め、それらのメソッドを呼び出すクラスが代わりにコンストラクターを使用して依存関係をインジェクトします。

経験則として、メソッドまたはクラスコンストラクターの4つ以上のパラメーターは悪い習慣/コードのにおいだと言われました。ただし、laravelファサードを注入するパスを選択した場合、これを実際に回避する方法はわかりません。

この考えは間違っていますか?私のクラス/関数は十分に無駄ではありませんか? laravelsコンテナーの要点が欠けているのですか、それとも自分でIoCコンテナーを作成することを考える必要があるのですか? 一部 その他 回答 は、laravelコンテナが問題を解決できることを示唆していますか?

とはいえ、この問題については明確なコンセンサスは得られていないようです...

45
myol

これは、コンストラクターインジェクションの利点の1つです。コンストラクターパラメーターが大きくなりすぎるため、クラスが多くのことを実行しているときに明らかになります。

最初に行うことは、責任が多すぎるコントローラーを分割することです。

あなたがページコントローラーを持っているとしましょう:

Class PageController
{

    public function __construct(
        Request $request,
        ClientRepositoryInterface $clientrepo,
        StaffRepositortInterface $staffRepo
        )
    {

     $this->clientRepository = $clientRepo;
     //etc etc

    }

    public function aboutAction()
    {
        $teamMembers = $this->staffRepository->getAll();
        //render view
    }

    public function allClientsAction()
    {
        $clients = $this->clientRepository->getAll();
        //render view
    }

    public function addClientAction(Request $request, Validator $validator)
    {
        $this->clientRepository->createFromArray($request->all() $validator);
        //do stuff
    }
}

これは、ClientControllerAboutControllerの2つのコントローラーに分割するための主要な候補です。

それを行った後も、依存関係が多すぎる場合は*間接依存関係と呼ぶものを探す時です(私はそれらの適切な名前を考えることができないためです!)-依存クラスによって直接使用されない依存関係、しかし代わりに別の依存関係に渡されます。

この例はaddClientActionです-clientRepostoryに渡すためだけにリクエストとバリデーターが必要です。

リクエストからクライアントを作成するための新しいクラスを作成し、依存関係を減らし、コントローラーとリポジトリーの両方を単純化することで、リファクタリングできます。

//think of a better name!
Class ClientCreator 
{
    public function __construct(Request $request, validator $validator){}

    public function getClient(){}
    public function isValid(){}
    public function getErrors(){}
}

私たちの方法は次のようになります:

public function addClientAction(ClientCreator $creator)
{ 
     if($creator->isValid()){
         $this->clientRepository->add($creator->getClient());
     }else{
         //handle errors
     }
}

依存関係の数が多すぎるということについて、厳密な規則はありません。良いニュースは、疎結合を使用してアプリを構築した場合、リファクタリングは比較的簡単です。

パラメーターなしのコンストラクターよりも6または7の依存関係を持つコンストラクター、およびメソッド全体に隠された一連の静的呼び出しが必要です

23
Steve

ファサードの1つの問題は、自動化された単体テストを実行するときに、ファサードをサポートするために追加のコードを記述する必要があることです。

ソリューションについては:

1。依存関係を手動で解決する

依存したくない場合に、依存関係を解決する1つの方法。コンストラクターまたはメソッドインジェクションは、app()を直接呼び出すことです。

/* @var $email_services App\Contracts\EmailServicesContract
$email_services = app('App\Contracts\EmailServicesContract');

2。リファクタリング

時々私があまりにも多くのサービスや依存関係をクラスに渡していることに気付いたとき、多分私は単一責任プリンシペに違反しました。そのような場合、サービスまたは依存関係をより小さなクラスに分割することにより、再設計が必要になる可能性があります。私は別のサービスを使用して、関連するクラスのグループをラップし、ファサードとして何かを提供します。本質的には、サービス/ロジッククラスの階層になります。

例:私は推奨製品を生成し、それを電子メールでユーザーに送信するサービスを持っています。私はサービスをWeeklyRecommendationServicesと呼び、依存関係として他の2つのサービスを取り込みます-Recommendationサービスは推奨を生成するためのブラックボックスです(そして独自の依存関係があります-おそらく製品のレポ、ヘルパーまたは2つ)、およびEmailServiceにはMailchimpが依存している可能性があります)。リダイレクトやバリデーターなどの一部の下位レベルの依存関係は、エントリポイントとして機能するサービスではなく、それらの子サービスに存在します。

。Laravelグローバル関数を使用

一部のファサードはLaravel 5の関数呼び出しとして使用できます。たとえば、redirect()->back()の代わりにRedirect::back()を使用でき、代わりにview('some_blade)を使用できます。 View::make('some_blade')の。 dispatchや他の一般的に使用されているファサードでも同じだと思います。

(Added to Add)4.トレイトの使用今日キューに入れられたジョブで作業していたので、依存関係を注入する別の方法がトレイトを使用することでもあることに気付きました。たとえば、LaravelのDispathcesJobsトレイトには次の行があります。

   protected function dispatch($job)
    {
        return app('Illuminate\Contracts\Bus\Dispatcher')->dispatch($job);
    }

トレイトを使用するクラスはすべて、保護されたメソッドへのアクセスと依存関係へのアクセスを持ちます。コンストラクタやメソッドのシグネチャに多くの依存関係があるよりもきれいで、グローバルよりも明確で(どの依存関係が関係しているか)、手動のDIコンテナ呼び出しよりもカスタマイズが簡単です。欠点は、関数を呼び出すたびに、DIコンテナーから依存関係を取得する必要があることです。

2
Extrakun

私のlaravelは、その美しいアーキテクチャーが理由で大好きです。今では、すべてのファサードをコントローラーメソッドに注入しないのはなぜですか?リダイレクトファサードをコントローラーに間違った方法でのみ注入すると、必要になる場合があります。そして、主に最も頻繁に使用されるものはすべて宣言する必要がありますが、一部またはそれを使用する場合は、メソッドを介してそれらを注入することがベストプラクティスであるため、上部で宣言すると、メモリの最適化とコードの速度。これが役立つことを願っています

1
ujwal dhakal

それほど多くの答えはありませんが、いくつかの非常に有効なポイントを作成した私の同僚と話し合った後の考えのためのいくつかの食べ物。

  1. laravel=の内部構造がバージョン間で変更された場合(これは過去に起こったようです)、デフォルトのファサードとヘルパーメソッドを使用しているときに、解決されたファサードクラスパスを注入すると、アップグレード時にすべてが壊れます。ほとんど(完全ではないにしても)この問題を回避します。

  2. 通常、コードを分離することは良いことですが、これらの解決されたファサードクラスパスを挿入するオーバーヘッドにより、クラスが乱雑になります。プロジェクトを引き継ぐ開発者にとって、バグの修正やテストに費やすことができるコードの追跡に費やす時間が増えることになります。新しい開発者は、注入されたクラスが開発者であり、どのクラスがラベルであるかを覚えておく必要があります。 laravelの内部に不慣れな開発者は、APIの検索に時間を費やす必要があります。最終的には、バグの導入や主要な機能の欠落の可能性が高まります。

  3. ファサードはすでにテスト可能であるため、開発が遅くなり、テスト容易性は実際には改善されません。迅速な開発は、最初にlaravelを使用することの強みです。時間は常に制約です。

  4. 他のほとんどのプロジェクトはlaravel facadesを使用しています。laravelを使用した経験のあるほとんどの人はfacadesを使用しています。以前のプロジェクトの既存の傾向に従わないプロジェクトを作成しています将来的に経験の浅い(または怠惰な)開発者は、ファサードインジェクションを無視し、プロジェクトは混合形式になる可能性があります(コードレビューアも人間です)。

1
myol

Laravel(ミドルウェア、コントローラーなど))のルーティングメカニズムの一部を形成するクラスメソッド 依存関係を挿入するために使用されるタイプヒントも -それらはありませんtすべてをコンストラクターに注入する必要があります。これは、4つのパラメーター制限の経験則に慣れていない場合でも、コンストラクターをスリムに保つのに役立ちます; PSR-2ではメソッド定義を拡張できます複数行にわたって おそらく、4つを超えるパラメータを要求することは珍しいことではないためです。

あなたの例では、RequestValidatorサービスは妥協案としてコンストラクタに注入できます。これらのサービスは複数のメソッドで使用されることが多いためです。

コンセンサスの確立に関しては、Laravelは、アプリケーションが全サイズ対応のアプローチを利用するのに十分似ているように、より多くの意見を持つ必要があります。将来のバージョンでドードーの道を進みます。

1
tjbp

さてあなたの考えや懸念、そして正しい、そして私もそれらを持っていました。 Facadeにはいくつかの利点があります(私は通常は使用しません)が、もし使用するのであれば、コントローラーは少なくとも入り口と出口にすぎないので、コントローラーでのみ使用することをお勧めします。

あなたが与えた例のために、私がそれを一般的にどのように扱うかを示します:

// MyServiceInterface is binded using the laravel container
use Interfaces\MyServiceInterface;
use Illuminate\Http\Request;
use Illuminate\Validation\Factory as Validator;

...
class ExampleController {

    protected $request;

    public function __constructor(Request $request) {
        // Do this if all/most your methods need the Request
        $this->request = $request;
    }

    public function exampleController(MyServiceInterface $my_service, Validator $validator, $user_id) 
    { 
        // I do my validation inside the service I use,
        // the controller for me is just a funnel for sending the data
        // and returning response

        //now I call the service, that handle the "business"
        //he makes validation and fails if data is not valid
        //or continues to return the result

        try {
            $viewinfo = $my_service->getViewInfo($user_id);
            return view("some_view", ['view_info'=>$viewinfo]);
        } catch (ValidationException $ex) {
            return view("another_view", ['view_info'=>$viewinfo]);
        }
    }
}



class MyService implements MyServiceInterface {

    protected $validator;

    public function __constructor(Validator $validator) {
        $this->validator = $validator;
    }

    public function getViewInfo($user_id, $data) 
    { 

        $this->validator->validate($data, $rules);
        if  ($this->validator->fails()) {
            //this is not the exact syntax, but the idea is to throw an exception
            //with the errors inside
            throw new ValidationException($this->validator);
        }

        echo "doing stuff here with $data";
        return "magic";
    }
}

コードは、それぞれが自分の責任を担う小さな個々の部分に分割することを忘れないでください。コードを適切に分割すると、ほとんどの場合、コンストラクターのパラメーターがそれほど多くなくなり、コードのテストとモックの作成が容易になります。

最後に1つだけ注意します。たとえば、「連絡先ページ」や「連絡先ページの送信」など、小さなアプリケーションや大きなアプリケーションのページを作成する場合、ファサードを備えたコントローラーですべてを確実に行うことができます。プロジェクトの複雑さ。

1
Tzook Bar Noy