Accediendo a propiedades privadas sin usar Reflection

Hace poco, mientras estaba trabajando en mi próximo gran proyecto (una librería de Event Sourcing en PHP) me topé con un problema muy concreto. Necesitaba serializar una clase llamada Event a json, y poder volver a deserializarla de vuelta. Lo sé: en PHP existen las funciones serialize y unserialize para guardar representaciones textuales de cualquier objecto fácilmente, pero necesitaba que fuera en json.

Había un desafío. Esta clase tenía propiedades privadas y protegidas que no eran expuestas por medio de getters para mantener encapsulamiento y prevenir cambios arbitrarios de estado. Además, algunas de ellas se seteaban automáticamente al momento de llamar al constructor. ¿Cómo acceder a esas propiedades privadas?

Implementar \JsonSerializable no era una opción tampoco, por dos razones. La primera es que entrega a la clase que quería serializar una responsabilidad que no le corresponde. La segunda es que esta interfaz no implementa deserialización.

Tampoco podía usar Reflection porque iba a estar serializando muchas de estas clases (en iteradores de unos 1.000 elementos). Todos saben que Reflection es costoso. Si lo has usado alguna vez, sobretodo en iteradores o loops, verás que es una de las pocas cosas que impacta de una manera bastante notoria la performance de tu aplicación.

Fue allí donde me topé con un artículo del genio Marco Pivetta en donde explica un truco para solucionar este tipo de casos.

Comprendiendo Closures

PHP 5.4 introdujo una clase llamada Closure. Closure es una clase especial invocable que representa un callable. Como todo callable, puede tener un contexto ($this) y un scope.

Lo genial es que, este closure posee un método llamado bind, que nos permite cambiar el contexto $this y el scope del callable de nuestro closure. Esto es un feature extremadamente poderoso.

Por ejemplo, consideremos la siguiente clase:

PHP

La propiedad privateValue no está expuesta mediante un getter, por lo que no podemos acceder a ella. Si intentamos accederla obtendremos un error. Esto es POO básica. Ni siquiera podemos acceder a esta propiedad por medio de extender la clase, porque su visibilidad es private, y no protected: está totalmente encapsulada.

Sin embargo, hay solo un scope que puede acceder a esta propiedad, y es el scope mismo de PrivateClass. ¿Qué pasaría si aplicamos a este caso la posibilidad que nos dan los Closures de cambiar el scope de una función?

PHP

Si ejecutas este código, la variable $value contendrá el valor de la propiedad privada.

Creando un Property Accessor

Teniendo el principio anterior claro, es fácil lograr abstracciones que nos ayuden a realizar esta operación de manera más eficiente. Este es el PropertyAccessor que estoy usando. Es estático, asi que úsalo con cuidado, aunque siempre puedes implementarlo no-estáticamente.

PHP

Considerando nuestra anterior clase PrivateClass, podríamos acceder a supropiedad privateValue simplemente haciendo:

PHP

O podemos obtener un arreglo de todas las propiedades visibles por esa clase, en ese scope.

PHP

Esto es significativamente más performant que usar Reflection.

¿Qué te parece truco? Una vez más, PHP nos permite gran flexibilidad dentro de la solidez del paradigma orientado a objetos.

Leave a Reply

Your email address will not be published. Required fields are marked *