13
Feb 10

Las referencias en PHP: qué son y el porqué de no usarlas

elefante

elefante

Según un estudio reciente, PHP es actualmente el tercer lenguaje de programación más popular.  Puede sorprender teniendo en cuenta los buenos comentarios que han recibido durante años lenguajes como Ruby y que se daba por seguro el retroceso de PHP. Yo creo que esto es debido a que la gente que está detrás de PHP ha sabido adaptarse a las nuevas tendencias y responder a tiempo. Dos ejemplos muy significativos:

  • PHP5
  • Zend Framework (y otros)

La comunidad “phpera” lejos de quedarse parada ante el adelantamiento por la derecha que parecía que les iba a hacer Ruby/Python/etc han mejorado el lenguaje increíblemente hasta el punto que el diseño OO de PHP tiene poco que envidiar a Java. Me imagino a los desarrolladores allá por el 2003 diciéndo “Dicen que PHP4 no es enterprise-ready, que su OO da asco, vale, tienen razón.”. Y de ahí salió PHP5, miras la lista de cambios entre PHP4 y PHP5, y resulta increíble que sólo les separe un número de versión. Algo parecido se puede decir de “Zend Framework”: “Ruby on Rails mola mucho, un killer app, hagamos algo parecido”. Y acabó surgiendo Zend Framework, un excelente framework MVC con “total” cobertura de test unitarios y completamente modular. Además, surgieron muchos otros framework que rivalizan con ZF, el “oficial”, creando una competencia absolutamente sana. Se trata de ejemplos como CakePHP, Symfony o CodeIgniter. El último episodio donde se ha demostrado este pragmatismo de los phperos ha sido dejar Zend_Entity y pasar a adoptar el ORM supuestamente de la competencia : Doctrine. Esto contrasta con la gente de Perl que ha dedicado la mayor parte de sus esfuerzos a crear una máquina virtual, académicamente muy interesante pero en la práctica ¿Hay nuevos usuarios de Perl existiendo PHP, Python y Ruby?.

Vale, a estas alturas te estarás preguntando a qué viene el título de esta entrada. Pues bien, todo esto de la actitud pragmática, los grandes avances etc. está muy bien pero existe un problema en PHP si bien en mi opinión es un mal menor: se trata de mantener la compatibilidad hacia atrás. No sólo un mal menor sino además un mal necesario que permite que los desarrolladores vayan adaptándose a los nuevos cambios progresivamente. La opción contraria podría resultar crítica para muchos usuarios, hay pocos casos donde ha sido necesario ese caso extremo, me viene a la cabeza VisualBasic 6 y poco más.

PHP es un lenguaje que empezó con objetivos mucho más modestos, seguramente Rasmus Lerdof ni en sus sueños más húmedos se imaginó que su herramienta evolucionaría hasta soportar sitios de la escala de Facebook. La política en PHP ha sido ir corrigiendo cosas y manteniendo compatibilidad hacia atrás avisando al desarrollador de las malas prácticas mediante warnings. En esta entrada hablaré de porqué no se debe utilizar a día de hoy referencias en PHP.

¿Qué son las referencias? “&”

Las referencias de PHP se parecen en esencia a las referencias (que no punteros) de C++. Son algo parecido a los enlaces duros de UNIX. Se han utilizado mucho en PHP debido a que en PHP4 el diseño OO era bastante distinto. En POO se trabaja con referencias de objetos en lugar de con los datos directamente debido a que la idea es mandar mensajes a los objetos y que ellos mismos sean los encargados de modificar su propio estado, y para eso basta la referencia del objeto para mandarle mensajes, difícilmente vas a necesitar acceder al objeto en sí mismo.
En PHP4, la implementación OO hacía que fuera necesario utilizar el operador referencia para hacer POO ya que de lo contrario estabas copiando objetos. También hay que tener en cuenta que hasta hace relativamente poco no se programaba OO en PHP por lo que se asumía que al igual que en C, si quieres trabajar con una estructura de datos muy grande, lo suyo es pasarlo por referencia y así evitar la copia. Esto me lleva a explicar un hecho poco conocido de PHP.

