جشنواره فطر سون لرن

آموزش Service Container در لاراول 7

دسته بندی: برنامه نویسی
زمان مطالعه: 28 دقیقه
۲۳ اسفند ۱۳۹۸

فهرست محتوای این مقاله

گام اول - Service Container چیست؟

Servicer container یکی از ابزارهای بسیار قدرتمند برای مدیریت وابستگی‌ها (dependency) کلاس می‌باشد و یکی از مفاهیم بسیار ضروری در لاراول است که حتماً باید آن را یاد بگیرید. dependency injection یک اصطلاحی است که این روزها به وفور شنیده می‌شود. و معنی واقعی آن به شرح زیر می‌باشد:

وابستگی‌های یک کلاس از طریق متد سازنده (constructor) یا در برخی موارد توسط توابع با استفاده از setter به داخل کلاس تزریق می‌شوند.

با استفاده از این قابلیت، لاراول متوجه می‌شود که یک کلاس را چگونه و با چه شرایطی بسازید.

گام دوم – آماده سازی محیط توسعه و نصب لاراول

شایان ذکر است ما از PHP Storm برای توسعه‌ی کد‌های خود استفاده می‌کنیم. شما می‌توانید از هر IDE دیگری استفاده نمایید.

دسترسی به کدهای پروژه

سورس کد در گیت هاب به آدرس https://github.com/VahidGarousi/Service-Container قرار داده شده است.

نصب composer

برای ساخت پروژه لاراولی خود به ابزار Composer نیاز داریم برای نصب آن می‌توانید از طریق لینک زیر وارد سایت شده و مراحل نصب را انجام دهید.

https://getcomposer.org

نصب لاراول

در ادامه برای نصب لاراول در سیستم خود به سایت لاراول به آدرس:

https://laravel.com/docs/7.x

مراجعه می‌کنیم.

با کلیک روی گزینه‌ی documentation وارد قسمت مستندات لاراول می‌شویم. همان‌طور که در قسمت Installing Laravel مشاهده می‌کنید از دستور  composer global require laravel/installer استفاده می‌کنیم.

توجه: دستور فوق را در CMD یا Git Bash وارد کنید.

گام سوم – آشنایی با ساختار پروژه – ساخت پوشه‌ها

ساختار پروژه

برای ساخت پروژه‌‌های خود بهتر است از ساختاری که در این آموزش گفته می‌شود استفاده کنید چرا که طبق تجربه‌ ساختاری مناسب برای نگهداری پروژه‌های نرم افزاری می‌باشد.

D:\dev\workspace\laravel

پس از ساخت این پوشه‌ها وارد پوشه laravel شده و با استفاده ازCMD یا Git bash از دستور زیر برای ساخت پروژه‌ی خود استفاده می‌کنیم.

laravel new service-container

عملیات ساخت پروژه با توجه به سرعت اینترنت شما ممکن است دقایقی طول بکشد تا پایان ساخت صبر کنید پس از پایان تصویر زیر را مشاهده می‌کنید.

گام چهارم – یک شروع پر سرعت – ساخت کلاس‌های پایه

ساخت کنترلر PayOrderController

در مرحله‌ی اول با استفاده از دستور زیر یک کنترلر با نام PayOrderController ایجاد می‌کنیم.

php artisan make:controller PayOrderController

ساخت کلاس PaymentGateway

سپس داخل پوشه‌ی app یک پوشه با نام Billing ایجاد می‌کنیم. داخل این پوشه یک کلاس PHP با نام PaymentGateway ایجاد می‌کنیم. و داخل آن یک متد با نام Charge می‌نویسیم که میزان شارژ را به عنوان ورودی دریافت می‌کنیم.

class PaymentGateway {
    public function charge($amount) {
        // Charge the bank
        return [
            'amount'              => $amount,
            'confirmation_number' => Str::random(),
        ];
    }
}

در این مرحله برای استفاده از این کلاس داخل کنترل PayOrderController یک متد با نام store می‌نویسم. برای اینکه بتوانیم گوگل کروم خروجی را مشاهده کنیم از dd استفاده می‌کنیم.

