« Volver al listado completo

Programación web con PHP

Módulo 2.4: Programación orientada a objetos

Hasta ahora hemos visto elementos de programación imperativa, donde las instrucciones se ejecutan secuencialmente a menos que se utilicen instrucciones de flujo de control que alteren esta secuencia.

PHP admite también el paradigma de la programación orientada a objetos, aunque de una forma más limitada a otros lenguajes como Java, C# o C++.

Muchas de las funciones de librería de PHP tiene una contrapartida equivalente pero mediante la orientación a objetos. Utilizaremos primero objetos de estas librerías para aprender su uso, para luego definir los nuestros propios.

Objetos y clases

El objetivo de la programación orientada a objetos es definir en el objeto, este nuevo tipo de dato, todas las operaciones y variables internas que pueda necesitar para operar. De esta forma queda configurado de forma compacta en una manera que las principales acciones a realizar con el objeto están definidas sobre el propio objeto.

Una clase es la definición de las diferentes características de todos los objetos creados que corresponden a la misma clase. Define sus propiedades iniciales, y el código de los métodos que posee (funciones sobre el objeto).

Al crear un objeto, existe un método especial el la clase llamado constructor que permite especificar ciertos parámetros de incialización del mismo.

Todo esto quedará mejor explicado en los sucesivos ejemplos que vamos a ir viendo.

Creación y uso de objetos

La directiva new se utiliza para instanciar un nuevo objeto de cierta clase (para crear el objeto de esa clase), de la siguiente forma:

//Creamos un nuevo objeto de clase “Date”
//pasando por parámetro al constructor una cadena que especifica
//una fecha concreta
$mifecha = new Date(2015-03-30);

Al especificar la clase del objeto tras new, podemos abrir y cerrar paréntesis sin especificar nada más, o pasar uno o más argumentos por parámetros a lo que llamamos el constructor del objeto, que los utilizará para configurarlo.

Una vez tenemos un objeto creado, podemos acceder mediante el operador -> a las propiedades (variables del objeto), o métodos (funciones del objeto).

Clases, métodos, visibilidad

Una clase define un tipo de objeto que contiene ciertas propiedades, que son sus métodos (funciones del objeto) y atributos (variables del objeto).

Las propiedades (tanto métodos como atributos) tienen definida cierta visibilidad, que indica si son accesibles solo por propiedades internas del objeto, o pueden ser accedidos desde el exterior.

A nivel sintáctico, definimos una clase mediante la expresión class, seguida entre corchetes por sus propiedades. Estas se definen como se haría para variables o funciones cualquiera, anteponiendo de manera opcional la visilibidad de las mismas.

La visibilidad puede ser:

Al establecer los atributos, es posible definir unos valores por defecto que será los que tenga el objeto cuando sea creado.

Al instaciar un nuevo objeto de cierta clase, si existe se llama a un método especial con el nombre __construct, al que se le pasan todos los parámetros utilizados durante la creación del mismo.

Una vez instanciado un nuevo objeto mediante new, es posible acceder a sus propiedades públicas mediante el operador flecha ->.

Dentro del código de un objeto, podemos referirnos al propio objeto mediante la variable especial $this.

class MiClase {
    var $atributoDefecto = 'Por defecto';
    public $atributoPublico = 'Público';
    protected $atributoProtegido = 'Privado';
    function metodoDefecto() {
        echo "Método con visibilidad por defecto";
    }
    public function metodoPublico() {
        echo "Método con visibilidad por defecto";
    }
    protected function metodoProtegido() {
        echo "Método con visibilidad por defecto";
    }
    private function metodoPrivado() {
        echo "Método con visibilidad por defecto";
    }
    public function metodoEjemplo() {
        echo $this->metodoProtegido();
        $this->metodoPrivado();
    }
}

$miObjeto = new MiClase();
$miObjeto->metodoEjemplo();
$miObjeto->atributoPublico = 'Publico2';
echo $miObjeto->atributoPublico;

Clases y métodos estáticos

Las propiedades convencionales operan en el ámbito del objeto una vez éste ha sido instanciado. Es posible crear otras propiedades que tiene un ámbito de aplicación de la clase en general, es decir, todos los objetos referencias a los mismos valores de estas propiedades.

De esta manera, un objeto puede acceder a una propiedad estática (común a todos los objetos de la misma clase), pero una propiedad estática no puede acceder a los del objeto.

Las propiedades estáticas se definen mediante la palabra reservada static, y se accede a ellas mediante el operador ::$ aplicados sobre la palabra reservada self o el nombre de la clase.

Las mismas consideraciones de visibilidad son aplicables a las propiedades estáticas.

class EjemploEstatico {
    static public $miVariableEstatica = 'Estática';
    public $miVariable = 'No estática';
    static public function miFuncionEstatica() {
        echo self::$miVariableEstatica;
    }
    public function ejemplo() {
        echo $this->miVariable.'<br>';
        echo self::miFuncionEstatica();
    }
}

Herencia

Una de las características más importantes de la programación orientada a objetos es la de la herencia. Ésta consiste en que, cuando una clase hereda a otra, obtiene por defecto todas sus propiedades (métodos y atributos), pudiendo definir algunas adicionales. Esto se expresa mediante la palabra clave extends y el nombre de la clase padre, al definir una clase hija que hereda de ésta.

