
The other day, while reading the Zend Frameworks newsletter, I noticed the theme of a single developer about the implementation of a plugin system without modifying some standard kernel. Such a task has to be solved quite often and in many cases - for example, it is unlikely that at least some CMS-system can do without the plug-in mechanism. Of course, the developers of such popular CMS systems as Drupal or Wordpress have already solved this problem for themselves by developing their own plug-in connection architecture on the fly without affecting the kernel functionality. However, a similar task, it seems to me, is still from the category of "eternal" and not all solutions can be applied in each specific case.
Not only web developers face similar problems, it is also relevant in the design of component desktop applications and complex systems. And some successful solutions can be spied on and borrowed from such developments. In this case, I'm talking about the Signal / Slot architecture, which is implemented in the Qt library (
detailed description ) and used there for communication between components. Similar functionality would be very useful in web development, in this case, in PHP projects.
I personally do not have enough events in PHP, similar to JavaScript, and Signal / Slot will just allow you to get the same functionality on the server side. In short, each object can generate a certain event (signal), and other objects subscribe to the desired signals, and when a signal arrives, all registered functions (slots) that listen to the specified signal are called. For such an architecture, an intermediate object is needed, which will store a list of all registered signals, as well as maintain a register of listening functions, and when an event occurs, launch them in the proper order. Is there a ready-made solution for such functionality for PHP projects, or is it necessary to write your own? Of course, it’s quite simple to write such a script, an experienced developer will take it a day or two from strength, but you can also use a ready-made solution that
is part of the ezComponents framework , to which I have long felt some weakness, despite the fact that there are more advanced and serious solutions, like Zend Framework (sorry, it still does not have such a module).
In the framework there is a component
ezcSignalCollection , which just implements the main component of the architecture. After creating an instance of an object, you can connect any number of signals and slots by simply calling the
connect method. For example, so that our function
_test_slot () which simply displays a string about the call time (via the echo operator), reacts to the “test_alert” signal (the signal can be any string, but it’s best to create some repository of predefined signals) you just need to connect it:
')
$tmp = new ezcSignalCollection();
function _test_slot()
{
echo 'Called by test_alert signal!';
}
$tmp->connect("test_alert", "_test_slot");
//
$tmp->emit("test_alert");
// :
Called by test_alert signal!
After connecting, we can call the
emit method in any place, which generates the specified signal. Note that when connecting, you must pass the function name as a string (as the PHP function
call_user_func is
used ) This is the simplest example. You can hang several functions on one event / signal and they will be executed in the order they were connected. However, this behavior is not always useful. For this, there is a priority mechanism - each slot can be assigned a priority level (integer, 1 - 65,536), and when called it will be taken into account - the smaller the number, the higher this slot will be, that is, the slot with priority 1 will be executed first, and then further, up to the last. If several slots have the same priority (and the default is 1000), then they will be executed in the order of connection. It is easy to set the priority - pass the required priority to the
connect tags with the third argument.
There would be very little benefit from this component if it allowed only functions to be used as slots. But the functionality of the
ezcSignalCollection is much broader - as the slots can act as methods of objects (I haven’t understood one point here yet - if an object has already been created, the method of this object will be called, and if not - a new instance is created or how?) And static methods . To do this, use the standard format - an array, where the first is the name of the class as a string, and then - the name of the method.
If you need to pass along with the signal some parameters, then you need to add them after the signal itself when it is generated in the
emit method. Thus, it is possible to transfer an unlimited number of parameters that will be passed to each function. However, take into account the nuances, in terms of passing values ​​by reference - you should contact the PHP Manual in the
section call_user_func_array in more
detail .
However, in the case of large and complex applications, this functionality alone may not be enough. And not so much because of limitations, but rather because of laboriousness - the signals must be unique, but the desire to make a unified system will lead us to the fact that different modules may have the same signals, that is, it is desirable to make them the same to facilitate understanding and readability. but here it is impossible. But there is a way out - we can create a static connection and define a method or function that will respond to signals generated by objects of the same class. That is, the “delete_item” signal generated by the Cache class will be processed by its handler, and the same signal, but from the heir of the News class, will be processed in its own way. In this case, it is possible to combine both a regular subscription to signals and a static one, given that the statically connected functions will be worked out after the usual ones. For example, the
_prepare_item method will be called in both cases, and after testing it, the necessary statically connected handler will already be called - this is how you can implement, in essence, the processing preprocessor.
To implement static slots, you need to pass the class name to the constructor when creating an
ezcSignalCollection object, but the rest of the code does not change, and to connect, use the
ezcSignalStaticConnections class.
The code of this module itself is quite simple, if not trivial, and having searched Google (for example,
this and
this ), I have found other versions of my own implementation in several forums, however, this component from
ezComponents is still more flexible and functional. I think this mechanism is useful if you plan to build a flexible system with plug-ins. Although even in the case of a monolithic application, the implementation of Signal / Slot can help in the implementation of a more flexible and beautiful architecture. Just try it.