PHP – Orientado a objetos

PHP orientado a objetos

Mediante la utilización de la programación orientada a objetos (POO), PHP nos va a ofrecer a los programadores la posibilidad de tener una programación más estructurada y fácil de implementar.

Veremos que gracias al concepto de objeto nos será posible desarrollar un código que podamos reutilizar todas las veces que estimemos oportuno sin tener que modificar nada.

Entender y desarrollar un código en base a una programación orientada a objetos es sinónimo de un alto nivel de conocimiento de programación y será el punto de partida en la creación de grandes proyectos, donde podremos compartir trabajo con otros programadores y asumir cada uno una parte independiente del proyecto, de tal forma que al final puedan unirse todas, dando un significado global a una aplicación.

Trabajando con programación orientada a objetos, cuando tengamos que utilizar alguna clase/objeto que haya desarrollado otra persona, no nos será necesario entender su funcionamiento, ni qué técnicas ha utilizado para desarrollarlo. Sólo nos hará falta conocer cómo acceder para poder usarlo en nuestro beneficio, proporcionándonos de esta forma un nivel de abstracción.

Concepto de clases y objetos

En un contexto de programación orientada a objetos, un objeto podrá ser cualquier elemento o concepto, tanto físico como podría ser un cliente, como conceptual que solo existe en el software y que, por ejemplo, podría ser un campo de entrada de texto o un archivo.

En la programación orientada a objetos, lo que diseñaremos y desarrollaremos serán objetos independientes, donde cada uno de ellos estará formado por unas propiedades y operaciones que van a interactuar entre ellas para lograr una serie de funcionalidades.

Las propiedades o variables serán los atributos que tendrán alguna relación con el objeto y las operaciones serán denominadas como métodos, funciones o acciones de los objetos, que utilizará el propio objeto para poder realizar tareas de modificación a sí mismo o para conseguir algún objetivo externo. Por lo tanto, podremos decir que un objeto estará formado por atributos y métodos.

Otro concepto importante relacionado con la programación orientada a objetos es la encapsulación. Los objetos tienen la capacidad de permitir o fomentar la ocultación de sus datos, de tal forma que el acceso a la información de su interior solo podrá ser posible mediante las operaciones con los objetos, a través de la interfaz del objeto. A esta ocultación de los datos se le denomina encapsulación.

Podremos actualizar los detalles de implementación de un objeto para obtener mayores beneficios en su rendimiento, añadir nuevas funcionalidades o resolver problemas, todo ello sin tener que modificar la interfaz de acceso al mismo.

Por otra parte, los objetos podrán ser agrupados en clases. Las clases representarán un conjunto de objetos que podrán variar, pero que tendrán un conjunto de características en común. Una clase contendrá objetos con operaciones que van a comportarse de la misma manera, y atributos que representarán las mismas cosas, aunque los valores de estos atributos puedan variar de un objeto a otro.

Por lo tanto, podemos decir que una clase será como una plantilla para un nuevo objeto. El modelo a partir del cual se va a implementar el objeto. A su vez, un objeto, será la instancia real de una clase, y a partir de ese momento, será cuando el objeto adquiera una entidad propia con sus características y funciones (los atributos y métodos).

A la hora de implementar la programación orientada a objetos en nuestros programas, los objetos serán entidades únicas, y serán instancias a una clase en concreto. La clase será definida con sus atributos y métodos, para posteriormente, cuando vayamos a crear un objeto, lo hagamos de tal forma que se base en una clase ya definida. A esto se le denomina instanciar una clase.

Crear clases, atributos y métodos

Para crear una clase, tendremos que utilizar la palabra reservada “class”. La estructura básica de una clase tendrá un aspecto como el siguiente:

El siguiente paso será la definición de sus atributos y sus operaciones. Como ya hemos explicado, los atributos serán las variables de la clase y para su definición haremos uso de la palabra reservada “var”.

En cuanto a los métodos, hará falta definir las funciones en la definición de la clase. Estas funciones podrán recibir parámetros o no hacerlo, y será necesario el uso de la palabra reservada “function” para crearlas.

Por lo tanto, la estructura completa de una clase sería la siguiente:

class nombre_de_clase
{
...
}

Constructor de una clase

En muchas ocasiones, la clase va a contener una función que será ejecutada de forma automática al crear la clase. A este método se le denomina constructor y será una función que se ejecutará de manera automática al crear la instancia de la clase (el objeto) y que resultará de gran utilidad cuando sea necesario inicializar algún valor de la clase.

Los constructores también podrán recibir parámetros, que podrán ser opcionales, haciéndolos más útiles.

