Evolución del modelo de orientación a objetos de PHP

  • Por
A lo largo de los años el modelo de orientación a objetos de PHP ha cambiado mucho. La programación orientada a objetos de las versiones anteriores a la 5 era muy rudimentaria y ahora es extremadamente avanzada.

Para entender la importancia de los cambios de PHP 5, en cuanto a orientación a objetos, es interesante observar cómo ha evolucionado el modelo de objetos de las versiones anteriores. Comenzamos a trabajar con objetos en PHP 3 con una propuesta extremadamente rudimentaria y aunque esto queda ya muy lejos, lo cierto es que nos da una idea de cómo el mundo de PHP se ha profesionalizado y cómo este lenguaje ha pasado de ser una herramienta ocasional a una alternativa seria, a la altura de los lenguajes de programación más avanzados.

Lo cierto es que este artículo te dará un poco de conocimiento general de PHP y te ayudará a tener una visión de pájaro del lenguaje más precisa, con un horizonte más lejano en el tiempo. Pero si no te interesa y estás con prisa podrías saltarte este capítulo del Manual de Orientación a Objetos de PHP, puesto que no es absolutamente imprescindible para poder programar en objetos con PHP.

Orientación a objetos en las versiones antiguas de PHP: PHP3 y PHP4

La versión 3 de PHP ya soportaba la programación orientada a objetos (POO), aunque es verdad que la mayoría de las características de este tipo de programación no estaban implementadas todavía. En concreto, con PHP3 podíamos crear clases e instanciar objetos. Las clases permitían agrupar tanto métodos como propiedades o atributos, pero la cosa se quedaba ahí.

En PHP4, se reescribió el motor de PHP para hacerlo mucho más rápido y estable, pero la POO, que había introducido la anterior versión del lenguaje, no se llegó a modificar prácticamente. Aun así, durante la vigencia de PHP 4, la programación orientada a objetos fue utilizada habitualmente, a menudo en aplicaciones de gran tamaño. Entornos donde se puso de manifiesto la falta de potencia de la POO en PHP 4 y la necesidad de mejorarla en una nueva versión.

El mayor problema de la POO en las versiones 3 y 4 de PHP se basaba en que, cada vez que se asignaba una variable que contenía un objeto a otra variable, o se pasaba un objeto por parámetro en una función, se realizaba una copia (un clon) de ese objeto y quedaba a disposición del programa en la nueva variable o parámetro.

$pepe = new persona("pepe"); 
$pepe2 = $pepe;

En un código como el anterior, se tiene un objeto persona alojado en la variable $pepe y en la segunda línea de código, se crea un clon de $pepe y se asigna a la variable $pepe2. En este caso y siempre siguiendo el anterior modo de trabajo de PHP, aunque $pepe y $pepe2 contienen un objeto idéntico, no se trata del mismo objeto sino de una copia. Todo esto implica que el espacio en memoria para guardar los dos objetos es el doble que si fuera un mismo objeto con dos nombres distintos.

Esta situación ocurría porque los objetos eran tratados del mismo modo que las variables normales, que se pasan por valor en las funciones y en caso de asignarse, se realiza una copia de la variable antes de asignarse al nuevo espacio.

Ejemplo del modo de trabajo con objetos de PHP 3 y 4

Vamos a realizar un ejemplo para ilustrar el modo de trabajo de PHP 3 y 4 con los objetos. En este ejemplo podrá quedar patente el proceso de clonación de los objetos al ser pasados en una función o al asignarse a otra variable.

Primero veamos una declaración de un objeto muy simple. Se trata de una "caja" que tiene un atributo que es el contenido y dos métodos, uno para introducir nuevos contenidos en la caja y otro para mostrar el contenido actual de la caja.

class Caja{ 
var $contenido; 

function introduce($cosa){ 
$this->contenido = $cosa; 
} 

function muestra_contenido(){ 
echo $this->contenido; 
} 
}

