
There is no limit to perfection. Therefore, no matter how good and multifunctional CMS is, but third-party developers will always have to add it, finish, expand it with some kind of functionality. And, of course, any modern engine should allow it.
Moreover, the engine's extension mechanism should allow to “hang” on it any number of extensions written by different developers who do not know about each other, nor about extensions that other developers write.
In different engines this can be done in different ways. The most common is probably hooks - a third-party developer who creates an extension for the engine, registers hook handlers, and then these handlers are called by the system in the right places, executing the extension code.
')
But when the engine is written using OOP and everything is decomposed into classes, the use of hooks is alien and “crutch”, and you want a cleaner and simpler OOP approach when the “boxed” class with overlapping parent methods expands .
That is how to solve such problems and a method was created that I called
Dynamic Auto-Inheritance .
And I will explain this method on the example of how this is implemented in the plugin support system in
Alto CMS .
Suppose there is an initial “boxed” class:
class ModuleUser { public function Init() {
And third-party developers need to extend the
ModuleUser class, and one wants to change the
Init () method, the other - the
GetRecord () method, and the third - to add its logic to both methods. And at the same time it is necessary to ensure the performance of all three extensions on any site and in any combination (i.e., somewhere there is one extension, somewhere - another, somewhere - two of them, and somewhere - and all three ).
So, let third-party developers write plug-ins independently of each other, which will be called unpretentiously
First ,
Second and
Third , and each of them requires a class-successor from
ModuleUser . In Alto CMS, such successor classes are structured as follows:
class PluginFirst_ModuleUser extends PluginFirst_Inherits_ModuleUser { public function Init() { parent::Init();
class PluginSecond_ModuleUser extends PluginSecond_Inherits_ModuleUser { public function GetRecord() { $oRecord = parent::GetRecord();
class PluginThird_ModuleUser extends PluginThird_Inherits_ModuleUser { public function Init() { parent::Init();
You, of course, noticed that the classes are not inherited directly from the
ModuleUser parent, but through the proxy classes -
PluginFirst_Inherits_ModuleUser , etc. It is in these intermediary classes that all the salt is laid.
In addition, special properties in plugins indicate that they use dynamic auto-inheritance from the
ModuleUser class:
class PluginFirst extends Plugin { protected $aInherits = array( 'module' => array( 'ModuleUser', ), );
Now, each time the kernel is loaded and the plug-ins are initialized, the
moduleUser class inheritance stack will be created (in our example, the order will be:
PluginFirst_ModuleUser ,
PluginSecond_ModuleUser ,
PluginThird_ModuleUser ). And as soon as there is a call to the
ModuleUser class (the creation of an object instance is performed by calling a special method), the autoloader will first check the inheritance stack and load the last class registered there (in our example,
PluginThird_ModuleUser ). At the same time, of course, the presence of the
PluginThird_Inherits_ModuleUser parent class is
checked ; it is not (and there is really no such class), then an attempt is made to load it. And here begins the "magic".
The autoloader analyzes the name of the parent class and understands that it is an intermediary class, it actually does not exist, and it is only an alias of the previous class in the inheritance stack, and this fact fixes with the help of the PHP function:
class_alias('PluginThird_Inherits_ModuleUser', 'PluginSecond_ModuleUser');
And now instead of the mediation class
PluginThird_Inherits_ModuleUser , the real class
PluginSecond_ModuleUser is loaded. Its parent is also a mediation class, and it becomes an alias of the previous class from the stack
PluginFirst_ModuleUser .
PluginFirst_ModuleUser is the last class on the stack, so its parent class becomes an alias for the original “boxed”
ModuleUser class.
As a result, the inheritance chain in the system is as follows:

Now, every time there is a request to create an instance of the
ModuleUser class, the object will actually be created from the
PluginThird_ModuleUser class, and taking into account the inheritance chain, it inherits everything that was intended by the developers of all three plug-ins.
Advantages of the dynamic auto-inheritance
- No crutch hooks, clean OOP with all the consequences
- Plug-ins (and descendant classes) can be developed independently of each other, there is no need to know what exactly the parent class will be called
- In the chain of inheritance can be an unlimited number of classes that will automatically be inherited from each other.
Disadvantages:
- As it was rightly noted in the comments, IDE will not understand such “tricks”, because the inheritance is “dynamic”
- ... - but frankly, I don’t see any other significant shortcomings.
I do not exclude that there may still be some flaws, but I simply have “eyes soiled”, and I haven’t stuck in them with my nose, so it will be interesting to hear the opinions of those who discover those.
It will also be interesting to know if anyone has encountered a similar way of expanding the functionality in other CMS (I haven’t yet come across this).
And as I already wrote, this technique lies at the heart of the Alto CMS plugin system, so if someone wants to see the implementation in more detail, then this can be done either
on the github or download the engine code from the
official site .
PS Just in case for perfectionists from coding - yes, I know about namespaces and I understand that everything can be done with their help (and I even agree that it is desirable to use them here), but the post is still about something else, so I suggest not to dwell on this side of the question.