Aunque nosotros también podamos realizar llamadas al constructor de la clase, su principal objetivo siempre será llamarse a sí mismo en la construcción del objeto, para de esta forma poder inicializar los atributos con determinados valores o crear nuevos objetos que se puedan necesitar para el óptimo funcionamiento del objeto, por ejemplo.

En la mayoría de lenguajes de programación, el constructor es un método que va a compartir el mismo nombre que la clase. Esto también ocurría en las versiones PHP3 y PHP4. Sin embargo, desde PHP5, el constructor para todas las clases recibe el nombre “__construct()” (con dos guiones bajos).

El constructor podrá admitir el paso de parámetros, si creemos que son necesarios cuando creemos un nuevo objeto. Por lo tanto, su sintaxis dentro de una clase tendría que se algo como lo siguiente:

Para mantener la compatibilidad, PHP buscará un método con el mismo nombre de la clase si no puede encontrar “ construct()”, pero este no debería ser siempre el caso, así que deberíamos usar siempre “__construct()”.

class nombre_de_clase
{
   var $variable1;
   var $variable2;
function primerafuncion($parametro1,$parametro2){
   …. 
}
function segundafuncion(){
  ….
}
}

Por último, comentar que de igual forma que en una clase puede existir un constructor, también podrá existir un destructor.

Un destructor nos posibilitará poder ejecutar una funcionalidad en concreto antes de que la clase sea destruida, algo que ocurrirá de forma automática cuando sean anuladas todas las referencias a la clase o éstas no se correspondan con el ámbito.

De la misma forma que un constructor se crea con la palabra reservada “ construct()”, un destructor se creará con la palabra reservada “ destruct()”. Un destructor no podrá admitir parámetros.

Uso de objetos, instancias de una clase

Una vez que hayamos declarado una clase, el siguiente paso será crear el objeto con el que queramos trabajar. A este paso se le denomina crear una instancia o instanciar una clase y se podrá realizar mediante el uso de la palabra reservada “new”.

En el proceso de instanciar una clase será necesario indicar a qué clase va a pertenecer el objeto, así como facilitarle los parámetros necesarios al método constructor si lo hubiera.

Veamos un ejemplo sencillo donde creamos dos objetos a partir de la misma clase, pero le pasamos valores diferentes al método constructor:

<?php
class ClaseEjemplo
{
function __construct($numero)
{
   echo "Método constructor llamado con el valor $numero<br>";
}
}

$miObjetoEjemplo = new ClaseEjemplo(5);
$objeto2 = new ClaseEjemplo(3);
function __construct($valor)
{
   //acciones
}

En el anterior script, en primer lugar hemos declarado una nueva clase denominada “cuadrado”, y al declarar su método constructor, hemos admitido el paso de un parámetro, para que al crear cada nuevo objeto, muestre un mensaje en pantalla con el valor del parámetro pasado al instanciar la clase. Por lo tanto, el resultado tendría que ser la aparición en el navegador de los siguientes mensajes:

Uso de atributos de una clase

Para ampliar las funcionalidades de una clase será necesario el uso de atributos. Para definir un atributo dentro de una clase tendremos que definirlo en primer lugar con la palabra reservada “var”, y una vez que haya sido definido, para utilizarlo, tendremos que añadirle delante del nombre del atributo la palabra reservada “$this->”, de tal forma, que para hacer referencia a un atributo, tendremos que escribir “$this->atributo”.

Partiendo del ejemplo anterior, vamos a ampliar nuestra clase con un nuevo atributo denominado “$mult”, el cual lo inicializaremos con el valor “2” y que lo utilizaremos dentro de la clase constructor para multiplicar el parámetro pasado por este atributo, consiguiendo de esta forma obtener el doble del parámetro utilizado a la hora de instanciar la clase:

También podríamos hacer uso del atributo desde fuera de la clase, es decir, desde nuestro script. Se haría de la siguiente forma:

<?php
class ClaseEjemplo
{
var $mult = 2;
function __construct($numero)
{
echo "Método constructor llamado con el valor $numero<br>";
echo "El valor doble del parámetro es :" . $this->mult *
$numero;
echo "<br>";
}
}
$miObjetoEjemplo = new ClaseEjemplo(5);
$objeto2 = new ClaseEjemplo(3);

Resultado:

Método constructor llamado con el valor 5
Método constructor llamado con el valor 5

Uso de métodos de una clase

Para explicar la utilización de método de una clase, vamos a modificar la clase con la que estamos trabajando, de tal forma que en lugar de tener que realizar la multiplicación de forma manual, esta operación vamos a incluirla en el nuevo método que denominaremos multxnum. Además, añadiremos otro método que realizará el cálculo de la mitad del valor:

