Главная
Блог разработчиков phpBB
 
+ 17 предустановленных модов
+ SEO-оптимизация форума
+ авторизация через соц. сети
+ защита от спама

Laravel: Dependency Injection на практике

Anna | 31.05.2014 | нет комментариев

В своих 2-х предыдущих статьях я рассказал о Dependency Injection и IoC контейнере, и о том, как они работают определенно в Laravel. Данный пост будет посвящен фактическому использованию DI и IoC на настоящем примере. А так же, какие все таки превосходства нам дают эти два красивых инструмента и паттерна в приложении.

Вступление

Перед нами стоит задача встроить вероятность отправки СМС. Мы могли бы написать класс для работы с определенным провайдером (gate) либо взять теснее написанный класс самим провайдером. Но нам говорят, что в грядущем допустима смена sms провайдера. Не напасть, первая мысль — написать компонент, в котором за несколько часов мы потом сумеем сменить реализацию отправки СМС. А сейчас давайте позабудем эту мысль и реализуем это больше прекрасно, не привязываясь к провайдерам и с вероятностью стремительно переключаться с одного провайдера на иной.

Дабы отменнее понимать данную доктрину, я рекомендую рассматривать СМС провайдера как драйвер для отправки СМС. Переключение должно протекать так же безболезненно, как отключить ваш ветхий монитор и подключить новейший либо поменять клавиатуру. Рассматривайте данный компонент системы как физическое устройство. Да и вообще, ваше приложение — это некоторый компьютер (устройство компонент), к которому подсоединяются разные компоненты, как в конструкторах Lego. Как мне кажется, рассматривая свое приложение таким образом, у вас получится особенно результативно подойти к дизайну архитектуры.

Реализация

Все классы для СМС я буду помещать в папке `app\Acme\Sms` и зарегистрирую под PSR-0 в composer.json:

"psr-0": {
    "Acme": "app"
}

Для начала нам необходимо описать интерфейс, тот, что будут применять все sms драйвера и перечислить способы, которые нам необходимы.


<?php namespace Acme\Sms;

interface SmsGateInterface
{

    /**
     * @param SmsRecipient $recipient
     * @param  string      $text
     */
    public function send(SmsRecipient $recipient, $text);

}

Нам понадобится пока только 1 способ `send`, тот, что будет отправлять СМС. Класс `SmsRecipient` хранит в себе данные по получателю:


<?php namespace Acme\Sms;

class SmsRecipient
{    
    public $phone;    
}

Установим класс для работы с провайдером SmsOnline в composer:

"require": {
    "laravel/framework": "4.0.*",
    "kkamkou/sms-online-api": "dev-master"
}

Сейчас нам необходимо написать драйвер этого провайдера и реализовать интерфейс, тот, что мы описали выше:


<?php namespace Acme\Sms;

use SmsOnline\Api as SmsOnlineApi;

class SmsOnlineGate implements SmsGateInterface
{

    private $api;

    public function __construct(SmsOnlineApi $api)
    {
        $this->api = $api;
    }

    /**
     * @param SmsRecipient $recipient
     * @param  string      $text
     */
    public function send(SmsRecipient $recipient, $text)
    {
        $this->api->send($recipient->phone, $text);
    }
}

Но DI класса `SmsOnline\Api` провести так легко у нас не получится, т.к. конструктор класса `SmsOnline\Api` принимает массив с конфигурацией. Сотворим конфигурационный файл для нашего СМС компонента (`app/config/sms.php`), и заодно поставим драйвер по-умолчанию `SmsOnlineGate`:


<?php
return [

    'default' => 'Acme\Sms\SmsOnlineGate',

    'drivers' => [

        'Acme\Sms\SmsOnlineGate' => [

            'user'       => '',
            'secret_key' => '',

        ],

    ],

];

Сейчас дело за IoC. Сделаем файл `app/bindings.php`, где мы будем настраивать IoC:


<?php
$smsConfig = Config::get('смс');

$smsGate = $smsConfig['default'];

App::bind('Acme\Sms\SmsGateInterface', $smsGate);    

Мы получаем драйвер для СМС по-умолчанию и говорим IoC, что когда приложение хочет `SmsGateInterface` отдай ему `SmsOnlineGate`. Кстати, если вы теснее PHP до версии 5.5, то рекомендую код переписать дальнейшим образом:

app/config/sms.php


<?php
use Acme\Sms\SmsOnlineGate;

return [

    'default' => SmsOnlineGate::class,

    'drivers' => [

        SmsOnlineGate::class => [

            'user'       => '',
            'secret_key' => '',

        ],

    ],

];

app/bindings.php


<?php
use Acme\Sms\SmsGateInterface;

$smsConfig = Config::get('смс');

$smsGate = $smsConfig['default'];

App::bind(SmsGateInterface::class, $smsGate);

