- PVSM.RU - https://www.pvsm.ru -
В этой статье я расскажу о том как создать легко расширяемую, модульную структуру. Подобная организация используется в Symfony [1]. Так же мы будем использовать Composer [2]. Что это такое и как его использовать можно почитать тут [3].
Итак, наша модульная структура будет базироваться прежде всего на принципах инверсии управления [4]. Мы будем использовать контейнеры IoC [5] и мою же библиотеку [6].
Начнем с создания библеотеки управления модулями. Я назвал её Modular [7].
Сначала опишем composer.json:
{
"name":"elfet/modular",
"type":"library",
"autoload": {
"psr-0": {
"Modular": "src/"
}
},
"require":{
"php":">=5.3.0",
"elfet/ioc":"dev-master"
}
}
Теперь там где мы будем использовать «modular» у нас будет подключаться IoC.
Предполагаемая структура нашей модульной системы будет такой:
index.php - Наш фронт контроллер
app/
app.ini - список модулей
ModuleOne/
module.ini - описание модуля
ModuleTwo/
Опишем класс фронт контроллера App:
namespace Modular;
use IoCContainer;
use ComposerAutoloadClassLoader; // Используем загрузчик из /vendor/autoload.php
class App
{
protected $rootDir; // Путь до папки app/
protected $ioc; // Наш ioc контейрен
protected $loader; // Загрузчик модулей.
public function __construct($rootDir, ClassLoader $classLoader)
{
$this->rootDir = $rootDir;
$this->ioc = Container::getInstance();
$this->loader = new Loader($this->ioc, $classLoader);
}
public function load()
{
$appConfig = parse_ini_file($this->rootDir . '/app.ini', true);
// Загружаем список модулей из app.ini
// Каждой записи позволяем определить расположение модуля
// и класс модуля.
foreach ($appConfig as $module => $config) {
// По умолчанию используем для модуля класс ModularModule
$config = array_merge(array(
'class' => 'ModularModule',
'path' => $this->rootDir . '/module/' . $module,
), $config);
// Загружаем модули
$this->loader->load(
$module,
$config['class'],
$this->rootDir . '/' . $config['path']
);
}
}
public function run()
{
$this->load();
}
}
Посмотрим как работает загрузка модулей:
public function load($moduleName, $moduleClass, $moduleDir)
{
// Добавляем файлы модуля в автозагрузку
// Имя директории должно соответствовать пространству имен модуля (Используется PSR-0)
$this->classLoader->add($moduleName, dirname($moduleDir));
// Создаём класс модуля
$module = new $moduleClass;
$module->setModuleDir($moduleDir);
// И загружаем его интерфейсы/классы в IoC.
// Модуль может переопределить метод load
// или описать используемые классы в module.ini
$module->load($this->ioc);
}
Создадим класс Module, который будет описывать наш модуль.
namespace Modular;
use IoCContainer;
use IoCAssocService;
class Module
{
private $moduleDir; // Директория нашего модуля.
public function load(Container $container)
{
$this->loadFromFile($container, $this->getModuleDir() . '/module.ini');
}
protected function loadFromFile(Container $container, $file)
{
$module = parse_ini_file($file, true);
foreach ($module as $class => $params) {
// В описании класса может быть указано несколько интерфейсов
// если они не указаны IoC сам определит их через Reflection (соответственно классы будут загруженны).
$interfaces = isset($params['interface']) ? (array)$params['interface'] : array();
// Остальные параметры мы будем использовать для создания класса.
unset($params['interface']);
// Создаём ассоциацию-сервис с оставшимися параметрами.
// Класс $class создаётся только при необходимости и всего один раз.
// Конструктор этого класса может принимать параметры.
$serviceAssoc = new Service($class, $params);
$container->assoc($serviceAssoc, $interfaces);
}
}
...
}
Теперь попробуем создать и затем расширить модуль. Для простоты попробуем создать записную книжку. Весь код её можно найти тут [8].
Создадим composer.json:
{
"require":{
"php":">=5.3.0",
"elfet/modular":"dev-master"
}
}
и выполним composer install. Теперь у нас есть папка vendor/ со всем необходимым.
Создадим папку app/Notepad/ и начнем с создания интерфейса хранилища StorageInterface:
namespace Notepad;
interface StorageInterface
{
public function set($key, $value);
public function get($key);
public function save();
public function load();
}
и так же простую реализацию FileStorage [9].
namespace Notepad;
use NotepadStorageInterface;
class FileStorage implements StorageInterface
{
protected $store = array();
protected $file;
public function __construct($file = 'store.json')
{
$this->file = realpath(__DIR__ . '/../cache/' . $file);
}
public function set($key, $value)
{
$this->store[$key] = $value;
}
public function get($key)
{
return isset($this->store[$key]) ? $this->store[$key] : null;
}
public function save()
{
file_put_contents($this->file, json_encode($this->store));
}
public function load()
{
$content = file_get_contents($this->file);
$this->store = (array)json_decode($content);
}
}
Опишим этот класс в module.ini [10]:
[NotepadFileStorage]
interface = NotepadStorageInterface
file = store.json
Теперь любой класс в конструкторе (например NotepadController [11]) которого содержится StorageInterface получит FileStorage:
public function __construct(StorageInterface $storage)
Весь код модуля Notepad доступен тут [12].
Попробуем создать модуль MyNotepad который будет расширять модуль Notepad. Например, мы теперь хотим использовать DbStorage. Создадим app/MyNotepad/DbStorage.php [13] и опишим его в app/MyNotepad/module.ini:
[MyNotepadDbStorage]
database = mystore.db
и добавим наш модуль в app.ini [14]
[Notepad]
path = Notepad/
[MyNotepad]
path = MyNotepad/
Теперь класс NotepadController получит при создании экземпляр класса MyNotepadDbStorage. Вот так вот просто, без изменения модуля Notepad, бы расширили его функциональность. На github [8] можно посмотреть как переопределить другие части Notepad.
Автор: Elfet
Сайт-источник PVSM.RU: https://www.pvsm.ru
Путь до страницы источника: https://www.pvsm.ru/php-2/12989
Ссылки в тексте:
[1] Symfony: http://symfony.com/doc/current/components/dependency_injection/introduction.html
[2] Composer: http://getcomposer.org/
[3] тут: http://habrahabr.ru/post/145946/
[4] инверсии управления: 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
[5] контейнеры IoC: http://habrahabr.ru/post/132084/
[6] библиотеку: https://github.com/Elfet/IoC
[7] Modular: https://github.com/Elfet/Modular
[8] тут: https://github.com/Elfet/modular-example
[9] FileStorage: https://github.com/Elfet/modular-example/blob/master/app/Notepad/FileStorage.php
[10] module.ini: https://github.com/Elfet/modular-example/blob/master/app/Notepad/module.ini
[11] Controller: https://github.com/Elfet/modular-example/blob/master/app/Notepad/Controller.php
[12] тут: https://github.com/Elfet/modular-example/tree/master/app/Notepad
[13] app/MyNotepad/DbStorage.php: https://github.com/Elfet/modular-example/blob/master/app/MyNotepad/DbStorage.php
[14] app.ini: https://github.com/Elfet/modular-example/blob/master/app/app.ini
Нажмите здесь для печати.