Tabla de Contenidos

04 - PHP POO I

A partir de PHP 5, el modelo de objetos ha sido reescrito para tener en cuenta un mejor rendimiento y una mejor funcionalidad. Este fue un cambio importante a partir de PHP 4. PHP 5 tiene un modelo de objetos completo.

Entre las características de PHP 5 están la inclusión de la visibilidad, clases y métodos abstractos y finales, métodos mágicos adicionales, interfaces, clonación y determinación de tipos.

PHP trata los objetos de la misma manera que las referencias o manejadores, lo que significa que cada variable contiene una referencia a un objeto en lugar de una copia de todo el objeto.

Elementos de la POO

Clases

La definición básica de una clase comienza con la palabra reservada class, seguida de un nombre de clase, y continuando con un par de llaves que encierran las definiciones de las constantes, propiedades y métodos pertenecientes a dicha clase.

class ClassName
{
    //Constantes
    public const BAR = "BAR";
    //Propiedades
    public $var = "valor predeterminado";
    //Métodos
    public function funcionClase() {
    }
}

En la definición de las constantes, propiedades o métodos tenemos que definir la visibilidad usando una de las palabras reservadas public, protected o private (según la visibilidad sea publica, protegida o privada).

Por defecto, la visibilidad será pública

Objetos

Para crear una instancia de una clase, se debe emplear la palabra reservada new seguida del nombre de la clase y dos paréntesis (en realidad se le pueden pasar valores a las clases dentro de esos paréntesis. Lo veremos más adelante cuando vemos los constructores).

Las clases deberían ser definidas antes de la instanciación (y en algunos casos esto es un requerimiento).

class ClaseA
{
    public $var1 = 5;
    public function hola() {
        echo "Hola mundo";
    }
}
$objeto1 = new ClaseA();

Una vez hemos creado el objeto, podemos acceder a sus propiedades y métodos mediante el operador flecha ().

$objeto1 = new ClaseA();
echo($objeto1->var1);
$objeto1->hola();

La pseudovariable $this

La pseudovariable $this está disponible cuando un método es invocado dentro del contexto de un objeto. $this es una referencia al objeto invocador (usualmente el objeto al cual el método pertenece, aunque puede que sea otro objeto si el método es llamado estáticamente desde el contexto de un objeto secundario).

class ClaseA
{
    public $var1 = 5;
    public function getVar1() {
        echo $this->var1;
    }
}
$objeto1 = new ClaseA();
$objeto1->getVar1();

En el ejemplo anterior, si queremos acceder a la variable $var1 dentro del método getVar1() debemos utilizar $this para indicarle a PHP que la variable que debe leer es la que está definida en la clase. Si cambiamos la línea por echo $var1 nos dará un error, ya que la variable $var1 no está definida en el método.

$this no sólo se utiliza para acceder a las propiedades de la clase, también lo usaríamos para ejecutar los métodos de la misma.

Constructores y destructores

A partir de PHP 5 se pueden declarar métodos constructores para las clases. Aquellas que tengan un método constructor lo invocarán en cada nuevo objeto creado, lo que lo hace idóneo para cualquier inicialización que el objeto pueda necesitar antes de ser usado.

Para declarar un constructor, utilizamos el método mágico _ _construct() (los métodos mágicos los provee PHP y nos permiten realizar ciertas tareas OO. Se identifican por el uso de dos guiones bajos _ _ como prefijo).

class ClaseA
{
    public $var1;
    public $var2;
    function __construct($var1, $var2) {
        $this->var1 = $var1;
        $this->var2 = $var2;
    }
}
$objeto1 = new ClaseA(5, 6);
echo "$objeto1->var1, $objeto1->var2";

También podemos definir métodos destructores con el método mágico _ _destruct().

class ClaseA
{
    public $var1;
    public $var2;
    function __construct($var1, $var2) {
        $this->var1 = $var1;
        $this->var2 = $var2;
    }

    function __destruct() {
        echo "Objeto destruido";
    }
}

El método destructor será llamado tan pronto como no hayan otras referencias a un objeto determinado, o en cualquier otra circunstancia de finalización.

Ésto último puede llevar a confusión. En el ejemplo anterior en ningún momento se borra el objeto ni se iguala a null ni nada parecido. Sin embargo se ejecutará su método destructor después de mostrar por pantalla el valor de las variables, ya que no hay más referencias al objeto en el código del programa.

Propiedades estáticas

Declarar propiedades estáticas las hacen accesibles sin la necesidad de instanciar una clase. Una propiedad estática no puede ser accedida con un objeto de clase instanciada (aunque un método estático si lo puede hacer).

