POO en PHP

Aunque PHP permitía definir objetos ya en su versión 4.0, no es hasta la versión 5.0 cuando el lenguaje empieza a potenciar este tipo de programación. El motor se reescribió por completo, realizando cambios sustanciales para usar este tipo de programación, aproximándose a otros lenguajes como Java o C#. Estos cambios importantes hicieron que la portabilidad de PHP 4 a PHP 5 no fuera inmediata en según que casos.

Antes de empezar a definir clases debemos tener claro, como resultado de la modelización, que atributos va a tener y de que acciones o métodos vamos a disponer para modificar el estado de un objeto creado en memoria, incluyendo propiedades con sus métodos de acceso (getters) y métodos de modificación (setters).

Definición de clases

Disponemos de la palabra reservada **class** y deberemos realizar la definición dentro del mismo bloque ****.

Al declarar los atributos no podremos inicializarlos a valores no constantes, sólo se permitirá en PHP inicializaciones a arrays con valores constantes.

Ningún nombre de atributo o método debe empezar con dos caracteres de subrayado '__', ya que entrarían en conflicto con los métodos "mágicos" que veremos posteriormente.

Veamos un ejemplo:

<?php
   class NombreClase
   {
      // Atributos
      var $Atributo1 = CONSTANTE1; //PERMITIDO
      var $Atributo2 = array('TEXTO1', 'TEXTO2'); //PERMITIDO
      var $Atributo3 = $variable; //NO PERMITIDO
      // Métodos
      function    Metodo1()
      {
         ;
      }
?> // NO PERMITIDO. No se permite dividir la definición. No tiene sentido.
<?php
      function __Metodo2() // No recomendable.
      {
         ;
      }
   }
?>

Encapsulación

Dispondremos, como en otros lenguajes, de los modificadores **public**, **private** y **protected**, aplicables a atributos y métodos e indicándonos el nivel de acceso a los mismos.

Vamos a ver cada uno de ellos:

  • public : Es accesible desde fuera de la clase. Podemos usar pascal casing en identificador para indicar que son públicos.

  • private: Sólo es accesible desde dentro del ámbito de la propia clase.Podemos usar un subrayado o camel casing para indicar que son privados.

  • protected: No es accesible desde fuera de la clase, pero si desde una clase derivada o subclase de la misma.

PHP5 asume que la palabra reservada **var** es equivalente a public.Para respetar el principio de encapsulación definiremos los atributos colocando directamente el modificador de acceso.

<?php
   class NombreClase
   {
      public $Atributo1; //pascal casing
      protected $atributo2
      private $_atributo3; //camel casing

      public function Metodo1() {
         ;
      }

      protected function Metodo2() {
         ;
      }
   }
?>

Construir y crear una instancia de un objeto en memoria

Para crear una instancia de un objeto utilizaremos el operador new con la siguiente sintaxis:

$objeto = new NombreClase([parámetros]);

Normalmente todas las clases contienen un método que es invocado cuando se crea el objeto en memoria. Este método se suele llamar **constructor**. Este método contiene fragmentos de código que sirven para inicializar un objeto a un estado determinado.

Una clase puede carecer de constructor, pero esto no es lo más habitual. Normalmente todas nuestras clases llevarán constructor y suelen seguir el esquema siguiente:

    // Este método es un constructor porque tiene el mismo nombre que la clase
    public MismoNombreQueLaClase (parametro1, parametro2…, parametron ) {
        propiedad1 = valor o parámetro1;
        propiedad2 = valor o parámetro2;
        .
        .
        propiedadn = valor o parámetro2;
    }

Método constructor en PHP

En versiones anteriores a la versión 5 de PHP (4 e inferiores), un método se convertía en constructor cuando tenía el mismo nombre que la clase en donde era definida.

PHP 5 introdujo una nueva forma de definir constructores a través de un método especial denominado **__construct([parámetros])**, aunque a la hora de crear una instancia con **new** seguiremos poniendo el nombre de la clase.

Según el manual de PHP, se le puso ese nombre especial para simplificar las llamadas al constructor de la superclase en casos de herencia. Como veremos más adelante, tenemos varias formas de invocar al constructor de la subclase que estamos heredando pero la forma recomendada es la siguiente: **parent::__construct(...)** y así no tener problemas en casos de cambio de nombre o en la jerarquía.

Una vez definidos una serie de atributos tenemos que darles valor en el constructor o desde algún método que actúe como interfaz de objeto.

Acceso a atributos de una clase desde el "exterior" (interfaz) y desde el interior

Según el principio de encapsulamiento: "los objetos nos deben permitir elegir qué información es publicada y qué información es ocultada al resto de los objetos o partes del programa. Para ello los objetos suelen presentar sus métodos como interfaces públicas y sus atributos como datos privados e inaccesibles desde otros objetos." Un parte de este principio se resuelve con los modificadores de acceso **public**, **private** y **protected**.