class PayOrderController extends Controller
{
    public function store(PaymentGateway $paymentGateway) {
        dd($paymentGateway->charge(250000));
    }
}

برای دسترسی به آدرس سایت خود در مرورگر در فایل web.php، مسیر(route) زیر را می‌نویسیم. با این کار به سیستم مسیریابی لاراول مشخص می‌کنیم که اگر درخواستی وارد آدرس https://127.0.0.1:8000/pay شد آن درخواست را به کنترلر PayOrderController و متد store پاس بدهد.

Route::get('/pay', 'PayOrderController@store');

ساخت متد store در کنترلر PayOrderController

این متد به عنوان ورودی PaymentGateway را دریافت می‌کند و متد charge را فراخوانی می‌کند با توجه به اینکه ما از dd استفاده نموده‌ایم در خروجی به شکل خاصی مشاهده خواهیم کرد.

class PayOrderController extends Controller {
    public function store() {
        $paymentGateway = new PaymentGateway();
        dd($paymentGateway->charge(25000));
    }
}

برای نمونه ساختن از کلاس می‌توانیم به دو روش استفاده از کلیدواژه new و resolve عمل کنیم.

class PayOrderController extends Controller {
    public function store() {
        $paymentGateway = resolve(PaymentGateway::class);
        dd($paymentGateway->charge(25000));
    }
}

گام پنجم - اجرای سرور و مشاهده‌ی خروجی کدها در مرورگر

برای این‌کار در محیط ترمینال از دستور php artisan serve استفاده می‌کنیم.

php artisan serve

اکنون با مراجعه به آدرس فوق که https://127.0.0.1:8000/pay خروجی کدهای خود را می‌توانیم مشاهده نماییم.

result-in-browser

حال اگر بخواهیم عملیات نمونه سازی از کلاس PaymentGateway به صورت خودکار انجام شود چه راهکاری داریم؟ در حال حاضر یک کلاس داریم و پیچیدگی خاصی نداریم اما به این فکر کنید که کلاس‌های بسیار زیادی داریم و اگر در یک کنترلر به تعداد زیادی از آن‌ها نیاز داشته باشیم باید چه‌ کار کنیم؟ راه حل تمامی این مشکلات Service Container است.

به قطعه کد زیر توجه کنید:

$paymentGateway = new PaymentGateway();

ما می‌توانیم به شیوه بهتری این کار را انجام دهیم، در ادامه به برخی مشکلات و حل آن می‌پردازیم. پس تا انتهای این مقاله با ما همراه باشید.

یکی از روش‌هایی که می‌توانیم این‌کار را انجام دهیم تغییر دادن قطعه کد به صورتی است که داخل متد و به عنوان ورودی دریافت نماییم. به این شکل لاراول با استفاده از سیستم رفلکشن (Reflection) خود یک نمونه از کلاس را  در داخل متد برای ما فراهم می‌کند.

class PayOrderController extends Controller {
    public function store(PaymentGateway $paymentGateway) {
        dd($paymentGateway->charge(25000));
    }
}

با رفرش کردن خروجی در مرورگر می‌بینیم که خروجی هم‌چنان درست کار می‌کند. لاراول عملیات inject را به خوبی انجام داده است.

به این صورت که ما داخل متد store به لاراول می‌گوییم که به یک نمونه از کلاس PaymentGateway نیاز داریم و لاراول این‌کار را انجام می‌دهد و از روی کلاسی که ساختیم یک نمونه برای ما می‌سازد.

این یک قابلیت فوق العاده برای ما برنامه نویس‌ها است.

حال تغییراتی که دادیم را به حالت قبل بر‌می‌گردانیم:

class PayOrderController extends Controller {
    public function store() {
        $paymentGateway = new PaymentGateway();
        dd($paymentGateway->charge(25000));
    }
}

اما یک سوال دیگر اگر بخواهیم به کلاسی که ساختیم ورودی بدهیم باید چه‌کار کنیم؟ قطعاً اگه خودمان بخواهیم این‌کار را انجام بدهیم که مشخص است. اما لاراول این‌کار را چطور انجام می‌دهد؟

