ララベルのサービスコンテナ入門


このポストでは、ララヴィルでサービスコンテナを使用する方法を見てみましょう.
私たちには、ストライプで支払うクラスがあるとしましょう
<?php

namespace App\Services;

class StripePaymentService {


    public function pay() 
    {
        return 'paid!';
    }
}
タイプヒントを使用すると、簡単にこのクラスを使用することができます.
<?php

use App\Services\StripePaymentService;

class PaymentsController extends Controller
{

    public function pay(StripePaymentService $stripe)
    {
        return $stripe->pay();
    }
メソッドを呼び出す前に初期化する必要はありません.
$stripe = new StripePaymentService()
$stripe->pay();
ここに沿ってコード化するのが好きなあなたのためのルートがあります:
Route::get('pay', 'PaymentsController@pay'); // it's GET for easy testing, should be POST in the real-world
しかし、あなたがそれに引数を供給する必要があるならば、どうですか?これはサービスコンテナーが起動する場所です.簡単にあなたがそれを必要とどこでもそれらを注入することができますので、それはあなたのクラスの依存関係を管理するための方法を提供します.すべてこのクラスを毎回初期化することなく.
さあ、StripPaymentServiceクラスのコンストラクタがあるとしましょう.支払い方法と通貨を指定できます.
class StripePaymentService {

    private $payment_method;
    private $currency;


    public function __construct($payment_method, $currency)
    {
        $this->payment_method = $payment_method;
        $this->currency = $currency;
    }


    public function pay()
    {
        return [
            'payment_method' => $this->payment_method,
            'currency' => $this->currency,
        ];
    }
}
この変更により、コンストラクタの引数を供給する方法がないので、以前からコードを使用することはできません.

これを解決する最も簡単な方法はAppServiceProvider クラス
<?php
// app/Providers/AppServiceProvider.php

namespace App\Providers;

use Illuminate\Support\ServiceProvider;
use Illuminate\Support\Facades\Blade;
use App\Services\StripePaymentService; 

class AppServiceProvider extends ServiceProvider
{

    public function register()
    {

        $this->app->bind(StripePaymentService::class, function () {
            return new StripePaymentService('card', 'usd');
        });

    }

}
キーコードはこれです.これが毎回クラスの新しいインスタンスを返すStripePaymentService 注入されます:
$this->app->bind(StripePaymentService::class, function () {
     return new StripePaymentService('card', 'usd');
});
このコードを追加すると、コードのこのビットは現在のように動作します.
public function pay(StripePaymentService $stripe)
{
        return $stripe->pay();
}
現実の世界では、これは通常どのように再生されていません.通常、StripPaymentServiceと相互作用する他のクラスがあります.それでは、コードに追加しましょう.
クリエイトアapp/Services/BasketService.php ファイル.これは私たちのバスケットロジックを処理します.しかし、同時に、それはまた、量(バスケットの合計)と割引を設定するにはStripepAmeServiceを使用する必要があります:
<?php
namespace App\Services;

use App\Services\StripePaymentService;

class BasketService {

    private $stripe;

    // total: 45
    private $items = [
        [
            'title' => 'Biscuit',
            'price' => 2,
            'qty' => 10
        ],
        [
            'title' => 'Oranges',
            'price' => 5,
            'qty' => 5
        ],
    ];

    private $total;

    public function __construct(StripePaymentService $stripe)
    {
        $this->total = 0;
        $this->stripe = $stripe;
    }


    public function summarize()
    {
        $this->total = collect($this->items)->map(function($row) {
            return $row['price'] * $row['qty'];
        })->sum();

        $this->stripe->setAmount($this->total);
        $this->setDiscount();
    }


