PHP module engine development
There are many different PHP engines , from simple enough to very heavy and bulky, including almost everything.
But in my opinion the best engine is extensible. And it’s not such that it offers to tie some more to the heap of its capabilities, but one in which there aren’t any of its capabilities, but there are only those that can be chosen.
Less than six months ago, I wondered how to create such an engine. The first thing to write was the bootloader. A kind of pseudo-module (more on that later) that loads other modules.
Let's decide on the structure: let's say we have a mod folder in which the modules are stored.
For example / mod / staticpages / *. According to the loader standard, the module should consist of a configuration file, the main class of the module, and optionally connected libraries.
Again, for example, there is a staticpages module (which is responsible for processing static pages), it will consist of manifest.ini and staticpages.php files .
The first is the module configuration, and the second is the file with the main module class.
First, let the config file have the following structure:
When processing this module, its manifest opens (we will use this term further), the necessary data is taken from it, the necessary operations are performed.
Add another section to the manifest:
Now, when processing this module, we can load (or rather include) a file with its class and create an instance of it.
Now the algorithm will be something like this:
1. Read all the / mod / folders .
2. If there is manifest.ini , continue.
3. We get the mainclass value , include this file.
4. Create an instance of the class named devname . This is exactly what this field is for.
As a result, we get an object of the StaticPages class with the name $ staticpages . The object will be global, for convenient interaction of other modules with it.
Now in the further code we can simply and quickly use the capabilities of this module.
But now we run into one more problem:
Let's say we have such a request: " ? Ins = staticpage & page = info ", which, in theory, should show a static page called info . But how does the Static Pages module learn about this , which should be responsible for it?
Of course, you can place the handler in the constructor of the class, such as if ins = staticpage , but at that time we do not know for sure whether other modules that are needed for the Static Pages to work were loaded , and in general - is it worth it?
So we need to add another section to the manifest:
After loading all the modules, we launch the second stage: launch methods.
In this section, the run parameter specifies the class method that you want to run at this stage. Well, at this point all the modules will already be loaded, and they can easily interact with each other.
Now one more situation - let's say the pex module (which is responsible for access rights) in this section is just starting to load access rights for the current user, but we need to limit the display of a static page by default, and it’s not good to interfere with the code of a third-party module.
We will have to introduce another concept, as well as a parameter in the manifest, namely: the queue and require.
The queue, in our case, is the queue of modules (which is obvious) for performing some action.
And now - we modify the [Run] section of the Static Pages module :
When trying to execute the template () method for our Static Pages module, the loader will come across a requirement to first perform a similar action for the pex module , move our module further in the queue and continue the same operation for the rest of the modules.
Now we can calmly wield the data received by pex after its Run stage.
Another important thing: checking the hard-installed modules for the operation of our Static Pages , as well as their versions. Add the following department:
It can be seen that our module requires a loader of version no lower than 0.2 (this is why it is a “pseudo-module”: it has both a manifest and a version, and it can be accessed like any other module, but it is “hard-wired” into system), also a module for working with the MySQL database , another lang module (which will be responsible for the encoding, date and time format, etc.), as well as a module for paging.
But our “queues” do not very well affect performance, and it will be tedious for developers to specify all the modules that depend on it one way or another. Therefore, it would be most reasonable to make a “couple of levels” of execution of modular methods.
These will be the new departments in the manifest: first the methods will be executed with [Prev], already known [Run] , [After] and [Finish].
For example, take part of the pex module manifest:
First, at the [Prev] stage, he receives the rights of the group to which the user belongs, but only after the auth module receives data about the user himself. Then he will execute the template () method , in which, for example, he will check whether the current user can browse the site (but he will also let other modules do their work).
And after that - he will check the template for the so-called nodesContainers , sections of the template for access to which some rights are needed (after all, in the previous stages different modules could add such sections and they would be left untreated).
Also, do not forget about the libraries that some modules may need, we add another section:
The bootloader will check this department and, if necessary, include all files from folders.
In the end, in index.php we will have something like this:
Almost complete automation of module loading, complete system modularity. Working with the database - mysql module . Working with templates - template module . The navigation bar is the speedbar module . Not necessary? Delete the folder.
With the help of convenient template containers, such as Speedbar here:, there is a module - it works and the section is displayed. No module - section is not shown at all.
On this principle, you can build a site of any complexity using existing modules plus specially written ones. Any module can use any other, and direct code execution gives almost 100% flexibility. Need ajax? We create a module that will wait until everything is completed and prepared, and then at the last moment it will cancel the display of the template and show only what is needed.
Of course, this method also has its drawbacks: due to the constant construction of queues, the performance decreases, the modules may not go where they need to, and the average user who is faced with all this will try to connect a couple of modules, rightly drop the whole thing.
But since the beginning of work, I have not at all targeted a wide audience, and everything invented is used only for myself. And while the “it” suits me perfectly.
The current model of this method can be viewed on my page (the link is in the profile), and the source codes are in the GitHub repository .
Thank you for your attention, if someone is interested in the topic, I will write a topic on the transition from theory to practice.
But in my opinion the best engine is extensible. And it’s not such that it offers to tie some more to the heap of its capabilities, but one in which there aren’t any of its capabilities, but there are only those that can be chosen.
Less than six months ago, I wondered how to create such an engine. The first thing to write was the bootloader. A kind of pseudo-module (more on that later) that loads other modules.
Let's decide on the structure: let's say we have a mod folder in which the modules are stored.
For example / mod / staticpages / *. According to the loader standard, the module should consist of a configuration file, the main class of the module, and optionally connected libraries.
Again, for example, there is a staticpages module (which is responsible for processing static pages), it will consist of manifest.ini and staticpages.php files .
The first is the module configuration, and the second is the file with the main module class.
First, let the config file have the following structure:
[Custom]
name = "Static Pages";
author = "ShadowPrince";
devname = "staticpages";
version = "0.1";
When processing this module, its manifest opens (we will use this term further), the necessary data is taken from it, the necessary operations are performed.
Add another section to the manifest:
[Module]
mainclass = "staticpages.php";
mainclassname = "StaticPages";
Now, when processing this module, we can load (or rather include) a file with its class and create an instance of it.
Now the algorithm will be something like this:
1. Read all the / mod / folders .
2. If there is manifest.ini , continue.
3. We get the mainclass value , include this file.
4. Create an instance of the class named devname . This is exactly what this field is for.
As a result, we get an object of the StaticPages class with the name $ staticpages . The object will be global, for convenient interaction of other modules with it.
Now in the further code we can simply and quickly use the capabilities of this module.
But now we run into one more problem:
Let's say we have such a request: " ? Ins = staticpage & page = info ", which, in theory, should show a static page called info . But how does the Static Pages module learn about this , which should be responsible for it?
Of course, you can place the handler in the constructor of the class, such as if ins = staticpage , but at that time we do not know for sure whether other modules that are needed for the Static Pages to work were loaded , and in general - is it worth it?
So we need to add another section to the manifest:
[Run]
run = "template()"
After loading all the modules, we launch the second stage: launch methods.
In this section, the run parameter specifies the class method that you want to run at this stage. Well, at this point all the modules will already be loaded, and they can easily interact with each other.
Now one more situation - let's say the pex module (which is responsible for access rights) in this section is just starting to load access rights for the current user, but we need to limit the display of a static page by default, and it’s not good to interfere with the code of a third-party module.
We will have to introduce another concept, as well as a parameter in the manifest, namely: the queue and require.
The queue, in our case, is the queue of modules (which is obvious) for performing some action.
And now - we modify the [Run] section of the Static Pages module :
[Run]
run = "template()"
require = "pex"
When trying to execute the template () method for our Static Pages module, the loader will come across a requirement to first perform a similar action for the pex module , move our module further in the queue and continue the same operation for the rest of the modules.
Now we can calmly wield the data received by pex after its Run stage.
Another important thing: checking the hard-installed modules for the operation of our Static Pages , as well as their versions. Add the following department:
[Require]
data = "core:0.2, template:0.1, mysql:0:1, lang:0.2";
It can be seen that our module requires a loader of version no lower than 0.2 (this is why it is a “pseudo-module”: it has both a manifest and a version, and it can be accessed like any other module, but it is “hard-wired” into system), also a module for working with the MySQL database , another lang module (which will be responsible for the encoding, date and time format, etc.), as well as a module for paging.
But our “queues” do not very well affect performance, and it will be tedious for developers to specify all the modules that depend on it one way or another. Therefore, it would be most reasonable to make a “couple of levels” of execution of modular methods.
These will be the new departments in the manifest: first the methods will be executed with [Prev], already known [Run] , [After] and [Finish].
For example, take part of the pex module manifest:
[Prev]
run = "getGroup()";
require = "auth";
[Run]
run = "template()";
[After]
run = "nodesContainers()"
First, at the [Prev] stage, he receives the rights of the group to which the user belongs, but only after the auth module receives data about the user himself. Then he will execute the template () method , in which, for example, he will check whether the current user can browse the site (but he will also let other modules do their work).
And after that - he will check the template for the so-called nodesContainers , sections of the template for access to which some rights are needed (after all, in the previous stages different modules could add such sections and they would be left untreated).
Also, do not forget about the libraries that some modules may need, we add another section:
[Include]
dirs = "";
The bootloader will check this department and, if necessary, include all files from folders.
In the end, in index.php we will have something like this:
include "core/lib/module/mod.php"; // главный класс каждого модуля
include "core/engine.php"; // класс псевдо-модуля загрузчика
Engine::loadLibs(); // загрузка всех необходимых библиотек для загрузчика (рекурсивно для удобства)
session_start(); // сессия
$engine = new Engine(); // обьект загрузчика
$engine->enableModules(); // создание обьектов модулей
$engine->modTemplates(); // этапы выполнения методов модулей
ModError::printErrors(); // вывод ошибок (для удобства у нас есть отдельный класс, подключился с библиотеками)
What do we have in the end?
Almost complete automation of module loading, complete system modularity. Working with the database - mysql module . Working with templates - template module . The navigation bar is the speedbar module . Not necessary? Delete the folder.
With the help of convenient template containers, such as Speedbar here:, there is a module - it works and the section is displayed. No module - section is not shown at all.
On this principle, you can build a site of any complexity using existing modules plus specially written ones. Any module can use any other, and direct code execution gives almost 100% flexibility. Need ajax? We create a module that will wait until everything is completed and prepared, and then at the last moment it will cancel the display of the template and show only what is needed.
Of course, this method also has its drawbacks: due to the constant construction of queues, the performance decreases, the modules may not go where they need to, and the average user who is faced with all this will try to connect a couple of modules, rightly drop the whole thing.
But since the beginning of work, I have not at all targeted a wide audience, and everything invented is used only for myself. And while the “it” suits me perfectly.
The current model of this method can be viewed on my page (the link is in the profile), and the source codes are in the GitHub repository .
Thank you for your attention, if someone is interested in the topic, I will write a topic on the transition from theory to practice.