Em um dia útil, escrevi testes de unidade para a lógica de negócios no projeto em que trabalho. Minha tarefa era inicializar algumas propriedades particulares da classe com valores específicos.
Os setters normais não podiam ser usados, uma vez que havia alguma lógica lá fora. Herdar ou bloquear uma classe também não funcionou, porque foi declarada final. E até a reflexão não se encaixava. Então comecei a procurar soluções para esse problema.
Encontrei um artigo interessante que descreve como a classe final pode ser bloqueada usando a biblioteca dg / bypass-finals . Gostei dessa opção e tentei implementá-la. Infelizmente, não obtive sucesso porque o projeto usa a versão antiga do PHPUnit.
Após refletir, lembrei-me da classe Closure
e, especificamente, sobre seu método estático bind()
, que pode implementar funções anônimas no contexto do objeto desejado de uma classe. Mais informações sobre isso podem ser encontradas na documentação oficial . Portanto, criei uma característica que usei em meus testes (talvez alguém também seja ú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); } }
Essa característica usa um objeto de classe, o nome da propriedade em que você deseja definir o valor e, de fato, o próprio valor. Em seguida, é declarada uma função anônima simples que, usando o ponteiro $this
, atribui o valor resultante a uma propriedade de classe. Em seguida, a classe Closure
entra com seu método estático bind()
. O método aceita um objeto de classe, a função anônima descrita acima e o nome completo da classe. Assim, uma função anônima é incorporada no contexto do objeto e o método bind()
retorna um objeto da classe Closure
, que podemos chamar de função regular, porque define o método mágico __invoke()
. E pronto!
No final, consegui resolver meu problema e lembrei-me do padrão de design Singleton. Será possível da mesma maneira implementar uma função anônima que criará novos objetos da classe? Claro que fui conferir!
Ao escrever um pequeno pedaço de código
Caixa de areia com um 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 com o mesmo princípio, mas, em vez de atribuir um valor a uma propriedade de classe, um novo objeto é criado usando o new
operador. A função \spl_object_id()
retorna um identificador exclusivo para o objeto. Mais informações sobre esse recurso podem ser encontradas na documentação . Usando spl_object_id()
e var_dump()
spl_object_id()
identificadores únicos de objetos e vejo que eles são diferentes! Ainda consegui confirmar essa teoria e criar uma nova instância da classe Singleton!
Neste artigo, eu queria compartilhar minha descoberta muito curiosa com a comunidade PHP.
Obrigado pela atenção!