📜 ⬆️ ⬇️

Breaking a design pattern - Singleton in PHP

One fine working day I wrote unit-tests for business logic on the project in which I work. I was faced with the task of initializing some private properties of a class with certain values.


Ordinary setters could not be used, since some logic was written there. It was also impossible to inherit or lock a class, because it was declared final. And even the reflection did not fit. Therefore, I began to look for solutions to this problem.


I found an interesting article that describes how to use the dg / bypass-finals library to lock the final class. I liked this option and I tried to implement it. Unfortunately, I did not succeed, because the project uses the old version of PHPUnit.


Upon reflection, I remembered the Closure class, and specifically about its static bind() method, which can embed anonymous functions in the context of the desired object of a particular class. More information about this can be found in the official documentation . Therefore, I created a trait that I used in my tests (maybe someone will also be useful)


 trait PrivatePropertySetterTrait { protected function assignValue($object, string $attribute, $value) { $setter = function ($value) use ($attribute) { $this->$attribute = $value; }; $setterClosure = \Closure::bind($setter, $object, \get_class($object)); $setterClosure($value); } } 

This trait takes the class object, the name of the property where the value is to be set and, in fact, the value itself. Next, a simple anonymous function is declared, which, using the $this pointer, assigns the resulting value to a class property. Next comes the battle class Closure with its static bind() method. The method accepts a class object, the anonymous function described above, and the full class name. Thus, the anonymous function is embedded in the context of the object and the bind() method returns us an object of class Closure , which we can call as a normal function, because it defines the magic __invoke() method. And voila!


In the end, I managed to solve my problem, and then I remembered the Singleton design pattern. Will it be possible to implement an anonymous function in the same way, which will create new class objects? Of course, I went to check it!


Writing a small piece of code


Sandbox with code

 <?php final class Singleton { private static $instance; public static function getInstance() { if (null === self::$instance) { self::$instance = new self(); } return self::$instance; } private function __construct() { } private function __clone() { } private function __wakeup() { } } $s1 = Singleton::getInstance(); \var_dump(\spl_object_id($s1)); $createNewInstance = function () { return new self(); }; $newInstanceClosure = Closure::bind($createNewInstance, $s1, Singleton::class); $s2 = $newInstanceClosure(); \var_dump(\spl_object_id($s2)); 

which works on the same principle, but instead of assigning a value to a class property, a new object is created using the new operator. The \spl_object_id() function returns a unique identifier for the object. More information about this feature can be found in the documentation . With the help of spl_object_id() and var_dump() I derive unique identifiers of objects and see that they are different! I still managed to confirm this theory and create a new instance of the Singleton class!


In this article I wanted to share with the PHP community my very curious find.


Thanks for attention!


')

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


All Articles