Romper el patrón de diseño - Singleton en PHP

Un buen día de trabajo, escribí pruebas unitarias para la lógica de negocios en el proyecto en el que trabajo. Mi tarea consistía en inicializar algunas propiedades privadas de la clase con valores específicos.


Los setters normales no se podían usar, ya que se explicaba algo de lógica. Heredar o encerrar una clase tampoco funcionó, porque se declaró final. E incluso la reflexión no encajaba. Entonces comencé a buscar soluciones a este problema.


Encontré un artículo interesante que describe cómo usar la biblioteca dg / bypass-finals para bloquear la clase final. Me gustó esta opción e intenté implementarla. Desafortunadamente, no tuve éxito, porque el proyecto usa la versión anterior de PHPUnit.


Al reflexionar, recordé la clase Closure , y específicamente sobre su método estático bind() , que puede implementar funciones anónimas en el contexto del objeto deseado de una clase. Se puede encontrar más información sobre esto en la documentación oficial . Por lo tanto, creé un rasgo que usé en mis pruebas (tal vez alguien también sea útil)


 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); } } 

Este rasgo toma un objeto de clase, el nombre de la propiedad donde desea establecer el valor y, de hecho, el valor en sí. A continuación, se declara una función anónima simple que, utilizando el puntero $this , asigna el valor resultante a una propiedad de clase. Luego, la clase Closure viene con su método estático bind() . El método acepta un objeto de clase, la función anónima descrita anteriormente y el nombre completo de la clase. Por lo tanto, una función anónima está incrustada en el contexto del objeto y el método bind() nos devuelve un objeto de la clase Closure , que podemos llamar como una función regular, porque define el __invoke() mágico __invoke() . Y voila!


Al final, logré resolver mi problema, y ​​luego recordé el patrón de diseño Singleton. ¿Será posible de la misma manera implementar una función anónima que creará nuevos objetos de la clase? ¡Por supuesto que fui a comprobarlo!


Al escribir un pequeño fragmento de código


Sandbox con un código

 <?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)); 

que funciona según el mismo principio, pero en lugar de asignar un valor a una propiedad de clase, se crea un nuevo objeto utilizando el new operador. La función \spl_object_id() devuelve un identificador único para el objeto. Se puede encontrar más información sobre esta función en la documentación . Usando spl_object_id() y var_dump() spl_object_id() identificadores únicos de objetos y veo que son diferentes. ¡Todavía logré confirmar esta teoría y crear una nueva instancia de la clase Singleton!


En este artículo, quería compartir mi hallazgo muy curioso con la comunidad PHP.


Gracias por su atencion!

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


All Articles