    public function setDiscount()
    {
        if ($this->total >= 40) {
            $this->stripe->setDiscount(10);
        }
    }

}
SleepApamentServiceを開き、金額と割引を処理するために更新します.
// app/Services/StripePaymentService.php

private $payment_method;
private $currency;

// add these:
private $amount;
private $discount;

public function __construct($payment_method, $currency)
{
    $this->payment_method = $payment_method;
    $this->currency = $currency;

    // add these
    $this->amount = 0;
    $this->discount = 0;
}

public function setAmount($amount)
{
    $this->amount = $amount;
}


public function setDiscount($discount)
{
    $this->discount = $discount;
}

public function pay()
{
    return [
        'payment_method' => $this->payment_method,
        'currency' => $this->currency,

        // add these
        'amount' => $this->amount,
        'discount' => $this->discount,
    ];
}
次に、paymentsControllerで、BaskServiceを使用する準備ができました.
// app/Http/Controllers/PaymentsController.php

use App\Services\StripePaymentService;
use App\Services\BasketService;

class PaymentsController extends Controller
{
    // update this
    public function pay(StripePaymentService $stripe, BasketService $basket)
    {

        $basket->summarize();
        return $stripe->pay();
    }
    // ...
}
ブラウザで実行すると、実際には期待通りに実行されないことがわかります.

この問題は、StripPharmentmentServiceクラスのバインド方法です.の代わりに$this->app->bind() , 電話をかけるべきだ$this->app->singleton() 代わりに.このようにして、コード内のどこかにクラスが注入されるたびに新しいインスタンスが生成されません.Singletonを使用すると、クラスが既に新しいインスタンスを作成する前にインスタンス化されているかどうかを確認することができます.既にインスタンス化されている場合、単に古いものを返します.
// app/Providers/AppServiceProvider.php
public function register()
{

    $this->app->singleton(StripePaymentService::class, function () {
        return new StripePaymentService('card', 'usd');
    });

}
この変更により、期待される出力が見えます.

このようにAppServiceProviderにBaskServiceを追加する必要はありませんでした.
$this->app->singleton(BasketService::class, function () {
    return new BasketService;
});
なぜなら、我々はまだそれを必要としないからです.おそらく、あなたはとにかくアイテムデータを保存するためにセッションを使用するでしょう、したがって、あなたは本当にそのためにsingletonを必要としません.
今、我々は支払いを収集する新しい方法を導入する必要がある場合はどうですか?PayPalの例
ほとんどの場合、コードはとてもよく似ているはずです.注意payment_method はPayPalのバランスが使用されると仮定してから削除されます.
<?php

namespace App\Services;

class PaypalPaymentService {

    private $currency;

    private $amount;
    private $discount;


    public function __construct($currency)
    {
        $this->currency = $currency;

        $this->amount = 0;
        $this->discount = 0;
    }


    public function setAmount($amount)
    {
        $this->amount = $amount;
    }


    public function setDiscount($discount)
    {
        $this->discount = $discount;
    }


    public function pay()
    {
        return [
            'currency' => $this->currency,
            'amount' => $this->amount,
            'discount' => $this->discount,
        ];
    }
}
その後、再度AppServiceProviderに追加する必要があります.
// app/Providers/AppServiceProvider.php

$this->app->singleton(PaypalPaymentService::class, function () {
    return new PaypalPaymentService('card', 'usd');
});
他の場所と同様に、あなたは以前にStripPaymentServiceを使用しました.
そして、あなたが支払いのより多くのタイプを受け入れるので、あなたは何度もこれをしなければなりません.長期的に本当に理想的ではない.
我々は、すべての支払いタイプ(ストライプ、PayPalなど)に基づいてインターフェイスを作成することによってこれを解決することができます.このように、我々はそれを必要とするところで、個々のクラスの代わりにインターフェースを注入することができます.
クリエイトアapp/Services/PaymentServiceInterface.php . これは、すべての支払いタイプの「青い印刷」として機能します:
<?php
namespace App\Services;

interface PaymentServiceInterface {

    public function setAmount($amount);

    public function setDiscount($discount);

    public function pay();
}
次に、個々のクラスで、あなたがしなければならないことは、PaymentServiceInterfaceを実装することです.残りのコードは同じです.
<?php

namespace App\Services;

use App\Services\PaymentServiceInterface; // add this

class StripePaymentService implements PaymentServiceInterface { // implement the PaymentServiceInterface


    private $payment_method;
    private $currency;

    private $amount;
    private $discount;


    public function __construct($payment_method, $currency)
    {
        $this->payment_method = $payment_method;
        $this->currency = $currency;

        $this->amount = 0;
        $this->discount = 0;
    }


    public function setAmount($amount)
    {
        $this->amount = $amount;
    }


    public function setDiscount($discount)
    {
        $this->discount = $discount;
    }


    public function pay()
    {
        return [
            'service' => 'stripe', // add this so we can see which class is being used
            'payment_method' => $this->payment_method,
            'currency' => $this->currency,
            'amount' => $this->amount,
            'discount' => $this->discount,
        ];
    }
}
PayPalPaymentServiceについても同様です.
<?php

namespace App\Services;

use App\Services\PaymentServiceInterface;

class PaypalPaymentService implements PaymentServiceInterface {

    private $currency;