<?php
class ClaseEjemplo
{
var $mult = 2;
var $numinterno;
function __construct($numero)
{
echo "Método constructor llamado con valor $numero<br>";
$this->numinterno=$numero;
echo "<br>";
}
function multxnum()
{
return $this->mult*$this->numinterno;
}
function divxnum()
{
return $this->numinterno/$this->mult;
}
}
$miObjetoEjemplo = new ClaseEjemplo(5);
echo "El valor doble es: ".$miObjetoEjemplo->multxnum();
echo "<br>";
echo "El valor dividido por 2 es: ".$miObjetoEjemplo->divxnum();
<?php
$miobjeto= new doble(5);
$miobjeto->mult=3;
echo $miobjeto->mult;
?>

Como podemos observar, en primer lugar hemos utilizado el método constructor para guardar el parámetro pasado al instanciar el objeto en un atributo interno, que luego utilizaremos para realizar las operaciones.

Por cada operación, hemos creado un método, donde cada uno devolverá un valor mediante la palabra reservada “return”.

Para hacer referencia a los métodos, al igual que con los atributos, haremos uso del símbolo “->” de tal forma que la sintaxis es $objeto->método().

En el ejemplo anterior, hemos usado los métodos directamente para incluirlos en el mensaje de salida a pantalla, pero también podríamos haber guardado su valor en una variable del programa principal, para poder utilizarla posteriormente en otras operaciones:

Visibilidad de atributos y métodos

Desde la versión PHP5 tenemos a nuestra disposición unos modificadores de acceso a través de los cuales podremos controlar la visibilidad de los atributos y los métodos.

Tendremos los tres siguientes modificadores de acceso:

Public. Ésta siempre es la opción predeterminada de cualquier atributo o método. De esta forma, podrán ser accedidos tanto dentro de clase, como desde el exterior cuando sea creado un objeto.

Private. Estableciendo este modificador de acceso, solo se podrá tener acceso a los atributos y métodos desde el interior de la clase. Si intentáramos desde el exterior, obtendríamos un error en la ejecución del programa. En un capítulo posterior, hablaremos de herencia de objetos. Pues bien, los elementos privados no podrán ser heredados.

Protected. Igual que el o modificador de acceso Private, pero en este caso sí que se permitirá la herencia de atributos y métodos Protected.

Para mostrar un ejemplo, vamos a seguir trabajando con la clase que hemos definido anteriormente. Podríamos declarar las funciones como públicas, puesto que vamos a usarlas todas desde fuera de la clase, pero las dos primeras variables, al ser solo de uso interno, podríamos declararlas como privadas:

<?php
$miObjetoEjemplo= new doble(5);
$eldoble=$miObjetoEjemplo->multxnum();
echo "El valor doble es: $eldoble"."<br>";
$lamitad=$miObjetoEjemplo->divxnum();
echo "El valor dividido por 2 es: $lamitad"."<br>";
?>

Herencia

Una de las técnicas más utilizadas en la programación orientada a objetos es la herencia. Con la herencia podremos crear nuevas clases que se basen en otras que ya estén creadas, pudiéndolas ampliar con nuevos métodos y atributos.

Para poder heredar una clase creada en PHP se tiene que utilizar la palabra reservada “extends” seguida de la clase original, que será denominada la superclase “parent”, mientras que la clase que hereda recibirá el nombre de subclase “child”. De una forma esquemática, así tendrían que ser declaradas:

class UNO{
//propiedades clase UNO
…
//métodos clase DOS
}
class DOS extends UNO {
//propiedades clase DOS
…
//métodos clase DOS
}
<?php
class ClaseEjemplo
{
var $mult = 2;
var $numinterno;
function __construct($numero)
{
echo "Método constructor llamado con valor
$numero<br>";
$this->numinterno=$numero;
echo "<br>";
}
public function multxnum()
{
return $this->mult*$this->numinterno;
}
public function divxnum()
{
return $this->numinterno/$this->mult;
}
}

En este supuesto práctico, la clase DOS va a heredar todas los atributos y métodos de la clase UNO para que pueda incorporarlos y, por lo tanto, utilizarlos, junto con sus propios atributos y métodos. Si no existiera esta posibilidad de heredar una clase, tendríamos que volver a definir los mismos métodos y atributos en la clase DOS, lo cual sería redundante y de poca utilidad.

Ahora veamos un ejemplo algo modificado y ampliado sobre el que hemos estado trabajando hasta ahora.