شما در نظر بگیرید ما قصد داریم داخل کلاس PaymenyGateway واحد پول را داشته باشیم و استفاده کنیم. طبیعی هست که برای این‌کار با استفاده از متد سازنده واحد پول را دریافت کنیم برای همین به شکل زیر این‌کار را انجام  می‌دهیم.

class PaymentGateway {
    private $currency;

    public function __construct($currency) {

        $this->currency = $currency;
    }

    public function charge($amount) {
        // Charge the bank
        return [
            'amount'              => $amount,
            'confirmation_number' => Str::random(),
        ];
    }
}

اگر به کنترلری که ساختیم برگردیم متوجه می‌شویم که خطایی مبنی بر اینکه مقداری برای واحد پول در نظر نگرفته‌ایم داده است.

ما برای واحد پول مقدار ir را می‌نویسیم تا خطا را رفع کنیم.

class PayOrderController extends Controller {
    public function store() {
        $paymentGateway = new PaymentGateway("id");
        dd($paymentGateway->charge(25000));
    }
}

اگر الان مرورگر خودمان را رفرش(Refresh) کنیم متوجه می‌شویم که هنوز واحد پول را نمی‌توانیم مشاهده کنیم. برای اینکه بتوانیم مشاهده کنیم به کلاس PaymentGateway می‌رویم و تغییرات زیر را ایجاد می‌کنیم:

class PaymentGateway {
    private $currency;

    public function __construct($currency) {

        $this->currency = $currency;
    }

    public function charge($amount) {
        // Charge the bank
        return [
            'amount'              => $amount,
            'confirmation_number' => Str::random(),
            'currency'            => $this->currency,
        ];
    }
}

اکنون ما می‌توانیم واحد پول را داخل مرورگر مشاهده کنیم اما اگر مثل شیوه‌ی قبلی که گفتیم عمل کنیم به مشکلی اساسی‌ برمی خوریم. چگونه واحد پول را از طریق متد store به کلاس PaymentGateway پاس بدهیم؟

class PayOrderController extends Controller {
    public function store(PaymentGateway $paymentGateway) {
        dd($paymentGateway->charge(25000));
    }
}

اگر به مرورگر برویم و صفحه را رفرش (Refresh) کنیم نتیجه زیر را خواهیم دید.

این خطا دقیقاً چیست؟

این خطا یعنی لاراول نمی‌تواند  از کلاس برای شما نمونه بسازد. به مقدار currency نیاز دارد که فراهم نشده است و باید این موضوع حل شود. فیلد currency اجباری هست و باید به کلاس PaymenyGateway پاس بدهیم ولی ما به کلاس این فیلد را ندادیم.

برای حل این مشکل ما باید از Service Provider استفاده کنیم. اگر ما به پوشه app و سپس به پوشه Providers مراجعه کنیم فایلی به اسم AppServiceProvider وجود دارد.

class AppServiceProvider extends ServiceProvider
{
    /**
     * Register any application services.
     *
     * @return void
     */
    public function register()
    {
        //
    }

    /**
     * Bootstrap any application services.
     *
     * @return void
     */
    public function boot()
    {
        //
    }
}

آشنایی با کلاس AppServiceProvider

این کلاس جایی است که به صورت خودکار اجرا می‌شود و مواردی که ما نیاز داریم را مقداردهی می‌کند و تنظیمات اولیه رو انجام می‌دهد.

برای اینکه بتوانیم مشکلی که داشتیم، یعنی پاس دادن یک مقدار برای currency را حل کنیم باید به صورت زیر عمل کنید.

class AppServiceProvider extends ServiceProvider {
    /**
     * Register any application services.
     *
     * @return void
     */
    public function register() {
        $this->app->bind(PaymentGateway::class, function ($app) {
            return new PaymentGateway("ir");
        });
    }

    /**
     * Bootstrap any application services.
     *
     * @return void
     */
    public function boot() {
       //
    }
}

اگر کد را نگاه کنید می‌بینید که داخل متد register، به app که به کلاس جاری اشاره می‌کند دسترسی پیدا کرده‌ایم و متد bind را صدا زده‌ایم.