    private $amount;
    private $discount;


    public function __construct($currency)
    {
        $this->currency = $currency;

        $this->amount = 0;
        $this->discount = 0;
    }


    public function setAmount($amount)
    {
        $this->amount = $amount;
    }


    public function setDiscount($discount)
    {
        $this->discount = $discount;
    }


    public function pay()
    {
        return [
            'service' => 'paypal',
            'currency' => $this->currency,
            'amount' => $this->amount,
            'discount' => $this->discount,
        ];
    }
}
次に、AppServiceProviderを更新し、渡されたリクエスト入力に基づいて、正しいクラスの新しいインスタンスを返します.
// app/Providers/AppServiceProvider.php

use App\Services\PaymentServiceInterface;
use App\Services\StripePaymentService;
use App\Services\PaypalPaymentService;

class AppServiceProvider extends ServiceProvider
{

    public function register()
    {
        $this->app->singleton(PaymentServiceInterface::class, function () {

            if (request()->has('stripe')) {
                return new StripePaymentService('card', 'usd');
            }
            return new PaypalPaymentService('usd');
        });
    }
}
最終的な手順は、代わりにPaymentServiceInterfaceを使用するようにStripPaymentServiceとPayPalPaymentServiceを使用したすべてのインスタンスを更新することです.
まず、BasketServiceがあります.
<?php
// app/Services/BasketService.php

namespace App\Services;

use App\Services\PaymentServiceInterface;

class BasketService {

    private $payor;

    private $items = [
        [
            'title' => 'Biscuit',
            'price' => 2,
            'qty' => 10
        ],
        [
            'title' => 'Oranges',
            'price' => 5,
            'qty' => 5
        ],
    ];

    private $total;

    public function __construct(PaymentServiceInterface $payor)
    {
        $this->total = 0;
        $this->payor = $payor;
    }


    public function summarize()
    {
        $this->total = collect($this->items)->map(function($row) {
            return $row['price'] * $row['qty'];
        })->sum();

        $this->payor->setAmount($this->total);
        $this->setDiscount();
    }


    public function setDiscount()
    {
        if ($this->total >= 40) {
            $this->payor->setDiscount(10);
        }
    }

}
次に、コントローラがあります.
use App\Services\PaymentServiceInterface;

class PaymentsController extends Controller
{

    public function pay(PaymentServiceInterface $payor, BasketService $basket)
    {

        $basket->summarize();
        return $payor->pay();
    }
}
さて、あなたが供給するstripe リクエストでは、StripEffmentServiceを起動します.

それ以外の場合は、PayPal 1を取得します.

さて、いつでも新しい支払いタイプを追加する必要があります、あなたがしなければならないすべては、PaymentServiceInterfaceに付着する新しいクラスを作成し、AppServiceProviderに対応するチェックを追加することです.
私があなたに行かせる前に1つの最終的なことはサービスプロバイダークラスの使用です.遅かれ早かれ、AppServiceProviderファイルは、以下のコードの束でいっぱいになりますregister() メソッド.この問題を回避するには、各インターフェイスの別のサービスプロバイダークラスを作成できます.
次のコマンドを実行します.
php artisan make:provider PaymentServiceProvider
これは新しいファイルを生成しますapp/Providers/PaymentServiceProvider.php . ここでは、このプロバイダに対応するインターフェイスを持つ特定のバインドコードをコピーして貼り付けます.この場合、PaymentServiceInterface :
<?php

namespace App\Providers;

use Illuminate\Support\ServiceProvider;

use App\Services\PaymentServiceInterface;
use App\Services\StripePaymentService;
use App\Services\PaypalPaymentService;

class PaymentServiceProvider extends ServiceProvider
{

    public function register()
    {
        $this->app->singleton(PaymentServiceInterface::class, function() {
            if (request()->has('stripe')) {
                return new StripePaymentService('card', 'usd');
            }
            return new PaypalPaymentService('usd');
        });
    }


    public function boot()
    {
        //
    }
}
最後のステップは、そのプロバイダをproviders 配列config/app.php ファイル
'providers' => [
    // ...
    App\Providers\PaymentServiceProvider::class,
]

概要


Laravelのサービスコンテナは便利なツール私たちのアーセナルで我々のコードをきちんと保つために追加されます.インターフェイスと組み合わせて使用することで、クラス依存性を簡単に管理できます.詳細については公式ドキュメントを読んでくださいService Container .
からのカバーイメージhttps://unsplash.com/photos/uBe2mknURG4