copy-on-write

La práctica de usar paso por referencia por motivos de optimización es equivocada actualmente debido a que PHP hace copy-on-write. Esto consiste en que PHP cuando se pasa por valor (casi siempre), no hace una copia de la variable desde el principio sino que trabajará en realidad con la referencia y hará una copia sólo en el momento en que detecte que vas a intentar modificar. Es decir, tú como programador feliz, estás en el ámbito de la función y con un parámetro por valor, y parece que estás trabajando desde el principio con una copia con lo cual si la estructura de datos es muy grande te puedes ver tentado a pasar por referencia pero es un error debido a que PHP internamente no hace la copia hasta que sea realmente necesario. Vale, pero puedes estar pensando: ¡ pero es que precisamente mi función va a modificar esa estructura y mucho así que se va a hacer esa costosa copia! Bien, el problema subyacente es que no se está pensando suficientemente a lo OO sino más bien proceduralmente. En PHP5 el modelo de objetos está rediseñado de modo que los objetos se trabajan con referencias como en Java, por lo que deja de ser necesario utilizar el operador referencia para hacer POO. Esto supone que el operador ‘=’ en la práctica es como si copiara sólo para los tipos primitivos mientras que para los objetos copia la referencia. Esta suele ser la típica explicación pero en realidad es errónea, lo cierto es que esta misma mala explicación también se suele dar en Java como simplificación. Lo que ocurre en realidad es que en PHP5 no trabajas con el objeto directamente, trabajas con su referencia, de modo que cuando pasas el objeto por valor, como mucho estarás provocando (copy-on-write) la copia de la referencia pero no del objeto. Si quieres una copia física del objeto lo que necesitarás es clonar el objeto (clone). Puedes utilizar el operador de referencia con objetos también pero resulta innecesario.

Vamos a ver más en profundidad cómo trabaja el intérprete de PHP las variables.

user@computer:$ apt-get source php5-cli.

La estructura de datos que almacena una variable en PHP se llama “zval_struct” (zval entre amigos). Un “zval” tiene los siguientes campos:

  • tipo (el tipo de la variable)
  • valor (el valor de la variable)
  • is_ref (flag que indica si la variable es una referencia &)
  • refcount (contador del número de símbolos que apuntan a este zval)

La estructura está definida en zend.h:

  1. struct _zval_struct {
  2. /* Variable information */
  3. zvalue_value value;             /* value */
  4. zend_uint refcount;
  5. zend_uchar type;        /* active type */
  6. zend_uchar is_ref;
  7. };

Un zvalue_value puede ser muchas cosas. Ahí reside la magia del tipado de php. Tenemos en la misma estructura de datos las distintas posibilidades, tanto tipos primitivos como objetos, en Ikuspro realicé una implementación similar sólo que la genericidad de Java facilita mucho las cosas. Los objetos no se almacenan en esta estructura sino que únicamente se guarda el puntero (en PHP-5).

  1. typedef union _zvalue_value {
  2.         long lval;                                      /* long value */
  3.         double dval;                            /* double value */
  4.         struct {
  5.                 char *val;
  6.                 int len;
  7.         } str;
  8.         HashTable *ht;                          /* hash table value */
  9.         zend_object_value obj;
  10. } zvalue_value;

En PHP4 probablemente el diseño era distinto de manera que en el valor iba directamente el objeto de manera que era necesario usar continuamente el operador referencia para hacer programación orientada a objetos correctamente.

Un zend_object_value es lo siguiente (zend_types.h):

  1. typedef struct _zend_object_value {
  2.         zend_object_handle handle;
  3.         zend_object_handlers *handlers;
  4. } zend_object_value;

