After the release of
my article on impurities, we continued discussions in the comments to the article and in personal messages. Today I saw in my blog a
comment from a reader who asked me to explain in what way I see the advantage of my implementation over the implementation of
Leonid Schleicher .
I was going to answer in the comments to the post of my site, but suddenly it turned out that the question is quite interesting. Or rather, not the question itself, but the analysis that I conducted, preparing the answer.
I doubt if any of the two of us with Leonid set himself the task of universal realization of impurities in projects of any size. Both of our implementations, it seems to me, do not go far beyond the level of home frameworks and are more likely an impurity implementation concept without eval () than a working tool. But, nevertheless, let's try to dive into the comparison.
My option is more productive by using a so-called registry to store cached information about impurities. But the caching option I used is only effective when repeatedly invoking the impurity method / property. If the access is one-time - in my version, it is likely to be decently slower than that of Leonid, because you must first build the cache. To eliminate this drawback, in my case, you should move from the static registry class model to the singleton registry model, initialized through an abstract factory, or implement the standard “registry” pattern of the framework level. This will allow the registry implementation to be varied depending on the project requirements and, for example, replace caching with direct calls to speed up one-time calls to methods.
')
As my reader noted, the implementation of caching complicated the code and made it more error-prone (these are not empty words, I caught more than one bug while debugging, and tests were too lazy to write) and somewhat more difficult to understand, of course.
Both I and Leonid seem to have made the same implementation error related to garbage collection. Pay attention to cyclic references: the aggregator class stores references to instances of impurity classes, and in turn, references to instances of aggregator classes are stored in turn. In PHP versions younger than 5.3, a memory leak will occur because the garbage collector does not know how to work with circular references. In 5.3, you can activate a special garbage collector for this purpose, but it slows down the work of the script as a whole, so this is undesirable. What's bad about it? It all depends on what and how impurities are used. If there are many aggregator objects with their impurities, they are large and complex, often created / destroyed - everything will be very bad. If the impurities are mixed in to the aggregators that exist throughout the life of the script, there will be nothing terrible, since all memory occupied by the script will be forcibly released upon its completion.
How to fix this error? In both implementations, it is quite simple to do this by using the PHP destructor, which is not too often used by PHP. It is necessary to add a destructor, forcibly resetting the reference to the owning class, to the impurity and the destructor to the aggregator class, clearing the list of impurities of this class.
Now it turned out that the Leonid variant is not compiled at all, because the magic method __call () in the Caller class cannot be protected. Fixed, earned. In 5.3, the
commentary version of this error no longer exists.
Leonid implemented magical methods in admixture so that the impurity could refer to the members of the aggregator class as his own. I did not guess. However, I will think about whether to implement this idea in myself. So far, I don’t really like the call graph, which is obtained at the same time, and some vagueness of the scope.
Leonid option static. For some reason, the code is written in such a way that impurities need to be created in the constructor of the aggregator, which, in general, slightly contradicts the concept of impurities. In my version of the impurities to the classes are connected dynamically, and this does not even require loading the classes themselves (neither aggregators nor impurities). That is, all impurities can be connected in one place of the project without sacrificing performance. This has its drawback: the error in the name of the class-aggregator or impurities is more difficult to track. After all, classes are not loaded until they actually start to be used.
Impurities in the Leonid variant are realized exclusively by inheritance. In my version of the composition, along with inheritance, is also available. Sometimes this is useful if a class cannot be inherited from an aggregator class.
Due to the “connection” in my version, impurities can be connected to classes directly in the process of executing the main code. That is, if you lose caution, it may turn out that this impurity is mixed into one copy of the class and not into the other. We have obtained excessive flexibility that is easy to eliminate, if necessary. By the way, this can be done by examining the cache (registry).
Leonid's version does not implement all the magic methods of the aggregator class (the main emphasis is on the methods), but this is easy to fix.
Both of our options react to the situation with the presence of impurity methods with the same name in the same way: the impurity method registered first will be called. In my opinion, is also not quite the correct option. However, the implementation of verification is somewhat costly, especially in cases where speed is required from impurities: after all, it is impossible to verify the presence of duplicates without loading all classes of impurities for a given aggregator. So, it seems, we both decided to come to terms with it.
Leonid forgot to add error checking in a situation if an impurity is attempted through himself to call the aggregator method with a nonexistent name. This is not good, because in the resulting implementation there is no error situation and just nothing happens. Or maybe so intended? In general, from my point of view, this is bad. Better get an error. However, everything is easily corrected.
Leonid has another curious situation, if in the admixture determine a protected or private method with a name, and then try to call a method with that name on the aggregator. Pay attention to the call graph. The aggregator calls __call (), it searches for a method with the specified name among the impurity methods using method_exists (). Matching is found because method_exists () for hidden and protected members also returns true. An attempt is made to call this method already on the impurity instance. Since the method is still protected and is not available from this scope, the magic impurity method __call () is called, which, in turn, calls the pseudo-magic aggregator method __access_call, in which Leonid forgot to add error checking. Since there is no error checking, nothing happens and we do not get any error, although I personally would like. Yes, and the graph itself turned out to be complicated, in my opinion, for the realization of impurities on 80 lines ... Of course, it is easy to fix the error.
In the commentary to the article, the author mentions that “each parent class must create instances of all its impurities. However, he can use common copies for all, obtaining them with the help of the Registry. ” In this case, it is convenient to receive ready-made copies through the registry only if the impurities do not have their own state. Otherwise, the idea loses its meaning and you need to look for another way.
In the comments on the site, the author mentions that he added support for static methods of impurities. In my version, this requires that you make an additional cache in the registry for static methods. Slightly more gestures. While refraining, I guess.
It should be noted that in the implementation of Leonid there are pseudo-magic aggregator methods that actually open access to hidden and protected class members in the public interface. Whether this is bad or good depends on the project. In principle, normal programmers should understand that methods that begin with two underscores do systemic, internal things and should be called only by the framework's internals. So, this is not a particular problem.
I actively use PHP in PHP for additional checks. I did not notice Leonid, but this is nothing.
In PHP 5.3, a static property with a list of impurities for static calls was added
to the comments page . I think it looks somewhat out of context, since at first glance it seems that impurities are divided into static and specific, which is not true.
By the way, the author's version is already working in the project, but mine is not yet. It lies on your computer idle ...
From the irrelevant, I can note that when you read the code on the site, I would like to see if the
syntax is not
highlighted in color , then at least that the indents are normal. Leonid's code had to be formatted for analysis in Zend Studio. I apologize if the code on the site was not intended for analysis by outsiders. It justifies me except that it is still obtained from open sources.
Leonid also asked in the comments about the advantages of impurities over ordinary delegation, but this is a topic for my next article, which will not take long to appear. The volume does not allow to state my point of view on this question here.
A huge request in the comments not to compare the two implementations in terms of "bad / good." Respect yourself. Speaking of strengths and weaknesses, refrain from demagogy. Facts, facts and facts again. Trolls who adore shouting "UG" and "KG / AM" without even understanding the question, are asked
to go to the landing , the train will soon depart.
Thanks for attention.