Starting from version 5.4.0, a new language design will appear in PHP -
traits , which allows the use of impurities (
mix in ). The impurity mechanism is another code reuse mechanism and is present in one form or another in other languages, such as Ruby, Python, Common Lisp, etc.
Impurities allow you to use existing code when implementing the behavior of classes, while avoiding the many problems of multiple inheritance. The impurities interacting with each other, combined into separate software libraries, are a very powerful tool for implementing complex logic.
It should be noted that implementations of impurities in PHP exist
at least from version 4.0.1, and are currently present, most often under the name of behavior, in a number of popular frameworks, for example, in Yii, Symfony, Doctrine, CakePhp, Propel.
')
The purpose of the article is to demonstrate and compare several basic approaches to the implementation of impurities in PHP up to version 5.4.0, based only on the functions of the language itself and not using third-party extensions, such as, for example, the
runkit_method_copy function from the PECL runkit.
When comparing, the following criteria will be used:
- whether the mix result is of the same type as the object itself
- can one impurity interact with another
- is it possible to check that the result of mixing has some impurity
- is it possible to add impurity to an arbitrary class
- is it possible to add an admixture to an already created object “on the fly”
- how simple is the implementation
Method One: Magic methods
The method is based on the idea of ​​using magic methods
__call ,
__get , and others: a collection of impurities is added to the result of mixing, and the implementation of magic methods selects the necessary impurity. Each impurity can be parametrized by reference to the result, thus supporting the interaction of impurities with each other.
Example of implementation:
abstract class Mixin { protected $mixedObject = null; public function setObject( MixedObject $object ) { $this->mixedObject = $object; } abstract public function getName(); } class MixedObject { private $mixins = array(); public function addMixin( Mixin $mixin ) { $mixin->setObject( $this ); $this->mixins[$mixin->getName()] = $mixin; } public function hasMixin( $mixinName ) { return array_key_exists( $mixinName, $this->mixins ); } public function __call( $name, $arguments ) { foreach ($this->mixins as $mixin) { if (is_callable( array( $mixin, $name ) )) { return call_user_func_array( array( $mixin, $name ), $arguments ); } } throw new \Exception('Unknown method call.'); } }
Usage example:
class Foo extends MixedObject { public function objectFunc() { return 'FooName'; } } class Debuggable extends Mixin { public function getName() { return 'Debug'; } public function getDebug() { return sprintf( "%s", $this->mixedObject->objectFunc() ); } } class Loggable extends Mixin { public function getName() { return 'Log'; } public function getLog( $level ) { return $this->mixedObject->hasMixin( 'Debug' ) ? sprintf( "%s %s", $level, $this->mixedObject->getDebug() ) : sprintf( "%s", $level ); } } $foo = new Foo(); $foo->addMixin( new Debuggable() ); $foo->addMixin( new Loggable() ); print $foo->getDebug(); print $foo->getLog( 'info' );
Obviously, the result is of the same type as the object itself. This approach also leaves it possible for impurities to communicate with both the object itself and with each other using the
$ this-> mixedObject link and the unique name system.
Advantages and disadvantages:
- [+] the solution is clear and understandable.
- [+] you can add an admixture to an already created object, you can even with the name previously used
- [-] the result must be inherited from the MixedObject class, thus, to use a mix, it is necessary to select a type hierarchy
- [-] the condition of the uniqueness of the names of impurities requires constant attention, and here it may be useful to introduce any conventions
Method two: Object context
This method is based on some feature of the
$ this variable. Namely:
This is a reference to the secondary object .
Highlighted words enable the following implementation:
class Foo { public function objectFunc() { return 'FooName'; } } class Debuggable { public function getDebug() { return sprintf( "%s", $this->objectFunc() ); } } class Loggable { public function getLog( $level ) { return is_callable( array( $this, 'getDebug' ) ) ? sprintf( "%s %s", $level, $this->getDebug() ) : sprintf( "%s", $level ); } }
... and use:
class MixedFoo extends Foo { public function getDebug() { return Debuggable::getDebug(); } public function getLog() { return Loggable::getLog( func_get_arg( 0 ) ); } } $foo = new MixedFoo(); $foo->getDebug(); $foo->getLog( 'info' );
Next, it is easy to automate the generation of the code for the
MixedFoo class, the subsequent
eval , the creation of the object of the generated class and its return, resulting in something like the following:
$foo = Mixer::Construct( 'Foo', array( 'Debuggable', 'Loggable' ) ); $foo->getDebug(); $foo->getLog( 'info' );
You can also make a separate interface for each impurity and add it to the list of
implements for the generated class.
interface IMixinDebuggable { public function getDebug(); } ... $foo = Mixer::Construct( 'Foo', array( 'IMixinDebuggable' => 'Debuggable', 'Loggable' ) );
This is possible, since the result of mixing will implement these interfaces, and checking for the existence of an impurity will then be reduced to the
instanceof native call:
class Loggable { public function getLog( $level ) { return $this instanceof IMixinDebuggable ? sprintf( "%s %s", $level, $this->getDebug() ) : sprintf( "%s", $level ); } }
Advantages and disadvantages:
- [+] There is no need to inherit an expandable object, so you can add any impurities to any classes.
- [+] Unlike the first method, the “mixed in” methods are obtained directly as a result, so there is no need to spend time iterating over the collection, and the code will work somewhat faster
- [-] there is no possibility to extend an already created object of an arbitrary class
- [-] code generation - this sucks
Conclusion
Both methods allow you to dynamically refine the behavior of classes, complementing them with the existing implementation, and have the right to use.
If we put the results in a separate table:
| Magic methods | Object context |
---|
whether the mix result is of the same type as the object itself | Yes | Yes |
can one impurity interact with another | Yes | Yes |
is it possible to check that the result of mixing has some impurity | Yes | Yes |
is it possible to add impurity to an arbitrary class | Not | Yes |
is it possible to add an admixture to an already created object “on the fly” | Yes | Not |
how simple is the implementation | Simple and obvious | Associated with code generation |
It is easy to see that the shortcomings of the first method are solved by the second, as well as vice versa, and also to draw a conclusion regarding the scope of one or another method.
The first is part of the project design, and therefore its scope is the task of designing complex business objects.
The second allows you to select impurities in separate independent libraries and apply them in any projects, so its scope is lightweight tasks specific to a number of your projects.
Thank.