El handle es un ID único de entre ese tipo concreto de objetos (no global). El tipo del objeto y su funcionalidad está en esta otra estructura que es encuentra en una tabla. Desde zend_object_value vemos que se apunta con handlers en la entrada adecuada.

  1. typedef struct _zend_object_handlers {
  2.         zend_object_add_ref_t                    add_ref;
  3.         zend_object_del_ref_t                    del_ref;
  4.         zend_object_delete_obj_t                 delete_obj;
  5.         zend_object_clone_obj_t                  clone_obj;
  6.         zend_object_read_property_t              read_property;
  7.         zend_object_write_property_              write_property;
  8.         zend_object_get_property_ptr_t           get_property_ptr;
  9.         zend_object_get_property_zval_ptr_t      get_property_zval_ptr;
  10.         zend_object_get_t                        get;
  11.         zend_object_set_t                        set;
  12.         zend_object_has_property_t               has_property;
  13.         zend_object_unset_property_t             unset_property;
  14.         zend_object_get_properties_t             get_properties;
  15.         zend_object_get_method_t                 get_method;
  16.         zend_object_call_method_t                call_method;
  17.         zend_object_get_constructor_t            get_constructor;
  18.         zend_object_get_class_entry_t            get_class_entry;
  19.         zend_object_get_class_name_t             get_class_name;
  20.         zend_object_compare_t                    compare_objects;
  21. } zend_object_handlers;

Los símbolos (nombres de variables) que apuntan a un zval se guardan en una tabla de símbolos. Normalmente solemos hablar de variables locales, variables globales, ámbitos de variables… Bien, de lo que se trata es que tenemos una tabla de símbolos por ámbito y en PHP el ámbito está unido a las funciones o métodos. En C por ejemplo, es diferente y los ámbitos van unidos a los bloques directamente de modo que tenemos tablas de símbolos por bloques. Por ejemplo en PHP tenemos:

  1.  
  2. $a = "hola/";
  3. echo $a;
  4. {
  5.           $a = "jaja/";
  6.           echo $a;
  7. }
  8. echo $a;

El resultado será “hola/jaja/jaja/”

En C:

  1. #include
  2. main() {
  3.  
  4. char *a = "hola/";
  5. printf("%s",a);
  6.  
  7. {
  8. char *a = "jaja/";
  9. printf("%s",a);
  10. }
  11.  
  12. printf("%s",a);
  13.  
  14. }

El resultado es “hola/jaja/hola/”.

Si te parece un detalle de importancia echa un vistazo a este código:

  1.  
  2. <?
  3. $a = "Hola";
  4. echo $a;
  5.  
  6. if ($a == "Hola") {
  7.         $b = "Adios";
  8. }
  9. echo $b;

Este código en C no tiene sentido.

Podemos ver los zval con xdebug_debug_zval

user@computer:$ apt-get install php5-xdebug

Veamos un ejemplo:

  1.  
  2. <?
  3.  
  4. $a = "Hola";
  5. xdebug_debug_zval(‘a’);
  6. $b = 2;
  7. xdebug_debug_zval(‘b’);
  8. $obj = new stdClass();
  9. $obj->atrib1 = "foo";
  10. $obj->atrib2 = "bar";
  11. xdebug_debug_zval(‘obj’);
  12. $obj2 = $obj;
  13. xdebug_debug_zval(‘obj2′);
  14. $obj3 = &$obj;
  15. xdebug_debug_zval(‘obj3′);

El resultado es:

a: (refcount=1, is_ref=0)='Hola'
b: (refcount=1, is_ref=0)=2
obj: (refcount=1, is_ref=0)=class stdClass { public $atrib1 = (refcount=1, is_ref=0)='foo'; public $atrib2 = (refcount=1, is_ref=0)='bar' }
obj2: (refcount=2, is_ref=0)=class stdClass { public $atrib1 = (refcount=1, is_ref=0)='foo'; public $atrib2 = (refcount=1, is_ref=0)='bar' }
obj3: (refcount=2, is_ref=1)=class stdClass { public $atrib1 = (refcount=1, is_ref=0)='foo'; public $atrib2 = (refcount=1, is_ref=0)='bar' }