Это комфортно тем, что при рефакторинге мы сумеем легко менять наименования классов, а IDE, в свою очередь, заменит эти строки включительно.
Дальше нам необходимо прописать конфигурацию для `SmsOnline\Api`, дополнив app/bindings.php


<?php
use Acme\Sms\SmsGateInterface;
use Acme\Sms\SmsOnlineGate;

// указываем нынешний драйвер
$smsConfig = Config::get('смс');

$smsGate = $smsConfig['default'];

App::bind(SmsGateInterface::class, $smsGate);

// настраиваем класс "SmsOnline"
App::bind(SmsOnline\Api::class, function ($app) {
    $gateConfig = Config::get('смс');
    $gateConfig = $gateConfig['drivers'][SmsOnlineGate::class];

    return new SmsOnline\Api($gateConfig);
});

Сейчас когда приложение затребует объект класса `SmsOnline\Api` — оно получит сконфигурированный экземпляр.

Применяя данный дизайн в вашем приложении вы сумеете легко переключаться между провайдерами — вам будет довольно написать драйвер для него и поменять конфигурацию, как скажем, во время разработки мы не хотим отправлять СМС через провайдера, следственно мы можем записывать куда-нибудь в БД либо даже в файл. Для этого мы напишем драйверы `DatabaseSmsGate` и `FileSmsGate` по тому же тезису. Самое время перейти к самой «аппетитной части» — покрытие кода тестами.

Тестирование

Собственно, это самый основной плюс в DI: комфортное тестирование в полнейшей изоляции. Взамен того, Дабы прососывать реальные объекты с рабочими способами — в тестах вы создаете Mock объекты с способами заглушек и проверяете, то что способ был вызван n раз с ожидаемыми доводами и в определенном порядке. Давайте разглядим как протестировать наш код, написанный выше.

Для начала мне необходимо установить phpunit и mockery. Ставлю так же через composer:

"require-dev": {
        "phpunit/phpunit": "3.8.*@dev",
        "mockery/mockery": "dev-master"
}

Во время тестирования всякого класса я хочу Дабы мои тесты выполнялись в полной изоляции. Скажем, когда вы тестируете класс `SmsOnlineGate`, в его способе `send` вызывается способ `send` из `SmsOnlineApi`, но он не должен вызываться физически. То есть вы проверяете только то, что способ `send` из `SmsOnlineApi` был вызван, но никак не физически. Для этого мы будем применять Mock объекты. Разглядим как будет выглядеть наш тест:


<?php

use Acme\Sms\SmsOnlineGate;
use Acme\Sms\SmsRecipient;

class SmsOnlineGateTest extends TestCase {

    /**
     * @var SmsOnline\Api
     */
    private $api;

    /**
     * @var SmsOnlineGate
     */
    private $gate;

    /**
     * @var SmsRecipient
     */
    private $recipient;

    public function setUp() {
        parent::setUp();

        $this->api = Mockery::mock(SmsOnline\Api::class)->makePartial();
        $this->recipient = Mockery::mock(SmsRecipient::class);

        $this->gate = new SmsOnlineGate($this->api);
    }

    public function test_send()
    {
        $text = 'текст sms';

        $this->api->shouldReceive('send')
            ->withArgs([$this->recipient->phone, $text])
            ->once();

        $this->gate->send($this->recipient, $text);
    }

}

Тест заключается в том, что мы проверяем, что способ send в `SmsOnline\Api` был подлинно вызван один раз с требуемыми параметрами. На самом деле он не был вызван, взамен этого был вызван способ из нашего Mock объекта, и в этом нам помог Mockery.
Нам необходим еще один тест, Дабы удостовериться, что когда приложение хочет получить `SmsGateInterface`, IoC возвращает нам `SmsOnlineGate`, т.к. он прописан у нас в конфиге по умолчанию:


<?php
use Acme\Sms\SmsGateInterface;
use Acme\Sms\SmsOnlineGate;

class SmsGateTest extends TestCase
{

    public function test_instance()
    {
        $instance = App::make(SmsGateInterface::class);

        $this->assertInstanceOf(SmsOnlineGate::class, $instance);
    }

} 

На этом все, что я хотел рассказать. Тут я не рассматривал инъекцию объектов в IoC через ServiceProvider, что является больше верным решением.
Я верю, что я довольно детально расписал мое видение в пользе DI и IoC на примере компонента отправки sms. Помните, что разработка должна приносить наслаждение, а вы себя обязаны Ощущать художником, тот, что рисует механизм c очаровательным внутренним устройством. Если у вас все еще остались вопросы — спрашивайте в комментариях, я с удовольствием на них отвечу.

Список пригодной литературы:

Источник: programmingmaster.ru

Оставить комментарий
Форум phpBB, русская поддержка форума phpBB
Рейтинг@Mail.ru 2008 - 2017 © BB3x.ru - русская поддержка форума phpBB