Está en la página 1de 84

Primera edición

PHP Avanzado

Luis Miguel Cabezas Granado


Francisco José González Lozano
PHP Orientado a
Objetos

1
• Orientación a Objetos
• Interfaces y Clases
abstractas
• Espacios de nombres
• Rasgos
• Métodos mágicos
• Principios SOLID de la
orientación a objetos
Capítulo 1

PHP Orientado a Un desarrollo orientado a objetos pensaría en cada problema como una
identidad única, como un objeto. Puesto que el DNI siempre va asociado
a una persona, puede crear un objeto que identifique a personas, y a su

Objetos vez dentro de ese objeto programar una función (método en este caso)
que pueda calcular la Letra.

La aproximación procedural (crear una función) no asocia esa función a


personas directamente y le podría pasar cualquier número entero, reci-
biendo una respuesta válida. En cambio, la aproximación de objetos
Introducción
crea la función dentro del objeto cliente y sólo permitiría, mediante técni-
PHP comenzó su andadura como un simple lenguaje de scripts. A medi-
cas que veremos más adelante, recuperar la Letra del DNI de una perso-
da que las versiones avanzaban, se fueron incluyendo algunas caracte-
na.
rísticas que permitían programar con orientación a objetos. Con la apari-
ción de PHP 5, los desarrolladores tuvimos una verdadera plataforma de La programación orientada a objetos desarrolla nuevos conceptos y ter-
programación orientada a objetos, gracias a la potencia del nuevo engi- minologías que deben aprenderse correctamente para poder generar
ne Zend 2.0, que permitía ejecutar más rápido y eficientemente este tipo buen código.
de programas. PHP 5.5 y el motor Zend 2.0 mejora considerablemente
el rendimiento de las aplicaciones y añade algunas nuevas característi- Definición de Clases
cas al paradigma de la programación orientada a objetos. Una clase es un tipo de dato que contiene, en una misma estructura, va-
riables y funciones. Una clase es una especie de plantilla desde la que
Actualmente, en las librerías técnicas, es posible encontrar lbros y ma-
los objetos son instanciados y toman su valor. Desde una clase se pue-
nuales sobre lenguajes orientados a objetos como Java o C++.
den construir varios objetos del mismo tipo. La definición es la siguiente:
La programación orientada a objetos en PHP 5 no necesita una sintaxis class Persona {
muy diferente, sino que es una aproximación a la realidad en otro cami- public $nombre;
no diferente al acostumbrado. Por ejemplo, si piensa en un programa function getNombre() {
que tenga que averiguar la Letra del DNI de un cliente, lo normal es que return $this->nombre;
cree una función con un parámetro al que le pase el número de DNI y le }
devuelva la Letra buscada. }

2
Mediante la palabra reservada class definimos una clase com- La clase anterior tiene tres propiedades y seis métodos para interactuar
pleta. Las variables que se utilizaran dentro de la clase (propie- con ellas (setters y getters). Los métodos que empiezan por set (por con-
dades) se definen entre el nombre de la clase y los métodos. vención) serán utilizados para dar valor a una propiedad y los que empie-
zan por get para obtener ese valor.
Los métodos son las acciones que puede realizar una clase y se definen
con la palabra reservada function. Nota: El nombre de los métodos siguen la convención adoptada por
PHP CamelCase, esto quiere decir que los métodos que tengan pala-
class Persona { bras compuestas hay que escribirlos con todas las palabras juntas y la
public $nombre;
primera letra de cada palabra en mayúscula exceptuando la primera
public $apellidos;
(lowerCamelCase). Si un método es el encargado de copiar datos ban-
$dni;
carios tendría que escribirse más o menos así copiarDatosBancarios.
function setNombre($nombre) {

$this->nombre = $nombre;

function getNombre() { Instancia de la Clase


return $this->nombre; Para que el código funcione, necesita crear el objeto. Esto se hace utili-
} zando el operador new seguido del nombre de la clase:
function setApellidos($apellidos) {
$luisMiguel = new Persona();
$this->apellidos = $apellidos;

}
Con el código anterior hemos creado un objeto que contiene todas las
function getApellidos() { variables y funciones de la clase Persona. La variable que contiene ese
return $this->apellidos; objeto es $luisMiguel.
}
Para acceder a las funciones desde el nuevo objeto creado tenemos
function setDni($dni) {
que utilizar la variable que contiene al objeto, en este caso $luisMiguel
$this->dni = $dni;
seguido del operador ( -> ) y el nombre del método.
}

function getDni() {
Para trabajar correctamente, graba la definición de la clase en un archi-
return $this->dni;
vo llamado clases.php y crea un nuevo archivo llamado ejemplo.php
}
con el siguiente contenido:
}
<!DOCTYPE HTML>

3
<html> $this->nombre = $nombre;
<body> $this->apellidos = $apellidos;

<?php $this->dni = $dni;

require_once(“clases.php”); }

$luisMiguel = new Persona();

$luisMiguel->setNombre(“Luis Miguel”);
Nota: PHP reconoce el juego de caracteres UTF8 en el código por
$luisMiguel->setApellidos(“Cabezas Granado”);
eso es posible poner variables que contengan caracteres propios de
$luisMiguel->setDni(“08868143X”); una lengua como la ñ o la ç.
?>

<h1>
Ahora tenemos la posibilidad de añadir propiedades al objeto en la pro-
Datos de <?= $luisMiguel->getNombre()
pia creación.
. “ “ $luisMiguel = new Persona(“Luis Miguel”, “Cabezas Granado”, “08868143X”);
. $luisMiguel->getApellidos(); ?>

</h1>

DNI: <?= $luisMiguel->getDni(); ?> Herencia


</body> La programación orientada a objetos permite heredar de otras clases.
</html> Con esta técnica se puede ahorrar mucho tiempo de trabajo. La clase
Al ejecutar el ejemplo obtenemos los datos del usuario dentro de algu- hija (clase que hereda de otra) adquiere estas propiedades:
nas etiquetas HTML.
• Automáticamente obtiene todas las variables miembro de la clase pa-
Constructor dre.

En PHP existen algunos métodos especiales en la definición de una cla- • También obtiene todos los métodos miembro de la clase padre, que

se (métodos mágicos). El más importante es el constructor. Ésta se eje- funcionarán exactamente de la misma forma.
cuta cada vez que se crea un nuevo objeto y permite crear las variables
iniciales que se necesitan. El nombre del método constructor debe ser: • La clase hija puede a su vez definir nuevas variables y funciones.

__construct() La sintaxis es la siguiente:


Podemos añadir un constructor a la clase Persona para añadir los datos
class Persona {
básicos en la creación de los objetos.
public $nombre;

function __construct($nombre, $apellidos, $dni) { public $apellidos;

4
function setNombre($nombre) { las culturas exista un nombre y al menos un apellido). La clase hija hace
$this->nombre = $nombre; una particularidad de la Persona para adecuarla al sistema Español, aña-
}
diendo el dni. Si creamos un objeto del tipo PersonaEspaña, todas las
function getNombre() {
propiedades y métodos de la clase superior se heredarán, estando dis-
return $this->nombre;
ponibles para el objeto creado.
}
function setApellidos($apellidos) { Si ahora se ejecuta el código instanciando la clase que hemos creado,
$this->apellidos = $apellidos; se puede ver que los métodos que tienen que ver con el nombre y los
} apellidos pueden utilizarse aunque no estén definidos en la clase Perso-
function getApellidos() { naEspaña.
return $this->apellidos;

} Redefinición de métodos
} Cuando definimos una clase hija, las funciones de la clase padre son au-
tomáticamente heredadas. Se llama redefinición de métodos a la crea-
class PersonaEspaña extends Persona { ción de funciones en la clase hija, con el mismo nombre que en la clase
public $dni; padre.
function setDni($dni) {

$this->dni = $dni; Podemos modificar la clase Persona para que recoja los dos apellidos
} en propiedades distintas. Si creamos una clase PersonaUSA que herede
function getDni() { Persona tendremos que redefinir el método getApellidos para que nos
return $this->dni; devuelva los apellidos en el orden correcto (suponiendo que en USA pri-
} mero se ponga el 2º apellido y después el 1º).
}
class Persona {

 public $nombre;
La palabra reservada extends indica que la nueva clase creada será public $apellido1;
una extensión (heredará) de la clase que se escribe justo a la derecha public $apellido2;
de la definición. function setNombre($nombre) {

$this->nombre = $nombre;
El ejemplo anterior muestra una forma de definición de Persona muy ge-
}
nérica, con propiedades universales (o casi, suponiendo que en todas
function getNombre() {

5
return $this->nombre; $luisMiguel->setNombre("Luis Miguel");
} $luisMiguel->setApellidos("Cabezas", "Granado");

function setApellidos($apellido1, $apellido2) { $luisMiguel->setId("66612345");

$this->apellido1 = $apellido1; ?>

$this->apellido2 = $apellido2; <h1>

} Datos de <?= $luisMiguel->getNombre()


function getApellidos() { . " "

return $this->apellido1 . " " . $this->apellido2; . $luisMiguel->getApellidos(); ?>


} </h1>

} ID: <?= $luisMiguel->getId(); ?>

class PersonaUSA extends Persona { </body>

public $id; </html>

function setId($id) {

$this->id = $id;

}
Herencia encadenada
function getId() { Algunos lenguajes de programación permiten heredar de varias clases a
return $this->id; la vez; esto es conocido como herencia múltiple. PHP no permite heren-
} cia múltiple, pero sí herencia encadenada, es decir, permite heredar de
function getApellidos() { varias clases padres correlativamente. Un ejemplo es:
return $this->apellido2 . " " . $this->apellido1;
class A {
}
}
}
class B extends A {
Para ejecutar el código tendremos que modificar el archivo }
ejemplo1.php de esta forma: class C extends B {

}
<!DOCTYPE HTML>
class D extends C {
<html>
}
<body>

<?php

require_once("clases.php");

$luisMiguel = new PersonaUSA();

6
Podemos crear una función que cambie el nombre de un objeto pasado
como parámetro para ver este comportamiento.
Nota: La herencia múltiple tiene una serie de problemas asociados function cambiaNombre($objeto,$nombre) {
que es mejor evitar. El más grave es el efecto diamante. Si una clase $objeto->setNombre($nombre);
hereda de otras dos de forma múltiple es posible que las clases padre }
tengan métodos con el mismo nombre y entrarían en conflicto al no
Si ahora en el código forzamos un cambio de nombre veremos que se
poder determinarse la preferencia de los métodos heredados.
hará efectivo en el resultado.

cambiaNombre($luisMiguel,"Pedro");
Para ver la Herencia encadenada se puede añadir una nueva clase a
nuestro archivo clases.php. Dentro de los objetos hay que tener también cuidado con el alcance de
las variables. Las variables definidas fuera del entorno del objeto no son
class PersonaExtremadura extends PersonaEspaña {
accesibles desde los métodos de las clases, a menos que anteponga la
public $tarjetaFamiliaNumerosa;
palabra global.
function setTarjetaFamiliaNumerosa($tarjetaFamiliaNumerosa) {

$this->tarjetaFamiliaNumerosa = $tarjetaFamiliaNumerosa;
Miembros públicos, privados y protegidos
}
Mientras no se especifique otra cosa, los métodos y propiedades de una
function getTarjetaFamiliaNumerosa() {
clase son siempre públicos, es decir, son accesibles desde fuera del ob-
return $this->tarjetaFamiliaNumerosa;
jeto de la forma: $objeto->método. Pero existe un camino para que las
}
variables y los métodos no puedan ser accesibles desde código externo
}
al objeto.

Tal y como están definidas las variables en la clase Persona (public), se


Valores y alcance de variables
podría acceder a las propiedades de la forma $objeto->propiedad. Las
En versiones anteriores de PHP, los objetos eran pasados por valor a
propiedades deberían estar protegidas del acceso exterior para evitar
otras funciones, por lo tanto, el estado de los objetos no se conservaba.
que se introduzcan datos erróneos y la entrada de datos hacerla desde
En PHP 5 los objetos son pasados por defecto por referencia, esto quie-
un método accesorio (setPropiedad).
re decir que las modificaciones a objetos que se hagan dentro alguna
función u objeto, tendrán efecto sobre el objeto original.
Propiedades y métodos privados
Si declaramos un método o propiedad como privada, sólo se podrá acce-
der a él desde la clase que lo define. Las clases que hereden de una cla-
7
se con propiedades o métodos privados no tendrán acceso a estos y }