Como puedes ver el único con is_ref=1 es la variable referencia. Un zval se elmina cuando refcount pasa a ser cero y esas referencias van desaparenciendo a medida que se van resolviendo los ámbitos. Es decir, si hemos creado un zval dentro de una función (localmente) tendremos en principio símbolos pertenecientes a la tabla de símbolos de esa función. Al terminar la función ese zval pasará a tener 0 de refcount y por tanto se podrá liberar. Se suele decir que la función unset() de php destruye objetos pero creo que eso no es cierto. Un ejempo:

  1. $orig = 4;
  2.  
  3. $a = $orig;
  4. xdebug_debug_zval(‘a’);
  5. $b = &amp;$a;
  6. xdebug_debug_zval(‘a’);
  7. unset($a);
  8. xdebug_debug_zval(‘b’);
  9. echo $b ."\n";
  10. $obj = new stdClass;
  11. $obj->foo = "bar";
  12. $obj2 = &amp;$obj;
  13. xdebug_debug_zval(‘obj’);
  14. unset($obj);
  15. xdebug_debug_zval(‘obj2′);
  16. echo $obj2->foo ."\n";

El resultado es:

a: (refcount=2, is_ref=0)=4
a: (refcount=2, is_ref=1)=4
b: (refcount=1, is_ref=0)=4
4
obj: (refcount=2, is_ref=1)=class stdClass { public $foo = (refcount=1, is_ref=0)='bar' }
obj2: (refcount=1, is_ref=0)=class stdClass { public $foo = (refcount=1, is_ref=0)='bar' }
bar

Como podemos ver lo que hace unset en realidad es disminuir el número del contador. Por último un ejemplo en el que pasamos la referencia de un objeto con lo cual hacemos que deje de apuntar a un objeto y pase a ser un string (tipo primitivo):

  1.  
  2. <?
  3.  
  4. function lafuncion(stdClass &$obj) {
  5.         $obj = "Sorpresa";
  6. }
  7.  
  8. $a = new stdClass();
  9. $a->foo = "bar";
  10. xdebug_debug_zval(‘a’);
  11. lafuncion(&$a);
  12. xdebug_debug_zval(‘a’);

El resultado es:

a: (refcount=1, is_ref=0)=class stdClass { public $foo = (refcount=1, is_ref=0)='bar' }
a: (refcount=1, is_ref=0)='Sorpresa'

Como conclusión: no merece la pena trabajar con el operador referencia si estamos haciendo POO, lo único que vamos a conseguir es liar el código y provocar bugs, los punteros y referencias no están al nivel de abstracción que solemos manejar en nuestra vida diaria mientras que los objetos, atributos y métodos sí. Desgraciadamente existe mucho código “legacy” que hace uso intensivo de pasos por referencia, incluyendo algunas funciones de ordenación de PHP. Una opción para convertir esas funciones es sustituir los parámetros por referencia por un objeto stdClass que los encapsule pasado por valor.

Links:

Para escribir este post me he basado en el código citado del intérprete de PHP y en los siguientes enlaces. Si crees que he cometido algún error pon un comentario, estaré encantado en aprender:

http://php.net/manual/en/features.gc.refcounting-basics.php
http://schlueters.de/blog/archives/125-Do-not-use-PHP-references.html


26
Dec 09

Desarrollo para Plataforma Flash en Linux: Minibuilder

Desde hace un tiempo Adobe ya no desarrolla Flash/Flex Builder para Linux. Una alternativa libre prometedora que he encontrado es Minibuilder. Se trata de un editor que utiliza la tecnología Adobe Air.

flash platform

flash platform

Veamos a continuación los pasos necesarios para desarrollar en Linux para plataforma Flash con esta herramienta:

Continue reading →


02
Dec 09

En defensa de los derechos fundamentales en internet

Ante la inclusión en el Anteproyecto de Ley de Economía Sostenible de modificaciones legislativas que afectan al libre ejercicio de las libertades de expresión, información y el derecho de acceso a la cultura a través de Internet, los periodistas, bloggers, usuarios, profesionales y creadores de internet manifestamos nuestra firme oposición al proyecto, y declaramos que…

Continue reading →


02
Nov 09

Probando Haxe

