📜 ⬆️ ⬇️

OOP hook is not a friend or Dynamic auto-inheritance classes

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() { // Init code here } public function GetRecord() { // Some code here return $oRecord; } } 


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(); // New code here } } 

 class PluginSecond_ModuleUser extends PluginSecond_Inherits_ModuleUser { public function GetRecord() { $oRecord = parent::GetRecord(); // Some code with $oRecord here Return $oRecord; } } 

 class PluginThird_ModuleUser extends PluginThird_Inherits_ModuleUser { public function Init() { parent::Init(); // Init code here } public function GetRecord() { // Yet another code here return parent::GetRecord(); } } 


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 { /** @var array $aInherits   (,   ) */ protected $aInherits = array( 'module' => array( 'ModuleUser', ), ); // Plugin code here } 


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


Disadvantages:

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.

Source: https://habr.com/ru/post/247351/


All Articles