اما در بخش اول که نوشته ایم  PaymentGatewat::class، منظورمان این است که هر وقت در تمامی درخواست هایی که ارسال می‌شود به این کلاس نیاز بود کاری که در بخش دوم که یک تابع است را انجام بده. در بخش دوم هم که برای خروجی یک نمونه از کلاس مذکور برگردانده‌ایم و واحد پول را مشخص کرده‌ایم.

حالا اگر در مرورگر صفحه را رفرش (Refresh) کنیم می‌بینیم که خطا رفع شده و نتیجه به صورت زیر قابل مشاهده است:

laravel-currency

با این‌کاری که ما کردیم لاراول متوجه می‌شود که چگونه کلاس PaymentGateway را بسازد و نیاز ما در کنترلرها و جاهای دیگر را برطرف می‌کند.

مزایای استفاده از Service Container

  • با کاری که ما انجام دادیم کنترلر‌ها نیازی ندارند تغییر پیدا کنند و بدونن عملیات پرداخت به چه شکل انجام می‌شود در اصل این کار توسط service هندل می‌شود.
  • به عنوان مثال الان پرداخت ما با استفاده از ریال ایران و روی کافه بازار کار می‌کند اما اگر بخواهیم به هر دلیلی از دلار آمریکا و روی گوگل پلی کار کنیم خیلی راحتیم. مواردی که تغییر پیدا می‌کنند فقط واحد پول در کلاس PaymentGatewat هست و در سمت دیگر می‌توانیم بسته به اینکه واحد پول چیست الگوریتم‌های مختلفی برای کارمان بنویسیم. و کارمان راحت‌تر می‌شود.
  • نکته‌ی بعد اینکه که فقط PaymentGatewat هست که می‌داند چگونه عملیات پرداخت را انجام دهد و Controller، از فرآیندی که  اتفاق می‌افتد بی‌خبر هست.
  • عملیات پرداخت به وظیفه‌ی PaymentGatewat است نه Controller.
  • شما اگر 50 عدد Controller داشته باشید که به عناوین مختلفی به PaymentGateway نیاز دارند. اگر از این روش استفاده نکنید باید فایل‌های زیادی را تغییر بدهید. بماند که نگهداری کدها برایتان واقعا سخت خواهد بود.

افزودن قابلیت فاکتور و اعمال تخفیف روی سفارشات:

تصور کنید ما می‌خواهیم به مواردی که نوشتیم قابلیت تخفیف اضافه کنیم یعنی بتوانیم تخفیف را روی مقدار amount که داخل کلاس PaymentGateway داریم اعمال کنیم.

برای اینکه بتوانیم از امکان تخفیف استفاده کنیم به سفارش یا OderDetails نیاز داریم به همین دلیل ما باید تعدادی کلاس برای سفارشات بسازیم. داخل پوشه app یک پوشه به نام Orders می‌سازیم و داخل آن یک کلاس به اسم OrderDetails می‌سازیم. برای شروع داخل متد سازنده به PaymentGateway نیاز داریم پس به‌صورت زیر دریافت می‌کنیم:

class OrderDetails {
    /**
     * @var PaymentGateway
     */
    private $paymentGateway;

    public function __construct(PaymentGateway $paymentGateway) {

        $this->paymentGateway = $paymentGateway;
    }
}

برای اینکه بتوانیم قابلیت تخفیف را به تمام PaymentGateway هایی که داریم اعمال کنیم و به نوعی دسترسی داشته باشیم به شکل زیر عمل می‌کنیم :

class OrderDetails {
    /**
     * @var PaymentGateway
     */
    private $paymentGateway;

    public function __construct(PaymentGateway $paymentGateway) {

        $this->paymentGateway = $paymentGateway;
    }

    public function all() {
        $this->paymentGateway->setDiscount(50);
        return [
            'name'    => 'Vahid',
            'address' => 'Iran - Tehran',
        ];
    }
}