Para definir una propiedad como estática, anteponemos la palabra static al nombre de la variable.

class ClaseA
{
    static public $var1 = 5;
}
echo ClaseA::$var1;

No se pueden acceder a las propiedades estáticas a través del objeto utilizando el operador flecha (). Para acceder al valor de la variable estática definida utilizamos el nombre de la clase seguido de doble dos puntos (::) y el nombre de la variable.

A las propiedades estáticas se puede acceder desde dentro de la clase utilizando la palabra reservada self seguido de doble dos puntos y el nombre de la variable.

class ClaseA
{
    static public $var1 = 5;
    public function getVar1() {
        echo self::$var1;
    }
}
$objeto1 = new ClaseA();
$objeto1->getVar1();

Métodos estáticos

Como ocurre con las propiedades, podemos declarar métodos estáticos en nuestras clases añadiendo la palabra reservada static a la declaración del método.

class ClaseA
{
    static public function hola() {
        echo "Hola mundo";
    }
}
ClaseA::hola();

Debido a que los métodos estáticos se pueden invocar sin tener creada una instancia del objeto, la pseudovariable $this no está disponible dentro de los métodos declarados como estáticos.

class ClaseA
{
    public $var1 = 5;
    static public function getVar1() {
        echo $this->var1;
    }
}
ClaseA::getVar1();

Para acceder a la variable anterior deberíamos utilizar self, aunque también deberíamos declarar la variable como estática, ya que si no seguiría dando error.

class ClaseA
{
    static public $var1 = 5;
    static public function getVar1() {
        echo self::$var1;
    }
}
ClaseA::getVar1();

Herencia

La herencia es un principio de programación bien establecido y PHP hace uso de él en su modelado de objetos. Este principio afectará la manera en que muchas clases y objetos se relacionan unas con otras.

Por ejemplo, cuando se extiende una clase, la subclase hereda todos los métodos públicos y protegidos de la clase padre. A menos que una clase sobrescriba esos métodos, mantendrán su funcionalidad original.

Para indicar que una clase hereda de otra, se utiliza la palabra reservada extends.

class ClaseA {
    public $var1 = 5;
}

class ClaseB extends ClaseA {
    public $var2 = 10;
}

$objeto1 = new ClaseB();
echo "Var1 = $objeto1->var1, Var2 = $objeto1->var2";

Una cosa a tener en cuenta cuando usamos herencia es que los constructores padres (y los destructores padres) no son llamados implícitamente si la clase hija define un constructor.

Si el hijo no define un constructor, entonces se puede heredar de la clase madre como un método de clase normal (si no fue declarada como privada).

Por ejemplo, si ejecutamos el siguiente código:

class ClaseA
{
    function __construct() {
        echo "Constructor de la clase A<br>";
    }
}

class ClaseB extends ClaseA
{
}

$objeto1 = new ClaseB();

La salida será:

Constructor de la clase A

Si definimos ahora un constructor en ClaseB:

class ClaseB extends ClaseA
{
    function __construct() {
        echo "Constructor de la clase B";
    }
}

Al ejecutra el archivo verás que sólo ejecuta el constructor de la clase B:

Constructor de la clase B

Para ejecutar un constructor padre, se require invocar a parent::_ _construct() desde el constructor hijo.

    function __construct() {
        parent::__construct();
        echo "Constructor de la clase B";
    }

Si compruebas la salida ahora, ves que al crear el objeto ya se ejecutan los dos constructores:

Constructor de la clase A
Constructor de la clase B

Abstracción de clases

A partir de PHP 5 podemos definir clases y métodos abstractos. Las clases definidas como abstractas no se pueden instanciar y cualquier clase que contiene al menos un método abstracto debe ser definida como tal. Los métodos definidos como abstractos simplemente declaran la firma del método, pero no pueden definir la implementación.

Cuando se hereda de una clase abstracta, todos los métodos definidos como abstractos en la declaración de la clase madre deben ser definidos en la clase hija (siempre que esta, a su vez, no sea abstracta); además, estos métodos deben ser definidos con la misma (o con una menos restrictiva) visibilidad.

Por ejemplo, si el método abstracto está definido como protegido, la implementación de la función debe ser definida como protegida o pública, pero nunca como privada.

Por otra parte, las firmas de los métodos tienen que coincidir, es decir, la declaración de tipos y el número de argumentos requeridos deben ser los mismos.