Modificaremos la clase padre con la que estábamos trabajando hasta ahora para eliminar la clase constructor, de tal forma que le pasaremos directamente el valor del número sobre el que vamos a realizar las operaciones, haciendo uso de su atributo desde el exterior ($miobjeto->numinterno). Por lo demás, la clase padre hace las mismas operaciones de multiplicar por 2 y dividir por 2 este parámetro.

A partir de aquí, crearemos una segunda clase hija de la primera, con una nueva función, que utilizaremos para volver a multiplicar el parámetro que hemos pasado a través del atributo por el resultado de la función “multxnum()” de la clase padre.

Veamos el ejemplo:

La herencia podrá tener varias capas de profundidad. Podríamos declarar una tercera clase, que ampliará las funcionalidades de la segunda, y por lo tanto, heredará de la primera y de la segunda todos sus atributos y métodos.

Sobrescritura de clases

En el capítulo anterior, hemos visto cómo crear una subclase, que además de heredar los atributos y métodos de la superclase, define los suyos propios. Pero en ocasiones puede que nos interese volver a declarar los mismos atributos y métodos con nuevas funcionalidades. A esta técnica se le llama sobrescritura o reemplazo de clases.

<?php
class ClaseEjemplo
{
var $mult = 2;
var $numinterno;
public function multxnum()
{
return $this->mult * $this->numinterno;
}
public function divxnum()
{
return $this->numinterno / $this->mult;
}
}
class ejemploHeredada extends ClaseEjemplo
{
function triple()
{
return ($this->multxnum() * $this->numinterno);
}
}
$miObjetoEjemplo = new ClaseEjemplo();
$miObjetoEjemplo->numinterno = 3;
$eldoble = $miObjetoEjemplo->multxnum();
echo "El valor doble es: .$eldoble" . "<br>";
$lamitad = $miObjetoEjemplo->divxnum();
echo "El valor dividido por 2 es: $lamitad" . "<br>";
$objetoHeredado = new ejemploHeredada();
$objetoHeredado->numinterno = 3;
echo "El valor por 3 es: " . $objetoHeredado->triple() . "<br>";

?>

Vamos a realizar una modificación en el ejemplo anterior de herencia, modificando la clase hija para que tenga el mismo atributo y el mismo método que la clase de la que hereda, pero con distinto valor y funcionabilidad para el método:

Como podemos observar, los cambios realizados a la subclase, no afectan para nada a la superclase. Una subclase heredará todos los atributos y operaciones de su superclase, a menos que les suministremos reemplazos.

<?php
class ClaseEjemplo
{
var $mult = 2;
var $numinterno;
public function multxnum()
{
return $this->mult * $this->numinterno;
}
public function divxnum()
{
return $this->numinterno / $this->mult;
}
}
class ejemploHeredada extends ClaseEjemplo
{
var $mult = 3;
function multxnum()
{
return $this->mult * $this->numinterno;
}
}
$miObjetoEjemplo = new ClaseEjemplo();
$miObjetoEjemplo->numinterno = 3;
$eldoble = $miObjetoEjemplo->multxnum();
echo "El valor doble es: .$eldoble" . "<br>";
$lamitad = $miObjetoEjemplo->divxnum();
echo "El valor dividido por 2 es: $lamitad" . "<br>";
$objetoHeredado = new ejemploHeredada();
$objetoHeredado->numinterno = 3;
echo "El valor por 3 es: " . $objetoHeredado->multxnum() .
"<br>";

No obstante, a partir de PHP5 tenemos la posibilidad de evitar que una función pueda ser reemplazada en una supuesta herencia. Para ello, será necesario añadir en nuestra primera clase a la función que no queremos que se reemplace, la palabra reservada “final”. Quedaría de la siguiente forma:

Si en la segunda clase que va a heredar la primera, intentáramos reemplazar la función “multxnum()”, tendríamos un error como el siguiente:

De la misma forma que podemos evitar que los métodos sean reemplazados, podremos realizar lo mismo con las clases. Si no queremos que la primera clase sea heredada, podríamos también utilizar la instrucción “final” por delante del nombre de la clase para evitarlo:

Si intentáramos definir la segunda clase para que herede de la primera, tendríamos un error como el siguiente:

Fatal error: Class dobleheredada may not inherit from final class (doble) in producto.php…