tampoco será posible desde fuera del objeto hacer llamadas. Para hacer public function getApellido2() {

return $this->apellido2;
un método o propiedad privado, sólo hay que anteponer la palabra priva-
}
te delante de la declaración.
public function getApellidos() {
class Persona { return $this->getApellido1() . " " . $this->getApellido2();
private $nombre; }
private $apellido1;
Ahora ya se puede redefinir el método en PersonaUSA.
private $apellido2;
public function getApellidos() {

return $this->getApellido2() . " " . $this->getApellido1();


De esta manera, las propiedades solo serán accesibles a través de los
}
métodos que interactúan con ellas. De la misma forma, se pueden decla-
rar métodos privados, que únicamente podrán utilizarse desde la propia
clase.
Propiedades y métodos protegidos
Un método que calcule la letra del DNI solo tiene sentido dentro de la Los métodos protegidos son menos restrictivos que los privados. Cuan-
clase PersonaEspaña y debería ser visible solo para esta clase. do declaramos un método como protegido, su visibilidad se extiende a la
clase que lo declara y a todas las clases que lo heredan.
private function calcularLetraDni ($dni) {

} Todos los métodos que no requieran acceso desde el exterior de la cla-


Al declarar las propiedades de la clase Persona como privados, la clase se deberían ser definidos como protegidos y debe anteponerse la pala-
PersonaUSA, que redefine el método getApellidos, no puede acceder a bra private a la propiedad o método.
las propiedades para obtener su valor y tendría que utilizar los métodos
En nuestro caso se pueden definir los métodos getApellido1 y getApelli-
de la clase padre. Como el método getApellidos de la clase Persona de-
do2 de la clase Persona como protegidos, porque solo lo van a utilizar
vuelve los dos apellidos juntos, tampoco sirve y hay que refactorizar el
esta clase y las clases derivadas.
código.
protected function getApellido1() {
A la clase Persona le añadir dos métodos nuevos y cambiamos el méto- return $this->apellido1;
do getApellidos. }

public function getApellido1() { protected function getApellido2() {

return $this->apellido1; return $this->apellido2;

8
} La interface anterior define la estructura básica que queremos para las
Propiedades y métodos públicos clases de humano que existen. Solo es necesario exponer qué métodos
Por defecto, todos los métodos y propiedades que se declaren en una básicos son obligatorios para que las clases que implementen esta inter-
clase son públicos, a menos que lleven asociados la palabra protected o face los desarrollen.
private. Como buena práctica de programación, las variables deben defi-
Para implementar la interface:
nirse como privadas o protegidas, para que no puedan modificarse des-
de fuera del objeto. class Persona implements Humano

Clases abstractas
Interfaces
Un interface no permite crear el cuerpo de ninguna función, dejando es-
A veces es necesario que un equipo de varias personas trabajen juntas.
ta tarea a las clases que la implementen. Las clases abstractas permiten
En este caso se hace imprescindible definir unas pautas generales de
definir funciones que deben implementarse obligatoriamente en las cla-
trabajo para que el resultado final sea el esperado. Si el desarrollo con-
ses que hereden y, además, permiten definir funciones completas que
siste en programar varios objetos, el analista de la aplicación puede defi-
pueden heredarse. Las clases abstractas deben llevar la palabra reser-
nir la estructura básica en papel o crear una pequeña plantilla con méto-
vada abstract en la declaración de la clase y en todos los métodos que
dos que el objeto final debería tener obligatoriamente. Esta plantilla es
sólo definan su nombre.
un interface y permite definir una clase con funciones definidas, pero sin
desarrollar, que obliga a todas las clases que lo implementen a declarar abstract class Humano {

estos métodos como mínimo. private $nombre;

private $apellido1;
Las interfaces aseguran que una clase cumple una serie de requisitos private $apellido2;
para que luego puedan utilizarse de una forma concreta. Por ejemplo, public function setNombre($nombre) {
todas las clases que implementen de la interface Iterator pueden utilizar- $this->nombre = $nombre;
se dentro de un bucle del tipo foreach. }

public function getNombre() {


interface Humano {
return $this->nombre;
public function setNombre($nombre);
}
public function getNombre();
public function setApellidos($apellido1, $apellido2) {
public function setApellidos($apellido1, $apellido2);
$this->apellido1 = $apellido1;
public function getApellidos();
$this->apellido2 = $apellido2;
}
}

9
protected function getApellido1() { declara como final, no podrá ser redefinido en ninguna clase que herede
return $this->apellido1; de la clase principal.
}

protected function getApellido2() { De esta forma se podrían declarar los métodos setDni y getDni de la cla-
return $this->apellido2; se PersonaEspaña como finales para que ninguna clase que herede de
} esta pueda modificar su funcionamiento.
public function getApellidos() {
final public function setDni($dni) {
return $this->getApellido1() . " " . $this->getApellido2();
$this->dni = $dni;
}
}
abstract public function getNombreCompleto();
final public function getDni() {
}
return $this->dni;
En lugar de un interface, hemos optado por utilizar una clase abstracta, }
porque tenemos claro como debe funcionar parte del código. También
se define el método abstracto getNombreCompleto, que obligatoriamen- Clases con métodos estáticos
te debe ser declarado en la clase que herede de ésta. En PHP se pueden declarar funciones dentro de una clase que no utili-
cen propiedades o métodos de la misma. Estos métodos pueden calcu-
La clase Persona queda así: lar valores numéricos, hacer una conexión a una base de datos o com-
probar que un correo electrónico esté bien definido. Son conocidos co-
class Persona extends Humano{
mo métodos estáticos y pueden ser utilizados sin necesidad de instan-
public function getNombreCompleto() {
ciar un objeto utilizando la palabra reservada static.
return $this->getNombre() . " " . $this->getApellidos();
}
Se puede implementar una comprobación del DNI de forma estática en
}
la clase PersonaEspaña.
En realidad, un interface es una clase que tiene todos sus métodos abs-
public static function comprobarDni($dni) {
tractos.
$letras = explode(",","T,R,W,A,G,M,Y,F,P,D,X,B,N,J,Z,S,Q,V,H,L,C,K,E");

Palabra clave final if (strlen($dni) < 9) {

$dni = "0" . $dni;

}
La palabra clave final afecta a métodos y clases. Si se define una clase $numero = intval($dni);
como final, no podrá ser heredada por ninguna clase. Si un método se $letra = strtoupper(substr($dni,-1));

10
if (strlen($dni) == 9 && $letra == $letras[$numero%23]) { $this->setNombre($nombre);
return true; $this->setApellidos($apellido1, $apellido2);

} else { }

return false; Como la clase PersonaEspaña necesita, además de los datos básicos,
} el DNI, podría implementar un constructor que almacenara la informa-
}
ción del DNI y llamara al constructor de la clase padre (en este caso Hu-
Para acceder a un método estático desde el cuerpo del programa hay mano) para añadir el resto de los datos. Podría ser algo así:
que escribir el nombre de la clase que lo implementa seguido del opera-
public function __construct($nombre, $apellido1, $apellido2, $dni) {
dor ( :: ) y el nombre del método. En el ejemplo vemos que se puede ac-
$this->setDni($dni);
ceder al método comprobarDni() creando un objeto o mediante la cons-
Humano::__construct($nombre, $apellido1, $apellido2);
trucción Nombre::nombreDefecto. Si intenta llamar a un método que no
}
es estático de esta forma se imprimirá un error en pantalla.
Para llamar al constructor se utiliza directamente el nombre de la clase
El código para comprobar un DNI antes de insertarlo en el objeto podría con el operador (::). El fichero de ejemplo nos quedaría entonces de la
ser algo parecido a esto: siguiente forma:

if ($luisMiguel::comprobarDni("08868143X")) { if (PersonaEspaña::comprobarDni("08868143X")) {

$luisMiguel->setDni("08868143X"); $luisMiguel = new PersonaEspaña("Luis Miguel", "Cabezas", "Granado",

} "08868143X");

}
Llamadas a funciones padre
El operador ( :: ) se puede utilizar también dentro de una clase para ha- Nota: Como el método comprobarDni es estático se puede utilizar sin
cer llamadas a funciones de la clase padre que estén sobrecargadas. Si necesidad de crear un objeto de la forma
define un constructor para la clase padre y otro para la clase hijo, cuan- PersonaEspaña::comprobarDni.
do cree el objeto, el constructor que se ejecuta es el de nivel inferior, el
constructor hijo. Para llamar al constructor padre debe utilizarse la no- Esta forma de actuar puede resultar complicada si no conoce el nombre
menclatura Nombre_clase_ padre::__construct(). de la clase padre. La solución a este pequeño inconveniente la tenemos
en la palabra parent, que hace alusión a la clase de la que heredamos.
La clase abstracta Humano podría implementar un constructor para aña-
dir los datos básicos en la creación de los objetos. Sobrecarga de métodos
public function __construct($nombre,$apellido1, $apellido2) {

11
Algunos lenguajes de programación orientados a objetos permiten decla- necerán al mismo espacio de nombres. De esta forma podemos añadir
rar más de un método con el mismo nombre. La definición de estos mé- el espacio de nombres Humano a nuestro fichero de clases.
todos varía en el número de parámetros o en los tipos de datos de los
namespace Humano;
argumentos. PHP no permite sobrecarga de métodos, pero puede simu-
lar esta actuación empleando la técnica de las funciones con un núme- Para utilizar las clases del espacio de nombres lo primero que hay que
ro variable de parámetros. hacer es incorporar el fichero al flujo del programa con un require_on-
ce() y después utilizar las clases o funciones de la forma normal, con la
private $alias; salvedad de que, al instanciar una clase, hay que añadir como sufijo de
public function __construct($nombre, $apellido1, $apellido2, $dni, $alias) la clase el nombre del namespace. El ejemplo siguiente muestra cómo
{ hacerlo:
if(func_num_args() > 4) {
require_once(“clases.php”)
$this->setAlias($alias);
$luisMiguel = new Humano\PersonaEspaña(......
}
$this->setDni($dni); La clase PersonaEspaña se instancia sumando el espacio de nombres
parent::__construct($nombre, $apellido1, $apellido2); con el nombre de la clase.
}

Aunque pueda parecer efectiva esta técnica no es nada recomendable Subniveles


y puede inducir a errores. Es posible crear árboles de niveles en ficheros de espacios de nombre.
El nivel raíz será el nombre descriptivo más a la izquierda y los niveles
Espacios de nombre sucesivos irán entre símbolos ( \ ).
El concepto del espacio de nombres es muy sencillo. Permite agrupar
El archivo de clases podría subdividirse en los siguientes espacios de
clases mediante un nombre descriptivo de la misma manera que una car-
nombre:
peta agrupa un conjunto de ficheros. De esta manera pueden existir va-
rias clases distintas con el mismo nombre. Por ejemplo, se podría tener namespace Humano;

un espacio de nombres llamado "Loterías" con una clase Loto y un espa- namespace Humano\Persona;

cio de nombres llamado "Flores" con una clase llamada también Loto. namespace Humano\Persona\PersonaEspaña;

Las clases comparten el nombre, pero son totalmente diferentes en su namespace Humano\Persona\PersonaEspaña\PersonaExtremadura;

declaración. Para crear un espacio de nombres hay que añadir al princi- Como las clases están ahora agrupadas por espacio de nombre, es ne-
pio del fichero la palabra reservada namespace seguido de un nombre cesario especificar el espacio de nombres cuando se hereda de una cla-
descriptivo. Todas las clases y funciones declaradas en el fichero, perte-

12
se padre. Por ejemplo, para que la clase PersonaEspaña herede de Per- Fluent interface
sona debería modificarse el código de esta forma: Es habitual que los métodos que añaden datos a los objetos (set) no de-
namespace Humano\Persona\PersonaEspaña; vuelvan nada (void). Esta técnica de la orientación a objetos permite en-
class PersonaEspaña extends \Humano\Persona\Persona { lazar métodos de asignación de datos de manera sencilla y quedando el
código desarrollado muy limpio.
Nota: La herencia debe empezar por (\) seguido del espacio de nom-
bres y terminando en el nombre de la clase de la que se hereda. Para realizarla simplemente hay que devolver en todos los métodos set
el mismo objeto que trata la petición y así podrá enlazarse con otra peti-
El código para utilizar las clases se hace algo más complejo: ción de asignación.

if public function setNombre($nombre) {

(Humano\Persona\PersonaEspaña\PersonaEspaña::comprobarDni("08868143X")) $this->nombre = $nombre;

{ return $this;

$luisMiguel = new Humano\Persona\PersonaEspaña\PersonaEspaña }

("Luis Miguel", "Cabezas", "Granado", "08868143X","Mago Lope"); Si todos los métodos devuelven el objeto podremos hacer una carga de
} valores de la forma siguiente:
Alias $luisMiguel->setNombre("Luis Miguel")
Si decide crear una estructura compleja de subniveles, es posible que ->setApellidos("Cabezas","Granado")

tenga que repetir muchas veces el espacio de nombres al instanciar las ! ->setAlias("Mago Lope")

clases. Para evitar esto se puede utilizar el operador use, que crea un ! ->setDni("08868143X");

alias a una clase o a un espacio de nombres para acortar su forma de Aunque no parece un gran cambio en realidad todo se vuelve más legi-
utilizarlo. ble. Veamos la verdadera efectividad con un ejemplo más claro.

Para ello se utiliza el operador use de la siguiente forma: Tenemos dos clases típicas de Pedidos y Lineas de Detalle.
use Humano\Persona\PersonaEspaña\PersonaEspaña as PersonaEspaña; namespace Pedidos;
if (PersonaEspaña::comprobarDni("08868143X")) { class Pedido {
$luisMiguel = new PersonaEspaña("Luis Miguel", "Cabezas", "Granado", private $lineas;
"08868143X","Mago Lope"); public function añadirLineaDetalle($linea) {

} $this->lineas[] = $linea;

13
} $linea2 = new Pedidos\LineaDetalle(5,"Tomates");
public function getLineasDetalle() { $pedido->añadirLineaDetalle($linea2);

return $this->lineas; $linea3 = new Pedidos\LineaDetalle(1,"Leche");

} $linea3->setEnLitros();

} $pedido->añadirLineaDetalle($linea3);

class LineaDetalle { ?>


private $cantidad; Resultado:

private $denominacion; <br>


private $liquido = false; <?php

public function __construct($cantidad, $denominacion) { print_r($pedido->getLineasDetalle());

$this->cantidad = $cantidad; ?>

$this->denominacion = $denominacion; </body>

} </html>

public function setEnLitros() { El código no tiene nada de especial. Se van creando las líneas de deta-
$this->liquido = true; lle y añadiendo al Pedido principal. Si el producto es en litros (líquido) se
}
activa el método setEnLitros.
}

Para poder utilizarlas tendríamos que escribir el código de la siguiente Estas clases las podemos transformar en fluent para obtener una limpie-
forma: za absoluta del código y obtener un resultado como este:

<?php $luis = new Pedidos\Cliente();

error_reporting(E_ALL); $luis->nuevoPedido()

?> ->con(2,"Patatas")

<!DOCTYPE HTML> ->con(5,"Tomates")

<html> ->con(1,"Leche")->liquido();

<body> Las modificaciones realizadas en las clases se ponen a continuación.


<?php
class Cliente {
require_once("clases.php");
private $pedido;
$pedido = new Pedidos\Pedido();
public function nuevoPedido() {
$linea1 = new Pedidos\LineaDetalle(2,"Patatas");
$this->pedido = new Pedido;
$pedido->añadirLineaDetalle($linea1);

14
return $this->pedido; incorporando al objeto devolviendo el Pedido para que se puedan enla-
} zar las nuevas adiciones.
public function getPedido() {

return $this->pedido; Como veremos más adelante, puede no ser una solución muy elegante
} a la hora de evitar el acoplamiento entre clases y es posible que incum-
} pla algunos principios a tener en cuenta (Demeter Law).
class Pedido {

private $lineas; Rasgos


public function __construct(){ Los traits (rasgos) son un mecanismo de reutilización de código en len-
return $this; guajes de herencia simple, como PHP. El objetivo de un trait es el de re-
} ducir las limitaciones propias de la herencia simple permitiendo que los
public function con($cantidad, $denominacion) { desarrolladores reutilicen a voluntad conjuntos de métodos sobre varias
$linea = new LineaDetalle($cantidad,$denominacion); clases independientes y pertenecientes a clases jerárquicas distintas. La
$this->añadirLineaDetalle($linea); semántica a la hora combinar Traits y clases se define de tal manera
return $this; que reduzca su complejidad y se eviten los problemas típicos asociados
} a la herencia múltiple.
public function liquido() {

$this->lineas[sizeof($this->lineas) - 1]->setEnLitros(); Si volvemos al ejemplo de las personas, podemos crear una nueva cla-
return $this; se PersonaTexas para contener los datos de este tipo de ciudadanos
} americanos.
public function añadirLineaDetalle($linea) {
namespace Humano\Persona\PersonaUSA\PersonaTexas;
$this->lineas[] = $linea;
class PersonaTexas extends \Humano\Persona\PersonaUSA\PersonaUSA {
}
public function decirHola($nombre) {
public function getLineasDetalle() {
return "Hola " . $nombre;
return $this->lineas;
}
}
}
}
La clase PersonaTexas tiene un método para saludar en Español (por-
La clase Cliente es la encargada de proporcionar un objeto del tipo Pedi-
que es de habla hispana). Este método lo podría tener también la clase
do, es la versión más sencilla del Patrón Factory. Se ha añadido a la cla-
PersonaEspaña y PersonaExtremadura pero no se puede implementar
se Pedido un método que crea Lineas de Detalle con los datos y los va
en una clase superior (Persona) porque no todas las clases hijas po-
15
drían decir hola en Español (PersonaUSA no debería saber saludar en Se pueden utilizar varios rasgos definiéndolos en la clase separados por
español). comas. Si dos rasgos tienen métodos con el mismo nombre, se produce
un error fatal.
La solución es repetir el código en la clase que lo necesite, pero esto se-
ría un grave error. Los traits soportan el uso de métodos abstractos y estáticos para impo-
ner requisitos a la clase a la que se exhiban. Además, pueden componer-
Nota: Tratamos de crear buen código, y repetir trozos de código en se de otros traits y crear estructuras complejas.
distintas clases o archivos es algo tan común como desastroso para el
mantenimiento de una aplicación. Métodos mágicos
Son métodos proporcionados por el intérprete de PHP. Todos empiezan
Debemos intentar seguir la metodología DRY (Don´t Repeat Yourself) por dos guiones bajos seguidos (__). Si se insertan en una clase, permi-
en todo momento. ten añadir una funcionalidad determinada a los objetos.

La solución es implementar un rasgo. __toString()


Este método está diseñado para responder con un literal cuando se invo-
trait decirHola {
ca a un objeto directamente, sin método asociado. Se suele utilizar para
public function decirHola($nombre) {
dar nombre a los objetos. Si los objetos son de personas, este método
return "Hola " . $nombre;
podría devolver el nombre completo de la persona.
}

} public function __toString() {

La implementación de los rasgos es muy similar a la de las clases, pero return $this->nombre . “ “ . $this->apellido1 . “ “ . $this->apellido2

estos no pueden instanciarse directamente. Necesitan tener al principio }

la palabra reservada trait.


__set()
Los miembros de la clase actual sobrescriben los métodos del Trait, que
Se utiliza para asignar valores a propiedades (existentes o no) de los ob-
a su vez sobrescribe los métodos heredados.
jetos. Todas las llamadas a propiedades pasarán por aquí a menos que
Para utilizar un rasgo dentro de una clase tendremos que indicarlo de la utilicemos un método set propio. Con esto nos ahorramos crear un set
siguiente forma: por cada propiedad, pero no es recomendable porque perdemos el foco
de lo que hace la clase y de las propiedades que almacena.
use \Humano\Persona\decirHola;

16
__get()
Se utiliza para recuperar el valor de la propiedad que le pasemos como
parámetro.

La clase PersonaEspaña podría implementar estos dos métodos de la


siguiente forma:

public function __set($variable, $valor) {


$this->$variable = $valor;

public function __get($variable) {

return $this->$variable;

Serialización
Los objetos son, en realidad, un conjunto de datos y funciones que guar-
dan una serie de estados de ejecución. Este conjunto de bits se pueden
almacenar, en un momento dado, y recuperar justo en el mismo estado
en el que se encontraba cuando lo guardamos. Esta técnica se llama se-
rialización y permite almacenar y recuperar el conjunto de bits que for-
man un objeto.

PHP 5 ofrece dos funciones, serialize() y unserialize(), que toman como


parámetro un objeto y devuelven una cadena de caracteres. Los objetos
serializados los podemos almacenar en base de datos, ficheros, etcéte-
ra.

$luisMiguel = new PersonaEspaña("Luis Miguel,

! "Cabezas","Granado","08868143X","Mago Lope");

$luisMiguelcopia = serialize($luisMiguel);

$miguelLuis = unserialize($luisMiguelcopia);

echo $miguelLuis->getNombre();

17
Capítulo 1

Técnicas avanzadas de lo puede lograrse con un workflow, generalmente significa que no inverti-
mos el suficiente tiempo en intentar buscar una solución limpia y buena.

orientación a objetos 3. No debe ser redundante


Debería cumplir con la regla DRY (Don't Repeat Yourself, o No Te Repi-
tas). Cuando se aplica correctamente el principio DRY, la modificación
de un único elemento del sistema no requiere cambios en otros elemen-
tos que no tengan relación lógica.
Código Limpio
El código limpio es un arte y debe entenderse como tal. No hay una defi-
4. Debe ser placentero leer el código
nición sencilla para explicar qué es, pero sí existen unas normas o suge- Cuando leamos el código debería parecer que estuviéramos leyendo
rencias que todo buen código debería seguir. poesía (según Alan Cox) Debemos sentir que es fácil de leer por cual-
quier desarrollador sin pasar horas investigándolo.
1. El código malo hace demasiadas cosas - el códi-
Para lograr esto debemos cumplir con el principio KISS (Keep It Simple,
go limpio es enfocado Stupid!, o Manténlo Simple, Estúpido!) y el principio YAGNI (You Ain't
Cada clase, cada método o cualquier otra entidad debería permanecer Gonna Need It, o No lo vas a necesitar). El principio KISS indica que la
sin cambios. Debería cumplir con SRP (Single Responsability Principle, mayoría de los sistemas funcionan mejor si se los mantiene simples en
o Principio de Responsabilidad Única). Dicho de otra manera, una clase lugar de complejos.
debe tener una y sólo una causa por la cual pueda ser modificada.
Por lo tanto, la simplicidad tiene que ser un elemento clave del diseño, y
2. El lenguaje con el que se escribió el código debe- se debe evitar la complejidad innecesaria. YAGNI es una práctica que
ría parecer que fue hecho para resolver el problema nos alienta a enfocarnos exclusivamente en las cosas más simples que
hagan funcionar al software.
"No es el lenguaje lo que hace parecer simple a un problema, sino que
es el desarrollador que hace que el lenguaje parezca simple" - Robert C.
5. Puede ser extendido fácilmente por cualquier otro
Martin
desarrollador
Esto significa, por ejemplo, que no debemos usar frameworks que ha- No escribimos código para nosotros mismos, o peor, para el compilador.
gan que el código y el lenguaje parezcan raros. Si decimos que algo só- Escribimos código para otro desarrollador. No seamos egoístas: pense-

18
mos en los demás. No torturemos a otros desarrolladores creando códi- En ingeniería de software, SOLID (Single responsibility, Open-closed,
go que es difícil de extender y mantener. Además, en algunos meses es Liskov substitution, Interface segregation and Dependency inversion)
posible que nosotros mismos seamos el "otro desarrallador". es un acrónimo mnemónico introducido por Robert C. Martin a comien-
zos de la década del 2003 que representa cinco principios básicos de la
6. Debe tener dependencias mínimas programación orientada a objetos y el diseño. Cuando estos principios
Mientras más dependencias tenga, más difícil va a ser de mantener y se aplican en conjunto es más probable que un desarrollador cree un sis-
cambiar en el futuro. Existen herramientas que nos ayudan a cumplir es- tema que sea fácil de mantener y ampliar en el tiempo.
te objetivo, marcando las dependencias "extrañas" de nuestro código.
Los principio SOLID son guías que pueden ser aplicadas en el desarro-
7. Cuanto más pequeño, mejor llo de software para eliminar código sucio provocando que el programa-
El código debería ser mínimo. Tanto las clases como los métodos debe- dor tenga que refactorizar el código fuente hasta que sea legible y exten-
rían ser cortos, preferentemente con pocas líneas de código. Debe estar sible. Debe ser utilizado con el desarrollo guiado por pruebas o TDD, y
bien dividido. Mientras mejor dividamos el código, más fácil se vuelve forma parte de la estrategia global del desarrollo ágil de software y pro-
leerlo. Este principio puede influenciar positivamente al punto 4 - va a gramación adaptativa.
facilitar la comprensión del código cuando lo lean otros desarrolladores.
Principio de Responsabilidad Única
8. Debe tener pruebas unitarias y de aceptación Una clase o un método deberían tener una única responsabilidad. Si
¿Cómo podemos saber que nuestro código cumple con los requerimien- nos fijamos en la clase PersonaEspaña, introduce un nuevo elemento
tos si no escribimos pruebas? ¿Cómo podemos mantener y extender el que solo es propiedad de las personas residentes en este país, el DNI.
código sin miedo a romper algo? El código sin pruebas no es código lim- class PersonaEspaña extends Persona {
pio. private $dni;

public function __construct($nombre, $apellido1, $apellido2, $dni) {


9. Debe ser expresivo $this->setDni($dni);
La expresividad del código significa que tiene nombres significativos. Es- parent::__construct($nombre, $apellido1, $apellido2);
tos nombres deben expresar la intención. No tienen que resultar engaño- }
sos. Tienen que ser distintivos. La expresividad hace que el código se final public function setDni($dni) {
documente a si mismo, resultando en que sea menos importante la docu- $this->dni = $dni;
mentación. return $this;

}
Principios SOLID final public function getDni() {

19
return $this->dni; $this->dni = new Dni($dni);
} }

public static function comprobarDni($dni) { public function getDni() {

$letras = explode(",","T,R,W,A,G,M,Y,F,P,D,X,B,N,J,Z,S,Q,V,H,L,C,K,E"); return $this->dni;

if (strlen($dni) < 9) { }

$dni = "0" . $dni; }


} Como vemos ha reducido su responsabilidad a una sola, tratar los datos
$numero = intval($dni); de una persona de España. El DNI lo trata como un dato, incorporando
$letra = strtoupper(substr($dni,-1));
a su estructura un objeto del tipo Dni. Hemos ganado en legibilidad y lim-
if (strlen($dni) == 9 && $letra == $letras[$numero%23]) {
pieza.
return true;

} else { El código de la clase Dni puede ser como el que sigue:


return false;
class Dni {
}
private $numero;
}
private $letra;
}
private $dni;
Si analizamos la clase podemos observar que hace más cosas de las public function __construct($dni) {
que debería. Esta clase es capaz de guardar datos de personas y ade- if (Dni::comprobarDni($dni)) {
más almacenar el DNI y comprobar si es correcto. La clase PersonaEs- if (strlen($dni) < 9) {
paña únicamente debería saber sobre los datos de una persona, pero $dni = "0" . $dni;
no debería saber analizar un DNI; se hace indispensable en este mo- }
mento dividir la clase en dos. $this->dni = $dni;

$this->numero = intval($dni);
La clase PersonaEspaña quedaría de la siguiente forma:
$this->letra = strtoupper(substr($dni,-1));

class PersonaEspaña extends Persona { }

private $dni; }

public function __construct($nombre, $apellido1, $apellido2) { public function __toString() {

parent::__construct($nombre, $apellido1, $apellido2); return $this->dni;

} }

public function setDni($dni) { public static function comprobarDni($dni) {

20
$letras = explode(",","T,R,W,A,G,M,Y,F,P,D,X,B,N,J,Z,S,Q,V,H,L,C,K,E"); private $letra;
if (strlen($dni) < 9) { private $dni;

$dni = "0" . $dni; public function __construct($dni) {

} if (Dni::comprobarDni($dni)) {

$numero = intval($dni); $this->ponerCerosIzquierdaAlDni($dni);

$letra = strtoupper(substr($dni,-1)); $this->setDatosDni($dni);


if (strlen($dni) == 9 && $letra == $letras[$numero%23]) { }

return true; }
} else { public function __toString() {

return false; if ($this->dni) {

} return $this->dni;

} } else {

public function getDni() { return "DNI no válido";

return $this->dni; }

} }

} public function comprobarDni($dni) {

Y el ejemplo para ver como funciona el conjunto: $letras = explode(",","T,R,W,A,G,M,Y,F,P,D,X,B,N,J,Z,S,Q,V,H,L,C,K,E");

$this->ponerCerosIzquierdaAlDni($dni);
$luisMiguel = new PersonaEspaña("Luis Miguel","Cabezas","Granado");
$this->setDatosDni($dni);
$luisMiguel->setDni("08868143X");
if (strlen($this->dni) == 9 && $this->letra == $letras[$this-
echo $luisMiguel->getDni();
>numro%23]) {
Hay varias cosas en el ejemplo anterior que no parecen muy correctas. return true;
El sistema funciona, pero hace aguas por alguna parte. } else {

$this->borrarDatosDni();
Por ejemplo, la clase Dni tiene código repetido y eso es muy difícil de return false;
mantener porque es posible que las modificaciones que se hagan en al- }
guna parte del código no se apliquen a la parte donde el código está re- }
petido causando un desajuste en el funcionamiento.

Una posible modificación de Dni puede ser la siguiente:class Dni { public function getDni() {

private $numero; return $this->dni;

21
} Imaginemos que la clase persona debe almacenar ahora el pasaporte y
private function ponerCerosIzquierdaAlDni(&$dni) { el libro de familia.
if (strlen($dni) < 9) {

$dni = "0" . $dni; La clase PersonaEspaña tendría que modificarse por cada nuevo docu-
} mento que tuviera que almacena violando el Principio abierto/cerrado.
}
private function setDatosDni($dni) {
Además, existe una dependencia de la clase PersonaEspaña con las cla-
$this->dni = $dni;
ses Dni, LibroFamilia y Pasaporte. Lo ideal es que las clases no sepan
$this->numero = intval($dni);
qué clases almacenan los datos, deberían tratarse de forma transparen-
$this->letra = strtoupper(substr($dni,-1)); te.
}
Hay que buscar una forma más elegante de resolver este problema.
private function borrarDatosDni(){

$this->letra = null; class PersonaEspaña extends Persona {

$this->numero = null; private $dni;

$this->dni = null; private $libroFamilia;

} private $pasaporte;

} public function __construct($nombre, $apellido1, $apellido2) {

Ahora la clase Dni se puede dedicar solo a su cometido, que es saber parent::__construct($nombre, $apellido1, $apellido2);

}
todo lo posible sobre este documento. Podría almacenar la fecha de ex-
public function setDni($dni) {
pedición, datos del padre y la madre, etc.
$this->dni = new Dni($dni);

Principio abierto/cerrado }

public function getDni() {


Todo componente debe estar abierto a nuevas funcionalidades, pero ce-
return $this->dni;
rrado a cambios en su código. Queremos extender el comportamiento
}
del componente sin modificar su código. Es posible extender el código
public function setLibroFamilia($libroFamilia) {
mediante herencia, pero lo mejor es prever por dónde se va a extender
$this->libroFamilia = new libroFamilia($libroFamilia);
el código para cumplir este principio.
}

Las técnicas de este principio nos van a permitir eliminar algunas depen- public function getLibroFamilia() {

dencias entre clases, que evitan a veces la reutilización. return $this->libroFamilia;

22
} $this->ponerCerosIzquierdaAlDni($dni);
public function setPasaporte($pasaporte) { $this->setDatosDni($dni);

$this->libroFamilia = new Pasaporte($pasaporte); }

} }

public function getPasaporte() { public function __toString() {

return $this->pasaporte; if ($this->dni) {


} return "DNI : " . $this->getDni();

} } else {

Para empezar vamos a crear una interface que deberán implementar to- return "DNI no válido";

dos los documentos que tengan que pertenecer a la clase PersonaEspa- }

}
ña.
public function nombreDocumento() {
interface Documento { return "DNI";
public function __toString(); }
public function nombreDocumento(); public function comprobarDocumento($dni) {
public function comprobarDocumento($documento); if (comprobarDni($dni)) {
public function setDocumento($documento); return true;
public function getDocumento(); } else {
} return false;
Esta interface contiene los datos básicos de cualquier documento legal }
o administrativo. Cada clase implementará además los métodos necesa- }

rios para su construcción y funcionamiento. public function setDocumento($dni) {

if (Dni::comprobarDni($dni)) {
La clase Dni podría quedar así: $this->ponerCerosIzquierdaAlDni($dni);

class Dni implements Documento{ $this->setDatosDni($dni);

private $numero; }

private $letra; }

private $dni; public function getDocumento() {

public function __construct($dni) { return $this->getDni();

if (Dni::comprobarDni($dni)) { }

23
public function comprobarDni($dni) { }
$letras = explode(",","T,R,W,A,G,M,Y,F,P,D,X,B,N,J,Z,S,Q,V,H,L,C,K,E"); Lo más básico que podemos hacer para una clase Pasaporte o LibroFa-
$this->ponerCerosIzquierdaAlDni($dni); milia es:
$this->setDatosDni($dni);
class Pasaporte implements Documento {
if (strlen($this->dni) == 9 && $this->letra == $letras[$this->
private $pasaporte;
numero%23]) {
public function __construct($pasaporte) {
return true;
$this->pasaporte = $pasaporte;
} else {
}
$this->borrarDatosDni();
public function __toString() {
return false;
return "PASAPORTE : " . $this->getPasaporte();
}
}
}
public function setPasaporte($pasaporte) {
public function getDni() {
$this->pasaporte = $pasaporte;
return $this->dni;
}
}
public function getPasaporte() {
private function ponerCerosIzquierdaAlDni(&$dni) {
retunr $this->pasaporte;
if (strlen($dni) < 9) {
}
$dni = "0" . $dni;
public function nombreDocumento() {
}
return "PASAPORTE";
}
}
private function setDatosDni($dni) {
public function comprobarDocumento($pasaporte) {
$this->dni = $dni;
return true;
$this->numero = intval($dni);
}
$this->letra = strtoupper(substr($dni,-1));
public function setDocumento($pasaporte) {
}
$this->setPasaporte = $pasaporte;
private function borrarDatosDni(){
}
$this->letra = null;
public function getDocumento() {
$this->numero = null;
return $this->getPasaporte();
$this->dni = null;
}
}
}

24
Ya tenemos las clases implementando un mismo interface, por lo tanto, La clase PersonaEspaña ha quedado muy reducida y aún así tiene mu-
todas ellas sabrán responder como mínimo a los métodos del interface. cha más funcionalidad porque es capaz de almacenar documentos de
El método nombreDocumento lo utilizaremos para saber el tipo de docu- todo tipo, ¡incluso los que todavía no existen!
mento que estamos tratando en cada momento.
Esta clase cumple con el principio Abierto/cerrado porque está abierta a
Inyección de Dependencias nuevas funcionalidades sin necesidad de modificar el código.
La clase PersonaEspaña tiene una fuerte dependencia con las clases
Dni, Pasaporte y LibroFamilia. Tanto es así que cada vez que se añade Nota: El método setDocumento obliga a que el parámetro implemente
un tipo de documento nuevo es necesario crear nuevos métodos para la interface Documento haciendo un casting en la captura de los da-
soportar la instancia de los nuevos objetos. tos.

En vez de crear un set y un get por cada documento distinto, lo que ha- Ejercicio: Añadir a la clase la posibilidad de almacenar una tarjeta de
cemos es crear un setDocumento genérico al que le inyectamos el obje- crédito.
to del documento que debe almacenar (se lo pasamos como paráme-
tro). Cualquier nuevo documento que se implemente seguirá funcionan-
El código para probar la clase podría ser el siguiente:
do siempre que se cumpla con el interface.
<?php
class PersonaEspaña extends Persona {
error_reporting(E_ALL);
private $documentos;
?>
public function __construct($nombre, $apellido1, $apellido2) {
<!DOCTYPE HTML>
parent::__construct($nombre, $apellido1, $apellido2);
<html>
}
<body>
public function setDocumento(Documento $documento) {
<?php
$this->documentos[] = $documento;
require_once("clasesSolid.php");
}
$miDni = "08868143X";
public function getDocumentos() {
$miPasaporte = "08868143-1325322";
return $this->documentos;
$miLibroFamilia = "08868143AB";
}
$dniLuisMiguel = new Dni($miDni);
}
$pasaporteLuisMiguel = new Pasaporte($miPasaporte);

$libroFamiliaLuisMiguel = new LibroFamilia($miLibroFamilia);

25
$miNombre = "Luis Miguel"; public function getAncho() {
$miApellido1 = "Cabezas"; return $this->ancho;

$miApellido2 = "Granado"; }

$luisMiguel = new PersonaEspaña($miNombre,$miApellido1,$miApellido2); public function setAlto($alto) {

$luisMiguel->setDocumento($dniLuisMiguel); $this->alto = $alto;

$luisMiguel->setDocumento($pasaporteLuisMiguel); }
$luisMiguel->setDocumento($libroFamiliaLuisMiguel); public function getAlto() {

return $this->alto;
foreach($luisMiguel->getDocumentos() as $documento) { }

! echo $documento . "<br>"; public getArea() {

} return $this->getAncho() * $this->getAncho();

?> }

</body> }

</html> Podemos crear ahora una clase Cuadrado derivada de la clase Rectán-
PRINCIPIO DE SUSTITUCIÓN DE LISKOV gulo.
Llamado así porque fue enunciada por primera vez por Bárbara Liskov, class Cuadrado {
premio Turing 2008, puede servirnos para determinar si nuestra jerar- private $ancho;
quía se ajusta al Principio Abierto/Cerrado. Los objetos de una clase de- private $alto;
berían poder sustituirse por instancias de las clases derivadas. public function setAncho($ancho) {

$this->ancho = $ancho;
De otra forma, una clase no solo debería ser de un tipo, sino comportar-
$this->alto = $alto;
se como tal.
}

public function getAncho() {


El ejemplo clásico es el de la definición de un Rectángulo como clase.
return $this->ancho;
class Rectangulo { }
private $ancho; public function setAlto($alto) {
private $alto; $this->alto = $alto;
public function setAncho($ancho) { $this->ancho = $ancho;
$this->ancho = $ancho; }
} public function getAlto() {

26
return $this->alto; Como ejemplo de documento se puede crear la clase Visa. El problema
} es que las tiendas virtuales no suelen almacenar los datos de la tarjeta
}
de crédito, por lo tanto, habría que redefinir el método getVisa para que
Vamos a hacer un pequeño test de las clases (sin PHPUnit, solo por pro- no se pueda obtener ningún dato. Técnicamente es posible y no da
bar los valores). error, pero el funcionamiento lógico, que es obtener los datos del docu-
mento, se hace poco operativo. Parece que en este caso es mejor crear
$rectangulo = new Rectangulo();
una interface del tipo TarjetaCredito con algunos métodos más seguros
$rectangulo->setAncho(10);
que permitan hacer otro tipo de operaciones.
$rectangulo->setAlto(2);

echo $rectangulo->getArea(); class Visa implements Documento {

El resultado es el esperado. Se ha creado un objeto Rectángulo, añadi- private $visa;

do sus propiedades y el área es la correcta. ¿Qué pasa si sustituimos la public function __construct($visa) {

clase Cuadrado por Rectangulo? $this->visa = $visa;


}
$rectangulo = new Cuadrado();
public function __toString() {
$rectangulo->setAncho(10);
return $this->nombreDocumento() . " : " . $this->getVisa();
$rectangulo->setAlto(2);
}
echo $rectangulo->getArea();
public function setVisa($visa) {
Como un cuadrado debe tener los dos lados iguales por definición, al $this->visa = $visa;
añadir el ancho o el alto como propiedad siempre se quedará el último }
valor especificado (en este caso el 2) y el área se calculará en función public function getVisa() {
de la última propiedad que se añada. Si setAncho(10) estuviera al final return "NÚMERO NO GUARDADO";
el área sería distinta. Aunque la aplicación es técnicamente correcta, las }
entidades fallan en algo. La clase Cuadrado no es un subtipo de Rectán- public function nombreDocumento() {
gulo. Liskov definió como subtipo a la propiedad que tiene un objeto A return "VISA";
de poder reemplazar a otro objeto B sin que el comportamiento de los }

objetos que los utilizan deba ser modificado (A es un subtipo de B). public function comprobarDocumento($visa) {

return true;
En este caso Cuadrado sería una subclase y no un subtipo. }

public function setDocumento($visa) {

$this->setVisa = $visa;

27
}
public function getDocumento() {

return $this->getVisa();

PRINCIPIO DE SEGREGACIÓN DE INTERFACES


Las clases que implementen una interfaz o una clase abstracta, no debe-
rían estar obligadas a implementar métodos que no utilizan.

Supongamos que tenemos una clase abstracta TelefonoMovil con la que


queremos representar tanto móviles actuales como viejas reliquias. No
sería una gran idea crear un método fotografiar() en la clase padre, da-
do que no todos los teléfonos tienen esta funcionalidad. Aunque la imple-
mentación del método en la clase concreta consistiera en no hacer nada
o lanzar una excepción, este diseño sólo serviría para crear confusión y
dificultar el mantenimiento.

PRINCIPIO DE INVERSIÓN DE DEPENDENCIAS


Depende de abstracciones, no de implementaciones concretas.

Se llama así porque, al contrario de lo que sucede a veces, las clases


concretas deberían depender de clases abstractas, y no a la inversa.

En el apartado de Inyección de Dependencias hay un buen ejemplo de


como depender de interfaces en vez de implementaciones concretas.

28
Patrones de diseño

2
• Singleton
• Factory Method
• Observer
• Decorator
Capítulo 2

Patrones de diseño • Estandarizar el modo en que se realiza el diseño.

• Facilitar el aprendizaje de las nuevas generaciones de diseñadores


condensando conocimiento ya existente.

Asimismo, no pretenden:

• Imponer ciertas alternativas de diseño frente a otras.

• Eliminar la creatividad inherente al proceso de diseño.


Introducción
Los patrones de diseño son la base para la búsqueda de soluciones a El uso de patrones no es obligatorio pero sí recomendable en el caso de
problemas comunes en el desarrollo de software y otros ámbitos referen- que tengamos muy claro donde aplicar un patrón u otro y siempre tenien-
tes al diseño de interacción o interfaces. do en cuenta que pueden no ser aplicable en ciertos casos. "Abusar o
forzar el uso de los patrones puede ser un error" (patternitits).
Un patrón de diseño resulta ser una solución a un problema de diseño.
Para que una solución sea considerada un patrón debe poseer ciertas Patrones de Creación
características. Una de ellas es que debe haber comprobado su efectivi- Los patrones de creación crean modelos de clases que permiten crear
dad resolviendo problemas similares en ocasiones anteriores. Otra es objetos abstrayendo al programador de la creación. Ayudan a que el sis-
que debe ser reutilizable, lo que significa que es aplicable a diferentes tema sea independiente de cómo se crean, componen y representan los
problemas de diseño en distintas circunstancias. objetos.

Los patrones de diseño pretenden: Patrón de Creación Singleton


• Proporcionar catálogos de elementos reusables en el diseño de siste- Intención: Asegurar que una clase tiene una única instancia y proporcio-
mas software. na un punto de acceso global a dicha instancia.

• Evitar la reiteración en la búsqueda de soluciones a problemas ya co- Motivación: Hay veces que es importante asegurar que una clase ten-
nocidos y solucionados anteriormente. ga una sola instancia, por ejemplo:

• Formalizar un vocabulario común entre diseñadores. • Un gestor de ventanas.

30
class Singleton {
• Una única cola de impresión.
private static $instancia;
• Un único sistema de ficheros. private $contador;

• Un único fichero de log, o un único repositorio. private function __construct() {

echo "He creado un " . __CLASS__ . "\n";


¿Cómo asegurarlo? una variable global hace el objeto accesible, pero
$this->contador =0;
se puede instanciar varias veces.
}

Aplicabilidad: Cuando debe haber una sola instancia, y debe ser acce- public static function getInstancia() {

sible a los clientes desde un punto de acceso conocido. if ( !self::$instancia instanceof self) {

self::$instancia = new self;


Cuando la única instancia debe ser extensible mediante subclasifica- }
ción, y los clientes deben ser capaces de usar una instancia extendida return self::$instancia;
sin modificar su código. }

public function getContador() {


Estructura:
return $this->contador;
F IGURA 2.1 Patrón Singleton }

public function incrementar() {

! $this->contador++;

public function disminuir() {

! $this->contador--;

El código anterior muestra una implementación muy sencilla del patrón


Singleton. Como se puede apreciar, contiene una propiedad estática (re-
cordamos que mantiene su valor para todos los objetos de una clase)
donde se guardará la instancia del objeto Singleton.

31
Lo siguiente a destacar es que el método constructor es privado, por lo Intención: Centraliza en una clase constructora la creación de objetos
tanto no se podrá crear un objeto de la clase de la forma new Singleton; de un subtipo de un tipo determinado, ocultando al cliente la casuística
para ello habrá que utilizar el método getInstance(), encargado de com- para elegir el subtipo que crear.
probar si la instancia ya existe y de crear una si no existe todavía.
Motivación: A veces es imposible elegir de antemano cuál objeto espe-
Nota: Hay que destacar el uso de self en lugar de $this en el método cífico debe ser instanciado, ya que la elección de los objetos a utilizar
getInstance(). Como norma general se utiliza self para métodos y pro- puede depender de algo en el entorno de ejecución.
piedades estáticas y $this para propiedades y métodos de objeto.
Aplicabilidad: !

Si hacemos un ejemplo podemos comprobar que las instancias son úni- • Una clase no puede prever la clase de objetos que debe crear.
cas. Aunque se crean dos instancias de la clase Singleton ($singleton y
• Centralizar la creación de objetos.
$singleton2) los valores se van mantienen. $singleton y $singleton2 ha-
cen referencia en al mismo objeto. • Proveer una interfaz para el cliente, permitiendo crear una familia de
objetos sin especificar su clase.
<?php

require_once("clases.php");
Estructura:
$singleton = Singleton::getInstancia();

echo $singleton->getContador() . "<br>";

$singleton->incrementar();
F IGURA 2.2 Factory Method
$singleton2 = Singleton::getInstancia();

$singleton2->getContador();
echo $singleton->getContador() . "<br>";

echo $singleton2->getContador() . "<br>";

?>

Ejercicio: Crear un Singleton que se encarge de recoger todas las va-


riables GET y POST.

Patrón de Creación Factory Method

32
Para ver un ejemplo de este patrón de diseño vamos a utilizar las clases Intención: Definir una dependencia uno-a-muchos entre objetos, de tal
Documento del capítulo anterior eliminando el constructor para simplifi- forma que cuando el objeto cambie de estado, todos sus objetos depen-
car todo. dientes sean notificados automáticamente.

Añadiremos una nueva clase encargada de crear objetos según el tipo Se trata de desacoplar la clase de los objetos clientes del objeto, aumen-
que se solicite. Como las clases cumplen con los principios de la orienta- tando la modularidad del lenguaje, creando las mínimas dependencias y
ción a objetos solo habrá que escribir unas pocas líneas: evitando bucles de actualización (espera activa o polling).

class FactoryMethod { Motivación: Mantener las dependencias entre objetos, sin necesidad de
! private $documento; conocer al otro objeto.
! public function crear($tipo){

! ! if (class_exists($tipo)) { Aplicabilidad:
! $this->documento = new $tipo();
! ! return $this->documento;
• El patrón Observador define una dependencia del tipo uno-a-muchos
! ! } else {
(1: n) entre objetos, de manera que cuando el objeto 1 cambia su esta-
! ! ! return "NO EXISTE EL TIPO DE DOCUMENTO";
do, el observador se encarga de notificar este cambio a todos los n
! ! }
objetos dependientes para que se actualicen automáticamente.
}
• El observador no es un mediador entre los sujetos (objetos que cam-
}
bian de estado) y los objetos dependientes, ya que el mismo es un ob-
El método crear evalúa el tipo de documento que se requiere, e instan- jeto dependiente. El observador recibe la orden de actualizar por par-
cia un objeto que se puede utilizar. te del sujeto "dominante".

Para probar la clase podemos hacer algo parecido a esto: • Distinguimos entonces el objeto observado y los objetos observado-
$factoria = new FactoryMethod(); res. El objeto observado  debe mantener una relación de los objetos
$documento = $factoria->crear("Dno"); que le observan y una forma de notificarles. Por su parte, los objetos
print_r($documento); observadores, sean del tipo que sean, deben implementar alguna fun-
$documento->setDocumento("08868143X"); cionalidad para actualizarse en función del nuevo estado del objeto
echo $documento; observado.

Patrón de comportamiento Observer


33
! }
• Proporciona mecanismos para “registrar” objetos observadores en el
objeto observable y notificar a aquellos cuando este sufre una notifica-
! public function deregistrarObserver($observer){
ción.
! ! if(in_array($observer, $this->observers)){
Estructura: ! ! $key = array_search($observer, $this->observers);

! ! unset($this->observers[$key]);
! ! }
F IGURA 2.3 Patrón Observer ! }
! abstract public function notificarObservers();

El método registrarObserver() comprobará si existe un observador del


mismo tipo y en caso negativo lo almacenará en un array (mediante in-
yección de dependencias).

El método contrario, desregistrarObserver(), se encargará de eliminar el


observador.

También se añade un método abstracto, que obligará a los objetos ob-


servables a notificar a sus observadores.

Vamos a crear una clase abstracta que permita registrar objetos observa- Los observadores deberían implementar la siguiente interface:
dores.
interface Observer{
abstract class Observable{ ! public function notificar($sender, $params);
! protected $observers; }
! function __construct(){
Todavía no hemos hecho nada. En este punto solo se ha definido cómo
! ! $this->observers = array();
deberían funcionar los objetos que pretendan seguir el patrón Observer.
! }
Lo siguiente es definir las clases que van a implementar y extender la
! public function registrarObserver($observer){
clase abstracta.
! ! if(!in_array($observer, $this->observers)){

! ! $this->observers[] = $observer; class MiObservable extends Observable{

! ! } ! public function __construct(){

34
! ! parent::__construct(); class SalvarLog implements Observer{
! } ! public function notificar($sender, $param){

! public function notificarObservers(){ ! ! echo "Guardando en BD $param enviado por ".

! ! foreach ($this->observers as $observer) { ! ! get_class($sender)."... <br /><br />";

! ! ! $observer->notificar($this, $this->param); ! }

! ! } }
! } Para ver el funcionamiento:
! public function Evento($texto){
<?php
! ! $this->param = $texto;
require_once("clases.php");
! ! $this->notificarObservers();

! }
$obj = new MiObservable();
}
$obj->registrarObserver(new Log());
La clase MiObservable hace una llamada al constructor padre para la
$obj->registrarObserver(new SalvarLog());
creación del array de objetos observadores.

El método Evento guardará el parámetro en una propiedad del objeto y $obj->Evento('Test 1');

sleep(2);
llamará a notificarObservers() encargado de enviar una señal a cada
$obj->Evento('Test 2');
uno de los objetos registrados; para ello se recurre al método notificar
sleep(2);
(que deben implementar todos los objetos observadores que implemen-
$obj->deregistrarObserver(new SalvarLog());
ten la interface Observer).
$obj->Evento('Test 3');

En un sistema de Logs podríamos tener varias clases que se encarga- ?>

ran de registrar eventos en un fichero, base de datos, etc.


Ejercicio: Crear una clase que reciba alertas cuando se modifique un
class Log implements Observer{ DNI apoyándose en la clase Dni.
! public function notificar($sender, $param){

! ! echo get_class($sender)." envio $param a las ".


Patrón estructural Decorator
! ! date('h:i:s', time())."<br />";

! }
Intención: Responde a la necesidad de agregar funcionalidad a un obje-
}
to por medio de la asociación de clases. Es un objeto con el que se pue-
de ejecutar funcionalidades de varias clases a la vez.
35
Motivación: Añadir responsabilidad a objetos individuales en lugar de a
D IAGRAMA 2.1 Patrón Decorator
toda la clase.

Aplicabilidad: !

• Añadir objetos individuales de forma dinámica y transparente.

• Cuando la extensión mediante la herencia no es viable.

• Hay una necesidad de extender la funcionalidad de una clase, pero


no hay razones para extenderlo a través de la herencia.

• Hay la necesidad de extender dinámicamente la funcionalidad de un


objeto y quizás quitar la funcionalidad extendida.

Estructura:

Como ejemplo tenemos una clase Libro que contiene la funcionalidad


básica de almacenar el autor y el nombre.

class Libro {

private $autor;

private $titulo;

public function __construct($titulo, $autor) {

$this->autor = $autor;

$this->titulo = $titulo;

36
public function getAutor() { }
return $this->autor; }

} La clase LibroDecorator añade nueva funcionalidad pero que en ningún


public function getTitulo() { caso modifica al objeto original. El método resetLibro() recupera los valo-
return $this->titulo;
res originales del objeto Libro y los almacena como propiedades.
}
} Para añadir nueva funcionalidad a la clase LibroDecorator podemos
Para añadir nueva funcionalidad a esta clase con el patrón Decorator crear nuevas clases que sean derivadas de esta y no de la clase origi-
creamos una nueva clase que será la encargada de “decorar” el objeto. nal.
Esta clase nueva recibe un objeto del tipo Libro. class LibroDecoratorExclamacion extends LibroDecorator {

class LibroDecorator { private $libroDecorado;

protected $libro; public function __construct(LibroDecorator $libroDecorado) {

protected $titulo; $this->libroDecorado = $libroDecorado;

protected $autor; }

public function __construct(Libro $libro) { public function decoraTituloExclamacion() {

$this->libro = $libro; $this->libroDecorado->titulo = "!" .

$this->resetLibro(); ! $this->libroDecorado->titulo . "!";

} }

public function resetLibro() { }

$this->titulo = $this->libro->getTitulo();

$this->autor = $this->libro->getAutor(); class LibroDecoratorEstrella extends LibroDecorator {

} private $libroDecorado;

public function getTitulo() { public function __construct(LibroDecorator $libroDecorado) {

return $this->titulo; $this->libroDecorado = $libroDecorado;

} }

public function getAutor() { public function decoraTituloEstrella() {

return $this->autor; $this->libroDecorado->titulo =

} ! Str_replace(" ","*",$this->libroDecorado->titulo);

public function __toString() { }

return $this->getTitulo().' -- '.$this->getAutor(); }

37
Todas las clases que derivan de LibroDecorator actuarán con los datos
almacenados en LibroDecorator y la clase original quedará intacta, es
decir, LibroDecoratorExclamacion solo “decora” o “añade funcionalidad”
sin tener que tocar Libro.

Para ver el efecto de estas clases podemos ejecutar el siguiente ejem-


plo:

$libroTolkien = new Libro("El Hobbit", "J.R.R. Tolkien");

$libroTolkienDecorado = new LibroDecorator($libroTolkien);

$libroTolkienDecoradoExclamacion = new

! LibroDecoratorExclamacion($libroTolkienDecorado);

$libroTolkienDecoradoEstrella = new
! LibroDecoratorEstrella($libroTolkienDecorado);

echo "Libro Original:<br>";

echo $libroTolkienDecorado . "<br><br>";

echo "Libro con Exclamaciones:<br>";

$libroTolkienDecoradoExclamacion->decoraTituloExclamacion();

$libroTolkienDecoradoExclamacion->decoraTituloExclamacion();

echo $libroTolkienDecorado . "<br><br>";

echo "Libro con Estrella:<br>";

$libroTolkienDecoradoEstrella->decoraTituloEstrella();

echo $libroTolkienDecorado . "<br><br>";

Ejercicio: Añadir una nueva clase que decore Libro para que invierta
el orden de salida de los datos (antes el título y después el autor).

38
Bases de datos

3
• SQLite
• MySQL
• Factoría de conectores
• PDO
Capítulo 3

Bases de datos permite trabajar a todos los ficheros como si fueran una única base de
datos.

Entre las características de SQLite tenemos:

• Implementa muchas funcionalidades de SQL 92, incluidos disparado-


res y transacciones.

• Protección de integridad de datos con commit y rollback.


SQLite • Los ficheros de datos pueden moverse de servidor y seguirán funcio-

SQLite es una librería que implementa un conjunto de sentencias bastan- nando.
te amplio de SQL 92 estándar. La librería es muy pequeña y 2 ó 3 veces
más rápida que los gestores de bases de datos MySQL o PostgreSQL. • Soporta bases de datos de 2 Terabytes.
Necesita muy poco tiempo de ejecución y muy poca memoria para el
• El código fuente es de dominio público.
proceso. No se necesita administrar la base de datos fuera del entorno
de programación, ya que no es un servidor, pero existe un problema deri-
Creación de una Base de datos
vado de esto. SQLite se basa en el almacenamiento de datos en un fi-
La interface de programación de la extensión SQLite es muy similar al
chero, por lo tanto, cada vez que se almacenan datos, el fichero que con-
gestor de bases de datos MySQL y PostgeSQL. La diferencia principal
tiene los registros se bloquea durante el tiempo de la actualización y no
es que SQLite, aunque acepta la definición de datos con la sentencia
permite a otros usuarios interactuar. Si la base de datos se utiliza sola-
CREATE, internamente sólo diferencia entre valores alfanuméricos o va-
mente para leer, la velocidad será muy elevada, pero si la utilizamos pa-
lores numéricos, es decir, que podemos omitir el tipo de los campos en
ra actualizar constantemente los datos, SQLite perderá más tiempo ne-
la definición de las Tablas.
gociando los bloqueos del fichero que dando servicio. Aun así, SQLite 3
añade un par de técnicas interesantes para lograr un mayor rendimien- Si la base de datos no existe, SQLite la crea. El ejemplo siguiente crea
to: Cuando existe un proceso escribiendo en la base de datos, no se ne- la base de datos de usuario e inserta algunas filas de ejemplo.
cesita bloquear el fichero para lectura y se permite alguna forma de ac-
<?php
ceso a los datos; además, se propone una forma de crear Tablas separa-
$base_datos = new SQLite3("Usuario.db");
das en distintas bases de datos y enlazarlas con el comando attach, que
$consulta = "CREATE TABLE Usuario

(id_usuario INTEGER PRIMARY KEY,

40
nombre CHAR(255) NOT NULL, Hay veces que necesitamos saber el último valor auto incremental que
cuenta INTEGER NOT NULL); SQLite ha generado para asociar una imagen, o un archivo, a un regis-
INSERT INTO Usuario (id_usuario, nombre, cuenta)
tro de una tabla. Para averiguar este dato se puede utilizar el método de
VALUES (1, \"Luis Miguel\",7011);
SQLite lastInsertRowID().
INSERT INTO Usuario (id_usuario, nombre, cuenta)

VALUES (2, \"María Fernanda\",3454); <?php

INSERT INTO Usuario (id_usuario, nombre, cuenta) $base_datos = new SQLite3("Usuario.db");

VALUES (3, \"Pedro\",3445); $base_datos->query("INSERT INTO Usuario (nombre, cuenta) VALUES (\"José

INSERT INTO Usuario (id_usuario, nombre, cuenta) ! ! ! A.\",2677)");

VALUES (4, \"Javier\",1123);"; $ultimo_usuario = $base_datos->lastInsertRowID();

$base_datos->query($consulta); $base_datos->close();

$base_datos->close(); echo "Último usuario creado : " . $ultimo_usuario;

?> ?>

Al ejecutar el código veremos que existe un nuevo archivo llamado En el ejemplo puede ver la posible utilización del método. Otro método
Usuario.db que contendrá los datos que se han añadido. útil es changes(), que devuelve el número de filas afectadas por una
consulta anterior. Si borra varias filas, esta función le devolverá el núme-
El método query() permite añadir varias líneas de SQL, que se ejecuta- ro de filas que han sido borradas.
rán de forma secuencial.
<?php

Algunos gestores de bases de datos necesitan añadir una sentencia es- $base_datos = new SQLite3("Usuario.db");

pecífica para que uno de los campos sea auto incremental, es decir, que $base_datos->query("INSERT INTO Usuario (nombre, cuenta) VALUES (\"José

su valor aumente poco a poco sin necesidad de añadirlo nosotros; en ! ! ! A.\",2677)");

realidad es gestionado por la propia base de datos. Los campos enteros $ultimo_usuario = $base_datos->lastInsertRowID();

que llevan asociada la sentencia PRIMARY KEY automáticamente son $base_datos->close();

tratados como campos auto incrementales y, en las órdenes INSERT se echo "Último usuario creado : " . $ultimo_usuario;

?>
puede omitir la inclusión del valor, porque SQLite le dará el valor correla-
tivo de la fila que le corresponda. Recuperar datos
Como hemos visto antes, el método query() da la posibilidad de hacer
Últimos cambios en una tabla cualquier tipo de consulta vista al principio del capítulo. Las sentencias
CREATE, INSERT, DELETE o UPDATE no necesitan devolver ningún

41
dato, pero la sentencia SELECT tiene que devolver los datos requeridos. MySQL
La variable que obtiene los datos se considera como un recurso y debe PHP soporta muchos de los gestores de bases de datos relacionales
aplicarse un método para recuperar las filas una a una. Lo podemos existentes en el mercado. Las dos alternativas más comunes son Post-
comprobar con un ejemplo. greSQL y MySQL. Aunque PostgreSQL es mucho mejor en cuanto a ca-
<?php racterísticas y funciones soportadas del SQL 92 estándar, MySQL se ha
$base_datos = new SQLite3("Usuario.db"); hecho más popular en el ambiente de los servidores Web. Cuando los
$resultado = $base_datos->query("SELECT * FROM Usuario"); servidores Web ofrecen su servicio como LAMP se refieren a Linux +
if (!$resultado) { Apache + MySQL + PHP.
echo "Parece que hay un error";
Este capítulo cubre las operaciones más comunes que los desarrollado-
} else {
res de PHP pueden hacer con MySQL, desde recuperar o modificar da-
while ($fila = $resultado->fetchArray(SQLITE3_ASSOC)) {
tos, buscar textos o hacer una copia de seguridad de la base de datos.
foreach ($fila as $indice => $valor) {

echo "$indice: $valor<br />";

}
Conexión a MySQL
! }
La conexión no puede ser más sencilla. Es un proceso de dos pasos:
}
• Se conecta con el servidor de MySQL.
$base_datos->close();

?> Se solicita la conexión a una base de datos específica.



Después de hacer una consulta con el método query(), la variable $resul- Es importante recordar que MySQL es un servidor que puede estar aloja-
tado recibe un tipo de dato SQLite3Result. El método fetchArray() extrae do en el mismo ordenador que PHP o en otro diferente. Por eso, la cone-
de la variable $resultado todas las filas devueltas en forma de array. Pa- xión se hace algo diferente a SQLite. Para conectar a MySQL es necesa-
ra ello hemos construido un bucle while donde la variable $fila va reci- rio enviar como parámetros la dirección del servidor, el usuario y la con-
biendo los registros. Para acceder a cada columna de un registro simple- traseña.
mente tenemos que hacer uso de la variable como si fuera un array (nu-

<?php
mérico o asociativo) y como índice el nombre de la columna o el número
$servidor = "localhost";
de registro de esta forma: $fila["nombre"], $fila["cuenta"], $fila[0], $fila[1].
$usuario = "root";
Dentro del bucle hemos utilizado otro bucle foreach para mostrar por
$pass = "";
pantalla todos los valores del array.
$base_datos = "Usuarios";

$base_datos_mysql = new mysqli($servidor,$usuario,$pass,$base_datos);

42
En el caso de MySQLi se utilizan dos propiedades públicas para almace-
$consulta = "CREATE TABLE Usuario nar estos datos:
(id_usuario INTEGER PRIMARY KEY AUTO_INCREMENT,

nombre CHAR(255) NOT NULL, • $base_datos_mysql->insert_id para averiguar el último valor de inser-
cuenta INTEGER NOT NULL); ción.
INSERT INTO Usuario (id_usuario, nombre, cuenta)
VALUES (1, \"Luis Miguel\",7011);
• $base_datos_mysql->affected_rows para obtener las filas afectadas.
INSERT INTO Usuario (id_usuario, nombre, cuenta)
VALUES (2, \"María Fernanda\",3454);
Recuperar datos
INSERT INTO Usuario (id_usuario, nombre, cuenta)
Para recuperar datos de una SELECT tendremos que actuar de la mis-
VALUES (3, \"Pedro\",3445); ma forma que con SQLite.
INSERT INTO Usuario (id_usuario, nombre, cuenta)
<?php
VALUES (4, \"Javier\",1123);";
require_once("configuracion.php");
$base_datos_mysql->multi_query($consulta);
$base_datos_mysql = new mysqli($servidor,$usuario,$pass,$base_datos);
$base_datos_mysql->close();
$resultado = $base_datos_mysql->query("SELECT * FROM Usuario");
?>
if (!$resultado) {

 echo "Parece que hay un error";
La clase mysqli permite conectar a un servidor. En este caso, hemos } else {
puesto como servidor a localhost porque estamos utilizando nuestro or- while ($fila = $resultado->fetch_array(MYSQLI_ASSOC)) {

denador local para hacer las pruebas. foreach ($fila as $indice => $valor) {

echo "$indice: $valor<br />";


Otra cuestión importante es que el servidor MySQL puede almacenar va- }
rias bases de datos de un mismo usuario, por eso tenemos que añadirla }
a la conexión, para seleccionar la que queremos utilizar.
 }
Por último, tenemos el método close() que se encarga de cerrar una co- $base_datos_mysql->close();
nexión con el servidor. ?>

Tal y como vimos en el apartado de SQLite, la clase mysqli tiene tam- Uno de los grandes problemas de PHP para utilizar sus objetos de cone-
bién definidos los métodos para averiguar el número de filas afectadas y xión a bases de datos es que cada clase tiene un nombre distinto que
el último valor autonumérico generado después de un INSERT. identifica al tipo de conexión (SQLite3, mysqli, PostgreSQL). Cambiar de
gestor de bases de datos un proyecto con muchas líneas de código, pue-

43
de suponer tener que cambiar cada una de las líneas que se refieran al base de datos, su dirección y otros detalles necesarios para su cone-
gestor de bases de datos en particular. Por eso lo ideal es crear una cla- xión.
se genérica que se sirva mediante un Factory Method.
El método query es muy parecido al método de msqli.
Ejercicio: Crear una Factoría que permita seleccionar entre las dos
$result es un poco más interesante. Si bien sigue siendo un objeto opa-
bases de datos distintas.
co, ahora es un generador: ya no es necesario llamar a una función para
extraer un valor y avanzarlo; eso lo hace foreach, dejando más en claro
PDO (PHP DATA OBJECT) que estamos haciendo algo con cada elemento.

PDO es una capa de abstracción sobre las llamadas a bajo nivel de con- PDO agrega soporte global para cosas como consultas preparadas, aun-
sultas a DB. Se compone de 3 clases: PDO, que se encarga de las cone- que el motor no las soporte nativamente. Un beneficio de esto es que
xiones, PDOStatement, que envuelve consultas y resultados y PDOEx- repetir una misma consulta con diferentes valores se vuelve más simple,
ception, su tipo nativo de excepciones. y a veces hasta más rápido (si el motor lo soporta, no es necesario man-
dar (y procesar) la consulta entera, sino sólo los parámetros nuevos).
En su forma más básica, usar PDO puede ser exactamente igual que
usar cualquiera de los otros drivers a mano: Simplemente iniciamos una <?php
conexión, ejecutamos la consulta y usamos los resultados. require_once("configuracion.php");

<?php
$base_datos_PDO = new PDO("mysql:unix_socket=/var/run/mysqld/mysqld.sock;
require_once("configuracion.php");
! ! ! dbname=$base_datos; charset=utf8",$usuario,$pass);

$base_datos_PDO = new PDO("mysql:unix_socket=/var/run/mysqld/mysqld.sock;


$query = $base_datos_PDO->prepare("INSERT INTO Usuario (nombre, cuenta)
! ! ! dbname=$base_datos; charset=utf8",$usuario,$pass);
! VALUES (:nombre,:cuenta)");
$resultado = $base_datos_PDO->query("Select * from Usuario");

foreach ($resultado as $row) {


$query->bindValue(":nombre", "Antonio Quesada", PDO::PARAM_STR);
echo $row['nombre'] . " " . $row['apellidos'] . '<br>';
$query->bindValue(":cuenta", 34552, PDO::PARAM_INT);
}
$query->execute();
?>
?>
La conexión se crea pasando un DSN (Data Source Name) ó Nombre
de Fuente de Datos, que no es más que un string que indica el tipo de

44
Al hacer prepare, creamos una consulta que tiene dos valores sin defi-
nir, :nombre y :cuenta. bindValue hace que el valor de ese hueco sea
igual al nombre que pasamos como parámetro.

Al usar consultas preparadas, no es necesario preocuparse por inyeccio-


nes SQL. El driver se encarga de sanitizar cada parámetro de la manera
que vea prudente (mysql_real_escape_string vs pg_escape_string vs
SQLite3::escapeString). Por defecto, asume que todos los parámetros
son strings, pero podemos especificar que se trata de otro tipo, si es ne-
cesario.

El beneficio más grande de PDO es la consistencia: Da igual que el use-


mos PostgreSQL, MySQL, SQLite o incluso Access; sólo necesitamos
conocer una forma de realizar consultas que sirve para casi todos los
casos que se nos ocurra.

Ejercicio: Crear un formulario que capture los datos de un usuario


(nombre, apellidos, dirección y teléfono) y los introduzca en una base
de datos que se pueda elegir en el formulario (MySQL o SQLite).

45
Servicios Web

4
• XML
• SimpleXML
• Cliente y Servidor SOAP
• REST
Capítulo 4

Servicios Web
<soap:Body>
<getProductDetails xmlns="http://warehouse.example.com/ws">

<productId>827635</productId>

</getProductDetails>

</soap:Body>

</soap:Envelope>

Como puedes ver, es un archivo XML con una definición determinada.


La etiqueta <getProductDetails> contiene el nombre del método que de-
be llamarse en el servidor y <productId> contiene un parámetro que se
Introducción le pasará al método de la llamada.
El trabajo como programador puede llevarle a desarrollar aplicaciones
para distintos Sistemas Operativos y en distintos lenguajes de programa- El documento anterior será transmitido vía HTTP al servidor de SOAP,
ción. Si, en algún momento, necesita comunicar dos programas que es- que parseará el documento, ejecutará la función y devolverá un docu-
tán funcio- nando en máquinas distintas (un servidor Windows y otro mento similar con los datos de la respuesta.
GNU/Linux) y escritos con lenguajes diferentes, puede que XML-RPC O
SOAP sea la solución perfecta. Nota: Aunque en la definición de SOAP va implícita la palabra Simple,
nada más lejos de la realidad. Entender todas las opciones de SOAP
El mecanismo permite que el ordenador cliente pueda acceder a los mé- y sus múltiples formas de uso puede llevarle meses de estudio. En es-
todos del ordenador servidor y ejecutar rutinas almacenadas remotamen- te capítulo nos centraremos en la parte más sencilla y funcional del
te. SOAP (Simple Object Access Protocol) es un protocolo creado entre protocolo.
varias compañías entre las que se encuentran Microsoft e IBM. Lo que
se define es una forma de comunicar objetos de distintas aplicaciones y
ordenadores mediante un protocolo estándar. XML
XML (Lenguaje de Marcas eXtensible) forma parte de SGML (Lenguaje
Una llamada SOAP consiste en dos partes: una pregunta y una respues- de Marcas Generalizado eStándar). Aun así, no es necesario saber na-
ta. Cada una de las partes es un archivo XML que contiene los paráme- da de SGML para utilizar XML. El lenguaje XML define una estructura de
tros solicitados al servidor o los datos devueltos. documentos que pueden ser leídos por personas y por ordenadores.

Una pregunta al servidor puede ser como la que sigue: El camino más sencillo para comprender XML es pensar en cómo se utili-
<soap:Envelope xmlns:soap="http://schemas.xmlsoap.org/soap/envelope/">
zan los documentos HTML. Estos documentos son estructurados y fun-

47
cionan con etiquetas y atributos. Las etiquetas van encerradas entre sím- </biblioteca>

bolos de mayor y menor (<b>) y deben cerrarse de la misma forma aña- Como puedes ver, la estructura del documento es muy similar a la de
diendo un símbolo de barra invertida (</b>). Los documentos HTML tie- una página Web. Está formado por etiquetas y atributos, cuya definición
nen una ortografía específica, es decir, unas reglas que definen la forma puede inventarse sobre la marcha, es decir, las etiquetas <libro>, <titu-
correcta de estructurar un documento; por ejemplo, la etiqueta <body> lo> o <seccion> podrían tener un nombre totalmente diferente, pero no
debe ir siempre después de </head> o, si no existe ésta, de <html>. Ade- así en HTML, que está obligado a mantener el lenguaje definido. Algu-
más, HTML contiene un diccionario cerrado de etiquetas que definen el nos navegadores, como Mozilla, interpretan los ficheros XML.
lenguaje y no podemos utilizar otras que no estén especificadas.

En cambio, XML no tiene un diccionario de etiquetas. Las etiquetas que


aparecen son las que nosotros creamos. La única norma que tenemos
que seguir es que toda etiqueta de inicio (<coche>) debe tener una eti-
queta de fin (</coche>). El documento siguiente muestra un archivo
XML bien formado:

<?xml version="1.0" ?>

<biblioteca>

<tema id="informatica">

<libro>

<titulo>Manual Imprescindible de PHP 6</titulo>

<autor>Luis Miguel Cabezas Granado</autor>

</libro>

<libro>

<titulo>Professional PHP 6</titulo>

<autor>Lecky Thomson</autor>

</libro>

<libro>

<titulo>Agile Web Development with Rails</titulo> Un documento XML está obligado a cumplir ciertas normas, que permi-
<autor>Sam Ruby</autor> ten definir un texto bien formado:
</libro>

</tema>

48
• Debe tener un único elemento raíz. Sólo puede haber un par de eti- leer un fichero completo con una sola instrucción e, inmediatamente des-
quetas que diferencien el inicio y el final del documento, como en pués, poder leer los datos como conjunto de variables PHP.
HTML, donde se utiliza <html> y </html>.
El conjunto de funciones que pertenece a la API SimpleXML es nuevo
• Los elementos deben ser hereditarios. La estructura <a><b></b></a> en PHP 5. Mantiene una absoluta flexibilidad a favor de la simplicidad y
se permite, pero no la siguiente <a><b></a></b>. En la primera forma bajo nivel de memoria usado. SimpleXML utiliza el menor número de lí-
la etiqueta <a> envuelve a la etiqueta <b>, que es la forma correcta neas de código para leer o escribir datos en un fichero XML. El ejemplo
de escribir un documento XML. HTML, sin embargo, permite la segun- siguiente muestra cómo podemos parsear el archivo biblioteca.xml:
da forma de componer un documento. <a
<?php
href="index.php"><b></a></b>.
$biblioteca = simplexml_load_file("biblioteca.xml");

foreach ($biblioteca->tema as $tema) {


• Todos los elementos tienen que tener una etiqueta de cierre. La pare-
! echo "Tema es " . $tema["id"] . "<br>";
ja <titulo></titulo> es correcta. HTML permite etiquetas sin cerrar co-
! foreach ($tema->libro as $libro) {
mo <b> o <li>.
! ! echo "<b> " . $libro->titulo ."</b> - ";

• Los elementos pueden tener entre las dos etiquetas cualquier tipo de ! ! echo $libro->autor . "<br>";

contenido, como <titulo>Manual imprescindible de PHP 5</titulo>. ! }

}
• Los caracteres &, <, >, las comillas simples y las comillas dobles es- ?>
tán prohibidas como contenido y deben utilizarse símbolos de escape
Lo primero que llama la atención es que en apenas 10 líneas de código,
para utilizarlas.
se ha conseguido extraer todos los datos necesarios, a diferencia de
SAX o DOM. La función simplexml_load_file() crea un objeto con el ar-
SimpleXML
chivo XML que pase como argumento. A partir de aquí puede acceder a
Probablemente, el punto más fuerte de PHP 4 fue la incorporación de
todos los datos, así como acceder a las variables de los objetos:
herramientas para el soporte de XML. PHP 5 implementa nuevas herra-
mientas de lectura, elaboración y modificación de archivos XML basado echo $biblioteca->tema->libro[0]->titulo;

en la librería libxml2 y una nueva API llamada SimpleXML. echo $biblioteca->tema->libro[1]->titulo;

Para extraer el contenido que necesita lo más sencillo es crear una es-
Si XML es un lenguaje bien construido y legible por personas y ordena-
tructura de bucles foreach para ir sacando los datos ordenados.

dores, los programas para manipular estos archivos deberían ser tam-
El nombre SimpleXML es totalmente descriptivo, aunque no hay que
bién sencillos de utilizar. SimpleXML nace con esta premisa permitiendo
con- fundir la palabra simple con básico. La prueba de su potencia está
49
en que los desarrolladores de PEAR van a utilizar SimpleXML para desa- define("SOAP_VERSION", "SOAP_1_2");

rrollar su librería de SOAP. ?>

El Servicio cliente crea un objeto del tipo SoapClient al que le pasa dos
parámetros: el primero a nulo, porque no existe una descripción del Ser-
Cliente SOAP vicio en formato WSDL, y el segundo un conjunto de parámetros que de-
Los servicios SOAP tienen asociados un archivo que define los métodos terminarán el lugar donde preguntar por el Servicio, la codificación, el
que se pueden usar y las variables de entrada que admiten. Actualmen- formato de la llamada.
te hay muchas compañías que permiten ejecutar código propio a través
de peticiones SOAP, como Amazon, Google, Yahoo, eBay, etcétera. Nota: WSDL (en ocasiones leído como wisdel) son las siglas de Web
Services Description Language, un formato XML que se utiliza para
Un cliente SOAP muy sencillo podría ser el siguiente: describir servicios Web .

<?php

require_once("configuracion.php");
Servidor SOAP
try { Para obtener algún resultado es necesario tener un Servidor que respon-
$cliente = new SoapClient(null, array( da a las peticiones.
'uri' => URI,
<?php
'location' => LOCATION
require_once("configuracion.php");
));
class servicios {
echo $cliente->saludo("Luis Miguel");
public function saludo($nombre) {
}
return "HOLA $nombre";
catch (SOAPFault $f) {
}
print $f->faultstring;
}
}

?>
try {
El archivo de configuración debería contener la dirección URI y la locali- $servidorSOAP = new SOAPServer(NULL,array('uri' => URI,
zación del archivo que hará de Servidor SOAP. 'location' => LOCATION));

$servidorSOAP->setClass('servicios');
<?php
$servidorSOAP->handle();
define("URI","http://172.20.102.147:3000/");
}
define("LOCATION","http://172.20.102.147:3000/servidor.php");

50
"urn:holamundowsdl">
catch (SOAPFault $f) { <types>

print $f->faultstring; <xsd:schema targetNamespace="urn:holamundowsdl">

} <xsd:import namespace="http://schemas.xmlsoap.org/soap/encoding/" />

?> <xsd:import namespace="http://schemas.xmlsoap.org/wsdl/" />

Para crear un Servidor SOAP hay que definir una clase con los métodos </xsd:schema>

que darán los servicios. Aquellos métodos que se declaren como públi- Aplicaciones prácticas de XML 309

310 Capítulo 16 </types>


cos serán los encargados de funcionar como funciones del Servicio
<message name="holamundoRequest">
Web.
<part name="saludo" type="xsd:string" />

</message>
Ejercicio: Crear por parejas un cliente y un Servidor SOAP que de-
<message name="holamundoResponse">
vuelva la hora del Sistema y el desfase entre máquinas de la hora.
<part name="return" type="xsd:string" />

</message>

WSDL <portType name="holawsdlPortType">

son las siglas de Web Services Description Language, un formato XML <operation name="holamundo">

que permite describir Servicios Web. Define la forma de comunicación, <documentation>Devuelve un Ok si el usuario existe en el LDAP

</documentation>
es decir, los requisitos del protocolo y los formatos de los mensajes ne-
<input message="tns:holamundoRequest"/>
cesarios para interactuar con los servicios Listados en su catálogo. Las
<output message="tns:holamundoResponse"/>
operaciones y mensajes que soporta se describen en abstracto y se li-
</operation>
gan después al protocolo concreto de red y al formato del mensaje. A
</portType>
continuación puedes ver un archivo WSDL completo.
<binding name="holawsdlBinding" type="tns:holawsdlPortType">
<?xml version="1.0" encoding="UTF-8"?> <soap:binding style="rpc"
<definitions xmlns:SOAP-ENV="http://schemas.xmlsoap.org/soap/envelope/" transport="http://schemas.xmlsoap.org/soap/
xmlns:xsd="http://www.w3.org/2001/XMLSchema" xmlns:xsi="http://www.w3.org/ http"/>
2001/XMLSchema-instance" xmlns:SOAP-ENC="http://schemas.xmlsoap.org/soap/ <operation name="holamundo">
encoding/" xmlns:tns="urn:holamundowsdl" <soap:operation soapAction="urn:holawsdl#holamundo" style="rpc"/>
xmlns:soap="http://schemas.xmlsoap. <input><soap:body use="encoded" namespace="urn:holawsdl"
org/wsdl/soap/" xmlns:wsdl="http://schemas.xmlsoap.org/wsdl/"
encodingStyle="http://schemas.xmlsoap.org/soap/encoding/"/></input>
xmlns="http://schemas.xmlsoap.org/wsdl/" targetNamespace=

51
<output><soap:body use="encoded" namespace="urn:holawsdl" require_once("configuracion.php");
require_once("clases.php");
encodingStyle="http://schemas.xmlsoap.org/soap/encoding/"/></output>
require("Zend/Soap/Server.php");
</operation>
require("Zend/Soap/Wsdl.php");
</binding>
require("Zend/Soap/Wsdl/Strategy/ArrayOfTypeComplex.php");
<service name="holawsdl">
require("Zend/Soap/AutoDiscover.php");
<port name="holawsdlPort" binding="tns:holawsdlBinding">
<soap:address
ini_set('soap.wsdl_cache_enabled', '0');
location="http://localhost:3000/holaserver.php"/>
ini_set('soap.wsdl_cache_ttl', '0');
</port>

</service>
try {
</definitions>
! if (isset($_GET['wsdl'])){
Como puede ver, resulta algo complejo de leer un archivo WSDL y lo me-
! $autodiscover = new
jor es utilizar alguna herramienta para crearlos. Existe una biblioteca de
Zend_Soap_AutoDiscover('Zend_Soap_Wsdl_Strategy_ArrayOfTypeComplex');
clases que forma parte de Zend Framework, que facilita esta tarea. Las
! $autodiscover->setClass('Fecha');
partes del archivo pueden diferenciarse en:
! $autodiscover->handle();

! } else {
• <definitions> Etiqueta raíz con la descripción del Servicio.
! ! $servidorSOAP = new SOAPServer(WSDL);

• <types> Define los tipos de datos que se utilizarán. ! ! $servidorSOAP->setClass('Fecha');

! ! $servidorSOAP->handle();
• <message> Mensajes de solicitud y respuesta del Servicio. ! }

}
• <portType> Métodos disponibles.
catch (SOAPFault $f) {

• <service> La URI del Servicio. print $f->faultstring;

}
Zend Framework utiliza una clase llamada Autodiscover que permite lo- ?>
calizar todos los métodos dentro de una clase y generar un archivo Para obtener el archivo WSDL habrá que invocar el Servicio como servi-
WSDL. El siguiente código muestra como hacer un Servidor con WSDL: dor?wsdl; así obtendremos el descriptor que servirá para hacer una lla-
<?php mada desde un cliente SOAP. Si la variable wsdl se pasa por GET, se
crea un objeto del tipo Autodiscover y realiza su función. Si no está defi-

52
nida esta variable, se crea un objeto SOAPServer y se procede como en ! ! $diferenciaHoras = new DateTime();

los ejemplos anteriores. ! ! $fechaStampRemoto->setTimestamp($fechaTimeStampRemoto);

! !
$diferenciaHoras->setTimestamp($fechaStampSistema->getTimestamp()
Hay que ayudar un poco a la clase Autodiscover a generar el WSDL di- ! ! + CORRECTOR - $fechaStampRemoto->getTimestamp());
ciéndole los tipos de los datos que se van a pasar como parámetro, y ! ! return $diferenciaHoras->getTimestamp();
los que se van a obtener como respuesta. Esto se hace mediante el es- ! }
tándar docblocks, que permite documentar el código de PHP de forma
que después se genera una documentación en forma de API. }
?>
Así, la clase Fecha quedaría de la siguiente forma:
El cliente SOAP solo tendría que hacer mención al WSDL para empezar
<?php
la ejecución.
class Fecha {

/** $cliente = new SoapClient(WSDL);

* Exporta la fecha del Sistema. Cuando se utiliza WSDL, la clase SoapClient tiene un método mágico
* para obtener todas las funciones que se pueden realizar con un Servicio
* @return string SOAP determinado.
*/
print_r($cliente->__getFunctions());
! public function fechaDelSistema() {

! ! return date("d-m-Y"); REST


! } REST (Representational State Transfer) es una técnica de arquitectura
de software que permite compartir información a través de HTTP. La
! /** idea básica es que cada documento o recurso a compartir tiene una úni-
* Calcula la diferencia entre la fecha del sistema y la remota. ca dirección URI, por ejemplo, el apartado de agenda de Gobex podría
* s e r w w w. g o b e x . e s / a g e n d a / 2 0 1 3 / 0 6 / 2 6 e n l u g a r d e
* @param integer http://www.gobex.es/cons/view/main/index/agendaPage.php?id=2.
* @return string

*/ Muchas compañías que utilizaban SOAP como método para compartir y


! public function diferenciaHoraria($fechaTimeStampRemoto) { trabajar con su información han visto en REST una forma más sencilla
! ! $fechaStampRemoto = new DateTime(); de interactuar con los desarrolladores.
! ! $fechaStampSistema = new DateTime();

53
Algunos frameworks modernos como Ruby on Rails exportan directa-
mente REST en varios formatos como XML o JSON.

Para leer datos de una aplicación típica de usuarios el código necesario


sería parecido a:

<?php

require_once("configuracion.php");

$direccionRest = curl_init();

curl_setopt($direccionRest, CURLOPT_URL, URI);

curl_setopt($direccionRest, CURLOPT_RETURNTRANSFER, 1);

$datosRest = curl_exec($direccionRest);

curl_close($direccionRest);

$xmlRest = simplexml_load_string($datosRest);

foreach ($xmlRest as $datos) {

echo $datos->nombre;

echo "<br />";

?>

Ejercicio: Crear una factoría que permita leer REST en formato XML
o JSON.

54
Pruebas Unitarias

5
• Test unitarios
• Dobles de test
• Unit Test at FIRST
• Coverage
Capítulo 5

Pruebas Unitarias te la validez de partes o unidades del software, también llamadas prue-
bas unitarias.

Este tipo de pruebas son las más pequeñas realizables en una aplica-
ción. Evaluarán cada componente individual de una aplicación a nivel de
método de clase. Esto permite aislar de forma más eficiente el comporta-
miento erróneo que pueda producirse en la aplicación.

Vamos a ver cómo pasamos de un código simple de pruebas a un códi-


Introducción go de prueba automatizado.
Hasta los buenos programadores cometen errores. La diferencia entre
En este ejemplo suponemos que tenemos un Array que inicialmente es-
un programador bueno y un programador malo es que el bueno realiza
tá vacío por lo que la función sizeof() debería devolver 0. A continuación
pruebas para detectar sus errores tan pronto como sea posible. Cuanto
añadimos un elemento de forma que sizeof() retorne 1. El código a pro-
antes haga pruebas para encontrar un error, mayor es la probabilidad de
bar tiene el siguiente aspecto.
encontrarlo y menor el coste de encontrarlo y corregirlo.
<?php
El propósito de las pruebas automatizadas es asegurar que el comporta- $fixture = Array();
miento deseado y el actual de una aplicación se mantenga consistente a $fixture[] = “elemento”;
lo largo del tiempo. ?>

Existen varios tipos de pruebas cada una dirigida a un aspecto específi- La manera habitual de probar el tamaño del array es imprimiendo por
co de la aplicación. pantalla el valor que devuelve la función sizeof() antes y después de aña-
dir un elemento y comprobar realmente que devuelve 0 y 1 respectiva-
UNIT TEST: Pruebas Unitarias mente.

<?php

$fixture = Array();
Existe una diferencia entre realizar pruebas, es decir, comprobar que tu
echo sizeof($fixture).”\n”;
programa se comporta como se espera, y ejecutar una batería de tests,
$fixture[] = “elemento”; echo sizeof($fixture).”\n”;
es decir, fragmentos de código ejecutable que prueban automáticamen-
?>

56
En este tipo de test somos nosotros los que tenemos que interpretar los Ahora ya podemos hablar de tests automatizados. El objetivo de tener
valores que se muestran por pantalla. Para que tanto nosotros como automatizados los tests es el de cometer menos errores.
otros programadores puedan entender directamente lo que se expresa
con estas pruebas sería útil automatizar su salida mostrando por ejem- Aunque podemos desarrollar nuestra propia infraestructura para realizar
plo ‘ok’ cuando la prueba sea correcta y ‘not ok’ cuando algo va mal en las pruebas unitarias, en la actualidad existen diversos frameworks que
los tests. proporcionan todas las utilidades y extensiones necesarias para ejecutar
pruebas y ver sus resultados. Este tipo de frameworks se conocen como
<?php xUnit ya que todos se derivan del creado originalmente por Kent Beck
$fixture = Array(); para Smalltalk: SUnit. A partir de éste se crearon JUnit para Java, NUnit
echo sizeof($fixture) == 0 ? ”ok\n” : “not ok\n”;
para .Net, etc. En PHP los frameworks más utilizados son PHPUnit,
$fixture[] = “elemento”;
Simple Test y PHPT.
echo sizeof($fixture) == 1 ? ”ok\n” : “not ok\n”;

?> Para la mayoría de los proyectos el estándar en PHP es PHPUnit que


Como se puede comprobar, existe código duplicado que podemos refac- dispone de los conceptos y características presentes en otros fra-
torizar creando una función a la que llamar para realizar las pruebas. meworks de pruebas. Nosotros utilizaremos PHPUnit para ver qué son y
para qué sirven las pruebas unitarias.
<?php

$fixture = Array(); PHPUnit puede ejecutarse desde una consola o shell y está implementa-
assertTrue( sizeof($fixture) == 0 ); do en la mayoría de los 3 IDE que soportan PHP, ya sea en forma de plu-
$fixture[] = "elemento"; gin o de forma nativa.
assertTrue( sizeof($fixture) == 1 );

function assertTrue($condition){ Llamamos Casos de Prueba a las clases que contienen la lógica necesa-
if(!$condition){ ria para probar a otras clases.
echo "Assertion failed.";
Para tener un mayor control sobre estos casos de prueba se suelen dis-
} else {
tribuir en el sistema de ficheros de la misma manera que los archivos de
echo "Assertion OK.";
nuestro proyecto que contienen las clases a probar. Para ello lo más ha-
}
bitual es tener un directorio /tests en nuestro directorio de proyecto, que
echo "<br/>";
a su vez tiene la misma distribución de archivos de clases y directorios
}
que nuestro proyecto. La diferencia es que estas clases son los casos
?>

de prueba que utilizaremos para probar las clases del proyecto.

57
Cuando utilizamos PHPUnit las clases que actúan como casos de prue- nerado por nosotros. Lo normal es tener nuestro código en una clase de-
ba heredan de la clase PHPUnit_Framework_TestCase o de subclases finida en un fichero independiente que llamamos MyOwnArray.php:
de ésta.
<?php

1. Los tests de una clase llamada Class se incluyen en una clase llama- class MyOwnArray

{
da ClassTest.
private $myArray;
2. En la mayoría de las ocasiones esta clase ClassTest hereda de PHPU- function __construct()
nit_Framework_TestCase. {

$this->myArray = Array();
3. Los test son métodos públicos, sin parámetros y con el prefijo test* en }
su nombre. public function addElementToMyArray($element)

{
4. En los tests se utilizan afirmaciones (asserts), como assertEquals(),
$this->myArray[] = $element;
para comprobar que el valor obtenido tiene la relación indicada con el
}
valor esperado, en este caso que ambos sean iguales.
public function getMyArray()

5. Para lanzar estos tests, se utiliza la línea de comandos. {

return $this->myArray;
Es posible ejecutar los tests llamando al fichero que contiene los tests o }
a una clase en particular. Si llamamos a la clase, ésta debe existir en el }
mismo directorio en el que ejecutamos PHPUnit y en un fichero con el ?>
mismo nombre que la clase. El test para el código siguiente quedaría de esta forma:

Si llamamos al fichero, éste debe contener una clase con el nombre <?php

igual que el del fichero. require_once("ejemplo5/MyOwnArray.php");

class MyOwnArrayTest extends PHPUnit_Framework_TestCase


Antes de continuar vamos a diferenciar en nuestro ejemplo el código {
que está siendo probado, también llamado Sujeto de Prueba o SUT public function testNewArrayIsEmpty()
(Subject Under Test), del propio código del test. En nuestro código esta- {
mos realmente probando funciones propias de PHP más que código ge- $myOwnArray = new MyOwnArray();

$this->assertEquals(0, sizeof($myOwnArray->getMyArray()));

58
} A continuación se indica el tiempo empleado para realizar los tests así
public function testArrayContainsAnElement(){ como la memoria que ha sido necesaria. Esta información es muy útil ya
$myOwnArray = new MyOwnArray();
que nos permite controlar la calidad de nuestros tests.
$myOwnArray->addElementToMyArray("elemento");

$this->assertEquals(1, sizeof($myOwnArray->getMyArray())); Finalmente, se nos muestra un resumen cuantitativo de los tests realiza-
} dos, indicando el estado general de los tests, el número de tests y el nú-
} mero de afirmaciones o asserts cumplidos.
?>
Este caso es el más favorable, donde todos los tests han pasado correc-
Para lanzar los tests utilizaremos el comando:
tamente. Vamos a modificar el ejemplo para provocar que alguno de los
phpunit --colors /Test tests nos muestre que nuestro código no funciona como se espera.

Nota: Al ser PHP 5.5 una versión RC, la biblioteca de PHPUnit no fun-
ciona muy bien y a efectos prácticos se ha descargado la última ver-
sión de PHPUnit en formato paquete ejecutable para facilitar la tarea.

Para ejecutar los test:


PHPUnit también indicará si se ha producido algún tipo de error sintácti-
./phpunit.phar --colors /Test co, parando por completo los tests y mostrando la salida informativa de
PHP acerca de errores y advertencias. Hasta que no se resuelvan estos
errores no se ejecutarán los tests.
Vamos a analizar la salida que da la ejecución del comando.
Analicemos lo que se nos muestra por pantalla cuando existen tests que
La primera línea indica la versión de PHPUnit y el nombre su creador, no cumplen las afirmaciones o asserts. En la línea de símbolos se nos
Sebastian Bergmann. En la línea siguiente se resume el estado de cada muestra un carácter punto ‘.’ por cada uno de los tests pasados con éxi-
uno de los tests en forma de símbolos. En este caso puede observar to, y una letra ‘F’ en el último test, obviamente indicando que se ha pro-
dos puntos seguidos, correspondiéndose con los dos tests pasados. En ducido un fallo en el último test.
breve conoceremos más símbolos y su significado.
A continuación, se muestra información más detallada de los fallos que
se hayan producido. En este ejemplo nos indica que existe un fallo y pa-

59
sa a describirlo. Si hubiese más fallos se nos mostraría una lista enume- • Assert o Afirmar. Se comprueba que el resultado de la acción ante-
rada con la información específica. rior coincide con el valor esperado.

Aplicado a nuestro ejemplo:

<?php


require_once "ejemplo6/MyOwnArray2.php";

class MyOwnArrayTest extends PHPUnit_Framework_TestCase {

! public function testNewArrayIsEmpty() {

! ! // Arrange


! ! $myOwnArray = new MyOwnArray2();!


En la descripción señala la clase y el método del test que falla ( ! ! //Act & Assert
MyOwnArray2Test::testArrayContainsAnElement). ! ! $this>assertEquals(0, sizeof($myOwnArray>getMyArray()));

! }
En la línea siguiente muestra la afirmación o assert no cumplido que en
nuestro caso nos dice que se nos devuelve el valor 2 en la operación
! public function testArrayContainsAnElement() {
realizada cuando esperábamos 1. Más abajo nos indica el fichero de
! ! // Arrange
test concreto donde se produce el error así como la línea de código que
! ! $myOwnArray = new MyOwnArray2();

la provoca.
! ! // Act

Finalmente, el resumen muestra el literal FAILURES! para que sepamos ! ! $myOwnArray>addElementToMyArray("elemento");

rápidamente y de un vistazo que existen errores y el número de test pa- ! ! // Assert

sados, el número de afirmaciones y el de fallos. ! ! $this>assertEquals(1, sizeof($myOwnArray>getMyArray())); !

! }
Los tests unitarios suelen seguir un patrón de diseño para tests que se }
resumen en realizar tres acciones bien diferenciadas llamadas con el ?>
acrónimo AAA:
Nota: Los comentarios solo son orientativos, no es necesario utilizar-
• Arrange o Preparar. En esta fase se inicializa y configura el SUT. los.

• Act o Actuar. Se lanza la acción que queremos probar del SUT.


60
Se debería añadir a estas tres acciones o fases una última de limpieza plo, y parte de ese código será similar en múltiples test si el fixture a pro-
que deje el sistema como estaba antes de realizar el test. De hecho, co- bar es el mismo o parecido.
mo veremos a continuación, se tiene en cuenta como una parte de los
test realizando la liberación de recursos al terminar cada test. Para evitar estas repeticiones PHPUnit pone a nuestra disposición una
forma de compartir la fase de preparación de nuestros fixtures. Antes de
En la siguiente URL aparecen todos los assertions que pueden utilizar- ejecutar cada método de test se llama a un método llamado setUp(). Es
se: en este método donde crearemos e inicializaremos según el caso los ob-
jetos a los que lanzaremos nuestros tests.
http://phpunit.de/manual/3.6/en/writing-tests-for-phpunit.html#writing-test
s-for-phpunit.assertions De igual forma, una vez que cada test termine, ya tenga éxito o no, se
invocará otro método propio de PHPUnit para limpiar el sistema de los
Ejercicio: Crear Un test para la clase DNI que compruebe un DNI fal- objetos creados. Este método se llama tearDown(). Prácticamente todos
so y otro verdadero. los frameworks de testing de tipo xUnit disponen de estos dos métodos.

Es importante recordar que tanto el método setUp() como tearDown() se


Si observa detenidamente, la parte de preparación es susceptible de re- ejecutan para cada uno de los métodos de nuestra clase de prueba.
factorización. Tenemos código que se repite y rompe una de las reglas
del buen programador: No te repitas, más conocida por sus siglas en in- Veamos cómo queda nuestro código de tests limpiando el código repeti-
glés: DRY (Don’t Repeat Yourself). Este ejercicio de refactorización nos do y utilizando los métodos que gestionan los fixtures en PHPUnit.
conduce a otro concepto propio de las herramientas xUnit llamados fixtu- <?php
res. require_once "ejemplo5/MyOwnArray.php";

Si siguiésemos el esquema anterior pasaríamos mucho tiempo repitien-


class MyOwnArray3Test extends PHPUnit_Framework_TestCase
do código en la fase de preparación de los test así como en otra posible
{
fase que dejase el sistema en el estado anterior al test.
protected $myOwnArray;

En nuestro caso la preparación del test se corresponde con la inicializa- protected function setUp(){

ción de la variable $myOwnArray que es el objeto en el que vamos a rea- $this->myOwnArray = new MyOwnArray();

lizar nuestro test. Este objeto es la característica o fixture a comprobar. }

La mayoría de las ocasiones no serán tan simples como nuestro ejem-


public function testNewArrayIsEmpty()

61
$this->assertEquals(0, sizeof($this->myOwnArray->getMyArray())); características de nuestros tests debe ser la independencia con respec-
} to a recursos externos y a otros tests. Para ello existen lo que se denomi-
nan Dobles de Prueba, en el sentido de los dobles que aparecen en las
public function testArrayContainsAnElement(){
películas en las acciones peligrosas sustituyendo a los actores y actri-
$this->myOwnArray->addElementToMyArray("elemento");
ces.
$this->assertEquals(1, sizeof($this->myOwnArray->getMyArray()));
} Veamos un ejemplo de clase y de una dependiente de ésta:

protected function tearDown(){


Fichero My_Calculator.php:
unset($this->myOwnArray); <?php
} class My_Calculator {
} ! public function add($a, $b) {
?> ! ! return $a + $b;
En ocasiones se puede obviar el método tearDown() debido a que, co- ! }!
mo sucede en nuestro caso, no es necesario eliminar los objetos que }

creamos ya que PHP dispone de su propio recolector de basura. Aun ?>

así, si prefiere mantener la simetría de ambos métodos y asegurarse Fichero My_Totaller.php:


que la gestión de la memoria sea más eficaz puede seguir utilizándolo
<?php

para limpiar la memoria de los objetos utilizados, sobre todo cuando se
crean multitud de objetos en el método setUp(). tearDown() tiene más require_once “My_Calculator.php”;

sentido en el caso de tener sockets de conexión o ficheros abiertos.


class My_Totaller {

Dobles de Test ! private $calculator = null;

La mayoría de los componentes de nuestra aplicación serán dependien- ! private $operands = array();

tes de otros componentes, no operará de forma totalmente independien- ! public function getCalculator() {

te. Podemos tener clases que se componen de objetos de otras clases y ! ! if (empty($this>calculator)) {

éstas últimas de objetos de otras clases a su vez. Este hecho puede pro- ! ! ! $this>calculator = new My_Calculator;

ducir que nuestros tests sean muy dependientes o, en el argot de la ! ! }

POO, estén muy acoplados con las objetos que componen el SUT. Sin ! return $this>calculator;

! }
embargo, como veremos en la siguiente sección, una de las principales

62
private $totaller;
! public function setCalculator(My_Calculator $calculator) { protected function setUp()

! ! $this>calculator = $calculator; {

! } $this->calculator = $this->getMock('My_Calculator');

$this->totaller = new My_Totaller();

! public function addOperand($operand) {


! ! $this>operands[] = $operand; $this->totaller->setCalculator($this->calculator);

! } }
public function testCalculateTotal()

! public function calculateTotal() { {

! ! $calculator = $this>getCalculator(); $total = 0;
 $this->calculator

->expects($this->at(0))
! ! foreach ($this>operands as $operand) {
->method('add')
! ! ! $total = $calculator>add($total, $operand);
->with(0, 1)
! ! }
->will($this->returnValue(1));
! return $total;
$this->calculator
! }
->expects($this->at(1))
}
->method('add')
?>
->with(1, 2)
Los dobles de test son objetos que utilizamos en lugar de los objetos de
->will($this->returnValue(3));
los que depende la clase que queremos probar. Veamos cómo trabaja
$this->calculator
PHPUnit con los dobles de prueba también llamados mocks o stubs.
->expects($this->at(2))
Más adelante veremos cual es la diferencia entre ambos.
->method('add')

->with(3, 3)
Fichero de test My_TotallerTest.php:
->will($this->returnValue(6));
<?php $this->totaller->addOperand(1);
require_once 'lib/Totaller.php'; $this->totaller->addOperand(2);

$this->totaller->addOperand(3);
class My_TotallerTest extends PHPUnit_Framework_TestCase $this->assertEquals(6, $this->totaller->calculateTotal());
{ ! }
private $calculator;

63
} si la definición del test doble llama al método with() que, como veremos
?> más adelante, indica los parámetros y su orden al llamar al método con-
En PHPUnit se utiliza el método getMock()para crear un doble de prue- creto del doble de prueba.
ba. Este método necesita como único parámetro el nombre de la clase
para la que se necesita crear un mock. El objeto devuelto por este méto- El método expects() acepta como parámetro un buscador o matcher,
do es una instancia de una subclase creada de forma dinámica desde la que es un objeto que representa una expectativa con respecto a la llama-
clase original. De esta manera esa instancia puede utilizarse en lugar de da del método indicado en el doble de prueba. Esa expectativa puede
una instancia de esa clase original con la salvedad de que el mock so- ser el número de veces que ese método será invocado o una referencia
breescribe los métodos públicos de la clase. Lo que se hace es indicar a una invocación específica a un método. En nuestro ejemplo, estamos
con el mock los parámetros que se tienen que pasar a los métodos que indicando el orden de ejecución ($this>at($orden)) en la cadena de lla-
vayan a usarse en las pruebas así como los valores que se devuelven, madas a ese método desde nuestro test. Es decir, que qué parámetros
simulando los objetos reales. Sin embargo, en ningún momento se es- espera y qué devuelve cuando se ejecute la primera vez ($this>at(0)),
tán utilizando instancias de las clases reales. cuando se llame la segunda ($this>at(1)) y la tercer ($this>at(2)).

Si analizamos el código de nuestro test, podemos observar que en el En el método method() indicamos el método que va a ser sustituido. Si a
método setup creamos un doble de prueba con getMock()y lo inyecta- continuación utilizamos el método with(), que es opcional, podemos aña-
mos en la clase a probar o SUT, My_Totaller a través del método setCal- dir restricciones a los parámetros del método nombrado en method(). Ca-
culator(). Posteriormente, el método testCalculateTotal() llama a calcula- da parámetro pasado en with()puede ser un matcher o un valor escalar
teTotal()de My_Totaller que internamente llama a getCalculator(), que es y se tiene que corresponder con cada parámetro y en el mismo orden
el que devuelve el doble de prueba. Aunque, si se observa detenidamen- del método sustituido. Pasar un valor escalar es equivalente a pasar ese
te, el comportamiento de nuestro mock lo estamos definiendo en el mis- valor encapsulado en una llamada a $this>equalTo(), que devuelve un
mo método que realiza el test. matcher que comprueba la equivalencia con el valor especificado. En el
apéndice D de este capítulo puede consultar más matchers para with().
Cuando definimos estos métodos, por defecto, si los creamos sin pará-
metros y sin asignarle una lógica siempre devuelven null. Cuando crea- El método will() se utiliza para especificar el resultado de la llamada al
mos un doble de prueba en cuya lógica hacemos ciertas comprobacio- método. En nuestro ejemplo usamos $this->returnValue() para devolver
nes como el número de parámetros, su tipo, valores esperados
 un valor concreto. También podemos usar varios métodos para jugar
de los parámetros, etc.; estamos hablando de un mock. En caso contra- con los valores devueltos, como por ejemplo, devolver valores diferentes
rio, cuando la lógica no realiza ninguna comprobación, se trata de un en una secuencia de llamadas utilizando el método $this->onConsecuti-
stub. Para detectar rápidamente si se trata de uno u otro basta con ver veCalls(). También podemos retornar uno de los parámetros pasados al

64
método con $this>returnArgument()o lanzar una excepción con $this- De hecho, cuanto más rápido sean nuestros tests, más a menudo los
>throwException(). lanzaremos porque no nos supondrá un gasto considerable de tiempo
de desarrollo.
Si el sistema de test dobles de PHPUnit le resulta limitado puede probar
con otros frameworks dedicados completamente a este apartado como También es posible ejecutar grupos de tests de forma independiente pa-
son Mockery (https://github.com/padraic/mockery) y Phake ra acortar los tiempos de prueba, pero siempre sin olvidar que todos los
(https://github.com/mlively/Phake). tests tienen que pasar a verde para asegurarnos de que no tenemos nin-
gún error en nuestro código. Es decir, que aunque a menudo ejecute-
Unit Test are FIRST mos grupos separados de test para ir más rápido, también es necesario,
Para realizar pruebas unitarias efectivas se han de seguir los criterios aunque con menor frecuencia, pasar todos los tests.
agrupados con el acrónimo FIRST (en inglés Fast, Isolated, Repeatable,
Selfverifying, Timely). Este acrónimo resalta una característica principal Acciones que pueden ralentizar los tests son accesos al sistema de fi-
de TDD (TestDriven Development), una forma de hacer pruebas unita- cheros, ya sea para leer o escribir; obtener información de páginas web
rias (que veremos más adelante) donde se crean los test antes que el u otros recursos remotos con su correspondiente latencia, etc. La solu-
código. ción pasa por simular este tipo de acciones utilizando una especie de
sustitutos ligeros que devuelvan los datos esperados por el SUT, ya que
Fast (Rápido) no evaluamos esas acciones, sino lo que el SUT hace con la informa-
El número de test que podemos llegar a tener puede ser considerable. ción obtenida con esas acciones.
Si los tests tardan en ejecutarse, pasaremos más tiempo ejecutando
tests que desarrollando nuestra aplicación. Cada test debe ser muy rápi-
Isolated (Independientes)
do para que la suma de todos ellos no suponga una pérdida de tiempo Cada tests debe tener una única razón para fallar. Debemos diseñar ca-
respecto a la ganancia que obtenemos por el uso de estos tests. da test para que se independiente no solo de factores externos sino tam-
bién entre ellos, un test no puede depender de ningún otro test.
Si la media de ejecución de nuestros tests es de un minuto y tenemos 5
tests, cada vez que ejecutemos los tests, algo que pasará a menudo, es- Cuando los tests son dependientes entre ellos puede suceder que reali-
taremos 5 minutos de media esperando a que terminen. Si llegamos a zar un cambio en uno de ellos genere una cadena de fallos en el resto
construir 30 tests, será nuestra vida la que pasa mientras esperamos a de tests. Un ejemplo de pobre desacoplamiento es cuando tenemos que
que las pruebas nos muestren el color verde. Si construimos tests que ordenar los tests para optimizar su ejecución. Cada test unitario debe
tardan demasiado en ejecutarse es que estamos haciendo mal esos estar contenido en sí mismo, como un caso completo que documenta un
tests. comportamiento concreto. Consideremos el siguiente ejemplo en el que
se comprueba la disponibilidad de un libro en una gestión de biblioteca.
65
public function testAvailability() {
• La volatibilidad de los recursos externos (sistemas de ficheros, bases
$this->lordOfTheRings.checkOut(TODAY); d e d a t o s , s e r v i c i o s w e b , l l a m a d a s a A P I , e t c . )

$this->assertFalse($this->lordOfTheRings->isAvailable());
Comportamiento no determinista debido a un uso incorrecto de hilos o
$this->lordOfTheRings->checkIn();
procesos.
$this->assertTrue($this>lordOfTheRings>isAvailable());

} • Dependencias de clases no inicializadas o que tardan en inicializarse.



Este tipo de test es el típico que dejamos como está por comodidad y Saturación. Por ejemplo, comparar todo el contenido HTML de una pá-
porque en parte pensamos que es más óptimo probar acciones tan sim- gina web cuando solo nos interesa una parte de toda esa información.
ples en el mismo test. Sin embargo, estamos probando dos acciones dis-
Cuando se produzcan fallos intermitentes y estemos un poco desorienta-
tintas y siempre seremos capaces de reconocer el error

dos en cuanto a su origen una forma de descifrar la causa es añadir pre-
que pueda producirse de forma más inmediata si dividimos este test en
condiciones en los test. Si estas precondiciones no se cumplen, el test
dos, cada uno probando una acción distinta.
parará antes de llegar al problema que queremos detectar:
public function testIsNoLongerAvailableAfterCheckout() {
public function testAddCustomer() {
$this->lordOfTheRings.checkOut(TODAY);
$this->assertNull($this->customerStore->getCustomer(USER_ID));
$this->assertFalse($this->lordOfTheRings->isAvailable());
$this->customerStore->add(new Customer(USER_ID));

} ...

public function testIsAvailableAfterCheckin(){ }

$this->lordOfTheRings.checkOut(TODAY); Selfverifying (Autoverificable)


$this->lordOfTheRings->checkIn();
Cada test solo puede fallar o tener éxito, no puede haber ambigüedad
$this->assertTrue($this->lordOfTheRings->isAvailable());
en cuanto al resultado. Si alguno de los tests está abierto a la interpreta-
}
ción del desarrollador, sobre todo cuando se trabaja en equipo, disminu-
De camino, cumplimos el patrón AAA típico de un test bien construido. ye drásticamente su utilidad. Esta característica de un test bien hecho
aumenta también la confianza en nuestro código, si todos los tests están
Repeteable (Repetible) en verde, podemos pasar al siguiente nivel de pruebas (de integración,
Es importante que cada uno de nuestros tests siempre ofrezcan el mis- de sistema, de carga) de forma segura.
mo resultado. Los fallos que se producen de forma intermitente en los
tests pueden tener varias causas: Una manera clásica de comprobar la métrica de cobertura del código
consiste en escribir pruebas para una gran cantidad de código.
• No se han eliminado correctamente datos estáticos u otra información
public function testCreateReport() {
que se encuentra en memoria.
66

 te ejemplos, permitiendo conocer más rápidamente y de forma directa
$catalog = new Catalog();


 qué es lo que nuestro código tiene que hacer y creando el código nece-
$catalog->add(new Book(BookTestData->AGILE_JAVA));

$catalog->add(new Book(BookTestData->JAWS));
sario para satisfacer esos tests, es decir, conseguir un verde.
$catalog->add(new Book(BookTestData->THE_TRIAL));
Además, al ajustarnos al test que tiene que satisfacer sabemos fácilmen-
$report = new InventoryReport($catalog);
te que nuestro requisito está implementado y no es necesario generar

echo $report->allBooks(); más código con mejoras inútiles no solicitadas por el usuario. Esto no
} significa que no haya que mejorar nuestro código una vez haya pasado
Si el sistema no se comporta correctamente tenemos que tener la espe- los tests, como ya veremos más adelante en los pasos a seguir cuando
ranza de que algún programador esté mirando la salida de nuestro códi- aplicamos TDD.
go para comprobar que se trata de la esperada.
Con TDD los tests son los primeros clientes de nuestro código. Al escri-
Los test automatizados deben verificar por sí mismos este tipo de condi- bir los tests antes que el código implica que tenemos que pensar en el
ciones: uso de nuestro código ante que en su implementación. Como resultado
nuestro código será más limpio al detectar más rápidamente las optimi-
public function testCreateReport() { $catalog = new Catalog();
zaciones de nombres de métodos, de variables, de listas de parámetros
... $this>assertTrue($report>contains(
y del resultado de aplicar los principios SOLID ya tratados con anteriori-
...
dad.
BookTestData.AGILE_JAVA.getClassification());

} La forma más tradicional de hacer pruebas siempre ha sido la de crear


Timely (Oportuno) éstas después del código que se trata de probar. En la historia reciente
del desarrollo de software, se han analizado los graves problemas que
Esta característica es la que define a TDTimely (Oportuno)

han existido siempre y donde se han propuesto soluciones que se ha
D (TestDriven Development). Nos indica lo oportuno que es el hecho de
comprobado que funcionan como las metodologías ágiles, los patrones
escribir los tests antes del código que prueban. Ya sea anterior o poste-
de diseño, el código limpio, los principios SOLID y TDD como parte de
rior a la creación del código que queremos implementar el hecho es que
XP, una metodología ágil. De estas soluciones se desprenden muchos
sabemos qué queremos construir antes de construirlo.
conocimientos gracias a la experiencia de programadores profesionales
Cuando escribimos las pruebas unitarias antes que el código ya esta- experimentados. Entre esa experiencia se ha comprobado que la crea-
mos especificando en ellas el comportamiento que queremos que tenga ción de pruebas unitarias después de la implementación carece de to-
nuestro código. Es decir, estamos creando las especificaciones median- das las ventajas descritas al crear los test antes que el código.

67
Los programadores que prueban el código después de su implementa- 1. La implementación de las funciones justas que el cliente necesita y
ción tienen poco o nulo interés en que sus pruebas sean especificacio- no más.
nes mediante ejemplos. Para ellos su único objetivo es comprobar si di-
versos aspectos de su código funciona o no. Estudios realizados indican 2. La minimización del número de defectos que llegan al software en fa-
que este tipo de tests unitarios cubren hasta dos terceras parte del códi- se de producción. 3. La producción de software modular, altamente
go. Al final, implica una llamada a elegir una de las dos opciones. Reali- reutilizable y preparado para el cambio.
zar test antes del código implica un sistema robusto, limpio, cubierto al
Cuando utilizamos TDD seguimos dos reglas básicas:
100% y donde la detección y corrección de errores es muy rápida. Tam-
bién implica la necesidad de invertir esfuerzo y parte de nuestro tiempo 1. Escribimos nuevo código sólo si un test automático ha fallado.
en escribir buenos tests, algo que puede implicar un aumento del tiempo
de un 15% a un 35%. 2. Eliminamos el código duplicado.

Escribir los tests después del código evita esa pérdida de tiempo que en Estas reglas se traducen en las siguientes tareas de programación:
la mayoría de las ocasiones es muy apreciada por los desarrolladores
1. Rojo. Escriba un pequeño test que, en principio, no va a funcionar.
que se resisten a adoptar TDD. Pero es una ventaja a corto plazo. A lar-
De ahí el color rojo indicando que ese test no pasa. Para ello, imagine
go plazo es un inconveniente. La detección y corrección de fallos pasa a
por un momento como le gustaría que la solución que ha pensado para
ser una labor tediosa, manual con largas sesiones de depuración y nun-
un requisito concreto aparezca en forma de código. Intente pensar en la
ca tendremos la certeza de que nuestros tests abarcan todo nuestro có-
interfaz, los métodos con sus parámetros, que le gustaría tener para ese
digo.
código. Está imaginando una historia. Incluya todos los elementos de la
TDD: TestDriven Development (Desarrollo guiado por pruebas) historia que está imaginando y que crea que son necesarios para obte-
ner las respuestas correctas.
Como dice Kent Beck en su libro “TestDriven Development by Example”,
el objetivo de TDD es código limpio que funcione, ya que se convierte en 2. Verde. Escriba el código necesario para que el test se ponga en ver-
un modo predecible de desarrollar, es decir, sabemos cuando hemos ter- de, sin tener en cuenta si su código es o no óptimo. Hágalo funcionar.
minado de implementar un requisito sin tener que sufrir largas jornadas L o i m p o r t a n t e e s q u e s u s t e s t s p a s e n a v e r d e . S i

de depuración. ya de inicio tiene una solución limpia y simple, escríbala. Si esta solu-
ción, limpia y simple, es obvia pero le llevará unos minutos, apúntela en
TDD es una técnica de diseño e implementación de software incluida alguna nota para retomarla más tarde y céntrese en el problema princi-
dentro de la metodología XP. Esta técnica tiene tres características fun- pal. Este paso suele ser duro para los programadores experimentados,
damentales:

68
que suelen tener más recursos al optimizar el código. • La jornada se hace mucho más amena.

3. Refactorice. Elimine todo el código duplicado resultado de hacer que • Uno se marcha a casa con la reconfortante sensación de que el traba-
el test pase a verde. Repase las notas generadas al escribir el código jo está bien hecho.
para que pase los tests y optimice su código.
Un ejemplo nos permitirá conocer más de cerca la forma de proceder de
Carlos Blé en su libro “Diseño Ágil con TDD”, nos señala las ventajas de TDD. Nos basamos en los siguientes requerimientos:
utilizar TDD:
Necesitamos una clase a la que se le pase dos cadenas de caracteres y
• La calidad del software aumenta. me devuelva la longitud de la cadena más larga.

• Conseguimos código altamente reutilizable. Si uno de los parámetros no es un string nos devolverá la longitud del
que sí lo es.

• El trabajo en equipo se hace más fácil, une a las personas.

Nos permite confiar en nuestros compañeros aunque tengan menos Si ninguno de los dos parámetros es un string nos devolverá false.
experiencia.
Antes de empezar haremos una lista de las características que ha de te-
• Multiplica la comunicación entre los miembros del equipo.
 ner nuestra aplicación desde un análisis inicial, ampliando los requisitos
Las personas encargadas de la garantía de calidad adquieren un rol iniciales. Esto nos permite barajar de entrada muchas posibilidades y ob-
más inteligente e interesante. tener todos los casos de prueba que pueden ser útiles para nuestro códi-
go, e incluso desechar aquellos que no aportan nada.
• Escribir el ejemplo (test) antes que el código nos obliga a escribir el
mínimo de funcionalidad necesaria, evitando sobrediseñar.
 Podemos empezar haciéndonos algunas preguntas antes de especificar
Cuando revisamos un proyecto desarrollado mediante TDD, nos da- qué acciones llevar a cabo:
mos cuenta de que los tests son la mejor documentación técnica que
podemos consultar a la hora de entender qué misión cumple cada pie- ¿ N e c e s i t a m o s c r e a r u n o b j e t o ? ¿ D e q u é c l a s e ?

za del puzzle. ¿ Te n e m o s q u e l l a m a r a a l g ú n m é t o d o d e e s e o b j e t o ?

¿Ese método tiene parámetros? ¿Son opcionales u obligatorios?

• Incrementa la productividad. ¿ Q u é p a s a s i n o l e p a s a m o s n i n g ú n p a r á m e t r o ?

¿Qué sucede si le pasamos un único parámetro de tipo distinto a string?
• Nos hace descubrir y afrontar más casos de uso en tiempo de diseño.
- ¿Y si es de tipo string?

69
22 7. Pasarle dos cadenas de diferente longitud y comprobar que devuelve
longitud de la más larga.
¿Y si pasamos los dos parámetros con tipos diferentes a string? ¿Y si
s o l o l o e s u n o d e l o s d o s ?
 E m p e z a m o s c r e a n d o n u e s t r o s p r i m e r t e s t : E j e m p l o 1 0 .

Si le paso de forma correcta dos parámetros de tipo string, ¿me devolve- Fichero StringManagerTest.php:
rá la longitud correcta? ¿Y si tienen la misma longitud?

<?php
De estas preguntas podemos ampliar los requisitos iniciales y definir class StringManagerTest extends PHPUnit_Framework_TestCase {
una serie de acciones para iniciar el diseño de nuestros tests. ! private $stringManager;

! protected function setUp() {


1. Necesitamos crear un objeto de una clase que llamaremos StringMa- ! ! $this>stringManager = new StringManager();
nager. ! }

}
2. Llamaremos a un método de ese objeto que será el que me devuelva
?>
la longitud de la cadena de caracteres más larga de las dos que le pa-
saremos como parámetros. Veamos qué salida nos proporciona PHPUnit:

3. S i n o l e p a s a m o s n i n g ú n p a r á m e t r o d e v u e l v e f a l s e .

4. Si le pasamos un único parámetro de tipo distinto a string devuelve
false.

4. Si le pasamos un único parámetro de tipo string devuelve su longitud.



6. Si le pasamos los dos parámetros de tipo distinto a string devuelve
false.
Como podemos observar, este test ya está en rojo. Nuestro primer test
5. Si le pasamos uno de los dos parámetros de tipo distinto a string y falla y nos indica el porqué. No existe ningún test en la clase que esta-
otro de tipo string, devuelve la longitud del tipo string, independiente- mos usando para hacer pruebas StringManagerTest.
mente de su orden.
Vamos a añadir el primer test para ver cómo esa ventana nos informa de
6. Pasarle dos cadenas de igual longitud y comprobar que devuelve su los tests pasados, su estado y el porcentaje de test satisfactorios. Para
longitud. realizar las tareas 2 y 3 incluimos el siguiente código:

70
Fichero StringManagerTest.php

<?php

class StringManagerTest extends PHPUnit_Framework_TestCase {

! private $stringManager;

! protected function setUp() {

! ! $this->stringManager = new StringManager();

! }

function testGetMaxLengthForEmptyParameters() {

! $result = $this->obj->getMaxLength();

! $this->assertFalse($result);

! }

}
El error es evidente: la clase que acabamos de crear no tiene el método
?>
que estamos probando en nuestro primer test, getMaxLength(). Lo crea-
Ya tenemos nuestro primer indicador desde las pruebas de que necesita- mos en StringManager.php:
mos escribir el código para probar. En este caso, de que no existe la cla-
<?php
se del objeto que queremos instanciar en las pruebas. Vamos a escribir
class StringManager {
el mínimo código necesario para que el test pase. Creamos un fichero
! public function getMaxLength() {
en nuestro proyecto para definir la clase que queremos probar.
! }

Fichero StringManager.php }

?>
<?php


Lanzamos los tests:
class StringManager {


}


?>

El resultado de pasar el test que acabamos de crear está ilustrado en la


siguiente imagen:

71
que realmente no hace nada. Pero veremos cómo a medida que crea-
mos test y código para pasarlos se va generando el código correcto y
optimizado.


 No es necesario refactorizar por ahora. Continuamos con los siguientes


Este mensaje de fallo de test ya se corresponde con un test fallido real, elementos de nuestra lista de acciones, el número 4: si le pasamos un
es decir, que no cumple la afirmación o assert que hemos definido en la único parámetro de tipo distinto a string devuelve false.
prueba. Como la función no devuelve ningún valor, se asigna el valor
Ejemplo 10.
null a la variable que usamos en el test.
Fichero StringManagerTest.php
Según TDD, debemos escribir el código mínimo para que pase el test.
Completamos la clase del SUT: function testGetMaxLengthForOneParameterNotString() {

! $notStringParameter = 1;
<?php
! $result = $this->stringManager->getMaxLength($notStringParameter);
class StringManager {
! $this->assertFalse($result);
! public function getMaxLength() {
! }
! ! return false;

! } Si lanzamos de nuevo los tests seguimos viendo el resultado en verde.


} Continuemos con más pruebas. El elemento 5 en la lista: si le pasamos
?> un único parámetro de tipo string devuelve su longitud. Probamos con
Si ahora lanzamos los test nos mostrarán un verde. Hemos implementa- una cadena con el contenido ‘Test’. Si todo va bien, nuestro método de-
do el código mínimo y solo el código mínimo, para que pasen los test. bería devolver su longitud, es decir, 4.
En estos momentos pueden surgir dudas porque tenemos una función !

72
function testGetMaxLengthForOneParameterNotString() { <?php
! $stringParameter = "Test"; class StringManager {

! $result = $this->stringManager->getMaxLength($stringParameter); ! public function getMaxLength($string) {

! $this->assertEquals($result,4); ! ! $stringLength = strlen($string);

} ! ! return $stringLength;

El resultado que obtenemos es el siguiente: ! }


}

?>

El resultado que obtenemos es que los tests iniciales también fallan:

Este test señala que ya no nos vale con que la función siempre devuel-
va false, ya esperamos un resultado correcto, la longitud del parámetro
que le pasamos. ¿Cómo afecta esto a nuestro código? Primero tiene
que pasar el test. Podríamos caer en la tentación de hacer que nuestro
método retornase el número 4 para que pase el test. Pero el resultado
es obvio.
Si nos paramos a analizar un poco, para hacer que pasen tenemos que
Haríamos pasar ese último test, pero fallarían los anteriores. La idea es realizar varias acciones. Al no pasar ningún parámetro el primer test nos
que pasen todas las pruebas. Esto implica que el código SUT tiene que señala que ese parámetro es obligatorio según la definición actual de
contener la lógica que estamos buscando. Aun así, si implementamos nuestro método. En el segundo test, al pasar un número en lugar de una
en un primer intento una lógica simple como esta: cadena de texto, éste es interpretado como una cadena de longitud 1.

Ejemplo 10.
73
Vamos a modificar el código de nuestros test para corregir estas deficien- 7. Si le pasamos uno de los dos parámetros de tipo distinto a string y
cias: otro de tipo string devuelve la longitud del tipo string, independiente-
mente de su orden.
<?php

class StringManager { 8. Pasarle dos cadenas de igual longitud y comprobar que devuelve su
! public function getMaxLength($string = null) {
longitud.
! ! $stringLength = (is_null($string) || is_numeric($string)) ?

! ! false : strlen($string); 9. Pasarle dos cadenas de diferente longitud y comprobar que devuelve
! ! return $stringLength; longitud de la más larga.
! }

}
Pasamos a tratar el elemento número 6: si le pasamos los dos paráme-
?>
tros de tipo distinto a string devuelve false.

Repasemos la lista de acciones: function testGetMaxLengthForTwoParametersNotString() {

! $notStringParameter1 = 1;
1. Necesitamos crear un objeto de una clase que llamaremos StringMa- ! $notStringParameter2 = 2;
nager. ! ! $result = $this->stringManager

! ! ! ! ->getMaxLength($notStringParameter1,
2. Llamaremos a un método de ese objeto que será el que me devuelva
! ! ! ! $notStringParameter2);
la longitud de la cadena de caracteres más larga de las dos que le pa-
! $this->assertFalse($result);
saremos como parámetros.
! }

3. Si no le pasamos ningún parámetro devuelve false. Este último test puede causar sorpresa por el hecho de que pasa sin pro-
blemas con el código del SUT actual. No es ningún inconveniente, como
4. Si le pasamos un único parámetro de tipo distinto a string devuelve veremos a continuación, los tests posteriores provocarán que este test
false. falle y nuestro código tenga que tener en cuenta este caso en particular.
5. Si le pasamos un único parámetro de tipo string devuelve su longitud. El séptimo caso indica que si le pasamos uno de los dos parámetros de
tipo distinto a string y otro de tipo string, devuelve la longitud del tipo
6. Si le pasamos los dos parámetros de tipo distinto a string devuelve
string, independientemente de su orden. Los tests nos quedan así:
false.

74
public function testGetMaxLengthForTwoParametersStringAndNotString() { ! $string2Len;
! $stringParameter = "Test"; ! return $stringLength;

! $notStringParameter = 1; ! }

! $result = $this->stringManager }

! ! ->getMaxLength($stringParameter, $notStringParameter); ?>

! $this->assertEquals($result, 4); Con estos cambios ya pasan todos nuestros tests. Nos faltan aún los
! } puntos 8 y 9. Para el octavo, le pasamos dos cadenas de igual longitud
y comprobamos que devuelve esa longitud. Esa condición ya se cumple
public function testGetMaxLengthForTwoParametersNotStringAndString() {
en la comparación de cadenas (incluso sin el comparador ‘=’) por lo que
! $notStringParameter = 1;
siguen pasando nuestros tests, aun así añadimos ese tests para tener
! $stringParameter = "Test";
cubierta esa posibilidad.
! $result = $this->stringManager

! ! ->getMaxLength($notStringParameter, $stringParameter); public function testGetMaxLengthForTwoStringParametersEqualLength() {

! $this->assertEquals($result, 4); ! $stringParameter1 = "1234"; $stringParameter2 = "Test";


! } ! $result = $this->stringManager->getMaxLength($stringParameter1,

Además de devolver un valor que no se corresponde con el esperado, ! ! ! $stringParameter2);

no está teniendo en cuenta el segundo parámetro del método. Vamos a ! $this->assertEquals($result, 4);

hacer pasar esta prueba. Como ya le estamos pasando los dos paráme- ! }

tros añadiremos el código necesario para devolver la longitud de la cade- El último punto y el más importante lo tratamos con el último test, donde
na de texto mayor . comprobamos que realmente se nos da la longitud de la cadena más lar-
ga.
<?php

class StringManager { public function testGetMaxLengthForTwoStringParameters() {


! public function getMaxLength( $string1 = null, $string2 = null ){ ! $stringParameter1 = "Test";
! if(( is_null($string1) && is_null($string2) ) || ( is_null($string1) ! $stringParameter2 = "Otro test";
&& is_numeric($string2) ) || ( is_numeric($string1) && is_null($string2) ) 

! $result = $this->stringManager
|| ( is_numeric($string1) && is_numeric($string2))) return false;
! ! ->getMaxLength($stringParameter1, $stringParameter2);
! $stringLength = 0;
! $this->assertEquals($result, 12);
! $string1Len = ( !is_null($string1) )? strlen($string1) : 0;
! }!
! $string2Len = ( !is_null($string2) )? strlen($string2) : 0;

! $stringLength = ( $string1Len >= $string2Len ) ? $string1Len:

75
Una vez cubiertos todos los casos posibles, vamos a ver qué posibilida- El siguiente comando genera un estudio sobre el código cubierto por
des existen de optimizar el código del SUT. En nuestro caso podemos test:
mejorar un poco las comprobaciones de los valores de los parámetros
del método. ./phpunit.phar --colors --coverage-html Coverage/ Test/

Podemos empezar simplificando las comparaciones de la clase String- Ejercicio: Generar el documento de cobertura para la clase DNI.
Manager.

if(!is_string($string1) && !is_string($string2)) return false;


 Lo siguiente es una captura del funcionamiento de la Cobertura de códi-


Después de tocar el código no debemos olvidar nunca de lanzar el jue- go.
go de tests que hemos creado para comprobar que no hemos roto el có-
digo en alǵun lado.

Coverage
Este concepto trata de dar una métrica del código cubierto por Test. Si el
porcentaje de código cubierto por los test es muy alto, podemos afirmar
que es un buen código y darlo por apto en un entorno de PRODUC-
CIÓN.

76
Calidad

6
phploc
phpcpd
phpmd
phpcs
phpDocumentor
Capítulo 6

Calidad Todas estas herramientas pueden instalarse a través de PEAR.

phploc
PHP Lines of Code nos da una información muy interesante acerca de la
topología del proyecto y su tamaño.

Su ejecución es muy sencilla:

phploc wordpress/
Introducción
Esta última parte trata de cubrir la evolución natural del código después
de la utilización de una metodología basada en Test. Veremos algunas
utilidades que permiten alcanzar una buena calidad en nuestros proyec-
tos.

Herramientas estáticas de análisis


El análisis estático del código se refiere a la evaluación del código sin
ejecutarlo, ni comprobar los posibles errores que pueda haber. Las herra-
mientas evalúan el código tal y como es, leyendo los archivos y midien-
do los elementos que se han escrito.

Un análisis de este tipo nos dará una foto de alto nivel de cómo es nues-
tro código fuente, como va incrementándose y como es de complejo. Es
una herramienta crucial, pero sólo cuando se ejecuta periódicamente.

Este tipo de herramientas cubre muchos aspectos diferentes: cuentan


clases y líneas, identifican dónde hay segmentos de código iguales (don-
de se ha hecho un copiar y pegar), etc. Veremos cómo una herramienta
de este tipo puede ayudarnos a codificar en base a un estándar y a do-
cumentar nuestro código.

78
La salida de phploc muestra información muy interesante y también pue- La detección del código repetido es muy importante si deseamos tener
de exportarse a XML para ser utilizada por sistemas de Integración Con- un software robusto, sin dependencias y DRY.
tinua.
phpmd
Nota: La Complejidad Ciclomática es una medida de cuántos caminos PHP Project Mess Detector ayuda a detectar código “que apesta” (code
posibles tiene una función o un método, o cómo de complejo es en tér- smells). Usa una serie de métricas para encontrar elementos dentro de
minos del número de test necesarios para cubrir el total de la función. un proyecto que se salen de un patrón de buenas prácticas:
Un número alto indica que debería refactorizarse.
phpmd wordpress/ text naming, codesize, unusedcode

phpmd encontrará:
phpcpd
PHP Copy Paste Detector busca en el código fuente patrones similares • Posibles errores.
de código con el ánimo de identificar donde se ha copiado y pegado (Es-
to no es DRY). Es una herramienta muy útil para incluir en un proceso • Código poco optimizado
regular de refactorización.
• Expresiones muy complicadas
El comando es muy sencillo:
• Parámetros, métodos y propiedades sin usar
phpcpd wordpress/
Estándares de codificación
Existen varias recomendaciones para el desarrollo de scripts de forma
estándarizada. Es recomendable que cuando se inicia el aprendizaje de
un nuevo  lenguaje de programación se adquieran buenas costumbres a
la hora de codificar.

PSR-0
Este es el primero de los estándares y nos especifica las siguientes re-
Como la herramienta anterior, también es capaz de exportar los datos a glas:
XML para añadirlos a una aplicación de Integración Continua.
Puntos Obligatorios

79
• Los namespaces y las clases deben tener la siguiente estructura • Las constantes deben ser definidas en MAYÚSCULAS y utilizando
\<Vendor name>\(<Namespace>)*<Class Name> guion bajo (_) cómo separador.

• Cada namespace debe tener un namespace superior ("Vendor na- • Métodos y funciones deben ser escritos utilizando la técnica camelCa-
me"). se.

• Cada namespace puede tener tantos sub-namespaces como se quie- • Debemos de validar que la función que vamos a crear no exista utili-
ra. zando la función function_exists().

• Los nombres de los namespaces o clases deben ser separados por • Las llaves deben de estar abajo sólamente en las clases y métodos.
un guion bajo (_).
• La identación debe ser con un tabulador establecido a 4 espacios.
• Todos los archivos deben tener la extensión .php.
• Las constantes true, false y null deben ser escritos en minúsculas.
• $ Los nombres de los namespaces o clases deben ser ordenadas
alfabéticamente. • El número de caracteres por línea deben ser de 80 columnas aunque
también esta aceptado que sean hasta 120.
PSR-1, PSR-2 y PSR3
• Ya no debes utilizar la palabra reservada var para declarar una propie-
Convenciones
dad, debes utilizar public, private, static o protected.
• Los archivos deben utilizar solamente <?php y las short tags <?=
• Debe haber un espacio después de cada estructura de control (if, for,
foreach, while, switch, try...catch, etc.).
• Los archivos sólo deben utilizar una codificación UTF-8.

• Las funciones sólo deben retornar un sólo valor. phpcs


PHP Code Sniffer buscará dentro del código patrones que se salgan de
• Los nombres de espacio y las clases deben seguir las reglas del la norma de codificación elegida para PHP. Como PHP tiene un gran re-
PSR-0. corrido y grandes proyectos, existen varios estándares distintos que se
pueden comprobar, siendo los más extendidos los de la familia PSR-X.
• Los nombres de clases deben ser escritas utilizando la técnica Studly-
Caps. Para invocar el comando tenemos:

phpcs --standard=PSR-2 StringManager.php


80
Para ver los estándares que se pueden comprobar podemos ejecutar el de las partes que no se entienden. Si necesita escribir comentarios en el
comando: código fuente lo más seguro es que se pueda refactorizar el código y ge-
nerar nuevos métodos descriptivos que permitan eliminar esos comenta-
phpcs -i rios.

Phpcs también es capaz de examinar ficheros Javascript, CSS y están- Además, los comentarios suelen quedar obsoletos en la evolución de
dares personalizados. los proyectos y pueden inducir a errores. Como el texto no es compilado

Se puede obtener un resumen de un proyecto completo con el coman-


do:

phpcs --standard=PEAR --report=summary *

ni interpretado no provoca errores si está desactualizado.

Aún así es posible que necesites tener documentación de un proyecto y


existen herramientas que pueden ayudar a generar esta documentación
si sigues unos pasos determinados mientras se codifica una aplicación.

Ejercicio: Corregir la clase StringManager para que pase el estándar La idea es escribir la documentación del proyecto mientras se genera el
PSR-2 código de la aplicación. Las herramientas de estandarización comproba-
rán que la documentación se está escribiendo y es acorde a la funcionali-
dad.
Documentar el código
Escribir la documentación del código fuente siempre es una tarea ardua
y pesada. Los comentarios del código fuente suelen ser justificaciones

81
}
phpDocumentor
/**
Utilizaremos esta aplicación para generar la documentación de la clase
* Move the character by a random amount
Robot, que sigue la norma de creación de comentarios.
*

<?php * @return boolean true

/** */

* Robot class code public function dance()

* {

* PHP Version 5 $xmove = rand(-2, 2);

* $ymove = rand(-2, 2);

* @category Example if ($xmove != 0) {

* @package Example $this->x += $xmove;

* @author Lorna Mitchell <lorna@lornajane.net> }

* @copyright 2011 Sitepoint.com if ($ymove != 0) {

* @license PHP Version 3.0 {@link http://www.php.net/license/3_0.txt} $this->y += $ymove;

* @link http://sitepoint.com }

*/ return true;

class Robot }

{ ?>

protected $x = 0; Para generar la documentación podemos apoyarnos en el siguiente co-


protected $y = 0; mando:
/**

* Retrieve this character's usual comment phpdoc -t docs -f StringManager.php


*
Esto genera en el directorio docs una página web con la documentación
* @return string The comment
del proyecto con un diagrama de clases, descripción de clases, métodos
*/

public function getCatchPhrase()


y propiedades, errores, etc.
{

return 'Here I am, brain the size of ...';

82
83

También podría gustarte