Tal y como hemos comentado en una de las definiciones de clases en PHP: "[..] dentro de una clase codificaremos el comportamiento de dicha clase.". De esto deducimos que una clase tendrá métodos que actúan de interfaz de objeto con código para modificar el comportamiento de un atributo, devolver su valor o incluso, tal y como hemos visto en el esquema del constructor, inicializar el valor de varios atributos.
En todos los lenguajes de programación que soportan programación orientada a objetos se suelen diferenciar dos modos de ejecución: fuera o dentro de la clase.

Acceso a atributos "fuera" de la clase

Para acceder a los atributos y a los métodos de una clase utilizaremos el operador **'->'**. Todo depende del modificador de acceso que hayamos establecido tanto en la declaración de los atributos como de los métodos. Normalmente los atributos se declararán con el tipo **protected** o **private** (para hacer cumplir el principio de encapsulación) y los métodos como **public** o **protected**.

Utilizaremos la sintaxis siguiente: (nombre_del_objeto) -> metodo o atributo

$objeto->nombreAtributo

$objeto->mostrar()

Veamos un ejemplo sencillo:

<?php
class NombreClase
{
   public $Atributo = "Mi nombre es atributo";

   function mostrar()
   {
      echo "Código ejecutado dentro de la clase: NombreClase<br />";
   }
} //aquí acaba la definición de la clase

//Trabajaremos con objetos que serán instancias de una clase
//Creamos un objeto con el operador new.
$objeto = new NombreClase();

// Llamamos al método Mostrar()
$objeto->Mostrar(); //Código ejecutado dentro de la clase: NombreClase<br />

echo $objeto->atributo; //Mi nombre es atributo

?>

Hay que tener cuidado al utilizar la sintaxis:

$objeto->NombreAtributo //PERMITIDO

y no

$objeto->$nombreAtributo //NO RECOMENDABLE

pues en este segundo caso sustituirá la variable $nombreAtributo por una cadena que represente su valor y si no esta definida por la cadena vacía. Si lo que buscamos es hacer esta sustitución a propósito, **$nombreAtributo** deberá contener como valor el identificador de algún atributo válido de la clase. Esta última práctica no es muy recomendable pues puede dar lugar a código ofuscado.

Acceso a atributos "dentro" de la clase

Si queremos referenciar un atributo desde algún método de la propia clase, incluido el constructor, utilizaremos la variable **$this** que representará al objeto dentro de la clase.

Siempre que estemos dentro de un método de un objeto, PHP automáticamente establece en la variable $this la referencia al objeto usted no tiene que hacer cualquier cosa para tener acceso a ella.

Utilizaremos igualmente el operador **'->'** pero en vez de colocar el nombre del objeto referenciado colocaremos la variable $this. Veamos un ejemplo completo:

<?php
class NombreClase
{
   public $atributo = "Mi nombre es atributo";

   function Mostrar()
   {
      echo "Código ejecutado dentro de la clase: NombreClase<br />";
      echo 'La variable $atributo tiene el siguiente valor: '. $this->atributo; . '<br />';
   }

}
//Trabajaremos con objetos que serán instancias de una clase
//Creamos un objeto con el operador new.
$objeto = new NombreClase();

// Llamamos al método Mostrar() para cada una de las instancias.
$objeto->Mostrar();

// Permitido pero no recomendable.
// Es recomendable utilizar un setter (método que actualiza el valor de un atributo)
$nombreAtributo = 'atributo';
echo $objeto->$nombreAtributo.'<br>'; //Mi nombre es atributo

// Es mejor utilizar un getter (método que devuelva al valor de un atributo)
echo $objeto->atributo.'<br>'; //Mi nombre es atributo
?>

Veamos un ejemplo más completo donde tenemos un atributo público, usamos el constructor por defecto de PHP __construct(), un método público mostrar() y además hacemos uso de la palabra reservada $this. El código sería el siguiente:

<?php
class NombreClase
{
   public $atributo;

   // Constructor. Tiene el mismo nombre que la clase.
   function __construct($param = 'defecto')
   {
      // Accedemos a la propiedad a través de $this.
      $this->atributo = $param;
   }

   function mostrar()
   {
      echo '$this->atributo = ' .
            $this->atributo . '<br>';
   }
} //aquí acaba la definición de la clase

//Trabajaremos con objetos que serán instancias de una clase
//Creamos un objeto con el operador new.
$objeto1 = new NombreClase();
$objeto2 = new NombreClase('objeto2');

// Llamamos al método Mostrar() para cada una de las instancias.
$objeto1->mostrar();
$objeto2->mostrar();

