- PVSM.RU - https://www.pvsm.ru -

Разработка архитектуры приложения с использованием инверсии зависимости

Разработка архитектуры приложения с использованием инверсии зависимости

В этой статье я хочу ещё раз поговорить о разработке архитектуры приложения с использованием инверсии зависимости (Inversion of Control [1]).
Я уже писал на хабре о библиотеке IoC [2] и о Modular [3]. Теперь я пошел ещё дальше и упростил все что только можно и попробую объяснить принципы построения архитектуры. А так же расскажу о новой библиотеке Granula [4].

Давайте представим, что мы хотим сделать библиотеку для управления пользователями на сайте. Первое что нам понадобится, это место где мы будем хранить информацию о
наших пользователях.
Опишем интерфейс хранилища:

interface StorageInterface
{
    public function set($key, $value);
    public function get($key);
    public function save();
    public function load();
}

Отлично, теперь нам нужна реализация этого интерфейса. Для начала будем хранить информацию в файлах. Создадим класс FileStorage.

FileStorage.php

class FileStorage implements StorageInterface
{
	private $file = 'data.json';
	private $data = array();	

    public function set($key, $value)
    {
    	$this->data[$key] = $value;
    }

    public function get($key) 
    {
    	return $this->data[$key];
    }

    public function save()
    {
 	file_put_contents($this->file, json_encode($this->data));
    }

    public function load()
    {
    	$this->data = json_decode(file_get_contents($this->file));
    }
}

Теперь создадим класс пользователя

class User
{
    public function __construct(StorageInterface $storage)
    {
    }
}

Теперь что бы создать экземпляр класса User:

$user = new User(new FileStorage());

Отлично, а что если кто-то из пользователей нашей библиотеки захочет вместо файлов использовать базу данных? Для этого ему нужно создать класс DatabaseStorage, реализовать интерфейс StorageInterface и заменить все вхождения FileStorage. Но изменение библиотеки сулит проблемы с её обновлениями.
Что бы этого избежать, давайте, введём опции:

$options = array(
    'StorageInterface' => 'FileStorage',
);

$user = new User($option['StorageInterface']);

Теперь что бы заменить FileStorage на DatabaseStorage, нужно всего лишь указать это в опциях:

$options['StorageInterface'] = 'DatabaseStorage';

То, что мы сейчас назвали опциями, на самом деле является контейнером IoC.

Именно такая архитектура позволяет строить наиболее гибкие приложения и библиотеки.
В своей предыдущей статье я рассказывал о библиотеке Modular, я продолжил развивать её, постарался упростить все для наилучшего понимания. Её основной задачей является обучение применения IoC на практике, создания модульной архитектуры приложения.

Теперь она называется Granula [4].

Любая библиотека может быть модулем для гранулы. Например из компонентов Symfony Components [5] можно создать MVC приложение на подобии самой Symfony.

Каждый модуль гранулы должен быть описан своим классом:

use GranulaModule;
use InversionContainer;
 
class MyModule extends Module
{
    public function build(Container $container)
    {
        // Опишите свой модуль здесь.
    }
}

Например, описание библиотеки, которую мы создавали в начале статьи будет таким:

$container['StorageInterface'] = 'FileStorage';

Можно даже сократить ещё больше:

$container[] = 'FileStorage';

Но в таком случае не будет работать ленивая загрузка классов, так как FileStorage будет загружен Inversion [6](библиотекой IoC контейнеров) сразу для определения его интерфейсов.

Пример описания модуля для Symfony Routing Component

        $container['request']
            = $container['SymfonyComponentHttpFoundationRequest']
            = new Factory('SymfonyComponentHttpFoundationRequest', 'createFromGlobals');

        $container['SymfonyComponentConfigFileLocator']
            = 'SymfonyComponentConfigFileLocator';

        $container['DoctrineCommonAnnotationsReader']
            = 'DoctrineCommonAnnotationsAnnotationReader';

        $container['SymfonyComponentRoutingLoaderAnnotationClassLoader']
            = 'GranulaRouterAnnotatedRouteControllerLoader';

        $container['SymfonyComponentConfigLoaderLoaderInterface']
            = 'SymfonyComponentRoutingLoaderAnnotationDirectoryLoader';

        $container['request.context']
            = $container['SymfonyComponentRoutingRequestContext']
            = new Service('SymfonyComponentRoutingRequestContext');

        $container['router']
            = $container['SymfonyComponentRoutingRouterInterface']
            = new Factory('GranulaRouterRouterFactory');

Затем все необходимые модули указываются в Front Controller:

class App extends GranulaApp
{
    public function register()
    {
        return array(
            new MyModule(),
            // Список модулей
        );
    }
}

И в файле index.php запускаются:

$app = new App();
$app->run();

Я оформил все необходимые модули для создания полноценного MVC приложения. Что бы поиграться с ним используйте Composer [7] для установки:

composer create-project granula/app www

В него включены:

  • Symfony Components
  • Twig
  • Doctrine ORM

Полезные ссылки

Contributors are Welcome!

Автор: Elfet

Источник [9]


Сайт-источник PVSM.RU: https://www.pvsm.ru

Путь до страницы источника: https://www.pvsm.ru/php-2/19922

Ссылки в тексте:

[1] Inversion of Control: http://ru.wikipedia.org/wiki/%D0%98%D0%BD%D0%B2%D0%B5%D1%80%D1%81%D0%B8%D1%8F_%D1%83%D0%BF%D1%80%D0%B0%D0%B2%D0%BB%D0%B5%D0%BD%D0%B8%D1%8F

[2] IoC: http://habrahabr.ru/post/132084/

[3] Modular: http://habrahabr.ru/post/149435/

[4] Granula: http://granula.github.com/

[5] Symfony Components: http://symfony.com/components

[6] Inversion: https://github.com/granula/inversion

[7] Composer: http://getcomposer.org/

[8] Управление зависимостями в PHP-коде: http://wiki.agiledev.ru/doku.php?id=ooad:manage_dependencies_in_php_code

[9] Источник: http://habrahabr.ru/post/158213/