ما با استفاده از متدی به نام all روی تمام PaymentGateway هایی که داریم، یک متد به نام setDiscpunt() را فراخوانی می‌کنیم و می‌خواهیم که روی سفارشات ما 50 درصد تخفیف لحاظ شود. این متد در کلاس  PaymentGateway وجود ندارد برای ساخت این کلاس به صورت زیر عمل می‌کنیم. همان‌طور که مشاهده می‌کنید برای خروجی یک سری مقدار را برگردانده‌ایم تا بتوانیم در خروجی استفاده کنیم.

متد setDiscount داخل کلاس PaymentGateway به صورت زیر است:

class PaymentGateway {
    private $currency;
    private $discount;

    public function __construct($currency) {

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

    public function charge($amount) {
        // Charge the bank
        return [
            'amount'              => $amount,
            'confirmation_number' => Str::random(),
            'currency'            => $this->currency,
        ];
    }

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

ما در این قسمت یک فیلد به نام discount ساخته‌ایم و در متد سازنده مقدار آن را برابر صفر قرار داده‌ایم و داخل متد  setDiscount، مقداری که داخل متد all وارد کرده‌ایم را اینجا دریافت می‌کنیم.

اما الان اگر به کنترلر PayOrderController مراجعه کنیم می‌بینیم که ما اصلاً Orderای نداریم که بخواهیم تخفیف را روی آن اعمال کنیم!

برای این‌کار داخل ورودی متد store، علاوه بر PaymentGateway، OrderDetails را دریافت می‌کنیم.

class PayOrderController extends Controller {
    public function store(PaymentGateway $paymentGateway, OrderDetails $orderDetails) {
        $order = $orderDetails->all();
        dd($paymentGateway->charge(25000));
    }
}

 

در ادامه با تغییری که داخل کلاس PaymentGateway ایجاد می‌کنیم می‌توانیم تخفیف را روی سفارشات(Orders) اعمال کنیم به تغییرات زیر دقت کنید:

class PaymentGateway {
    private $currency;
    private $discount;

    public function __construct($currency) {

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

    public function charge($amount) {
        // Charge the bank
        return [
            'amount'              => $amount,
            'confirmation_number' => Str::random(),
            'currency'            => $this->currency,
            'discount'            => $this->discount,
        ];
    }

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

اکنون به مرورگر رفته و صفحه را رفرش(Refresh) می‌کنیم خروجی به صورت زیر قابل مشاهده است:

آشنایی با دیزاین پترن Singleton و استفاده از آن:

شاید برای شما هم سوال شده است چرا تخفیف لحاظ نشده است؟ مگر ما 50 درصد تخفیف لحاظ نکردیم؟  اما تخفیف اعمال نشده چرا؟

دلیل اصلی مشکل ما این است که داخل کلاس AppServiceProvider گفتیم که هر درخواستی به PaymentGateway نیاز داشت یک نمونه جدید بسازد.

برای رفع این مشکل باید به کمک دیزاین‌پترن Singleton بررسی کنیم که اگر نمونه‌ای از PaymentGateway وجود داشت همان را برگرداند و اگر وجود نداشت یک نمونه جدید بسازد.

در واقع PaymentGatewayای که ما داخل کلاس OrderDetails درخواست می‌کنیم به یک نمونه‌ی جدید اشاره دارد و این دقیقاً همان‌ جایی است که باعث می‌شود تخفیفی که اعمال کردیم به درستی روی مبلغ amount اعمال نشود.

شاید فراموش کرده باشید به کدام قسمت کد اشاره می‌کنیم، مجددا کد را اینجا قرار می‌دهیم. ما قرار هست روی کد زیر تغییراتی اعمال کنیم:

public function register() {
    $this->app->bind(PaymentGateway::class, function ($app) {
        return new PaymentGateway("ir");
    });
}

برای این تغییر و استفاده از دیزاین پترن Singleton باید به روش زیر عمل کنیم:

ما این‌بار به جای استفاده از bind، از singleton استفاده کردیم. فرآیند آن به این صورت است که لاراول بررسی می‌کند که بارِ اول چه درخواستی PaymentGateway را صدا می‌زند؟ همان بار اول یک نمونه می‌سازد و بر می‌گرداند دفعات بعدی هر درخواستی به PaymentGateway نیاز داشت و آن را صدا زد همان خروجی دفعه اول که یک نمونه از PaymentGateway هست را بر می‌گرداند. این به صورت خیلی ساده مفهوم Singleton است.

class AppServiceProvider extends ServiceProvider {
    /**
     * Register any application services.
     *
     * @return void
     */
    public function register() {
        $this->app->singleton(PaymentGateway::class, function ($app) {
            return new PaymentGateway("ir");
        });
    }

    /**
     * Bootstrap any application services.
     *
     * @return void
     */
    public function boot() {
        //
    }
}

حالا اگر مرورگر را رفرش (Refresh) کنیم خروجی را به صورت زیر می‌بینیم :

آشنایی با ساخت اینترفیس و استفاده از آن:

برای ادامه ما می‌خواهیم فایل PaymentGateway را به BankPaymenytGateway تغییر بدهیم تا یک interface بسازیم.

class BankPaymentGateway {
    private $currency;
    private $discount;

    public function __construct($currency) {

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

    public function charge($amount) {
        // Charge the bank
        return [
            'amount'              => $amount,
            'confirmation_number' => Str::random(),
            'currency'            => $this->currency,
            'discount'            => $this->discount,
        ];
    }

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

حالا  اگر با PHPStorm کار می‌کنید می‌توانید با راست کلیک روی اسم کلاس و از منوی باز شده گزینه‌ی Refactor و بعد گزینه‌ی Extract و سپس گزینه‌ی Interface را انتخاب کنید تا برای شما یک دیالوگ باز شود و متد هایی که می‌توانند interface شوند را به شما نمایش دهد. ما اسم این interface را PaymentGatewayContract قرار می‌دهیم.

ساخت اینترفیس PaymentGatewayContract

پس از این‌کار یک فایل اینترفیس با ساختاری به شکل زیر ایجاد می‌شود.

interface PaymentGatewayContract {
  public function charge($amount);
  public function setDiscount($amount);
}

ما با استفاده از این قابلیت به کلاس هایی که قرار است عملیات پرداخت را پیاده سازی کنند اجبار می‌کنیم که چه متدهایی را داشته باشند. توجه کنید چگونگی انجام عملیات پرداخت به عهده خود کلاس است.

بنابراین داخل کلاس BankPaymentGateway تغییرات زیر رامشاهده می‌کنیم در اصل می‌گوییم که کلاس BankPaymentGateway  اینترفیسی که قبلاً ساخته‌ایم را پیاده سازی (implement) کند.

class BankPaymentGateway implements PaymentGatewayContract {
    private $currency;
    private $discount;

    public function __construct($currency) {

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

    public function charge($amount) {
        // Charge the bank
        return [
            'amount'              => $amount,
            'confirmation_number' => Str::random(),
            'currency'            => $this->currency,
            'discount'            => $this->discount,
        ];
    }

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

ما با این کار یک API  یکسان برای تمامی کلاس‌هایی که قرار هست عملیات پرداخت را پیاده سازی کنند ساختیم و همه‌ی آن کلاس‌ها موظف هستند تا آن چیزی که ما انتظارش را داریم پیاده سازی کنند. اما نحوه‌ی پیاده سازی بر عهده‌ی BankPaymentGateway و  CreditPaymentGateway است.

هر متدی که داخل Interface بسازیم باید توسط کلاس‌ها پیاده سازی شوند. حال در PayOrderController تغییراتی اعمال می‌کنیم:

این تغییرات شامل چه چیزهایی می‌شوند؟ با توجه به تغییراتی که داخل فایل‌هایی که داشتیم انجام دادیم. می‌خواهیم یک تغییر کوچک اما مهم را بدهیم همان‌طور که اشاره شد interface‌ها یک سری قرارداد هستند که باید توسط کلاس‌ها پیاده سازی شوند؟ BankPaymentGateway ورودی قبلی متد store بود، اما الان می‌توانیم PaymentGatewayContract را به عنوان ورودی دریافت کنیم.

class PayOrderController extends Controller {
    public function store(PaymentGatewayContract $paymentGateway, OrderDetails $orderDetails) {
        $order = $orderDetails->all();
        dd($paymentGateway->charge(25000));
    }
}

حال مرورگر را Refresh می‌کنیم.

اما یک خطای جدید می‌بینیم:

اولین مشکلی که داریم این هست که، این خطا به این معنی است که لاراول نمی‌تواند یک iterface بسازد.

برای حل این مشکل باید برویم سراغ فایل AppServiceProvider، ما داخل این فایل قبلاً نوشتیم که هر وقت درخواستی به  BankPaymentGateway نیاز داشت، یک نمونه‌ی جدید برگرداند. اما الان به جای BankPaymentGateway می‌گوییم که هر وقت درخواستی به PaymentGatewayContract نیاز داشت خروجی رابرگرداند.

حال اگر صفحه را Refresh کنیم خروجی به شکل زیر هست که یک نوع خطای دیگر است.

ما داخل کلاس OrderDetails از BankPaymentGateway استفاده کردیم که خطای دومی که دریافت می‌کنیم به همین خاطر هست. با تغییر BankPaymentGateway به PaymentGatewayContract مشکل برطرف می‌شود. در قطعه کد زیر می‌بینیم که ما داخل متد سازنده از PaymentGatewayContract استفاده کردیم.

class OrderDetails {
    /**
     * @var BankPaymentGateway
     */
    private $paymentGateway;

    public function __construct(PaymentGatewayContract $paymentGateway) {

        $this->paymentGateway = $paymentGateway;
    }

    public function all() {
        $this->paymentGateway->setDiscount(50);
        return [
            'name'    => 'Vahid',
            'address' => 'Iran - Tehran',
        ];
    }
}

خروجی در مرورگر :

همان‌طور که مشاهده می‌کنید همه چیز به درستی کار می‌کند.

شاید برای شما سوال پیش بیاید که آیا می‌شود کدهایی که نوشتیم را بهتر از قبل کرد؟

ساخت کلاس CreditPaymentGateway

حال یک PaymentGateway دیگر به نام CreditPaymentGateway می‌سازیم. همان‌طور که مشاهده می‌کنید این کلاس نیز، اینترفیس PaymentGatewayContract رو ایمپلمنت کرده است.

class CreditPaymentGateway implements PaymentGatewayContract {

    private $currency;
    private $discount;

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

    public function charge($amount) {
        // Charge the bank
        return [
            'amount'              => $amount - $this->discount,
            'confirmation_number' => Str::random(),
            'currency'            => $this->currency,
            'discount'            => $this->discount,
        ];
    }

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

چرا از همان اینترفیس PaymentGatewayContract تبعیت می‌کند؟ چون قوانینی که ما تعریف کردیم باید بین تمامی کلاس‌ها ثابت باشند تمامی آن‌ها متدی برای شارژ کردن و همین‌طور یک متد برای اعمال تخفیف روی قیمت کل باید داشته باشند.

ما داخل این کلاس می‌خواهیم قابلیت مالیات را به فاکتورهایی که داریم اضافه کنیم به این صورت که 3 درصد کل مبلغ فاکتور به کل مبلغ اضافه می‌شود. این یک مورد جدید است که فقط داخل این روش وجود دارد و در روش قبلی وجود نداشت.

class CreditPaymentGateway implements PaymentGatewayContract {

    private $currency;
    private $discount;

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

    public function charge($amount) {
        $fees = $amount * 0.3;
        // Charge the bank
        return [
            'amount'              => ($amount - $this->discount) + $fees,
            'confirmation_number' => Str::random(),
            'currency'            => $this->currency,
            'discount'            => $this->discount,
        ];
    }

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

اکنون ما چگونه می‌توانیم شیوه‌ی پرداخت را تغییر بدهیم؟ معلوم است باید داخل فایل AppServiceProvider، به جای اینکه یک نمونه از BankPaymentGateway بسازیم یک نمونه از CreditPaymentGateway  بسازیم و بر گردانیم.

تغییری که داخل فایل AppServiceProvider اعمال کردیم:

class AppServiceProvider extends ServiceProvider {
    /**
     * Register any application services.
     *
     * @return void
     */
    public function register() {
        $this->app->singleton(PaymentGatewayContract::class, function ($app) {
            return new CreditPaymentGateway("ir");
        });
    }

    /**
     * Bootstrap any application services.
     *
     * @return void
     */
    public function boot() {
        //
    }
}

تا اینجای کار ما توانستیم ساختاری را پیاده سازی نماییم که بر اساس آن بدون کوچک‌ترین تغییری داخل Controller‌های خود و OrderDetail، شیوه‌ی پرداخت را از حالت Bank به Credit تغییر بدهیم.

ما با کمک اینترفیس و کد هایی که نوشتیم زیر ساختی را پیاده کردیم که داخل آن برای اینکه شیوه‌ی پرداخت، بانک یا اعتباری است مهم نبود. این یکی از مزایای اینترفیس هست که به کمک ما آمد.

اکنون اگر بخواهیم این امکان را داشته باشیم که به‌صورت خودکار بین شیوه‌های پرداخت جابجا شویم باید چه‌کار کنیم؟ خیلی ساده‌ است با بررسی اینکه داخل رکوئستی(Request) که دریافت کرده ایم.

به کد‌های زیر توجه کنید:

class AppServiceProvider extends ServiceProvider {
    /**
     * Register any application services.
     *
     * @return void
     */
    public function register() {
        $this->app->singleton(PaymentGatewayContract::class, function ($app) {
            if (request()->has('credit')) {
                return new CreditPaymentGateway('ir');
            }
            return new BankPaymentGateway('ir');

        });
    }

    /**
     * Bootstrap any application services.
     *
     * @return void
     */
    public function boot() {
        //
    }
}

با بررسی اینکه آیا داخل رکوئستی که دریافت کرده‌ایم کلید credit وجود دارد یا خیر؟ اگر وجود داشت از شیوه پرداخت به صورت Credit یک نمونه می‌سازیم و در غیر اینصورت از حالت Bank یک نمونه می‌سازیم.

شما می‌توانید در عمل از صفحه Checkout خود این مورد را برررسی کنید مثلا کاربر کدام شیوه‌ی پرداخت را انتخاب کرده است.

با فرخوانی آدرس زیر : https://127.0.0.1:8000/pay خروجی که می‌بینیم متناسب با بررسی که انجام می‌شود به‌صورت زیر است:

و با فراخوانی آدرس زیر : https://127.0.0.1:8000/pay?credit=true ما برای مثال نوشته‌ایم true باشد طبیعتاً خروجی به شکل زیر هست :

جمع بندی:

آشنایی با service container لاراول باعث می‌شود با ساز و کار هسته لاراول بیشتر آشنا شوید. در این مقاله همراه با یک مثال کاربردی با Service Container که یکی از ابزار‌های کارآمد در توسعه نرم‌افزار‌های تحت وب توسط لاراول هست آشنا شدیم. در مقاله بعدی که با عنوان خصوصیات یک ماژول خوش ساخت است همراه شما خواهیم بود.

چه امتیازی به این مقاله می دید؟
نویسنده وحید گروسی
با ورود به دانشگاه و آشنایی با هنر برنامه نویسی، خود را غرق در آن دیدم و رفته رفته شروع به فعالیت در این حوزه نمودم. پس از اتمام دوره کارشناسی، در سال 1395 به سمت بازار کار در حوزه های گرافیک، طراحی، چاپ و بسته بندی کشیده شدم و همزمان و البته با سرعتی کمتر، تدوین را آموختم. پس از آشنایی با برنامه نویسی اندروید، تلاشم را برای یادگیری و مطالعه در این حوزه افزایش دادم و اکنون به مدت 3 سال است که به صورت تخصصی برنامه نویسی اندروید را پی گرفته و در حال فعالیت در این زمینه می باشم.

نظرات کاربران

رضا

عالی.کاملا ساده و روان بود

fateme

ممنون از وقتی که گذاشتید
مفید بود

مجتبی

خیلی هم عالی
دم شما گرم

وحید گروسی

زنده باشید
خیلی خوشحالم از اینکه موردتوجهتون قرار گرفته

ارسال دیدگاه
خوشحال میشیم دیدگاه و یا تجربیات خودتون رو با ما در میون بذارید :