// Permitido pero no recomendable.
// Es recomendable utilizar un setter
// (método que actualiza el valor de un atributo)
$nombreAtributo = 'atributo';
echo $objeto1->$nombreAtributo.'<br>';

// Es mejor utilizar un getter
// (método que devuelva al valor de un atributo)
echo $objeto2->atributo.'<br>';
?>

**¿Se cumple el principio de encapsulación en la clase anterior?**

No porque la propiedad **atributo** de la clase "NombreClase" permite acceder y modificar el valor de esa propiedad. Para resolver este problema se recomienda declarar la propiedad "atributo" como protegida y crear dos métodos públicos getter (getAtributo) y setter (setAtributo) como interfaces de acceso a esa propiedad.

Por último y como recomendación, los métodos no suelen imprimir por pantalla ningún tipo de código sino que suelen devolver cadenas y que esa el usuario el que imprima dicha cadena. Para resolver esa recomendación cambiaremos el código del método Mostrar() y en vez de echo haremos un return de la cadena.

Veamos el resultado final:

<?php
class NombreClase
{
   private $_atributo;

   // Constructor. Tiene el mismo nombre que la clase.
   function __construct($param = 'defecto')
   {
      // Accedemos a la propiedad a través de $this.
      $this->_atributo = $param;
   }

   function getAtributo()
   {
      return $this->_atributo;
   }

   function setAtributo($param)
   {
      $this->_atributo = $param;
   }

   function mostrar()
   {
      return '$this->_atributo = ' .
             $this->_atributo . '<br>';
   }
}

//Trabajaremos con objetos que serán instancias de una clase
//Creamos un objeto con el operador new.
$objeto1 = new NombreClase();
$objeto2 = new NombreClase('objeto2');

// Llamamos al método Mostrar() para cada una de las instancias.
echo $objeto1->mostrar();
echo $objeto2->mostrar();

echo $objeto1->getAtributo() . '<br>';

$objeto2->setAtributo('nuevo valor');
echo $objeto2->getAtributo() . '<br>';

$nombreAtributo = '_atributo';

$objeto2->_atributo = "Error fatal"; //esto provocará un error fatal
$objeto1->$nombreAtributo = "Error fatal"; //esto provocará un error fatal

?>

Herencia

PHP proporciona la palabra reservada **extends** para implementar la herencia.Esta sería la sintaxis:

class B extends A {
   ...
}

En este caso la subclase **B** hereda de la superclase **A**. La herencia en PHP es simple como en otros lenguajes tipo Java o C#, de esta manera se evitan diseños demasiado ofuscados además de conflictos de nombres por transitividad en la jerarquía.

Deberemos tener en cuenta respecto a otros lenguajes, que los constructores son heredados por la clase derivada y no son llamados automáticamente desde el constructor de dicha clase derivada.

**¿Qué sucede si en la subclase B tenemos un método MetodoX con el mismo nombre que en la superclase A?**

Al llamarlo desde la subclase se ejecutará el código del método de la subclase.

Entonces, **¿Cómo podemos ejecutar el código de MetodoX de la clase base A?**

Si llamamos al método tal cual: $this->MetodoX(), estaremos realizando una llamada recursiva. Para solucionar esto utilizaremos el operador '**::**' (similar al operador de resolución de ámbito utilizado en C++) de la siguiente forma:

A::MetodoX()

De esta manera podremos referenciar a cualquier método con el mismo nombre en una clase antecesora. Incluso podremos utilizar la forma **B::MetodoX()** indicando que estamos llamando al método en el ámbito de la propia subclase, como forma alternativa pero menos aconsejable que la sintaxis $this->MetodoX();

En lugar de usar el nombre literal de la clase base en su código, se debería usar la palabra reservada **parent**, el cual hace referencia al nombre de su clase base tal y como se realiza en la declaración **extends** de su clase. Al hacer esto, evitamos usar el nombre de su clase base en más de un lugar. Llegado el caso de que nuestro árbol de jerarquía cambie durante la implementación, el cambio se puede efectuar con facilidad simplemente modificando la declaración extends de su clase.

Si tenemos este ejemplo:

class A {
   function MetodoX() {;}
}
class B extends A {
   function B(){
      parent::MetodoX(); //similar a -> A::MetodoX()
   }
}

Como vemos en el código anterior, podemos usar el nombre de la clase junto con los dos puntos para hacer referencia de un método de la clase padre. Aunque es mucho más recomendable usar la palabra **parent** para hacer referencia a la clase y no usar el nombre de la misma. Esta segunda forma es mucho más tolerante a cambios aunque menos autoexplicativa.

