Create plugin system php

Создание плагина для PHP Composer’а

При развертывании Magento-приложений с использованием Magento Composer столкнулись с проблемой, что различные экземпляры одного и того же приложения (девелоперский, тестовый и т.д.) должны использовать различные локальные настройки (например, параметры подключения к БД). До этого, в другом проекте, использовался подход, когда в приложении (под контролем версий) находятся шаблоны конфигурационных файлов с placeholder’ами и скрипт, замещающий placeholder’ы локальными значениями и копирующий результат в нужное место. Локальные значения хранились отдельно для каждого экземпляра по месту развертывания. Хотелось привычный подход сохранить и для нового метода развертывания. Поиск устраивающего плагина на packagist.org завершился ненахождением, в силу чего и родилась идея сделать подобный плагин самостоятельно. При создании плагина пришлось надергать информацию из различных источников — информации с сайта Composer’а по плагинам оказалось недостаточно. Что и привело к написанию этой статьи.

composer.json

Параметр name заполняется по своему вкусу, у меня получился praxigento/composer_plugin_templates.

С параметрами type и require все достаточно однозначно — они должны быть и быть именно такими.

Параметр autoload.psr-4 определяет настройку автозагрузки классов плагина в соответствии с PSR-4 (рекомендуется использовать именно этот стандарт, т.к. PSR-0 устарел 21 октября 2014 года). Согласно этой настройки наши исходники располагаются в подкаталоге ./src/.

Параметр extra.class определяет основной класс плагина, который подгружается Composer’ом (или набор классов, если значение параметра — массив).

Параметр scripts.test позволяет запускать тестирование плагина из командной строки: «$ composer test«.

Точка входа

Класс, заданный в extra.class, является точкой входа Composer’а в наш плагин. Согласно требованиям Composer’а этот класс должен имплементировать интерфейс Composer\Plugin\PluginInterface.

namespace Praxigento\Composer\Plugin\Templates; use Composer\Composer; use Composer\IO\IOInterface; use Composer\Plugin\PluginInterface; class Main implements PluginInterface < protected $composer; protected $io; public function activate(Composer $composer, IOInterface $io) < $this->composer = $composer; $this->io = $io; > > 

Доступ к параметрам

Конфигурация параметров работы плагина осуществляется через секцию extra в composer.json основного пакета, в котором используется наш плагин.

Плагин должен взять настройки для своей работы из файла, который задается через параметр extra.praxigento_templates_config конфигурационного файла проекта (composer.json). Мы это делаем при инициализации плагина:

class Main implements PluginInterface, EventSubscriberInterface < public function activate(Composer $composer, IOInterface $io) < $this->composer = $composer; $this->io = $io; $extra = $composer->getPackage()->getExtra(); $configFile = $extra['praxigento_templates_config']; > > 

Обработка событий

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

class Main implements PluginInterface, EventSubscriberInterface < public static function getSubscribedEvents() < $result = array( ScriptEvents::POST_INSTALL_CMD =>array( array( 'onPostInstallCmd', 0 ) ), ScriptEvents::POST_UPDATE_CMD => array( array( 'onPostUpdateCmd', 0 ) ), ); return $result; > public function onPostInstallCmd(CommandEvent $event) < >public function onPostUpdateCmd(CommandEvent $event) < >> 

Запуск тестов

Настройки юнит-тестирования — в файле phpunit.xml.dist:

Читайте также:  Nnmclub to forum viewforum php

Загрузка autoloader’а, совместимого с PSR-4 (без него не запускаются тесты через IDE PhpStorm) в скрипте phpunit.bootstrap.php:

require __DIR__.'/vendor/autoload.php'; 

Запуск тестов через composer:

Заключение

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

Источник

How to Build a PHP Plugin Module System (Step-By-Step Example)

Welcome to a tutorial and example on how to create a PHP plugin module system. So you have a project that has grown massively large and needs to get things done in an organized manner.