Haxe es el sucesor de MTASC, pero no se limita a tener como objetivo la tecnología flash sino que también genera Javascript, PHP y otros. Además, se acompaña de una máquina virtual, Neko, que le sirve para la ejecución en servidor. Yo personalmente creo que es un proyecto que está intentando abarcar demasiado pero hay varios puntos que me han animado a probarlo:

  • Las nuevas funcionalidades y rendimiento mejorado de AVM2 (máquina virtual en las últimas versiones de Flash).
  • El lenguaje de Haxe es mejor que ActionScript.
  • La posibilidad de portar aplicaciones flash a plataforma nativa fácilmente. Esto se está utilizando por ejemplo para aplicaciones iPhone.


A continuación los pasos necesarios para la instalación y un par de ejemplos de Haxe contra Flash y CPP usando el mismo código:

Continue reading →


02
Oct 09

Ikuspro en beta

Ikuspro es el nombre del software que he desarrollado para mi PFC. Se trata de un simulador gráfico de programas.
La idea del proyecto es de mi director de PFC que es profesor del departamento de LSI de EUITI-BI e imparte clases de “Fundamentos de Informática” por lo que pensó que una herramienta de este tipo podría ser útil para quien se enfrenta por primera vez a la metodología de programación.
Con Ikuspro la ejecución de los programas se hace visual y gracias a ello se alivia un poco la sensación de abstracción que suele inspirar la programación. Es algo parecido a lo que ocurre con Logo, aquella tortuga que hacíamos mover por toda la pantalla sin darnos cuenta de que estábamos programando por primera vez.

De todos modos lo mejor es que ponga un ejemplo. Este código por ejemplo:

editor

Como puedes ver este código es C.

Ikuspro en la versión actual incluye un intérprete de C que he desarrollado con el objetivo de que puedan funcionar los ejercicios utilizados en la asignatura de “Fundamentos de Informática”. Este ejemplo es muy sencillo para no recargar esta entrada pero el intérprete de C de Ikuspro es bastante más avanzado soportando funciones, arrays, condicionales, bucles e incluso punteros parcialmente.

Hablo del intérprete como algo independiente de Ikuspro ya que una parte interesante del proyecto es que Ikuspro está pensado en realidad como un simulador gráfico que se puede extender en el futuro con nuevos intérpretes para otros lenguajes sin mayor dificultad. Siendo un poco más técnicos, lo que hace Ikuspro es ejecutar el intérprete adecuado para el tipo de programa en un hilo independiente comunicándose ambos como productor-consumidor vía mensajes XML.

A continuación la simulación gráfica que genera Ikuspro al término de este programa y digo al término ya que con Ikuspro se va viendo la ejecución del programa línea a línea, en realidad como si de un debugger se tratara sólo que mucho más fácil y sobre todo didáctico para el usuario objetivo.

simulacion

Además, Ikuspro también genera comentarios didácticos acerca de lo que va sucediéndo línea a línea y muestra la salida estándar del intérprete, que es fiel a lo esperable.
A continuación un pantallazo de Ikuspro donde puedes ver las diferentes partes y hacerte una idea del funcionamiento del mismo:

ikuspro

Enlace a imágen ampliada

Ikuspro está desarrollado enteramente en Java. El intérprete C que incorpora se ha desarrollado con ANTLR.

Por último, el título de esta entrada es “Ikuspro en beta” porque esta semana he terminado de realizar mis pruebas así que está listo para realizar pruebas con usuarios. Ikuspro actualmente está numerado como 0.9 en lugar de 1.0 para dar cabida a alguna idea interesante y factible que pueda surgir en las pruebas con usuarios.

Si has llegado a leer hasta aquí y te gustaría probar Ikuspro:

Página web provisional del proyecto Ikuspro


22
Apr 09

Editores UML gratuitos

Aquí mantendré una lista comparativa de software gratuito para edición de diagramas UML. El prerrequisito para que el software sea incluido en la lista es la gratuidad excluyéndose licencias temporales y similares.

diagramas_uml

diagramas_uml

Continue reading →