Ahora vamos a ver unas pocas líneas de código que hacen uso de la clase Caja para ilustrar el modo de trabajo de los objetos en PHP 4. Vamos a instanciar el objeto, luego lo asignamos a otra variable, con lo que se creará un clon de ese objeto, continuamos modificando el clon y veremos que pasa.

$micaja = new Caja(); 
$micaja->introduce("algo"); 
$micaja->muestra_contenido(); 

echo "<br>"; 

$segunda_caja = $micaja; 
$segunda_caja->introduce("contenido en segunda caja"); 
$segunda_caja->muestra_contenido(); 

echo "<br>"; 

$micaja->muestra_contenido();

En la primera línea de código se instancia la caja y se aloja el objeto en la variable $micaja. En la segunda línea se introduce el string "algo" en el contenido de la caja. Luego se muestra el contenido, con lo que saldrá el string "algo" en la página web.

En el segundo bloque de código se asigna el objeto $micaja a la variable $segunda_caja, con lo que se crea el mencionado clon del objeto $micaja y se asigna a la nueva variable. Luego se introduce un nuevo contenido a la instancia alojada en la variable $segunda_caja. Atención aquí, porque se ha modificado el clon alojado en la variable $segunda_caja, dejando inalterable el objeto original $micaja.

Para comprobarlo, se muestra el contenido del objeto $segunda_caja, con lo que aparece en la página web el string "contenido en segunda caja". También se muestra el contenido de $micaja, que no se ha modificado a pesar de actualizar el contenido de su clon, con lo que se muestra el string "algo".

Espero que no sea demasiado difícil de entender. Podéis hacer la prueba por vosotros mismos para comprender bien el ejercicio. De todos modos, vamos a hacer otro ejemplo en el que se utiliza la clase Caja, que esperamos sirva para aclarar mejor el trabajo con objetos en PHP 3 y 4.

$micaja = new Caja(); 
$micaja->introduce("algo"); 
$micaja->muestra_contenido(); 

echo "<br>"; 

function vacia_caja($caja_vaciar){ 
$caja_vaciar->introduce("polvo"); 
} 

vacia_caja($micaja); 

$micaja->muestra_contenido();

En este ejemplo hemos creado una función que recibe por parámetro un objeto de la clase caja. Como los parámetros en las funciones se reciben por valor en lugar de referencia, cuando se pasa el parámetro del objeto caja, en el fondo lo que se está realizando es una copia de ese objeto, de modo que dentro de la función se trabaja con un clon del objeto, en lugar del objeto mismo.

En el código se instancia el objeto caja y se introduce "algo" en su contenido. Luego, se declara una función que recibe el objeto y modifica su contenido, introduciendo el string "polvo" en el contenido de la caja. En las siguientes líneas de código, se llama a la función declarada anteriormente, pasando por parámetro el objeto $micaja. Dentro de la función, como decía, se modifica el contenido de la caja, aunque realmente se está modificando el contenido de un clon.

Por último, se muestra el contenido del objeto $micaja. En este caso aparece "algo", a pesar de que en la función ese "algo" se modificó por "polvo". A pesar de poder parecer pesado, vuelvo a repetir que en la función se modificó un clon del objeto y no el objeto original.

Los comportamientos descritos anteriormente no son muy habituales en otros lenguajes de programación orientada a objetos, como Java, donde el objeto no se duplica cada vez que se realiza una asignación o paso por parámetro.

Para evitar el comportamiento que hemos descrito, PHP dispone de la opción de paso de parámetros por referencia, que se realiza con el carácter "&". Por ejemplo, para asignar el propio objeto y no un clon podríamos haber utilizado este código:

$segunda_caja = &$micaja;

Para recibir un parámetro por referencia en lugar de por valor en una función utilizaríamos esta declaración de función:

