PHP 5.3 “Como cayó, quedó”
Con ese changelog… debería ser el release de una major version, no una minor…
En fín, este sería el milestone de php que se conocera de acá en adelante con la frase “como cayó, quedó”.
Mi sincero y objetivo consejo: no usen los namespaces, así como están son al reverendo pedo, lo bueno es que pueden igual usar el keyword “use” para hacer name aliasing y achicar los nombres largos que ya venian usando.
Los “closures” al final quedaron como era de esperarse… no son más que syntactic sugar para la creación de functor objects anonimos. No resuelven el scope, se debe explicitar las variables que se copian al scope del functor.
A que estar atento en los namespaces de PHP 5.3
Introducción
Se viene PHP 5.3 así como quedo, algo feo para mi gusto. Veamos a que atenerse en cuanto a namespaces.
Los namespaces de PHP son bastante distintos a similares construcciones de otros lenguajes. Consideremos este escenario: dos desarrolladores PHP están trabajando en componentes distintos, sin posibilidad de saber uno lo que hace el otro, ¿cómo evitar que al integrar los dos componentes falle la aplicación por colisiones de nombres? Una opción es hacer lo que venimos haciendo hasta ahora, prefijar todos los nombres con el nombre del componente. La nueva opción es indicar como namespace el nombre del componente, que básicamente es lo mismo que el prefijo (porque más que eso no hace).
Namespace ahora es un keyword
Y sí, rompieron la compatibilidad hacia atrás, y no es un cambio mayor de versión.
Después de varias idas y vueltas, cotejando el impacto de usar un keyword u otro, quedo namespace como keyword. No importa cual hubieran elegido entre los dos favoritos, package y namespace, cualquier de los dos son palabras de uso frecuente (por ejemplo, xml tiene namespaces). Cualquier clase que tuviera como nombre de variable a namespace, ahora va a dejar de funcionar. JODANSE.
Consejo: nada que hacer. Refactoring. Con suerte no usas namespace como nombre.
Lo que hubiera estado bueno: no hacia falta tomar un keyword nuevo. Había opciones que no requerían un statement para indicar el namespace. Y si no quedaba otra… hubiera sido para PHP6 el cambio.
El namespace implicito y especial de nombres internos
Segundo gotcha! del tema de los namespaces. Hay todo un conjunto de nombres internos, que corresponden a funciones y clases de la librería estándar y extensiones, que debería ser “visible” desde cualquier namespace, para que uno pueda fácilmente refactorizar código sin namespace a código con namespace. O sea, si yo indico que trabajo en un namespace igual quiero poder usar count(), strlen(), etc tal y como siempre lo hice.
El tema acá es que una función o clase interna se puede “sobrecargar” en un namespace, o sea, redefinirla. Esto es útil para los componentes que definen su propia clase de excepciones, por ejemplo. Pero hay que estar atento que la función count() adentro de un namespace puede que no devuelva la cantidad de elementos de un array sino lo que al desarrollador de ese componente le parecio que count() debía hacer.
Consejo: usar namespaces solo para clases, evitar declarar funciones dentro de namespaces a menos que uno sepa exactamente lo que está haciendo. También uno puede referirse inequívocamente a la función del espacio interno anteponiendo ::, por ejemplo, ::count().
Lo que hubiera estado bueno: que no se pueda sobrecargar los nombres del espacio interno, pero que exista un namespace inicial con ciertos nombres que resulta práctico sobrecargar, por ejemplo: Exception. De esta manera se evitan prácticas que pueden resultar perjudiciales y no tienen sentido (como sobrecargar las funciones básicas), y se le da la flexibilidad al desarrollador para sobrecargar los nombres que si tiene sentido sobrecargar.
Colisión de nombres no detectada, función pisa metodo estatico
Acá la pregunta que seguro se van a hacer todos es “¿cómo? ¿no era que con esto resolvíamos el tema de las colisiones de nombres?”, bueno, no del todo… hay un “pequeño” problema.
Supongamos que tengo una función llamada bar() en el namespace test::foo. Ahora supongamos que tengo una clase llamada foo, que tiene un método estático llamado bar(), y se encuentra en el namespace test. Históricamente en PHP los nombres de funciones y nombres de métodos no tenían chances de colisionar, pero… a ver quien me dice a que corno apunta test::foo::bar()!
Lo cierto es que esto que acabo de ejemplificar es un caso de colisión de nombres del que php5.3 no te da aviso alguno. Si intentas el ejemplo, PHP va a llamar a la función, no al método estático. De manera que ahora existe una manera de “pisar” métodos estáticos, o sea, desde otro namespace se vuelve inaccesible el método estático de la clase.
Consejo: no declarar funciones en namespaces (¿para qué?) o evitar en lo posible anidar namespaces.
Lo que hubiera estado bueno: realmente no hacia falta namespaces que contuvieran funciones ni tampoco varios niveles de anidación. Cuanto más simple, mejor.
Multiples namespaces en un mismo archivo
No se si esto esta documentado o explicado en detalle en la documentación, pero se puede declarar el namespace más de una vez en una archivo. Esto fue para resolver una situación muy ad-hoc y es un moco feo que no debería haber llegado a producción. Se puso para que se pudieran concatenar archivos php para generar un único archivo que contuviera todo, una optimización medio fea que ya no tiene sentido teniendo a disposición phar.
Consejo: por el amor de todo lo que es buen diseño, ¡NO DECLAREN EL NAMESPACE MÁS QUE UNA SOLA VEZ POR ARCHIVO! Y castiguen a todo el que lo haga.
Lo que hubiera estado bueno: que quitaran esto.
El “use” (import/alias de nombres) solo funciona para clases y namespaces.
Otra de las cosas que no se arreglaron. El use es la herramienta más útil que nos da esto de los namespaces, y en realidad ni siquiera nos hace falta usar el resto. El “use” es básicamente un generador de alias de nombres, con el podemos hacer de esos nombre realmente largo en nombre cortos, por ejemplo:
use Really_Long_Long_Long_Name as ShortName;
Pero hay un tema, solo se puede renombrar clases y namespaces, pero no así funciones ni constantes, que son elementos que también se encuentran dentro de un namespace. O sea, sirve para acortar nombres de clases, que es genial y que es lo que queríamos, pero no para acortar nombres de funciones o constantes. Con esto surge la pregunta: entonces, ¿para qué esta la opción de tener funciones y constantes dentro de un namespace?
Consejo: creo que lo dije ya, esto de los namespaces en PHP solo viene bien para usarlos con clases.
Lo que hubiera estado bueno: que se enfocaran más en esta herramienta que era lo que realmente importaba.
El “use” no permite importar todo un namespace
Esto es lo que todos los que usan namespaces en otros lenguajes van a pedir seguro, pero no hay caso… esto es PHP! (cualquier alusión a la novela gráfica 300 es pura coincidencia). Hay que entender que el use no es un import, en realidad es solo una manera de asignar nombres más cortos dentro del scope de un archivo php.
Impacto en performance
Infaltable este tema. La promesa era que la implementación de namespaces no iba a afectar la performance, que el mayor impacto iba a ocurrir en la compilación, de manera que con un opcode cache se podía salvar el overhead. Lo cierto es que esto no es así, algo hay que pagar por el uso de los namespaces. De todas maneras estamos hablando de mediciones en el rango de las millonesimas de segundo. No hay sistema PHP que tenga suficiente procesamiento como para que se note realmente el impacto del uso de namespaces. Igual vale la pena notar que nada es gratis.
En los análisis de tiempo que hice observe impactos de performance en llamadas a métodos estáticos y en la construcción de objetos, lugares donde la lógica de resolución de nombres tiene sus vueltas (en realida solo esperaba un impacto importante en las llamadas estáticas, no comprendo porque hay un overhead importante con la construcción de objetos).
De PHP5.2 a PHP5.3 sin declarar namespace, el impacto en las llamadas a métodos estáticos es mínimo. Tarda un 4% más (que es solo 1 millonésima de segundo medido en mi pc: de 25 millonésimas a 26).
Declarando un namespace, el impacto en las llamadas a métodos estáticos es de un 20% más de tiempo (5 millonésimas de segundo en mi PC: de 25 millonésimas a 30).
Declarando un namespace, el impacto en la construcción de objetos es de un 33% más de tiempo (3 millonésimas de segundo en mi PC: de 9 millonésimas a 12).
Consejo: no creo que haya que preocuparse por el overhead, pero este existe, cuando se empiece a probar en producción se vera si hay que considerar o no el impacto en performance. Si no es aceptable el overhead, no hace falta usar namespaces, con el prefijo en nombres nos veníamos arreglando bastante bien, y el “use” alivia el tema de los nombres largos aún si no usamos namespaces (notese que el “use” se resuelve siempre en compilación así que el overhead lo amortigua el opcode cache, por lo que no es una preocupación).
¿Y los features de encapsulación?
La otra pregunta que varios se van a hacer es qué sentido tienen los namespaces si no puedo ajustar los limitadores de visibilidad que me permiten manejar la encapsulación. Así como una clase tiene funciones privada, protegidas y publicas, un namespace debería poder indicar que nombres se exportan y que nombres son internos al paquete.
PD: Desactualización de las IDEs
La lógica de resolución de nombres no es nada parecido a cuanto se viene haciendo en PHP. Las IDEs van a tener que actualizarse, y esta actualización no es coser y cantar.