Las propiedades que tengan la visibilidad de privada no serán accesibles desde esta nueva clase, aunque seguirán existiendo dentro del objeto.

Esto nos permite definir inicialmente unas clases más genéricas, que sirvan de base para muchas otras clases más específicas que compartan ese código en común.

En cualquier momento podemos sobreescribir las propiedades del método padre, así como acceder directamente a las mismas mediante la palabra reservada parent.

class HTMLTag {
    public $tag = 'input';
    public $attributes = array();
    public function __toString() {
        $result = '<'.$tag.' ';
        foreach($attributes as $key => $value ) {
            $result .= $key .'="'.$value.'" ';
        }
        $result .= '\>';
        return $result;
    }
    public function setAttribute($attributeName, $value) {
        $this->attributes[$attributeName] = $value;
    }
}

class HTMLInputText extends HTMLTag {
    public $tag = 'input';
    public $attributes = array( 'type'=>'text');
    public function __toString() {
        $result = parent::__toString();
        $result .= '</input>';
    }
}

Clases abstractas, interfaces

Al definir unas clases que son utilizadas por otras, podemos establecer que una de ellas es abstracta, queriendo siginificar que solo es utilizada como base para definir otras clases, y que no puede ser instanciada directamente.

También podemos definir una función como abstracta, indicando únicamente su signatura (visibilidad, nombre y parámetros). Con esto establecemos que cualquier clase hija de ésta, debe concretizar el código de dicha función, o de lo contrario seguir siendo considerada abstracta.

Por último podemos definir interfaces, que son una especie de definición de métodos de clase. Si una clase incluye todos los métodos de una interfaz según su signatura, entonces se dice que implementa dicha interfaz, y un objeto esa clase puede ser admitida siempre que se esté pidiendo un objeto con un interfaz dado. Nótese que la clase en realidad puede tener definida muchas más propiedades, y que puede implementar más de una interfaz.

En la definición de la clase (después de indicar a qué clase hereda si es que es hija de alguna) definiremos mediante la palabra reservada implements los interfaces que la clase implementa separados por comas.

interface Printable {
    function __toString();
}

interface Atributable {
    function setAttribute($attributeName, $value);
}
abstract class HTMLInputAbstract extends HTMLTag implements Printable, Atributable {
    abstract function setAttribute($attributeName, $value);
    public $tag = 'input';
}
class HTMLInputCheckbox extends HTMLTag implements Printable, Atributable {
    public $attributes = array( 'type'=>'checkbox');
}

Cargador de clases

Antes de instanciar una clase, es preciso que se haya cargado su definición. Esto puede hacerse de manera manual incluyento todos los include o require necesarios.

A partir de PHP5 tenemos la posibilidad de definir una función que será llamada cuando se intente instanciar una clase no definida, de manera que nos de la oportunidad de buscar mediante alguna convención de uso el fichero, y cargarlo en memoria automáticamente.

Esto podemos hacerlo llamando a la función __autoload (este mecanismo quedará obsoleto en el futuro) o especificando el nombre de la función con spl_autoload_register.

function __autoload($nombre_clase) {  
    include $nombre_clase . '.class.php';  
}
function mi_autocargador($clase) {  
    include $clase . '.class.php';  
}  
spl_autoload_register('mi_autocargador');

Introducción a patrones. Singleton, factoría, iteradores

La programación orientada a objetos, además de permitir enpcapsular los datos relacionados y las operaciones que trabajan sobre ellos, permite la construcción de ciertos esquemas complejos de uso común en complejos fuertemente basados en este paradigma.

Estos esquemas se denominan patrones de diseño, y aunque su conocimiento y utilización es un concepto avanzado, intentaremos dar aquí unos ejemplos de alguno de ellos.

Singleton: Este patrón implica que al crear un nuevo objeto de una clase que sigue el patrón singleton.

class Singleton { 
    private static $instancia = null; 
    private $contador; 
    private function __construct() { 
        echo "Creación " . __CLASS__ . "<br>"; 
        $this->contador=0; 
    } 
    public static function getInstance() { 
        if ( self::$instancia == null ) { 
            self::$instancia = new self;
        }
        return self::$instancia;
    } 
    public function incrementar() { return ++$this->contador; } 
    public function disminuir() { return --$this->contador; } 
}

Factoría: Es una clase que genera diferentes clases de objetos que cumplen todos el mismo interfaz.

class HTMLTagFactory {
    public function create($type) {
        switch($type) {
            case 'inputText':
                return new HTMLInputText();
            break;
            case 'checkbox':
                return new HTMLInputCheckbox();
            break;
            case 'select':
                return new HTMLSelect();
            break;
        }
    }
}

Iterador: Es un interfaz que se puede implementar con una clase para que recorra todos los elementos de una colección, potencialmente realizando una operación sobre ellos.

abstract class Iterator implements Traversable {
    abstract public function current();
    abstract public function key();
    abstract public function next();
    abstract public function rewind();
    abstract public function valid();
}

PHP ya ofrece implementados muchos iteradores para muchos tipos distintos de colecciones de elementos, ver referencia: http://php.net/manual/es/spl.iterators.php


Siguiente »