Por ejemplo, si la clase derivada define un argumento opcional y la firma del método abstracto no lo hace, no habría conflicto con la firma. Esto también se aplica a los constructores a partir de PHP 5.4. Antes de PHP 5.4, las firmas del constructor podían ser diferentes.

Para definir una clase (o método) como abstracta, se utiliza la palabra reservada abstract.

abstract class ClaseA
{
    abstract public function hola();
    public function hola2() {
        echo "Hola universo<br>";
    }
}

class ClaseB extends ClaseA
{
    public function hola() {
        echo "Hola mundo<br>";
    }
}

$objeto1 = new ClaseB();
$objeto1->hola();
$objeto1->hola2();

Interfaces

En PHP también podemos definir interfaces. Las interfaces se definen de la misma manera que una clase, aunque reemplazando la palabra reservada class por la palabra reservada interface y sin que ninguno de sus métodos tenga su contenido definido.

Todos los métodos declarados en una interfaz deben ser públicos, ya que ésta es la naturaleza de una interfaz.

Para implementar una interfaz, se utiliza el operador implements. Las clases pueden implementar más de una interfaz si se deseara, separándolas cada una por una coma.

interface Interface1 {
    public function hola();
    public function hola2();
}

class ClaseA implements Interface1 {
    public function hola() {
        echo "Hola mundo";
    }
    public function hola2() {
        echo "Hola universo";
    }
}

$objeto1 = new ClaseA();
$objeto1->hola();
$objeto1->hola2();

La palabra reservada final

PHP 5 introdujo la nueva palabra clave final, que impide que las clases hijas sobrescriban un método,. Si la propia clase se define como final, entonces no se podrá heredar de ella.

Ejercicios

Ejercicio 1

Crea una clase Empleado con 2 propiedades: nombre y sueldo. Implementa los getters y setters de las dos propiedades (ten en cuenta la visibilidad adecuada de las propiedades y métodos).

Crea dos empleados rellenando su nombre y sueldo, y haz que salga por pantalla la frase nombre_empleado tiene un sueldo de sueldo_empleado.

Ejercicio 2

Modifica el ejercicio anterior para que el nombre y el sueldo del empleado se rellenen en el momento en que se crea el empleado y no se pueda modificar después.

Ejercicio 3

Añade un método a la clase Empleado que devolverá un booleano indicando si el empleado tiene que pagar impuestos (si su sueldo es mayo que 1200) o no (si es menor).

Muestra la frase nombre_empleado tiene que pagar impuestos o nombre_empleado no tiene que pagar impuestos después de la frase nombre_empleado tiene un sueldo de sueldo_empleado.

Ejercicio 4

Modifica el ejercicio anterior para que el mostrar la frase de si tiene que pagar impuestos o no sea un método de la clase Empleado (diferente del método que calcula si tiene que pagar impuestos o no).

Comprueba que las visibilidades de los métodos son las correctas.

Ejercicio 5

Plantea una clase Calculadora que contenga cuatro métodos estáticos (sumar(), restar(), multiplicar() y dividir()) estos métodos reciben dos valores y calcularán la operación asociada.

Ejercicio 6

Modifica el ejercicio anterior para que el primero de los números sea una propiedad de la clase Calculadora y valga siempre 8.

Ejercicio 7

Modifica la clase Calculadora del ejercicio 5 para que los métodos no sean estáticos pero que puedan seguir llamándose de forma estática (Busca información sobre el método mágico callStatic).

Ejercicio 8

Crea una clase Mamifero con las propiedades especie, sonido y familia, y un constructor donde se le pasarán la especie y el sonido (pero no la familia) para rellenarlos. Además, tendrá un método sonido() donde se mostrará la frase Sonido de especie, de la familia familia: sonido.

Crea otras dos clases que hereden de Mamifero llamadas Perro y Gato. Ambas clases tendrán sólo un constructor donde se rellenará la familia a la que pertenece cada una (cánidos o felinos).

Comprueba que la aplicación funciona de forma correcta creando un perro y un gato y ejecutando su métodos sonido().

Ejercicio 9

Crea una clase Trabajador. Define como atributos su nombre y sueldo. La clase tendrá el método calcularSueldo().

Implementa la clase Empleado, que heredará de la anterior. Para calcular el sueldo tener en cuenta que se le paga 9.50 la hora.

Plantea otra clase Gerente que herede de la clase Trabajador. Para calcular el sueldo tener en cuenta que se le abona el sueldo base (2500) más un porcentaje del beneficio de la empresa (parámetro que se le pasará al constructor de la clase Gerente).

Crear los métodos necesarios en las clases y comprobar su funcionamiento.