Respecto a la herencia, PHP aporta dos nuevas palabras reservadas: **final** y **self**.

  • final: aplicada a una clase, indicará que no se puede heredar de la misma y aplicada a un método indicaremos que es un método no reemplazable en las subclases.

  • self: Al igual que parent podemos usarlo junto con el operador de resolución "::" para hacer referencia a un método de la clase "padre". La palabra self se suele utilizar para hacer referencia a un método de la propia clase sin tener que indicar el nombre de dicha clase y para cuando no podemos utilizar la variable $this dentro de la clase, ya que, la clase contiene atributos o métodos estáticos.

Ejemplos:

Antes de continuar con el siguiente punto vamos a ver una nueva característica introducida por PHP que nos permite cambiar la funcionalidad interna de las clases a través de la implementación de ciertos métodos. A estos nuevos métodos se les llama métodos mágicos.

<?php
class NombreClase
{
   private $_atributo = 'defecto';

   function getAtributo()
   {
      return $this->_atributo;
   }

   function mostrar()
   {
      return 'self::getAtributo() = ' .
             self::getAtributo(). '<br>';;
   }
}

$objeto = new NombreClase();

echo $objeto->mostrar(); //self::getAtributo() = defecto

?>

Qué y cuales son los métodos mágicos

Los métodos mágicos, son métodos que son llamados automáticamente por PHP ante determinadas situaciones. Se caracterizan porque su nombre empieza por "__" (dos guiones bajos).

Los métodos mágicos (así se les llama en el manual) son estos:

  • __construct()
  • __destruct()
  • __toString()
  • __clone()
  • __get()
  • __set()
  • __call()
  • __autoload()
  • __isset()
  • __unset()
  • __sleep()
  • __wakeup()

Cada uno de estos métodos es llamado por PHP cuando cierto "evento" ocurre (este evento depende de cual método estemos hablando). Al implementar un método de estos, nos aseguramos de que cierto código se ejecute cuando el evento (llamada) en cuestión ocurra.

Podríamos decir que sería como sobrescribir algunos métodos de la clase Object de Java, de manera que cambiamos la funcionalidad interna del objeto. Por ejemplo, si sobrescribimos el método toString() o finalize().

Por ejemplo, el método __destruct() es llamado cuando un objeto es destruido. Al implementarlo, nos aseguramos de que PHP, al destruir un objeto ejecute cierto código (obviamente este "cierto código" es la implementación que le demos al método). Un ejemplo sería el tener dentro de una clase un array de objetos. En la segunda parte de la sesión de programación orientada a objetos, veremos un ejemplo más detallado de este método.

Destrucción de objetos

En cuanto a la destrucción de objetos, PHP implementa una especie de recolector de basura (GC) similar a los de Java o C# el cual elimina de la memoria aquellos objetos que han quedado "huérfanos" por no tener ningún tipo de referencia.

Como hemos visto en el apartado anterior, PHP introduce un método especial denominado __destruct(), el cual será llamado tan pronto como todas las referencias a un objeto en particular sean borradas o cuando el objeto sea explícitamente destruido. Esto sirve para liberar recursos en aquellos objetos que hagan un gran acopio de los mismos y vayan a dejar de ser utilizados, puesto que el GC puede no liberarlos de forma inmediata, ni liberará tampoco aquellos objetos referenciados desde el objeto "huérfano".

Veamos un ejemplo donde la propiedad referencia a un objeto:

<?php
   class A {
      private $dato;
      private $datoClaseB;

      public function __construct($dato, $datoClaseB) {
         $this->dato = $dato;
         $this->datoClaseB = $datoClaseB;
      }

      public function __destruct() {
         $this->datoClaseB = null
      }
   }

   class B {
      private $dato;

      public function __construct($dato) {
         $this->dato = $dato;
      }
   }
   $instanciaB = new B(100);
   $instanciaA = new A(200, $instanciaB);
   $instanciaA->__destruct();
?>

Convertir un objeto a cadena

Otra de los métodos mágicos interesantes es el método especial denominado **__toString()** que devolverá una cadena representando el estado actual de un objeto.

Si en una clase sobrescribimos el método, éste se invocará al intentar imprimir la clase por ejemplo, haciendo **echo $objeto**.

    <?php
       class Persona {
          protected $dni;
          protected $nombre;

          public function __construct($pDni, $pNombre) {
             //constructor
             echo ;
             $this->dni = $pDni;
             $this->nombre = $pNombre;
          }

          public function __toString()
          {
             $texto = 'DNI: '.$this->dni.'<br>';
             $texto .= 'Nombre: '. $this->nombre . '<br>';
             return $texto;
          }
       }
       $alumno = new Alumno('12345678','Pepe Pérez');
       echo $alumno; //Llamada implícita al método __toString()
    ?>

Al ejecutar ese código tendremos como resultado lo siguiente:

DNI:12345678
Nombre: Pepe Pérez

results matching ""

    No results matching ""