function vacia_caja(&$caja_vaciar){

La posibilidad de utilizar el carácter "&" para forzar un paso por referencia no deja de ser un problema, puesto que nos obliga a utilizar ese mecanismo en múltiples lugares y es muy fácil olvidarse del "&" en algún sitio, con lo que nuestro programa ya no realizará los resultados esperados. Muchos programadores han gastado horas en encontrar el problema y en cualquier caso, es una molestia tener que estar pendientes de incluir constantemente el signo "&" en el código para hacer que funcione como ellos desean.

Orientación a objetos en PHP 5 y PHP 7

Ahora vamos a conocer cómo PHP 5 implementa la reciente la orientación a objetos, ofreciendo un listado de las novedades con respecto a los objetos en versiones anteriores. Ahora la programación orientada a objetos con PHP 5 es realmente avanzada y se mantiene en PHP 7, por lo que todo lo que vas a leer a continuación tiene validez también en la versión más reciente del lenguaje. No obstante, a medida que han pasado los años todavía nos han traído novedades que han mejorado este panorama y que comentamos en futuros artículos del manual, como los namespaces o traits.

En la parte inicial de este artículo comentamos las carencias del modelo de orientación a objetos en PHP 3 y 4, que afortunadamente han quedado solventadas en la versión PHP 5.

Como decíamos, uno de los problemas más básicos de las versiones anteriores de PHP era la clonación de objetos, que se realizaba al asignar un objeto a otra variable o al pasar un objeto por parámetro en una función. Para solventar este problema PHP5 hace uso de los manejadores de objetos (Object handles), que son una especie de punteros que apuntan hacia los espacios en memoria donde residen los objetos. Cuando se asigna un manejador de objetos o se pasa como parámetro en una función, se duplica el propio object handle y no el objeto en si.

Nota: También se puede realizar una clonación de un objeto, para obtener una copia exacta, pero que no es el propio objeto. Para ello utilizamos una nueva instrucción llamada "clone", que veremos más adelante.

Algunas características del trabajo con POO en PHP 5

Veamos a continuación una pequeña lista de las nuevas características de la programación orientada a objetos (POO) en PHP5. No vamos a describir exhaustivamente cada característica. Ya lo haremos más adelante en este mismo manual.

1.- Nombres fijos para los constructores y destructores

En PHP 5 hay que utilizar unos nombres predefinidos para los métodos constructores y destructores (Los que se encargan de resumir las tareas de inicialización y destrucción de los objetos. Ahora se han de llamar __construct() y __destruct().

2.- Acceso public, private y protected a propiedades y métodos

A partir de ahora podemos utilizar los modificadores de acceso habituales de la POO. Estos modificadores sirven para definir qué métodos y propiedades de las clases son accesibles desde cada entorno.

3.- Posibilidad de uso de interfaces

Las interfaces se utilizan en la POO para definir un conjunto de métodos que implementa una clase. Una clase puede implementar varias interfaces o conjuntos de métodos. En la práctica, el uso de interfaces es utilizado muy a menudo para suplir la falta de herencia múltiple de lenguajes como PHP o Java. Lo explicaremos con detalle más adelante.

4.- Métodos y clases final

En PHP 5 se puede indicar que un método es "final". Con ello no se permite sobrescribir ese método, en una nueva clase que lo herede. Si la clase es "final", lo que se indica es que esa clase no permite ser heredada por otra clase.

5.- Operador instanceof

Se utiliza para saber si un objeto es una instancia de una clase determinada.

6.- Atributos y métodos static

En PHP5 podemos hacer uso de atributos y métodos "static". Son las propiedades y funcionalidades a las que se puede acceder a partir del nombre de clase, sin necesidad de haber instanciado un objeto de dicha clase.

7.- Clases y métodos abstractos

También es posible crear clases y métodos abstractos. Las clases abstractas no se pueden instanciar, se suelen utilizar para heredarlas desde otras clases que no tienen porque ser abstractas. Los métodos abstractos no se pueden llamar, se utilizan más bien para ser heredados por otras clases, donde no tienen porque ser declarados abstractos.

8.- Constantes de clase

Se pueden definir constantes dentro de la clase. Luego se pueden acceder dichas constantes a través de la propia clase.

9.- Funciones que especifican la clase que reciben por parámetro

Ahora se pueden definir funciones y declarar que deben recibir un tipo específico de objeto. En caso que el objeto no sea de la clase correcta, se produce un error.

10.- Función __autoload()

Es habitual que los desarrolladores escriban un archivo por cada clase que realizan, como técnica para organizar el código de las aplicaciones. Por esa razón, a veces resulta tedioso realizar los incluyes de cada uno de los códigos de las clases que se utilizana en un script. La función __autoload() sirve para intentar incluir el código de una clase que se necesite, y que no haya sido declarada todavía en el código que se está ejecutando.

11.- Clonado de objetos

Si se desea, se puede realizar un objeto a partir de la copia exacta de otro objeto. Para ello se utiliza la instrucción "clone". También se puede definir el método __clone() para realizar tareas asociadas con la clonación de un objeto.

Autor

Miguel Angel Alvarez

Miguel es fundador de DesarrolloWeb.com y la plataforma de formación online EscuelaIT. Comenzó en el mundo del desarrollo web en el año 1997, transformando su hobby en su trabajo.

Compartir

Comentarios

Toño el Maldito

29/9/2006
Primeramente me alegra saber que hay gente predispuesta a ocupar su tiempo para explicar o ayudar a otras personas a entender mas a fondo los recursos de cuales podemos valernos para nuestros propios fines.

En cuanto al artículo debo comentarte que no estoy del todo de acuerdo con las 'falencias' mencionadas en cuanto a la programación orioentada a objetos con el PHP4. Tu sugerencia está errónea porque si los métodos sólo operaran recibiendo los parámetros por referencia sí sería un gran dolor de cabeza. En cuanto a la forma en la que se manejan los recursos yo creo que no hay más opciones, crear 'clones' nos evita que los procedimientos asociados a un objeto 'clon' alteren al objeto original.

Ya está, hagamos buen uso de los conceptos del paso de variables por valor y del paso de variables por referencia y en cuanto a olvidar el '&' me parece sólo una excusa mas para echarle palo a la forma en que se están operando los objetos, en ese caso también sería válido decir "y si se nos olvida un ';' o si se nos olvida un '==' dentro de un 'if", el simbolo ese está para diferenciar el paso de variables, ya sean por valor o por referencia.

Tomalo como crítica constructiva, SALUDOS xD

Juan Paulo

06/1/2009
0

Carlos

28/8/2009
pequeño inciso
Hola. Ya sé que hace más de 3 años que se escribió el artículo, pero aun así me gustaría puntualizar una cosa. No veo mucho sentido a hacer un método que se llame "vaciar_caja($caja_a_vaciar)" y que reciba un objeto caja como parámetro. Lo ideal sería implementar en la propia clase Caja un método "vaciar_caja()" y lo llamamos desde la instancia de Caja que queramos vaciar (o sea, "$caja1.vaciarCaja()").

Lo veo mucho más lógico, y así no nos encontraríamos con el problema que describes. Aunque bueno, esto lo escribo en plan anacrónico porque en php5 todo este sistema ha cambiado. Ya se que solo se trataba de un ejemplo muy forzado para que quedase bien clara la problemática con los objetos en php3 y 4, pero puede confundir a personas que no tengan todavía muy claros los conceptos básicos de la orientación a objetos.

Sin más, gracias por este estupendo manual, me está siendo de gran ayuda.

Osiris Kratys

03/11/2010
solo un comentario
estoy de acuerdo con Toño el Maldito, esto que usted presenta como un error puede que sea mas una ventaja, en ocasiones se necesitará utlizar esa diferencia.. pero vale, un agradecimiento por compartir sus conocimientos!!