<?php
final class doble
{
var $mult=2;
var $numinterno;
public function multxnum()
….
Fatal error: Cannot override final method doble::multxnum() producto.
php….
final function multxnum()
{
return $this->mult*$this->numinterno;
}

Polimorfismo

Seguramente, el polimorfismo es la característica más importante en la programación orientada a objetos.

Como es un concepto algo complejo, mejor vamos a explicarlo de la manera más práctica que podamos. El ejemplo que vamos a simular a continuación, es el que vamos a aprovechar para realizar un ejercicio final de programación orientada a objetos, como resumen de los visto anteriormente.

Imaginemos que tenemos la posibilidad de poder comprar dos coches: un Seat y un BMW. Ambos tienen una característica en común: avanzar. Ambos coches podrán “avanzar”, pero cada uno lo hará de distintas maneras. Uno lo hará más rápido, y por lo tanto, consumirá más, y otro lo hará más lento y consumiendo menos.

Bien, pues mediante el polimorfismo, podemos para un mismo nombre de método, representar un código diferente en clases distintas. O dicho de otra manera, clases diferentes tendrán un comportamiento distinto, para el mismo método.

En el siguiente capítulo vamos a realizar un ejercicio completo donde podremos ver la forma de trabajo mediante polimorfismo.

Ejemplo explicado

En el siguiente ejemplo vamos a utilizar las siguientes clases:

Vehiculo. Será la clase padre de las subclases “Seat” y “BMW”. Define un atributo protegido $Gasoil, es decir, podrá ser heredado, pero no podrá ser modificado desde el exterior de una clase. Este atributo es inicializado al valor 80 litros, y será el valor devuelto por el método getGasoil().

BMW. Subclase hija de “Vehículo” que añade un nuevo método denominado avanzar() y en el cual utilizará el atributo heredado “Gasoil” para indicar que su forma de “avanzar” es descontando 10 litros cada vez. Esta será la diferente forma de actuar de un mismo método en diferentes clases, en este caso, diferente a la subclase “Seat”. Esta es la base del polimorfismo.

Seat. Subclase hija de “Vehículo” que añade también un nuevo método denominado avanzar() y en el cual utilizará el atributo heredado “Gasoil” para indicar que su forma de “avanzar”, que esta vez será descontando 5 litros cada vez. Volvemos a comentar lo mismo. Mismo método, distinto resultado.

Conductor. Define un atributo privado $vehiculo, que por lo tanto, ni podrá ser heredado ni accesible desde el exterior de la clase. Este atributo servirá para almacenar un objeto que será pasado a la clase en la creación del objeto mediante su método constructor.

Dicho de otra forma, cuando instanciemos la clase desde el programa principal para crear un nuevo objeto, le pasaremos como parámetro un objeto. Como podemos ver en el ejemplo, los objetos que se pasarán a esta clase, serán objetos de las subclases “Seat” y “BMW”, y por lo tanto, en el interior de esta clase, al almacenarlos en el atributo $vehiculo, podremos hacer uso del método “avanzar()” original de estas subclases, y el método “getGasoil()”, original de la clase padre “Vehiculo”.

En la zona de ejecución del programa, podremos ver como instanciamos las subclases “Seat” y “BMW”, en los objetos $conductor1 y $conductor2 respectivamente para poder utilizarlas.

Para cada una de ellas, utilizaremos el método “avanzarVehiculo()” para poder comprobar cómo consume gasoil cada uno de ellos.

Al ejecutar estos métodos, el valor del atributo $Gasoil disminuye de forma diferente en cada objeto, lo cual podemos comprobarlo al mostrarlo por pantalla mediante el método $conductor1->Gasoil() y $conductor2>Gasoil().

A continuación, el código del ejemplo comentado:.

<?php
class Vehiculo
{
protected $Gasoil = 80; //Comienza con 80 litros de Gasoil
public function getGasoil()
{
return $this->Gasoil;
}
}
class BMW extends Vehiculo
{
public function avanzar()
{
$this->Gasoil -= 10;
}
}
class Seat extends Vehiculo
{
public function avanzar()
{
$this->Gasoil -= 5;
}
}
class conductor
{
private $vehiculo;
function __construct($objeto)
{
$this->vehiculo = $objeto;
}
public function avanzarVehiculo()
{
$this->vehiculo->avanzar();
}
public function Gasoil()
{
return $this->vehiculo->getGasoil();
}
}
$conductor1 = new conductor (new Seat);
$conductor1->avanzarVehiculo();
$conductor2 = new conductor (new BMW);
$conductor2->avanzarVehiculo();

// Al Seat del conductor 1 le quedan 75 litros de Gasoil

echo "Al Seat del conductor 1 le quedan " . $conductor1->Gasoil() . " litros de Gasoil <br>";

// Al BMW del conductor 1 le quedan 75 litros de Gasoil

echo "Al BMW del conductor 2 le quedan " . $conductor2->Gasoil() . " litros de Gasoil <br>";