The general steps and considerations for developing a plugin system are:

    • First, develop a core system that has a set of “base functions” and manages the loading of plugin modules.
    • Then, build the modules on top of the base system. E.G. Email, users, products, newsletters, orders, etc…
    • The modules should communicate with each other to speed up development. For example, get a list of emails with the user module, and send newsletters out using the email module.

    But just how does this entire “plugin system” work? Let us walk through an example module system in this guide – Read on!

    TABLE OF CONTENTS

    SYSTEM CORE DEVELOPMENT

    All right, let us now get started with the core of the PHP plugin system.

    PART 1) CREATE LIB FOLDER & ACCESS RESTRICTION

    First, let us create a lib folder to contain all our library/module files, and add a .htaccess protect it. For you guys who are new to the Apache web server – This will prevent users from directly accessing and messing with the system files. That is, directly accessing http://site.com/lib will throw a 403 unauthorized error.

    P.S. We can still load the files in the lib folder via PHP without any issues.

    P.P.S. If you are using IIS or NGINX, it is also possible to put this access restriction in place.

    PART 2) CORE STARTER

    1. Set how PHP handles errors.
    2. Define the database settings.
    3. Automatically detect the system file paths.
    4. Start the session.
    5. Load our core library and create a $_CORE = new Core() object.

    Simply require lib/CORE-Go.php at the top of your scripts to kickstart the engine.

    PART 3) CORE LIBRARY

    3A) DATABASE FUNCTIONS

    pdo = new PDO( "mysql:host=". DB_HOST .";charset=". DB_CHARSET .";dbname=". DB_NAME, DB_USER, DB_PASSWORD, [ PDO::ATTR_ERRMODE => PDO::ERRMODE_EXCEPTION, PDO::ATTR_DEFAULT_FETCH_MODE => PDO::FETCH_ASSOC ]); > // (C) CLOSE CONNECTION WHEN DONE function __destruct () < if ($this->stmt !== null) < $this->stmt = null; > if ($this->pdo !== null) < $this->pdo = null; > > // (D) RUN SQL QUERY // $sql : sql query // $data : array of parameters function exec ($sql, $data=null) : void < $this->stmt = $this->pdo->prepare($sql); $this->stmt->execute($data); > // (F) FETCH SINGLE ROW // $sql : sql query // $data : array of parameters function fetch ($sql, $data=null) < $this->exec($sql, $data); return $this->stmt->fetch(); > // (F) FETCH MULTIPLE ROWS // $sql : sql query // $data : array of parameters // $arrange : (string) arrange by [$arrange] => results // (array) arrange by [$arrange[0] => $arrange[1]] // (none) default - whatever is set in pdo function fetchAll ($sql, $data=null, $arrange=null) < // (F1) RUN SQL QUERY $this->exec($sql, $data); // (F2) FETCH ALL AS-IT-IS if ($arrange===null) < return $this->stmt->fetchAll(); > // (F3) ARRANGE BY $DATA[$ARRANGE] => RESULTS else if (is_string($arrange)) < $data = []; while ($r = $this->stmt->fetch()) < $data[$r[$arrange]] = $row; >return $data; > // (F4) ARRANGE BY $DATA[$ARRANGE[0]] => $ARRANGE[1] else < $data = []; while ($r = $this->stmt->fetch()) < $data[$r[$arrange[0]]] = $r[$arrange[1]]; >return $data; > > >
    • (B & C) When $_CORE = new Core() is created, the constructor will automatically connect to the database. The destructor closes the connection.
    • (D) exec() A helper function to run an SQL query.
    • (E) fetch() Get a single row from the database.
    • (F) fetchAll() Get multiple rows from the database (and arrange the data).

    3B) MODULE LOADER

    // (G) LOAD SPECIFIED MODULE // $module : module to load function load ($module) : void < // (G1) CHECK IF MODULE IS ALREADY LOADED if (isset($this->loaded[$module])) < return; >// (G2) EXTEND MODULE $file = PATH_LIB . "LIB-$module.php"; if (file_exists($file)) < require $file; $this->loaded[$module] = new $module($this); > else < throw new Exception("$module module not found!"); >> // (H) "MAGIC LINK" TO MODULE function __get ($name) < if (isset($this->loaded[$name])) < return $this->loaded[$name]; > >

    Some people build complicated plugin systems with all sorts of namespace and object-oriented mambo stuff… But no, I prefer to keep things simple. A quick code trace to explain how this “module system” work, when we call $_CORE->load(«Module») :

    • It will automatically require «lib/LIB-Module.php» .
    • Create an object $_CORE->loaded[«Module»] = new Module() .
    • The magic __get() will literally “link” the modules back to the core object. That is, $_CORE->Module will refer to $_CORE->loaded[«Module»] .

    3C) MODULE LINKING

    // (I) ALL MODULES SHOULD EXTEND THIS CORE CLASS class Ext < // (I1) LINK MODULE TO CORE public $Core; public $error; function __construct ($core) < $this->Core =& $core; $this->error =& $core->error; > // (I2) MAKE MODULES LINKING EASIER function __get ($name) < if (isset($this->Core->loaded[$name])) < return $this->Core->loaded[$name]; > > >
    • Create the new library file – lib/LIB-Module.php .
    • Define class Module extends Ext , add the functions.
    • Call $_CORE->load(«Module») and use it $_CORE->Module->functions() .

    Since all modules are linked back to the core, we can use the core functions and even access other modules to speed up development.

    BUILDING MODULES

    Now that we have a working core system, let us walk through developing a dummy user module on top of it.

    PART 4) USER TABLE

    CREATE TABLE `users` ( `user_id` bigint(20) NOT NULL, `user_name` varchar(255) NOT NULL, `user_email` varchar(255) NOT NULL ) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4; ALTER TABLE `users` ADD PRIMARY KEY (`user_id`), ADD UNIQUE KEY `user_email` (`user_email`), ADD KEY `user_name` (`user_name`); ALTER TABLE `users` MODIFY `user_id` bigint(20) NOT NULL AUTO_INCREMENT, AUTO_INCREMENT=1;

    Yep, just a simple user table that we will use as an example for this section.

    PART 5) USER MODULE LIBRARY

    Core->fetchAll( "SELECT * FROM `users`", null, "user_id" ); > // (B) GET USER BY ID OR EMAIL // $id : user id or email function get ($id) < return $this->Core->fetch( "SELECT * FROM `users` WHERE `user_". (is_numeric($id)?"id":"email") ."`=?", [$id] ); > // (C) ADD A NEW USER function add ($name, $email) < // (C1) CHECK IF ALREADY REGISTERED if (is_array($this->get($email))) < $this->error = "$email is already registered!"; return false; > // (C2) PROCEED ADD return $this->Core->exec( "INSERT INTO `users` (`user_name`, `user_email`) VALUES (?, ?)", [$name, $email] ); > // (D) UPDATE AN EXISTING USER function edit ($name, $email, $id) < return $this->Core->exec( "UPDATE `users` SET `user_name`=?, `user_email`=? WHERE `user_id`=?", [$name, $email, $id] ); > // (E) DELETE USER function del ($id) < return $this->Core->exec( "DELETE FROM `users` WHERE `user_id`=?", [$id] ); > >
    • Remember from earlier that $_CORE->load(«Module») will load lib/LIB-Module.php and create $_CORE->loaded[«Module»] = new Module() ?
    • So calling $_CORE->load(«User») will load lib/LIB-User.php and create $_CORE->loaded[«User»] = new User() .
    • There is also a pointer back to the core object – $_CORE->loaded[«User»]->Core =& $_CORE .
    • So in this module, we are pretty much just using the core database functions $this->Core->exec() , $this->Core->fetch() , $this->Core->fetchAll() to do all the user database work.

    PART 6) DONE – USE IT!

    load("User"); // (C) ADD USER echo $_CORE->User->add("Jon Doe", "jon@doe.com") ? "OK" : $_CORE->error; // (D) GET USER $user = $_CORE->User->get("jon@doe.com"); print_r($user);

    With that, the module is ready for use. Don’t think this needs any explanation.

    EXTRA) LINKED MODULES

    Core->load("User"); $users = $this->User->getSubscribers(); // (B) SEND NEWSLETTER $this->Core->load("Email"); foreach ($users as $u) < $this->Email->send(. ); > > >

    For you guys who still don’t see how this is a modular plugin system – Consider how easy we can build more modules, and use them to help each other.

    DOWNLOAD & NOTES

    Here is the download link to the example code, so you don’t have to copy-paste everything.

    SUPPORT

    600+ free tutorials & projects on Code Boxx and still growing. I insist on not turning Code Boxx into a «paid scripts and courses» business, so every little bit of support helps.

    EXAMPLE CODE DOWNLOAD

    Click here for the source code on GitHub gist, just click on “download zip” or do a git clone. I have released it under the MIT license, so feel free to build on top of it or use it in your own project.

    That’s all for the tutorial, and here is a small section on some extras and links that may be useful to you.

    MANY WAYS TO CREATE A PLUGIN SYSTEM

    Before the trolls start to spew rubbish – There are no rules on “a plugin system must be made this way”, and there are endless ways to create a plugin system. This is ultimately a sharing of the mechanics behind my own modular system called “Core Boxx“.

    So yep, if you cannot accept “evil pointers” and want to call this “unorthodox OOP” – Go ahead. If there are parts that you don’t like, discard them. Take the parts that work for you and create your own system.

    THE END

    Thank you for reading, and we have come to the end of this guide. I hope that it has helped you with your project, and if you want to share anything with this guide, please feel free to comment below. Good luck and happy coding!

    Источник

Оцените статью