Вы находитесь на странице: 1из 200

Programación avanzada en PHP

Gorka Urrutia
Table of Contents
Introducción . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 1  

Estado del libro . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 1  

Contenido de la web . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 1  

Herramientas. . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 2  

Composer . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 2  

Control de versiones (Git). . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 6  

Pruebas automatizadas . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 6  

Editores. . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 7  

Las buenas prácticas . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 9  

Introducción express a PHP. . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 10  

Variables . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 10  

If . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 13
 

Arrays . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 20  

Bucles . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 25  

Bucles y arrays . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 29  

Comillas dobles vs comillas simples . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 30  

Programación orientada a objetos . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 32  

Introducción . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 32  

¿Qué es un objeto? . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 32  

¿Y qué es una clase? . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 32  

Crear objetos . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 33  

Vamos a ejecutar el script . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 34  

Tus amigas las funciones print_r() y var_dump() . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 35  

Cambiar el valor de una propiedad. . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 36  

Acceder al valor de una propiedad . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 37  

Cada objeto tiene sus propiedades. . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 38  

Valores iniciales de una propiedad . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 39  

Cambiar las propiedades desde dentro . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 40  

¿Qué pasa si olvido el paréntesis al llamar a un método? . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 42  

Pasar parámetros a un método . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 43  

Llamar a un método desde otro . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 49  

¿Y si pongo el $ en el nombre de la propiedad al llamarla? . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 49  

¿Y si pongo un método dentro de un string?. . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 50  

¿Puede haber un método y una propiedad con el mismo nombre? . . . . . . . . . . . . . . . . . . . . . . . . . . . 50  

¿De qué clase es mi objeto? . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 51  

¿Esta propiedad existe? ¿y este método? . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 52  

Constructores y destructores . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 52  

Un ejemplo paso a paso. . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 59  


El enunciado . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 59  

Simplificaciones . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 60  

El constructor . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 60  

¿Se está moviendo? . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 61  

Acelerando el coche. . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 63  

Glu, glu, glu . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 64


 

Mejoras en el diseño . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 67  

Sigue jugando, hay miles de premios . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 68  

Encapsulación . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 69  

Proteger nuestras variables. . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 69  

Hacer algo público conlleva una obligación . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 70  

Getters y Setters . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 70  

Herencia . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 73
 

Heredar para reutilizar . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 74  

No abusar de la herencia . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 77  

Constructores y herencia . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 77  

Public, private y protected. . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 79  

Overriding - Sobrescritura de métodos . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 82  

Introducción al desarrollo guiado por pruebas . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 83  

La herramienta: phpunit . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 83  

La clase Coche desde cero . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 84  

¿Por dónde empezar? . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 85  

Sobre los nombres de los test . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 86  

Probamos phpunit por primera vez . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 86  

El segundo test . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 89  

Mejoras en el código . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 90  

Cambios en el código que rompen los test . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 91  

Un test por si acaso . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 93  

Repostando dos veces . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 94  

Acelerando el coche. . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 95  

Comprobar el combustible . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 97  

El método estado() . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 98  

AssertSame . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 99  

Otros métodos assert. . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 100  

No ejecutar todas las pruebas. . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 100  

Conclusión . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 100
 

Constantes . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 101
 

Definir constantes . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 101  

Nombres de constantes . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 102  

¿Para qué una constante dentro de una clase? . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 102  

¿Todavía no ves utilidad a las constantes? . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 104  


¿Qué valores podemos usar en una constante? . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 105  

¿Y si no le doy un valor? . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 106  

¿Public y private? . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 106  

Interfaces . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 107
 

Vamos a montar un garaje . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 107  

Ampliamos el negocio. . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 108  

¿Para qué tiene métodos el interfaz? . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 110  

¿Y por qué no crear una clase Vehiculo en lugar de un interfaz? . . . . . . . . . . . . . . . . . . . . . . . . . . . . 111  

Características de los interfaces en PHP . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 111  

Clases abstractas. . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 113  

¿Para qué una clase abstracta? . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 113  

Usar el nuevo método . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 115  

Ojo, la propiedad limpio es privada. . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 115  

¿Qué métodos deben ser abstractos y cuáles no? . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 115  

¿Por qué no se puede instanciar una clase abstracta? . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 115  

¿Cuándo usar una clase abstracta? . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 117  

¿Por qué no usar siempre clases abstractas? . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 117  

¿Qué otras opciones hay? . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 117  

Traits . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 118
 

¿Qué es un trait? . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 118  

¿Por qué usar un Trait? . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 119  

Características de los traits . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 121  

Recapitulando. . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 127  

Métodos y propiedades Static . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 128  

Métodos estáticos. . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 128  

Propiedades estáticas . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 130  

A las propiedades static las carga el dibablo. . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 131  

¿Qué utilidad tienen las propiedades static? . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 136  

¿Es mejor usar una constante o una propiedad static? . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 137  

¿Librería de métodos estáticos? . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 137  

Llamar a una propiedad estática usando una variable . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 138  

Llamar a un método estático usando una variable . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 140  

Repaso . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 141
 

Repaso a los elementos de una clase . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 142  

Clases y métodos Final . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 145  

Métodos final . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 145  

Propiedades final . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 146  

Clases final. . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 146


 

¿Cuándo usar final para una clase? . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 146  

¿Y si una clase final no hace todo lo que necesito? . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 147  

Composición . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 148  
Un sencillo ejemplo . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 148  

Composición con más de un objeto . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 149  

Solucionar el problema con herencia . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 150  

¿Por qué no usar traits? . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 151  

Motores y asientos de varios tipos. . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 152  

Aquí viene de perlas un interface . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 153  

Chapuzas Acme, S.A. . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 154  

Métodos mágicos . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 157  

__get() y \__set() . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 157


 

__isset() y __unset(). . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 161  

__call() y __callStatic() . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 161  

__toString(). . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 164
 

serialize() y unserialize() . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 165  

__sleep() y __wakeup() . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 167  

__invoke() . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 169
 

Excepciones . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 172
 

Excepciones en lugar de if/else para controlar fallos . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 172  

Ver el mensaje de error de la excepción . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 175  

Un ejemplo de excepciones . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 175  

Las excepciones son para situaciones excepcionales . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 177  

Todas las excepciones son hijas de Exception . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 179  

Las excepciones hay que tratarlas. . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 180  

Códigos de error adicionales. . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 180  

Crear nuevas excepciones . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 181  

Múltiples catch . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 182  

finally . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 184
 

Introducción a Git . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 186  

¿Qué es git? . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 186


 

Aprende a usarlo en la línea de comandos . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 186  

Instalar git . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 186


 

Comenzar con git . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 186  

Configurar git para nuestro proyecto . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 187  

Git, el ojo que todo lo ve (si tú quieres) . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 187  

Pedirle a git que controle nuestros ficheros . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 188  

El fichero .gitignore . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 189  

Guardar un estado del proyecto. El commit . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 189  

Las tres zonas de Git . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 190  

Términos y definiciones . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 191  

Símbolos. . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 191
 

array . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 191
 

Buenas prácticas . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 191  


Camel Case. . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 191 

Código espagueti . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 191  

Control de versiones . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 191  

Framework . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 191  

Getter . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 191
 

IDE (Integrated Develompent Environment) . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 192  

Instanciar. . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 192
 

JSon . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 192
 

Lenguaje no tipado . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 192  

Notice / Aviso (mensajes de error en PHP) . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 192  

Operador de resolución de ámbito . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 192  

Palabra reservada . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 193  

Patrón de diseño . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 193  

Setter. . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 193
 

Snake Case . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 193


 

Principios SOLID . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 193  

Terminal. . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 194
 

Warning / Advertencia (mensajes de error en PHP) . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 194  


Introducción
Estado del libro
Este libro está todavía en fase beta. Quiere decir que aún está incompleto y estoy trabajando
activamente en él. Por esa razón lo estoy ofreciendo con un precio muy ventajoso a través de la
web:

https://phpsensei.es

Si tienes alguna duda con respecto al contenido del libro, has visto algún error o crees que alguna
sección no está lo suficientemente bien explicada, por favor, contacta conmigo:

info@phpsensei.es

Contenido de la web
También estoy trabajando en contenido extra para la web y material de apoyo para el libro. Entre
otras cosas estará listo en breve:

• Cuestionarios de cada capítulo para que pongas a prueba lo aprendido.

• Ejercicios para que practiques.

• Código fuente disponible para descargar.

1
Herramientas
Antes de meternos de lleno en la programación orientada a objetos en PHP voy a compartir las
herramientas que suelo usar.

Estas no son las únicas herramientas que existen y puede ser que existan otras mejores. Yo las
recomiendo porque todas y cada una de ellas me han ayudado a ser más productivo en mi trabajo y
consiguen ahorrarme un montón de tiempo.

Composer
Cuando quieres añadir una librería o una clase a tu proyecto tienes que descargarlo y copiarlo a tu
proyecto. Si la clase o librería saca una nueva versión tendrás que encargarte de actualizarla de
manera manual.

¿No sería fantástico que hubiera una herramienta que hiciera este trabajo por nosotros?

Por suerte existe y se llama composer. Composer es un gestor de paquetes de PHP. nos permite
indicar las librerías que son necesarias para nuestro proyecto. De esta forma podemos distribuirla
de manera más sencilla y será más fácil trabajar con otros colaboradores.

Todas las dependencias se instalan en una carpeta llamada vendor.

Instalar Composer

Composer se puede instalar desde un Terminal (debemos tener instalado PHP previamente) o a
través de ejecutables:

• En Linux/Unix/OsX hay un paquetes disponibles pero puedes instarlo también desde un


Terminal. Puedes ver las instrucciones aquí.

• En Windows hay un ejecutable o puedes seguir las instrucciones.

Crear un proyecto con Composer

Toda la configuración del Composer se hace a través de un fichero llamado composer.json que tiene
formato JSon. Podemos crearlo y editarlo manualmente, no es complicado. Pero Composer nos
ofrece un asistente (que funciona en un Terminal) y que puede hacer el trabajo inicial por nosotros.

Vamos a ver cómo usar Composer en nuestro proyecto. Para eso abre un Terminal y vete a la
carpeta de tu proyecto. Para usar el asistente hay que usar este comando:

$ composer init

El asistente nos hará una serie de preguntas. Para cada pregunta verás que hay un texto entre
corchetes. Si no escribimos una respuesta y pulsamos la tecla Enter el texto entre corchetes se usará
como respuesta. Por ahora puedes dejar en blanco todas las respuestas hasta que nos pregunte
"Search for a package":

2
Welcome to the Composer config generator

This command will guide you through creating your composer.json config.

Package name (<vendor>/<name>) [gorka/ejemplos]:


Description []:
Author [Gorka <info@gulvi.com>, n to skip]:
Minimum Stability []:
Package Type []:
License []:

Define your dependencies.

Would you like to define your dependencies (require) interactively [yes]?


Search for a package:

Cuando nos pregunta "Search for a package" podemos indicar los paquetes de terceros que
queremos instalar. En nuestro caso vamos a instalar phpunit, que es la herramienta que nos va a
permitir escribir pruebas automatizadas:

Search for a package: phpunit

Found 15 packages matching phpunit

  [0] phpunit/phpunit
  [1] eher/phpunit
  [2] jbzoo/phpunit
  [3] hiqdev/php-units
  [4] zerkalica/phpunit
  [5] phpunit/phpunit-mock-objects
  [6] phpunit/phpunit-selenium
  [7] phpunit/phpunit-story
  [8] phpunit/phpunit-skeleton-generator
  [9] phpunit/dbunit
  [10] phpunit/phpunit-dom-assertions
  [11] phpunit/phpcov
  [12] phpunit/phpunit-mink-trait
  [13] phpunit/php-timer
  [14] phpunit/php-token-stream

Enter package # to add, or the complete package name if it is not listed:

Composer nos va a listar todos los paquetes que coinciden con lo que le hemos pedido.

3
Packagist
Esta lista de paquetes está en una web llamada Packagist (https://packagist.org). Si
 buscas en es web phpunit verás que el listado coincide con el que te muestra
Composer: https://packagist.org/search/?search_query%5Bquery%5D=phpunit

De todos éstos, por ahora, nos interesa el primero [0] phpunit/phpunit, así que tecleamos un 0 y
seguimos:

Enter package # to add, or the complete package name if it is not listed: 0


Enter the version constraint to require (or leave blank to use the latest version):

Ahora nos pregunta por la versión del paquete que queremos instalar. Si no indicamos nada
intentará coger la última versión.

Ahora composer comprobará si el paquete se puede añadir a nuestro proyecto o no. Es posible que
se queje si alguna dependencia no funciona. Por ejemplo, si instalamos la versión 6.1 de phpunit o
superior podemos ver este error si nuestra versión de PHP no está actualizada:

Loading composer repositories with package information


Updating dependencies (including require-dev)
Your requirements could not be resolved to an installable set of packages.

  Problem 1
  - phpunit/phpunit 6.1.3 requires php ^7.0 -> your PHP version (5.6.20) does not
satisfy that requirement.
  - phpunit/phpunit 6.1.2 requires php ^7.0 -> your PHP version (5.6.20) does not
satisfy that requirement.
  - phpunit/phpunit 6.1.1 requires php ^7.0 -> your PHP version (5.6.20) does not
satisfy that requirement.
  - phpunit/phpunit 6.1.0 requires php ^7.0 -> your PHP version (5.6.20) does not
satisfy that requirement.
  - Installation request for phpunit/phpunit ^6.1 -> satisfiable by
phpunit/phpunit[6.1.0, 6.1.1, 6.1.2, 6.1.3].

Así que tendríamos que actualizar PHP para poder usar la versión 6.1 de phpunit en nuestro
proyecto. Si no podemos actualizar PHP podemos instalar una versión más antigua de phpunit;
puedes ir a Packagist (https://packagist.org) para ver qué versiones están disponibles.

Si ha ido todo bien nos preguntará de nuevo si queremos instalar algún otro paquete. Por ahora no
vamos a instalar nada más así que dejamos la respuesta en blanco.

Y, de nuevo, nos vuelve a preguntar si queremos instalar paquetes (paciencia que acabamos
enseguida). Esta vez los paquetes que podamos necesitar en la fase de desarrollo (es posible que
necesites algún paquete para el desarrollo pero no en el servidor de producción):

Would you like to define your dev dependencies (require-dev) interactively [yes]?

4
Así que repetimos toda la operación de antes con el phpunit. Una vez terminado el proceso nos
mostrará un JSon de lo que hemos seleccionado y nos pide confirmación:

{
  "name": "gorka/ejemplos",
  "require": {
  "phpunit/phpunit": "^6.1"
  },
  "require-dev": {
  "phpunit/phpunit": "^6.1"
  },
  "authors": [
  {
  "name": "Gorka",
  "email": "info@gulvi.com"
  }
  ]
}
Do you confirm generation [yes]?

Al confirmar se nos creará un fichero llamado composer.json con el contenido que nos acaba de
mostrar. Todavía no hemos instalado nada, solo hemos generado el fichero que usará Composer
para gestionar nuestro proyecto.

Generar el proyecto

Ya tenemos nuestro fichero composer.json, ahora es el momento de descargar todos los paquetes
que hemos pedido (en nuestro ejemplo phpunit). Para descargar los paquetes necesarios y sus
dependencias:

$ composer install

Verás cómo se descargan unos cuantos paquetes además de phpunit. Todos esos paquetes
adicionales son las dependencias de phpunit, los paquetes que necesita para funcionar.

Una vez terminado el proceso verás que en la carpeta de tu proyecto hay dos nuevos "inquilinos":

• La carpeta vendor. Esta es la carpeta donde se instala todo lo que descargamos. No deberías
modificar nunca el contenido de esta carpeta ya que son paquetes de terceros que se pueden
actualizar y se perderán tus cambios.

• El fichero composer.lock. Este es otro fichero importante donde se guardan las versiones
exactas de lo que te has descargado. Si trabajas en el proyecto con más gente es importante
tener una única copia de este fichero y compartirla (en el control de versiones).

El fichero composer.json

Esta es la pieza central del Composer de tu proyecto. En este fichero puedes modificar las versiones

5
de los paquetes que quieres tener instaladas. Puedes eliminar un paquete o añadir nuevos.

Puedes editarlo "a mano" o usar algunos de los comandos de Composer para hacer el trabajo por tí.
Por ejemplo, para añadir el paquete psr/log:

$ composer require psr/log

O también podemos añadirlo solo para desarrollo:

$ composer require --dev psr/log

Recuerda, esto no nos descargaría el paquete psr/log solo modifica el fichero composer.json. Para
aplicar los cambios y descargar/desinstalar/actualizar los paquetes que hayamos cambiado
usaremos:

$ composer update

¿Qué incluyo en el Git (o en otro control de versiones)?

Si usas un sistema de control de versiones (como ¿Qué es git?) deberías añadir los ficheros:

• composer.json

• composer.lock

Pero no debes añadir la carpeta vendor. Si compartes estos dos ficheros con el resto del equipo de
desarrollo ellos solo tendrán que hacer composer update para tener la carpeta vendor exactamente
igual que tú.

Control de versiones (Git)


El sistema de control de versiones es una pieza fundamental en cualquier proyecto. Nos permite
tener una copia de seguridad de nuestro proyecto, experimentar sin miedo a romper nada,
deshacer cambios que fallan, etc.

En este apartado gana por goleada Git. Esta herramienta se merece su propio capítulo así que te
recomiendo que le eches un vistazo.

Pruebas automatizadas
Otra de las piezas fundamentales para el éxito de un proyecto son las pruebas automatizadas.
Consiste en escribir una serie de pruebas que podremos ejecutar de forma automatizada para
comprobar los aspectos más importantes de nuestra aplicación (o de toda ella).

Una metodología que tiene ya unos añitos pero que está ganando fuerza últimamente es el
desarrollo guiado por pruebas (TDD en inglés). Consiste en escribir primero las pruebas que tiene

6
que superar nuestra aplicación y luego empezar a escribir el código de la misma. Hay un capítulo
de introducción al TDD en el que veremos un ejemplo.

PHPUnit

Esta es una de las herramientas más usadas en PHP para las pruebas automatizadas. En el capítulo
de introducción al TDD será la que usemos para los ejemplos.

Simple Test

Muchos usuarios dicen que Simple Test es más fácil de usar que PHPUnit. Personalmente no tengo
experiencia con ésta así que aquí no puedo opinar ni dar una recomendación.

La web de Simple Test está un poco desactualizada, pero el proyecto en GitHub sigue vivo.

Navegadores automáticos

Otra opción muy útil son los navegadores automáticos. Éstos nos permiten crear "guiones" para que
un programa prueba toda nuestra aplicación a través de un navegador web. La ventaja es que así
podemos probar nuestra aplicación aunque haga uso de JavaScript (cosa que con PHPUnit no se
puede).

Editores
Los editores de código (también llamados IDE - Integrated Development Environment) nos facilitan
la tarea de escribir nuestras aplicaciones. Podríamos usar un editor de texto normal y corriente
pero vamos a ser mucho menos productivos.

Estos entornos de desarrollo modernos suelen ofrecer una serie de ventajas añadidas con respecto
a un editor normal:

Autocompletado de código
según vamos tecleando se nos sugieren opciones que coinciden con lo que hemos escrito. Si, por
ejemplo, ponemos un $ y una a nos sugerirá todas las variables que empiecen por a. También
nos sugerirá los métodos o propiedades de una clase. Muy útil, ahorra tiempo y evita errores.

Integración con otras herramientas


Muchos de estos editores nos permiten integrar herramientas como Git, subida de archivos por
FTP, etc.

Plantillas de código
Esta es otra que ahorra mucho tiempo. Las plantillas de código son esos fragmentos que
escribimos una y otra vez y otra vez y otra vez… Por ejemplo para la definición de una función
tenemos que teclear function () {}. Con una plantilla de código bastaría con teclear una letra,
pulsar el tabulador y listo.

7
PHPStorm

Esta es mi preferida, otra que gana por goleada. Es capa de "entender" nuestro código de PHP,
buscar errores comunes, hacer sugerencias, ir directamente al fichero donde está definida una
clase o función, etc.

Entre sus desventajas destacaría que es un poco pesada y tarda un poco en cargar.

Otra "desventaja" es que es de pago, aunque puedes probarla gratis durante treinta días. La licencia
cuesta unos 90 euros al año. Si no me equivoco, si no comprar licencia puedes seguir usándola con
la limitación de que a los treinta minutos se cierra el programa y tienes que volver a abrirlo.

Ver: https://www.jetbrains.com/phpstorm/

Sublime Text

Esta es otra que me gusta mucho. Suelo usarla para proyectos pequeños o para modificaciones
rápidas.

También es de pago, pero es un pago único de unos 70 euros. Hay que pagar una licencia de
actualización cuando sacan una versión principal (por ejemplo de la versión 3 a la 4).

Web: https://www.sublimetext.com

Atom

Este es software libre y gratuito. Muy ligero y extensible. Cada día lo uso más pero, por ahora, solo
para proyectos pequeños.

Una curiosidad: este libro lo estoy escribiendo con Atom al que he instalado los plugin de
AsciiDoctor.

Web: https://atom.io/

Otros Editores

Exiten otro buen montón de editores que puedes usar para tus proyectos de PHP; Entre otros
Eclipse, Aptana. Estos dos los usé durante mucho tiempo pero me parecían excesivamente pesados
y complejos.

También puede usar editores de línea de comandos como Vim pero eso es más para presumir
delante de amigos muy frikis.

8
Las buenas prácticas
Capítulo en preparación.

9
Introducción express a PHP
Este libro está pensado para programadores con experiencia en PHP. A pesar de eso voy a empezar
con un rápido repaso a las características básicas de PHP y a algunos de sus aspectos que,
normalmente, no se conocen.

Variables
Una variable en PHP se define usando el símbolo $ seguido del nombre de la variable. En el
momento de definir una variable tenemos que darle un valor inicial. Por ejemplo:

$nombre = 'Gorka';

Las variables en PHP aguantan todo lo que les echemos; podemos meterle un número entero, un
número decimal, una cadena, un array sencillo, un array de miles de dimensiones, un objeto, etc.
Esto es lo que se llama un lenguaje no tipado.

Nombres de las variables

Las variables deben seguir estas sencillas reglas:

• No puedes usar caracteres "raros". Para los desarrolladores de PHP (y para la mayoría de
lenguajes de programación) nuestras tildes y ñ son caracteres "raros" (ellos sí que son raros).

• Solo letras (insisto, sin tildes ni ñ), números y guiones bajos.

Las "buenas prácticas" recomiendan usar, para los nombres de variables, uno de estos dos sistemas:

• Camel Case

• Snake Case

¿Cuál es mejor? Dependerá del grupo en el que andes. Por ejemplo, en WordPress han optado por el
snake_case, en Laravel por camelCase.

Lo que nunca, nunca debes haces es mezclar los estilos. La mezcla de estilos hace que el código sea
un poco más difícil de leer. Bastante duro es a veces, no lo compliques más.

Por favor, usa nombres descriptivos

Ten siempre presente un detalle muy importante:

Aunque tu código lo va a ejecutar una máquina serán otros humanos (o tú)


quienes tengan que modificarlo o mantenerlo. ¡Hazte entender!

Esta es la razón principal para usar nombres descriptivos en las variables. Con solo leer el nombre
de una variable deberíamos saber para qué se usa.

Si quieres ser el blanco de las iras de tus compañeros de proyecto o de otros desarrolladores no

10
dudes en usar nombres poco descriptivos o confusos. Si, por el contrario, no quieres preocuparte
por lo que pueda haber caído accidentalmente en tu taza de café usa nombres "buenos".

Ejemplos de malos nombres de variables:

• $variable → Variable sabemos que es, como el humor del que lo tenga que entender, que pasará
de bueno a peor.

• $var → Cuando veo esto me entran ganas de ir al bar.

• $par → ¿qué es ésto? ¿para qué se usa? ¿es un parámetro? ¿o se refiere a un número par?

• $usuario1 → ¿qué es ese 1? ¿por qué no 2?

Usar nombres poco descriptivos da una idea de la calidad del resto del código. Un buen nombre
para una variable no es tan difícil de conseguir y sus beneficios son altos.

Conversiones automáticas

Una cosa fantástica de PHP (y a la vez una cosa horrible de PHP) es que los datos se convierten de
un tipo a otro "mágicamente". Por ejemplo, podemos sumar una cadena con un número entero:

$peras = '10';
$manzanas = 20;

echo $peras + $manzanas;

Resultado: 30. ¿Quién dijo que no se podían mezclar peras con manzanas?

Cuando se encuentra con situaciones similares a ésta PHP intenta convertir los datos de manera
automática. En este caso tenemos una suma así que PHP transforma ambos valores a números
antes de hacer la suma.

Hay otras situaciones en las que PHP no puede convertir uno de los valores a un número. Si esto
ocurre la cadena se consideraría como un cero:

$peras = 'Peras';
$manzanas = 20;

echo $peras + $manzanas;

Aquí el resultado es 20 porque Peras no se puede convertir a un número.

Esta conversión automática está bien (aunque a veces puede causar problemas inesperados) pero
no esperes que PHP haga magia:

11
$peras = 10;
$manzanas = [10, 20];

echo $peras + $manzanas;

Intentar sumar un número entero con un array como en este ejemplo hace que a PHP le explote la
cabeza. No hay una conversión posible así que verás un bonito Fatal error si ejecutas este código.

Moldes (type casting)

Si necesitamos que la conversión de un dato de un tipo a otro sea una concreta podemos usar un
molde (en inglés a esto lo llaman type casting).

Hay varias opciones:

• (int) o (integer) → Son equivalentes. Convierten el valor a un número entero (o lo intenta


convertir).

• (bool) o (boolean) → Son equivalentes. Convierten el valor a true o false.

• (float) o (double) o (real) → No son exactamente equivalentes. Convierten a un número decimal.

• (string) → Convierte a una cadena.

• (array) → Convierte en un array.

• (object) → Convierte en un objeto.

• (unset) → Convierte el valor en null.

Algunos comentarios:

Si convertimos un número decimal a entero lo que hace es truncar el número. Es decir, se "come" la
parte decimal:

echo (int) 1.9; // Muestra un 1

Si convertimos un entero a un objeto se creará un objeto con la propiedad scalar (ya hablaremos
sobre objetos y propiedades largo y tendido):

$numero = 1;
print_r((object) $numero);

Esto da como resultado:

(
  [scalar] => hola
)

12
Y si convetimos un array en un objeto cada uno de los índices del array se convertirá en una
propiedad del objeto:

$array = [ 'nombre' => 'Gorka' ];


print_r( (object) $array );

y mostrará:

stdClass Object
(
  [nombre] => Gorka
)

Si el array no tiene índices o usa números para los índices podemos tener algún problemilla que
otro. Pruébalo a ver.

Ojo con las conversiones

Las conversiones entre números decimales y enteros a veces pueden ser un quebradero de cabeza.
Mira el siguiente ejemplo:

var_dump((int)(39.3 * 100.0)); // Devuelve 3929 en lugar de 3930


var_dump((int)(39.2 * 100.0)); // Esto devuelve 3920 que es lo esperado

 Plagiado de aquí.

Esto se debe a cómo se almacenan los números en PHP. Por esta razón se recomienda que cuando
trabajes con, por ejemplo, precios que incluyan decimales se usen siempre números enteros
(multiplicando por 100). Por ejemplo, si tienes que usar el precio 10.99 usa mejor 1099. Luego,
cuando vayas a mostrarselo al usuario ya puedes dividirlo por 100. Este sencillo truco te puede
ahorrar muchos dolores de cabeza.

If
Creo que a estas alturas no hace falta que te cuente cómo funciona el if… pero voy a hacerlo. El if
permite ejecutar bloques de código de manera selectiva:

if (condicion) {
  // código a ejecutar
}

Si se cumple la condición se ejecuta el código que está entre llaves. Si no se cumple no se ejecuta,
simple.

13
No salgas sin llaves

Muchos programadores están muy tentados de omitir las llaves cuando, después del if, solo hay una
línea de código. Pero muchas veces (muchas) añadimos posteriormente una línea adicional. ¡Y se
nos olvidan las llaves!

Así que, igual que no debes olvidar las llaves cuando sales de casa no te olvides de poner llaves en
tu código.

Las comparaciones son odiosas

En una condición podemos usar las siguientes comparaciones posibles:

• $a == $b (¿es $a igual que $b?)

• $a != $b (¿es $a distinto de $b?)

• $a > $b (¿es $a mayor que $b)

• $a < $b (¿es $a menor que $b)

• $a >= $b (¿es $a mayoro igual que $b)

• $a <= $b (¿es $a menor o igual que $b)

No es lo mismo == que ===

La comparación habitual que se suele hacer en un if suele ser de este tipo:

$numero = 1;
$cadenaNumero = "1";
if ( $numero == $cadenaNumero ) {
  echo "Estoy aquíiiii"; // Esto se muestra
}

Vemos que esta condición se cumple porque $numero es 1 y $cadenaNumero es la cadena "1" que se
puede convertir en un 1. Pero hay algunas ocasiones en que queremos saber si son exactamente
iguales. Es decir si tienen el mismo valor y son del mismo tipo. En estos casos usamos el operador
===:

$numero = 1;
$cadenaNumero = "1";
if ( $numero === $cadenaNumero ) {
  echo "Estoy aquíiiii"; // Esto NO se muestra
}

Aquí la condición no se cumple porque, aunque los dos contienen el valor 1, no son del mismo tipo
(uno es un entero y el otro una cadena).

Del mismo modo tenemos el operador !=='.

14
¡Yoxorno!

En un if podemos usar operadores lógicos: Y/O/Xor/No. Estos operadores nos permiten anidar varias
comparaciones dentro de un if. Dentro de un if puedes anidar todas las comparaciones que quieras
usando:

• || (ó)

• && (y)

• xor

• and

• or

No es lo mismo || que or. Ni es lo mismo && que and. Ambos sirven para anidar comparaciones
dentro de un if pero la diferencia está en la precedencia o prioridad; && y || tienen prioridad sobre
and y or.

En la mayoría de situaciones no vas a ver ninguna diferencia, pero si tienes mezcladas asingaciones
(el operador =) y and y && puedes tener resultados aparentemente inesperados. Si no quieres
complicarte la vida usa siempre && y || que son las más habituales.

Puedes ver más información sobre la precedencia de los operadore en la web de PHP.

Entender la condición del if

Las condiciones del if pueden ser muy complejas pero, al final, lo único que importa es si la
expresión que está dentro del paréntesis se puede convertir en cero o en un valor distinto de
cero. Si lo que hay entre paréntesis se puede convertir en un cero no se cumple. Si el resultado de
lo que hay entre paréntesis es un valor distinto de cero la condición se cumple.

Para ser más exactos la condición es verdadera si el resultado final:

• es distinto de cero.

• es distinto de null.

• es una cadena de texto no vacía.

• es un array no vacío.

Vamos a ver algunos ejemplos:

Aquí la expresión del if (lo que está entre paréntesis) es un uno, distinto de cero, por lo tanto se
cumple:

if ( 1 ) {
  echo "Estoy aquíiiii"; // Esto sí se muestra
}

Esta, en cambio no se cumple, es cero:

15
if ( 0 ) {
  echo "Estoy aquíiiii"; // Esto no se muestra
}

Otro ejemplo ¿Se muestra el mensaje o no?

if ( -1 ) {
  echo "Estoy aquíiiii";
}

Sí, se muestra. Recuerda, lo que comprueba el if es si el valor que queda al final entre paréntesis es
cero o distinto. En este caso el -1, que es distinto de cero. Por lo tanto no se cumple la condición.

Una fácil:

if ( true ) {
  echo "Estoy aquíiiii"; // Se muestra
}

Y esta, como imaginarás, no se muestra:

if ( false ) {
  echo "Estoy aquíiiii";
}

¿Y ésta? ¿Se mostraría el mensaje?

$nombre = 'Gorka';
if ( $nombre ) {
  echo "Estoy aquíiiii";
}

Sí, porque una cadena que no está vacía. Así que en este otro código el mensaje no se imprimirá:

$nombre = '';
if ( $nombre ) {
  echo "Estoy aquíiiii"; // No se muestra
}

La que viene ahora es algo más complicada ¿Se muestra o no se muestra?

16
$nombre = '0';
if ( $nombre ) {
  echo "Estoy aquíiiii";
}

No, no se muestra. Y es porque la cadena, aunque no está vacía se puede convertir en un cero. Y
sabemos que un cero hace que la expresión sea falsa.

Ojo con el =

En ocasiones podemos confundirnos y usar el operador de asignación (el =) en lugar del de


comparación (el ==) y tendríamos un resultado inesperado:

$numero = 10;
if ( $numero = 5 ) {
  echo "Estoy aquíiiii"; // Esto se muestra
}

Recordemos, la condición resultante es verdadera si el resultado de lo que hay en el paréntesis es


distinto de cero. La operación $numero=5 hace que la variable $numero tome el valor 5. Por lo tanto
esto es equivalente a: if( 5 ) y se cumplirá siempre, no importa cual sea el valor de $numero.
Además, $numero cambiará de valor.

Este es un fallo muy habitual que suele dar muchos dolores de cabeza. Es difícil darse cuenta de
que hay un = en lugar de == y, además, no se produce ningún error o aviso de PHP.

Para solucionarlo hay quien usa lo que llaman la notación Yoda (sí, del maestro Yoda). Consiste en
escribir las comparaciones al revés (al igual que el personaje que hablaba al revés):

$numero = 10;
if ( 5 = $numero ) {
  echo "Estoy aquíiiii"; // Esto se muestra
}

Esto sí lanzará un error de PHP y es más fácil de detectar.

Personalmente no me gusta porque:

• Queda feo (cuestión de gustos).

• El código es menos legible (es más intuitivo leerlo de la otra forma).

• No sirve de nada cuando ambos lados de la comparación son variables.

Y ya que estamos ¿Qué piensas que sucederá aquí? ¿Se mostrará el mensaje?

17
$numero = 10;
if ( $numero = 0 ) {
  echo "Estoy aquíiiii";
}

En este caso no se muestra porque la variable $numero tomará el valor 0. Al ser cero el resultado de
lo que hay en el paréntesis la condición es falsa. Aparentemente el resultado es el correcto pero,
recuerda, a partir de ese punto $numero tiene el valor 0, que seguro que es algo que no queríamos.

Unos if más claros

Rápidamente, sin pensarlo. Di qué es lo que comprueba este if:

if ( !($numero % 2) )
{
  // aquí algo de código
}

% es el operador módulo
El operador módulo devuelve el resto de la división. En este caso devuelve el resto de 10/2.

Si lo has usado más veces puede que lo veas rápido. Si no puede que te tengas que devanar un poco
los sesos. ¿Qué te parece si hacemos un pequeño cambio?

if ( esPar($numero) ) {
  // aquí algo de código
}

function esPar( $numero ) {


  return !($numero % 2);
}

Ahora dime, ¿qué hace este código? ¿A que ahora lo ves mucho más claro? Efectivamente, con ese if
comprobamos si el número es par o no. A pequeñas cosas como ésta es a lo que me refiero cuando
digo que el código debe ser legible.

No todo se evalúa

El if es un vago. Cuando tenemos usamos los operadores &&, ||, and y or en el momento que el if
sabe cuál va a ser el resultado final ya no continúa.

Por ejemplo, cuando usamos &&:

$edad = 10;
if ( $edad > 30 && $edad < 50 )

18
Cuando hay un && las dos comparaciones deben ser verdaderas. Si una de ellas falla el resultado
final será false. Así que, en este caso, no es necesario hacer la segunda comprobación y PHP se la
salta directamente.

Lo mismo ocurre con el operador ||:

$edad = 10;
if ( $edad < 30 || $edad > 50 )

Si se cumple una de ellas el resultado será true. Así que, como la primera se cumple ya no es
necesario comprobar la segunda.

Si no te lo crees y eres de los que les gusta comprobar las cosas prueba con ésto:

$numero = 10;
if ( $numero>5 && esPar($numero) ) {
  echo "Es mayor que 5 y par\n";
}

function esPar( $numero ) {


  echo "Comprobando si el número es par\n";
  return !($numero % 2);
}

Si lo ejecutas verás que el resultado es:

Comprobando si el número es par


Es mayor que 5 y par

Pero si cambiamos $numero=10 por $numero=0 y al ejecutar el programa no veremos nigún


mensaje. Eso es porque no se ha llegado a llamar a la función esPar().

Si quieres probar con el || habría que hacer un par de cambios:

if ( $numero>5 || esPar($numero) ) {
  echo "Es mayor que 5 o par\n";
}

Posiblemente pensarás ¿Y en qué me afecta eso? En general no debería afectarte pero si esa función
a la que llamas modifica el valor de alguna variable y esperas que siempre se haga puedes tener
problemas.

If-else

Esto ya lo sabes seguro, un if puede llevar asociado un else. Si la condición del if no se cumple se
ejecuta el bloque de código detrás del else:

19
if ( condicion ) {
  // Esto se ejecuta si la condición es verdadera
}
else {
  // Esto se ejecuta solo si la condición NO es verdadera
}

If anidados

Podemos anidar todos los if que queramos (un if dentro de otro). Aquí no hay limitaciones. El único
problema es que cuantos más if anidados tengamos más difícil va a ser nuestro código de leer. Las
buenas prácticas recomiendan no poner más de dos if anidados. Y si necesitas más niveles de if
puedes meterlos dentro de una función.

Arrays
Un array es un conjunto de valores agrupados dentro de una variable. Cada uno de los elementos
del array queda identificado por un índice que debe ser único. A este índice también se le suele
llamar clave o key (en inglés).

Un array se puede definir así:

$nombres = [ 'Mickey', 'Donald', 'Pluto' ];

o también así (esta es la forma "antigua"):

$nombres = array( 'Mickey', 'Donald', 'Pluto' );

La forma antigua:
NOTE
array() es la forma que se usaba "antiguamente" (ultumamente "antiguo" parece que significa "el
mes pasado"). Ahora la forma preferida parece ser [], aunque el estándar no lo mencione y en la
documentación oficial de PHP todos los ejemplos usen la forma array(). De todos modos la forma
abreviada [] no está disponible para versiones de PHP anteriores a la 5.4.

Podemos ver el contenido de un array usando print_r:

$nombres = [ 'Mickey', 'Donald', 'Pluto' ];

print_r($nombres);

20
print_r() y var_dump
print_r() y var_dump() son dos funciones de apoyo que nos permiten ver el
 contenido de una variable. Se usan mucho en la fase de desarrollo para saber qué
estamos haciendo.

Si ejecutamos el código anterior mostrará:

Array
(
  [0] => Mickey
  [1] => Donald
  [2] => Pluto
)

Aquí podemos ver los valores del array junto con sus claves. Por ejemplo, Mickey tiene la clave 0,
Donald tiene la clave 1 y Pluto la clave 2. Si no se indica nada PHP asignará automáticamente
índices al array empezando desde cero.

Si queremos podemos decir qué clave queremos para cada uno de los elementos usando el
siguiente formato:

'clave' => 'valor'

Por ejemplo:

$nombres = [
  10 => 'Mickey',
  20 => 'Donald',
  30 => 'Pluto'
];

print_r($nombres);

Al ejecutarlo mostrará:

Array
(
  [10] => Mickey
  [20] => Donald
  [30] => Pluto
)

Podemos especificar claves solo para algunos de los elementos. Al resto de elementos se les
asignará un número de manera automática:

21
$nombres = [
  'Mickey',
  'Donald',
  10 => 'Pluto',
  'Minnie'
];

print_r($nombres);

que mostrará:

Array
(
  [0] => Mickey
  [1] => Donald
  [10] => Pluto
  [11] => Minnie
)

Aquí se puede ver que los índices, si no especificamos nada, empiezan desde cero. Si a uno de los
elementos le pondemos un índice la cuenta empezará a partir de ese número para los siguientes
elementos.

Las claves pueden ser números pero no es obligatorio, también podemos usar un texto
entrecomillado. Si la clave no es un número los siguientes elementos usarán como índice el último
número válido. Veamos un ejemplo:

$nombres = [
  'Mickey',
  'Donald',
  'perro' => 'Pluto',
  'Minnie'
];

print_r($nombres);

Al ejecutar este código se mostrará:

Array
(
  [0] => Mickey
  [1] => Donald
  [perro] => Pluto
  [2] => Minnie
)

22
¿Qué puede ir dentro de un array?

Hay una cosa curiosa con los arrays:

Un array puede tener como elementos cualquier tipo de dato. Y cada


elemento puede ser de un tipo diferente de los demás.

Hemos visto en los ejemplos arrays cuyos elementos eran todos cadenas de texto. Pero podemos
usar cualquier tipo de dato (y, recuerda, cada elemento puede o no tener un índice):

$array = [
  'nombre' => 'Gorka',
  20,
  50.45,
  [ 1, 2, 3 ],
  (object) [ 10, 20 ], ①
];
print_r($array);

① Esto convierte el array [ 10, 20 ] en un objeto.

Cuando ejecutemos esto tendremos una mezcla interesante:

Array
(
  [nombre] => Gorka
  [0] => 20
  [1] => 50.45
  [2] => Array
  (
  [0] => 1
  [1] => 2
  [2] => 3
  )

  [3] => stdClass Object


  (
  [0] => 10
  [1] => 20
  )

Este ejemplo no tiene ningún sentido, pero está bien para ilustrar que, como he dicho, en un array
podemos meter cualquier tipo de dato.

23
Una coma al final

Si te has fijado en el ejemplo de antes parece que se me había colado una coma después del último
elemento:

$array = [
  'nombre' => 'Gorka',
  20,
  50.45,
  [ 1, 2, 3 ],
  (object) [ 10, 20 ], ①
];

Esto no es un error en PHP. De hecho, en varias comunidades recomiendan poner una coma
después del último elemento del array. No he visto que el estándar diga nada ni a favor ni en contra
así que es cuestión de gustos.

Arrays anidados o arrays multidimensionales

Antes hemos visto que los elementos de un array pueden ser de cualquier tipo, incluso otros arrays.
Esto es muy conveniente ya que nos permite trabajar cómodamente con grupos de datos:

$ciudades = [
  [
  'nombre' => 'Bilbao',
  'habitantes' => 350000,
  'altitud' => 6
  ],
  [
  'nombre' => 'Madrid',
  'habitantes' => 3500000,
  'altitud' => 657
  ],
  [
  'nombre' => 'Ciudad de México',
  'habitantes' => 8850000,
  'altitud' => 2250
  ],
  [
  'nombre' => 'Buenos Aires',
  'habitantes' => 8850000,
  'altitud' => 25
  ],
];

Acceder a los elementos del array

Hasta ahora hemos visto cómo definir un array y verlo con print_r o var_dump pero eso no es muy

24
útil ¿verdad? Lo verdaderamente útil es poder acceder a los distintos elementos del array. Para ver
el valor de un elemento podemos hacer:

echo $array[1];

Así podemos mostrar el valor del elemento con índice 1 del array. O si tenemos un elemento con un
índice nombre:

echo $array['nombre'];

En un array multidimensional podemos hacer (usamos el del apartado anterior):

echo $ciudades[1]['habitantes'];

Y esto cogería el elemento 1 (Madrid) y mostraría el número de habitantes de esta ciudad.

No solo podemos acceder a los elementos del array, también podemos cambiar sus valores:

$array['nombre'] = 'Gorka';

O con un array multidimensional:

$ciudades[1]['habitantes'] = 100000000; // ¡Cuánta gente!

Bucles
Foreach

Uno de los bucles más usados en PHP es el foreach. Se usa de la siguiente forma:

foreach($array as $elemento) {
  // Código a ejecutar para cada elemento.
}

Este bucle ejecuta el código dentro de las llaves una vez por cada uno de los elementos del array. En
cada "pasada" guarda el elemento del array con el que va a trabajar en la variable $elemento (esta
variable no hay que definirla antes).

25
$numeros = [ 10, 14, 40, 50, 65 ];
foreach($numeros as $numero) {
  echo "$numero ";
}

En este ejemplo, se repiten el bucle cinco veces, una por cada elemento. En cada repetición la
variable $numero tomará el valor de un elemento del array $numeros. En la primera "pasada" la
variable $numero tendrá el valor 10, en la segunda 14, etc.

Si el array tiene índices (o claves) podemos usar esta otra forma del foreach más completa:

foreach($array as $clave => $elemento) {


  // Código a ejecutar para cada elemento.
}

Esta vez, para cada elemento del array su valor se almacenará en la variable $elemento y su índice
en la variable $clave.

Si, por ejemplo, tenemos un array con los datos de una persona:

$datos = [
  'nombre' => 'Gorka',
  'apellido' => 'Urrutia'
];
foreach( $datos as $campo => $valor ) {
  echo "El $campo es $valor.\n";
}

Y esto mostrará:

El nombre es Gorka.
El apellido es Urrutia.

Porque en cada vuelta que da el foreach coge el índice y lo guarda en $campo y el valor en $valor.
En la primera vuelta $campo será el índice nombre y $valor será Gorka.

For

El for es un bucle muy común en los lenguajes de programación. En PHP es más raro verlo porque
se suele usar foreach mucho más.

El formato de un bucle for es:

26
for( valor_inicial; condición; incrementos ) {
  // Código a ejecutar en cada vuelta
}

Un ejemplo sería:

for( $i=0; $i < 10; $i++ ) {


  echo "$i ";
}

While

El bucle while también es muy común en otros lenguajes:

while ( condicion ) {
  // Código a ejecutar en cada vuelta
}

Si quisiéramos tener el mismo resultado que con el ejemplo del for haríamos:

$i = 0;
while( $i < 10 ) {
  echo "$i ";
  $i++;
}

Do-while

do {
  // Código a ejecutar en cada vuelta
} while ( condicion );

El mismo ejemplo de antes con el do-while:

$i = 0;
do {
  echo "$i ";
  $i++;
} while ( $i < 10 );

Diferencia entre while y do-while

Puede que pienses que el while y el do-while son iguales. La diferencia está en que con el do-while

27
tenemos garantizado que el código entre llaves se ejecutará al menos una vez. Con el while el
código puede no ejecutarse nunca (si la condición no es verdadera).

Vamos a verlo en este ejemplo:

echo "\nCon el while: ";


$i = 0;
while( $i < 0 ) {
  echo "$i ";
  $i++;
}

echo "\nCon el do-while: ";


$i = 0;
do {
  echo "$i ";
  $i++;
} while ( $i < 0 );

Y el resultado será:

Con el while:
Con el do-while: 0

Con el do-while se muestra el cero porque, aunque la condición es falsa, el código se ejecuta al
menos una vez.

¡Un bucle infinito!

Y ahora vamos a ver una de las cosas más divertidas (y que más te pueden hacer sufrir) de los
bucles: los bucles infinitos. Éstos son bucles que no terminan nunca y se pueden producir por
alguno de los siguientes motivos:

• La condición se cumple siempre.

• Las variables usadas en la condición no cambian nunca.

Unos ejemplos de bucles infinitos:

for( $i = 100; $i > 0; $i++ ) {


  echo "$i) Soy un bonito bucle infinito\n";
}

¿Eres valiente?
 Si eres valiente puedes ejecutar este código.

¿Por qué es un bucle infinito? Porque el for seguirá ejecutándose mientras se cumpla la condición.

28
Como $i empieza en 100 la condición $i>0 se cumplirá siempre (porque $i empieza en 100 y en cada
vuelta aumenta su valor).

for( $i = 100; $i > 0; ) {


  echo "$i) Soy un bonito bucle infinito\n";
}

Este es un bucle infinito porque el valor de $i no cambia en cada vuelta.

Es raro olvidarse el incremento en un bucle for pero en un while o un do-while es más fácil porque
no hay una "sección" específica para ello. Por ejemplo, en un do-while podemos olvidar el $i++:

$i = 0;
do {
  echo "$i ";
} while ( $i < 10 );

o en un while:

$i = 0;
while( $i < 10 ) {
  echo "$i ";
}

Bucles y arrays
Los bucles y los arrays van muy de la mano. El mayor uso de los foreach es para "recorrer" arrays (y
también objetos). Por ejemplo, podemos usar el array $ciudades de antes para mostrar el listado de
ciudades:

foreach($ciudades as $ciudad) {
  echo $ciudad['nombre'] . "\n";
}

En cada ciclo del foreach cogemos un elemento del array $ciudades y lo guardamos en la varible
$ciudad. Para el primer elemento la variable $ciudad tomará el valor:

[
  'nombre' => 'Bilbao',
  'habitantes' => 350000,
  'altitud' => 6
]

Así que podemos acceder al nombre de la ciudad usando: $ciudad[nombre].

29
isset()

Cuando trabajamos con un array es muy habitual que algún elemento no exista (por ejemplo
cuando analizamos los datos enviados en un formulario). Por esa razón es conveniente que, si hay
alguna posibilidad de que un índice no exista, usar la función isset().

Esta función comprueba si existe una variable, o el elemento de un array (o una propiedad en un
objeto). Si existe devuelve true, si no existe devuelve false. La forma de usarla es:

isset( $variable ); // ¿Existe la variable?


isset( $array['elemento'] ); // ¿Existe el índice del array?

Si, por ejemplo, tenemos este array:

$datos = [ 'nombre' => 'Gorka' ];

y pretendemos acceder a un elemento inexistente:

echo $datos['edad'];

Tendremos un aviso:

PHP Notice: Undefined index: edad in intro.php on line 14

Para evitarnos problemas, antes de acceder a un elemento que puede no existir, usaremos la
función isset():

$datos = [ 'nombre' => 'Gorka' ];


if ( isset( $datos['edad'] ) ) {
  echo $datos['edad'];
}

Comillas dobles vs comillas simples


La diferencia entre las comillas dobles y las simples es que en las primeras podemos meter
variables que se convertirán en sus valores. Por ejemplo:

$nombre = 'Gorka';
echo "Mi nombre es $nombre.\n";

Esto mostrará:

30
Mi nombre es Gorka.

Pero con comillas simples:

echo 'Mi nombre es $nombre.\n';

veremos este otro resultado:

Mi nombre es $nombre.\n

Evitar errores

Hay ocasiones en las que el texto que queremos mostrar algún texto sin espacios justo detrás de la
variable con comillas dobles. Supongamos algo absurdo como esto:

$nombre = 'Gorka';
echo "Mi nombre es $nombres.\n"; // Queremos una 's' justo después del nombre

Al ejecutar esto veremos un error:

PHP Notice: Undefined variable: nombres in index.php

El problema es que PHP no es capaz de saber que solo queremos coger $nombre y que la s es parte
del texto que queremos mostrar. Para evitarlo podemos añadir unas llaves:

$nombre = 'Gorka';
echo "Mi nombre es {$nombre}s.\n";

Son más rápidas las comillas simples

Hay gente que prefiere las comillas simples porque, supuestamente, se procesan más rápido que las
comillas dobles. Según la gente de The PHP Benchmark eso no es cierto.

De todos modos, si tu aplicación es muy lenta el problema no estará aquí así que no te obsesiones
con ésto.

31
Programación orientada a objetos
Introducción
¿Por qué deberíamos usar programación orientada a objetos? Hay varias razones:

• El código es más limpio.

• Es más fácil de ampliar y mantener.

• Permite hacer tests, pruebas unitarias, pruebas de validación, etc.

• El código es reutilizable.

Por cierto reutilizar código no es usar el copy/paste, es utilizar la programación orientada a objetos
de manera correcta. De ésto y de las otras ventajas de la programación orientada a objetos vamos a
hablar largo y tendido.

¿Qué es un objeto?
Un objeto es un tipo especial de variable que tiene lo que se llaman propiedades y métodos. Las
propiedades son variables asociadas a un objeto. Los métodos son funciones asociadas a un objeto.

Imagina, por ejemplo, tu coche. Tu coche es un objeto que puede ir hacia delante, ir marcha atrás,
encender luces, etc. Todos los coches, al menos los que funcionan, pueden hacer todas esas cosas.
Esto serían los métodos del objeto y son iguales para todos los coches (aunque unos puedan ir más
rápidos que otros).

Pero cada coche tiene sus características propias: color, potencia, kilometraje, etc. Esto serían las
propiedades del objeto y son únicas de cada objeto.

Ahora te preguntarás ¿cómo se crea un objeto? Para crear un objeto necesitamos primero lo que se
llama una clase.

¿Y qué es una clase?


Así que tu coche, el coche de tu vecino y todos los coches del mundo pertenecen a la clase "Coche".
En programación una clase es lo que define a los objetos de un mismo tipo.

Podemos pensar en una clase como una especie de plantilla a partir de la cual podemos crear
objetos.

Una clase se define así:

class Coche
{

32
Y ahora vamos a añadir a esta clase las propiedades color y potencia:

class Coche
{
  public $color;
  public $potencia;
}

Una propiedad se define igual que una variable normal pero anteponiendo la palabra reservada
public. Por ahora basta con saber que una propiedad public estará accesible desde fuera de la clase.
Las propiedades también pueden ser private o protected. Ya veremos los tipos de propiedades y
para qué se usa cada uno más adelante.

Lo siguiente que vamos a hacer es añadirle un método al que llamaremos acelerar:

class Coche
{
  public $color;
  public $potencia;

  public function acelerar() {


  echo "Acelerando";
  }
}

Crear objetos
Una vez tenemos definida una clase podemos crear todos los objetos que queramos a partir de ella.
Un objeto se define así:

$miCoche = new Coche;

Este objeto coche tendrá las propiedades $color y $potencia y el método acelerar().

Instanciar

Cuando creamos un objeto a partir de una clase decimos que estamos


instanciando la clase.

Si queremos acceder a una propiedad haríamos:

$miCoche->color;

Y si lo que queremos es acceder a uno de sus métodos:

33
$miCoche->acelerar();

Nota

 El símbolo → (un guión seguido de >) es el que nos permite acceder a las
propiedades y métodos del objeto.

Vamos a ejecutar el script


Puedes creerte todo lo que cuento en este libro o puedes probarlo por tu cuenta. Empieza por crear
un fichero al que llamaremos index.php. En este fichero copia el siguiente código:

class Coche
{
  public $color;
  public $potencia;

  public function acelerar() {


  echo "Acelerando";
  }
}

$miCoche = new Coche;

$miCoche->acelerar();

Para ejecutar el programa podríamos hacerlo desde un terminal así:

$ php index.php

Nota
 Deberás tener instalado el PHP en tu ordenador.

Al ejecutar el script veremos que se muestra el texto:

$ php index.php
acelerando

que es el texto que muestra el método acelerar().

34
Tus amigas las funciones print_r() y var_dump()
print_r()

Seguro que ya conoces estas dos funciones print_r() y var_dump(), lo que hacen es mostrar el
contenido de una variable. Si las usamos con un objeto veremos la información de sus propiedades.
Añade la siguiente línea al final de index.php:

print_r($miCoche);

Al ejecutar el script añadiendo este print_r veremos el siguiente mensaje:

Coche Object
(
  [color] =>
  [potencia] =>
)

La función print_r nos muestra que la variable $miCoche es un objeto de la clase Coche y que tiene
dos propiedades color y potencia que tienen valores en blanco.

¿y por qué no se muestran los métodos que tiene el objeto?

Me alegra que hagas esa pregunta. Cada objeto tiene sus propias propiedades; un coche puede ser
rojo, azul, verde, etc. o puede tener más o menos potencia. Si embargo todos los coches pueden
acelerar así que no tendría sentido decir que un coche puede acelerar; si es un coche puede
acelerar. De igual forma, un objeto del tipo Coche va a tener exactamente los mismos métodos que
todos los demás objetos del tipo Coche.

var_dump()

Esta función es similar a print_r pero nos muestra la información de manera diferente:

object(Coche)#1 (2) {
  ["color"]=>
  NULL
  ["potencia"]=>
  NULL
}

En primer lugar nos indica que es un objeto de tipo Coche (entre paréntesis). El #1 indica que es el
primer objeto instanciado para esa clase. El (2) nos dice que el objeto tiene dos propiedades.

Por último viene el listado de propiedades del objeto. En este listado se muestra el valor de cada
uno de los elementos. Como en nuestro ejemplo no hemos asignado ningún valor a las propiedades
se muestra NULL. Si hubiésemos dado un valor al color del coche:

35
class Coche
{
  public $color;
  public $potencia;

  public function acelerar() {


  echo "Acelerando";
  }
}

$miCoche = new Coche;


$miCoche->color = 'Azul';

var_dump($miCoche);

el resultado hubiese sido:

object(Coche)#1 (2) {
  ["color"]=>
  string(4) "Azul"
  ["potencia"]=>
  NULL
}

En este caso nos indica que la propiedad color es una cadena de cuatro caracteres y contiene el
valor "Azul".

Como vemos var_dump() nos da más información acerca del objeto, pero print_r() nos lo muestra de
manera más resumida y, quizá, más fácil de leer. Como suele ocurrir, es cuestión de gustos (o de lo
que necesitemos).

Cambiar el valor de una propiedad


Las propiedades de un objeto se pueden cambiar como cualquier otra variable. Basta con hacer:

$miCoche->color = 'Rojo';

Dentro del index.php quedaría:

36
class Coche
{
  public $color;
  public $potencia;

  public function acelerar() {


  echo "Acelerando";
  }
}

$miCoche = new Coche;

$miCoche->color = 'Rojo';

print_r($miCoche);

Y el resultado al ejecutar este código será:

Coche Object
(
  [color] => Rojo
  [potencia] =>
)

Acceder al valor de una propiedad


De la misma forma podemos recuperar el valor que tiene una propiedad de un objeto:

class Coche
{
  public $color;
  public $potencia;

  public function acelerar() {


  echo "Acelerando";
  }
}

$miCoche = new Coche;

$miCoche->color = 'Rojo';

echo $miCoche->color;

Y, claro está, podemos usarla como una variable normal:

37
$color = $miCoche->color;

Un pequeño toque de elegancia

Te habrás fijado que al ejecutar el código el resultado queda algo así en la línea de comandos:

$ php index.php
rojo$

Esto sucede porque al mostrar el valor de Rojo no añadimos un salto de línea y el símbolo de la
línea de comandos se solapa con el texto Rojo. Podemos arreglarlo añadiendo el carácter de salto de
línea \n:

echo $miCoche->color . '\n';

O también:

echo $miCoche->color . PHP_EOL;

PHP_EOL

 PHP_EOL es una constante predefinida de PHP que significa End Of Line (Fin de
línea) y es equivalente a usar \n. Queda bien y parece que sabes más si lo usas.

Cada objeto tiene sus propiedades


Antes he comentado que cada objeto tiene sus propiedades y que éstas son diferentes a los demás
objetos. ¿Qué quiere decir ésto? Para entenderlo vamos a crear dos objetos: $miCoche y $tuCoche
(mi coche es mejor porque es el mío):

38
class Coche
{
  public $color;
  public $potencia;

  public function acelerar() {


  echo "Acelerando";
  }
}

$miCoche = new Coche;


$tuCoche = new Coche;

$miCoche->color = 'Rojo';
$tuCoche->color = 'Azul';

print_r($miCoche);
print_r($tuCoche);

El resultado de ejecutar este código será:

Coche Object
(
  [color] => Rojo
  [potencia] =>
)
Coche Object
(
  [color] => Azul
  [potencia] =>
)

Como podemos apreciar cada objeto tiene sus propiedades diferentes. Si cambiamos el color a un
coche no afectará a los otros coches.

Valores iniciales de una propiedad


A una propiedad, si nos interesa, podemos darle un valor inicial. Por ejemplo, podemos decir que
un coche, por defecto, será rojo:

39
class Coche
{
  public $color = 'Rojo';
  public $potencia;

  public function acelerar() {


  echo "Acelerando";
  }
}

Pero esto no tiene mucho sentido ¿verdad? Un coche puede ser de cualquier color.

Vamos a hacer algo con más sentido. Tenemos en nuestra clase de ejemplo un método para acelerar
que no hace nada interesante. ¿Qué tal si lo modificamos para que haga algo más real? Por ejemplo,
vamos a hacer que al acelerar aumente la velocidad. Para ello empecemos añadiendo una nueva
propiedad a la que llamaremos $velocidad:

class Coche
{
  public $color;
  public $potencia;
  public $velocidad = 0;

  public function acelerar() {


  echo "Acelerando";
  }
}

Al principio el coche estrá parado, así que tiene sentido que el valor inicial de $velocidad sea cero:

public $velocidad = 0;

Como hemos visto en estos dos ejemplos, podemos dar valores iniciales a las propiedades si lo
necesitamos. Pero usa el sentido común cuando des valores iniciales a una variable. En PHP si a
una variable no le damos un valor inicial, por defecto, tendrá el valor NULL.

Cambiar las propiedades desde dentro


Ahora que tenemos una forma de controlar la velocidad, con la propiedad $velocidad podemos
empezar a usarla. Vamos a hacer que cada vez que se llame al método acelerar() suba la velocidad.
Para eso necesitamos poder acceder y modificar el valor de $velocidad desde el método acelerar().

¿Cómo crees que se podría hacer ésto? Se te podría ocurrir que podemos acceder a la propiedad
$velocidad así:

40
public function acelerar() {
  $velocidad = $velocidad + 1;
}

y el código quedaría así:

class Coche
{
  public $color;
  public $potencia;
  public $velocidad = 0;

  public function acelerar() {


  $velocidad = $velocidad + 1;
  }
}

$miCoche = new Coche;


$miCoche->acelerar();

Pero, si ejecutas este programa el resultado será:

PHP Notice: Undefined variable: velocidad in index.php on line 9

¡No veo el error!


Dependiendo de cómo esté configurado tu PHP es posible que no se te muestren
los avisos como éste. Cuando estás en la fase de desarrollo es importante que se
 muestren todos los errores y los avisos para poder corregirlos. Si no ves los errores
puedes activarlos en el php.ini o añadiendo al comienzo de tu script:
error_reporting(E_ALL); ini_set(display_errors, 1);

Se está quejando de que no existe la variable $velocidad. Y no existe porque esa variable $velocidad
es una variable local que solo existe dentro del método acelerar(); no es la propiedad $acelerar. Es
importante entender esto bien.

¿Y entonces? ¿Cómo accedemos a la propiedad desde dentro de la clase? Para eso tenemos la
variable especial $this. $this es una variable que nos permite acceder a los métodos y propiedades
desde dentro del código de la clase. En nuestro ejemplo lo haríamos así:

41
class Coche
{
  public $color;
  public $potencia;
  public $velocidad = 0;

  public function acelerar() {


  $this->velocidad = $this->velocidad + 1;
  }
}

$miCoche = new Coche;


$miCoche->acelerar();
print_r($miCoche);

El resultado será:

Coche Object
(
  [color] =>
  [potencia] =>
  [velocidad] => 1
)

Y cada vez que llamemos al método acelerar() la propiedad $velocidad se incrementará.

Recordemos que en PHP ésto:

$this->velocidad = $this->velocidad + 1;

es equivalente a ésto:

$this->velocidad += 1;

¿Qué pasa si olvido el paréntesis al llamar a un


método?
Es posible que al llamar a un método se nos olvide poner el paréntesis. En ese caso lo que
realmente estaríamos haciendo sería llamar a una propiedad, no al método. Si, por ejemplo, si en
lugar de poner acelerar() ponemos:

$miCoche->acelerar;

42
El resultado sería:

PHP Notice: Undefined property: Coche::$acelerar in


/home/gorka/Proyectos/Libros/libro-php/index.php on line 14

Pasar parámetros a un método


A un método podemos pasarle datos en forma de parámetros. Vamos a permitir que el coche pueda
acelerar una cantidad variable, para eso modificamos el método acelerar():

public function acelerar( $incremento ) {


  $this->velocidad += $incremento;
}

El parámetro $incremento será una variable local que solo existirá dentro del método acelerar().

Para llamar a un método que tiene parámetros usamos el mismo sistema que para una función
"normal":

$miCoche->acelerar(10);

Más de un parámetro

Podemos pasar todos los parámetros que queramos:

public function metodo( $parametro1, $parametro2, $parametro3 ) {

No es conveniente abusar a la hora de definir parámetros. Cuantos más parámetros usemos más
complejo va a ser nuestro método y más difícil va a ser recordar qué es cada uno y su orden.

¿Qué podemos pasar como parámetro?

A un método podemos pasarle cualquier tipo de valor: un número (entero o decimal), una cadena
de texto, un array, un objeto, etc.

Lo que no podemos hacer es algo como ésto:

public function metodo( $miArray[10] ) { // ¡Mal!


}

No podemos esperar que el parámetro se almacene en el elemento 10 de un array. En realidad esto


no tiene ningún sentido pero he visto hacerlo en más de una ocasión así que me ha parecido
conveniente comentarlo.

43
Si necesitaras hacer algo así ¿por qué no mejor haces ésto?

public function metodo( $valor ) {


  $miArray = [];
  $miArray[10] = $valor;
}

Variables como parámetros

Cuando llamamos a un método podemos usar un valor como hemos hecho antes:

$miCoche->acelerar(10);

O podemos usar una variable que contenga el valor que queremos pasar:

$miCoche->acelerar( $cantidad );

También podemos pasar el valor de la propiedad de otro objeto:

$miCoche->acelerar( $otroObjeto->cantidad );

O un elemento de un array:

$miCoche->acelerar( $miArray[10] );

Nota
Antes he dicho que no se puede usar $miArray[10] como el parámetro que espera
 una función. Pero sí que podemos pasar el elemento 10 de un array como un valor.
El array existe y su valor también, esto sí tiene sentido.

Pasar un objeto como parámetro

Hemos visto en el apartado anterior que se puede pasar cualquier cosa como parámetro, incluido
un objeto. Vamos a crear un nuevo método en nuestra clase para ilustrarlo. Este nuevo método será
repostar() que nos permitirá indicar el tipo de combustible y los litros que queremos:

public function repostar( $tipoCombustible, $litros ) {


  echo "Glu, glu";
}

Vamos a llenar el coche con gasolina. Como queremos ver un ejemplo de pasar un objeto a un
método necesitaremos otro objeto. Ese objeto va a ser del tipo Gasolina así que empecemos creando

44
esta clase:

class Gasolina
{
  public $octanaje = "";
}

Vamos a dejar esta clase así de simplona porque no necesitamos más complejidad por ahora. Solo
nos queda crear un objeto de la clase Gasolina y enviarlo al método repostar()

$gasolina = new Gasolina;


$miCoche->repostar($gasolina, 20);

¿Y si olvido algún parámetro?

Cuando llamamos a un método tiene que coincidir el número de valores que le pasemos con el
número de parámetros que espera. Volvamos al ejemplo de antes pero vamos a quitarle el valor
que le estábamos pasando:

class Coche
{
  public $color;
  public $potencia;
  public $velocidad = 0;

  public function acelerar( $incremento ) {


  $this->velocidad = $this->velocidad + $incremento;
  }
}

$miCoche = new Coche;


$miCoche->acelerar();
echo "Velocidad actual: {$miCoche->velocidad}\n";

Al ejecutar este código PHP se quejará por duplicado:

PHP Warning: Missing argument 1 for Coche::acelerar(), called in index.php on line 14


and defined in index.php on line 8 ①
PHP Notice: Undefined variable: incremento in index.php on line 9 ②

① Aquí se queja de que al método acelerar() le falta un parámetro.

② Aquí se queja de que la variable $incremento no está definida. Esto está relacionado con el hecho
de que no le hemos pasado este parámetro.

45
Valores por defecto

En ocasiones es interesante dar un valor por defecto a un parámetro. Es decir, si no se recibe un


parámetro usar un valor predefinido. En el caso de acelerar() podríamos hacer que si no hay
parámetro éste tendrá el valor 1:

public function acelerar( $incremento = 1 ) {

Así, si hacemos:

$miCoche->acelerar();

ahora no tendremos un error y el valor que incrementará la velocidad será 1:

Velocidad actual: 1

Pero si le indicamos un valor se usará éste:

$miCoche->acelerar( 10 );

y el resultado será:

Velocidad actual: 10

Si tenemos más de un parámetro podemos dar un valor por defecto a cualesquiera de ellos. Pero,
ojo, podría suceder ésto:

public function acelerar( $incremento = 1, $otroParametro ) {

Si a este método le pasamos solo un valor éste se usará para el primer parámetro ($incremento) y el
segundo parámetro ($otroParametro) se quejará de que le falta un valor. Vamos a verlo:

$miCoche->acelerar( 10 );

Al ejecutar este código tendremos:

PHP Warning: Missing argument 2 for Coche::acelerar(), called in index.php on line 14


and defined in index.php on line 8

46
Especificar el tipo de parámetro

Hemos visto que PHP nos da mucha libertad a la hora de meter datos en una propiedad y de pasar
valores a un método (podemos pasar casi cualquier cosa). Sin embargo esta libertad a veces tiene
un precio. Habrá métodos que solo deberían aceptar un tipo de parámetro.

Por ejemplo, en el método acelerar() solo deberíamos aceptar números. Si pasamos un array o una
cadena de texto a ese método no tendría sentido. Así que podemos obligar a que un método acepte
solo un tipo de parámetro.

Para ésto debemos poder justo delante del parámetro el tipo de valor que esperamos; En este caso
sería un valor de tipo int (entero):

public function acelerar( int $incremento = 1 ) {

Ahora, si intentamos pasar un valor que no sea entero, por ejemplo:

$miCoche->acelerar("mucho");

el fatal resultado será:

PHP Fatal error: Uncaught TypeError: Argument 1 passed to Coche::acelerar() must be


of the type integer, string given, called in index.php on line 22 and defined in
index.php:8
Stack trace:
#0 index.php(22): Coche->acelerar('a10')
#1 {main}
  thrown in index.php on line 8

Aquí tenemos un error fatal. PHP se está quejando de que le enviamos al método acelerar() un dato
de tipo string (cadena de texto) cuando el único que acepta es de tipo int (entero).

Como dato curioso a este método le podemos pasar una cadena siempre y cuando ésta pueda
convertirse en un número entero. La siguiente llamada funcionaría sin problemas:

$miCoche->acelerar("10");

También podemos obligar que el valor pasado sea de otros tipos:

• string - Obliga a que el parámetro sea una cadena.

• int - El parámetro debe ser número entero.

• float - El parámetro debe ser un número de punto flotante.

• array - El parámetro debe ser un array.

• object - Debemos pasar un objeto.

47
Otros tipos
 Consulta la sección de tipos de datos en la web de PHP.

Y, claro está, también podemos obligar a que el parámetro sea un objeto de un tipo determinado.
Hemos visto el método repostar() al que le podíamos pasar un objeto de tipo Gasolina. A ese método
podíamos haberle pasado cualquier tipo de parámetro. Imaginemos que queremos obligar a que el
parámetro sea de tipo Gasolina. Para eso haríamos:

public function repostar( Gasolina $tipoCombustible, $litros ) {


  echo "Glu, glu";
}

La clase coche podría quedar así:

class Coche
{
  public function repostar( Gasolina $tipoCombustible, $litros ) {
  echo "Glu, glu";
  }
}

$miCoche = new Coche;


$miCoche->repostar("Gasolina", 10);

Y al ejecutar el código el resultado sería:

PHP Catchable fatal error: Argument 1 passed to Coche::repostar() must be an instance


of Gasolina, string given, called in index.php on line 9 and defined in index.php on
line 3

Estamos pasando una cadena que contiene el valor "Gasolina" en lugar de un objeto de tipo
Gasolina.

Especificar el tipo de dato que debe devolver un método

También tenemos la opción de indicar el tipo de dato que va a devolver un método. Esta vez vamos
a crear un método que nos diga la velocidad a la que va el coche. Este valor debería ser de tipo
entero.

El método quedaría así:

public function velocidad() : int {


}

Estamos diciendo que el método debe devolver un entero pero no está devolviendo ningún valor.

48
Por esta razón, cuando llamemos al método así:

$miCoche->velocidad();

tendremos un error como éste:

PHP Fatal error: Uncaught TypeError: Return value of Coche::velocidad() must be of


the type integer, none returned in index.php:14
Stack trace:
#0 index.php(22): Coche->velocidad()
#1 {main}
  thrown in index.php on line 14

Para evitar este error el método tendrá que devolver un número entero:

public function velocidad() : int {


  return $this->velocidad;
}

Llamar a un método desde otro


Al igual que accedemos a una propiedad desde un método usando $this podemos acceder a un
método desde otro. Por ejemplo podríamos tener un método llamado comprobar() que llamase a
otros métodos para comprobar el estado de ciertas partes del coche:

public function comprobar() {


  $this->comprobarNivelAceite();
  $this->comprobarPresionRuedas();
}

public function comprobarNivelAceite() {


}

public function comprobarPresionRuedas() {


}

¿Y si pongo el $ en el nombre de la propiedad al


llamarla?
En ocasiones puede que queramos llamar a un método u otro dependiendo del valor de una
variable. Esto podríamos hacerlo con un if, un switch u otros métodos. En PHP, tan flexible él,
tenemos la opción de usar la propia variable para llamar al método. Lo mejor para entenderlo un
ejemplo:

49
class Coche {
  public function acelerar()
  {
  echo "Brum, brum\n";
  }

  public function frenar()


  {
  echo "Iiiiiiiijjh\n";
  }
}

$miCoche = new Coche();


$accion = 'acelerar';
$miCoche->$accion();

Aquí, al hacer $miCoche→$accion(); lo que hacemos es sustituir el valor de $accion antes de llamar
al método. En este ejemplo $accion tiene el valor 'acelerar' así que ese será el método al que se
llame.

Como imaginarás, si el valor de la variable no coincide con ningún método tendremos un error.

¿Y si pongo un método dentro de un string?


Siguiendo con el ejemplo del apartado anterior podrías querer hacer algo así:

echo "$miCoche->acelerar()";

En este caso PHP entenderá que quieres mostrar el contenido de la propiedad acelerar y, después,
mostrar un paréntesis.

Para que se llame al método acelerar() dentro del string deberás usar llaves:

echo "{$miCoche->acelerar()}";

¿Puede haber un método y una propiedad con el


mismo nombre?
Sí, no vas a tener ningún problema (salvo la confusión que esto te pueda generar). La única
diferencia que habrá entre el método y la propiedad es el paréntesis. Podemos verlo con un
ejemplo:

50
class Coche
{
  public $litros = 10;

  public function litros() {


  return "Hay {$this->litros} litros." . PHP_EOL;
  }
}

$miCoche = new Coche();


echo $miCoche->litros . PHP_EOL;
echo $miCoche->litros();

El resultado de este código sería:

10
Hay 10 litros.

Uno de los problemas de compartir nombres es que si, por ejemplo, olvidas un paréntesis el
programa no funcionará como esperas pero no verás ningún error.

¿De qué clase es mi objeto?


En ocasiones necesitarás saber el tipo de una determinada variable. Hay varias funciones en PHP
para saber si una variable es de un determinado tipo (como is_array, is_numeric, etc.) pero hay una
función genérica que nos devuelve el tipo: Se trata de la función gettype. Esta función nos dirá si la
variable es un objeto, un array, un entero, etc, pero no nos dirá de qué clase es:

$miCoche = new Coche();


echo gettype($miCoche);

Aquí solo nos dirá que la variable es de tipo object.

¿Con guión bajo o sin guión bajo?


Aquí tenemos un ejemplo de una de las razones por las que PHP ha tenido mala
 fama tanto tiempo. Las funciones is_array y similares llevan guiones bajos pero
gettype no lleva un guión bajo ¿por qué no la han llamado get_type?.

Si queremos información acerca de la clase tendremos que usar la función get_class:

$miCoche = new Coche();


echo get_class($miCoche);

Esta sí nos dirá que la variable $miCoche es del tipo Coche.

51
Warning si no es objeto

 get_class espera que la variable a analizar sea un objeto. Si no es así mostrará un


aviso (warning).

¿Esta propiedad existe? ¿y este método?


También suele ser útil saber si un determinado objeto o una clase tienen definido un método. Par
eso tenemos la función method_exists():

$miCoche = new Coche();

var_dump(method_exists($miCoche, 'litros'));
var_dump(method_exists($miCoche, 'combustible'));

En el primer caso devolvería el valor true, porque existe el método litros en el objeto $miCoche y en
el segundo false (combustible no existe).

También podemos comprobar si existe el método en una clase sin usar un objeto:

var_dump(method_exists('Coche', 'litros'));
var_dump(method_exists('Coche', 'combustible'));

Si lo que queremos es comprobar si existe una propiedad podemos usar isset():

var_dump(isset($miCoche->litros));
var_dump(isset($miCoche->combustible));

Constructores y destructores
En PHP hay una serie de métodos llamados métodos mágicos (magic methods) que cumplen
funciones especiales dentro de una clase. Entre éstos está el método __construct() al que se llama
de forma automática al crear un objeto, no es necesario que la llamemos nosotros. Este método
recibe el método de constructor.

Un constructor se define así:

class Coche {
  function __construct()
  {
  }
}

Veamos un constructor en acción:

52
class Coche
{
  function __construct()
  {
  echo "Mensaje del constructor.\n";
  }
}

echo "Vamos a crear el objeto.\n";


$miCoche = new Coche();
echo "El objeto ya está creado.\n";

Al ejecutar este código veremos:

Vamos a crear el objeto.


Mensaje del constructor.
El objeto ya está creado.

Se puede ver que, aunque no hemos llamado a $miCoche→__construct(), éste ha mostrado su


mensaje.

¿Para qué sirve un constructor?

Un constructor se debe usar (no es obligatorio) cuando queremos dejar nuestro objeto listo para ser
usado.

Vamos a verlo más claro con un ejemplo. Supongamos la clase Coche con la que controlamos el tipo
de combustible de un coche. Podríamos hacer algo así (sin usar constructor):

class Coche
{
  public $combustible;

  public function definirTipoCombustible($combustible)


  {
  $this->combustible = $combustible;
  }

  public function mostrarTipoCombustible()


  {
  return $this->combustible;
  }
}

$miCoche = new Coche();


$miCoche->definirTipoCombustible('gasolina');
echo "El coche usa " . $miCoche->mostrarTipoCombustible() . PHP_EOL;

53
Si lo ejecutamos el resultado sería:

El coche usa gasolina

Todo correcto. Ahora imaginemos que olvidamos la llamada a definirTipoCombustible():

$miCoche = new Coche();


echo "El coche usa " . $miCoche->mostrarTipoCombustible() . PHP_EOL;

El resultado sería:

El coche usa

¿Qué usa el coche? No lo sabemos, porque la variable $combustible todavía no tiene un valor
definido. Tenemos el objeto $miCoche creado pero no está listo para usar porque le falta el valor del
combustible.

Podrías argumentar que no es un problema porque podemos dar un valor inicial a $combustible:

public $combustible = 'gasolina';

Esta podría ser una solución. Pero esto nos obliga a que los coches, por defecto, sean de gasolina. No
es una solución ideal.

Aquí es donde entran en juego los constructores. Con un constructor podríamos hacer:

class Coche
{
  public $combustible;

  public function __construct($combustible)


  {
  $this->combustible = $combustible;
  }

  public function mostrarTipoCombustible()


  {
  return $this->combustible;
  }
}

$miCoche = new Coche('gasolina');


echo "El coche usa " . $miCoche->mostrarTipoCombustible() . PHP_EOL;

Y aquí tenemos la garantía de que, cuando vayamos a usar el objeto $miCoche, éste ya estará listo.

54
¿Qué pasaría si no le damos el parámetro al constructor? Pues que tendremos un bonito
recordatorio de que hemos metido la pata:

PHP Warning: Missing argument 1 for Coche::__construct(), called in intro.php on line


18 and defined in intro.php on line 7
PHP Notice: Undefined variable: combustible in intro.php on line 9

El error es doble: por un lado se queja de que el constructor no está recibiendo el parámetro que
necesita y por otro lado la variable $combustible no está definida (porque no la hemos pasado).

¿Y si pasamos el parámetro en blanco? En este caso PHP no se quejará por la falta de parámetro.
Pero si necesitamos que se nos pase un dato podemos hacer la comprobación dentro del
constructor y lanzar un error si viene en blanco.

Los "antiguos" constructores

Cuando digo los antiguos constructores no me refiero a aquellos que construyeron las pirámides de
Egipto (que no, no fueron extraterrestres). Me refiero al formato que se usaba antes para los
constructores en PHP. Es posible que los hayas visto en algún ejemplo:

class Coche
{
  public function Coche()
  {
  }
}

En PHP 7 esta forma de definir un constructor se considera obsoleta y en futuras versiones no


funcionará así que es mejor usar el sistema nuevo.

Por último es importante tener en cuenta que un constructor no devuelve ningún valor. No esperes
que ésto funcione:

55
class Coche
{
  public $combustible;

  public function __construct($combustible)


  {
  $this->combustible = $combustible;
  return "Esto es un coche";
  }

  public function mostrarTipoCombustible()


  {
  return $this->combustible;
  }
}

$miCoche = new Coche("gasolina");

La cadena "Esto es un coche" se perderá en los confines de un fabulosos y misterioso mundo


virtual.

Destructores

Hay otra función especial que se ejecuta cuando el objeto se elimina de la memoria o cuando
termina la ejecución del programa. El nombre de esta función es el destructor.

Se define igual que el constructor:

function __destruct()
{
}

Si añadimos esta función a nuestro programa:

56
class Coche
{
  public $combustible;

  public function __construct($combustible)


  {
  $this->combustible = $combustible;
  }

  public function __destruct()


  {
  echo "Objeto destruído.\n";
  }

  public function mostrarTipoCombustible()


  {
  return $this->combustible;
  }
}

$miCoche = new Coche("gasolina");


echo "El coche usa " . $miCoche->mostrarTipoCombustible() . PHP_EOL;
echo "Fin del programa.\n";

Vemos que se llama a esta función al final del programa:

El coche usa gasolina


Fin del programa.
Objeto destruído.

En realidad el destructor se ejecuta cuando todas las referencias a él desaparecen. ¿Qué quiere
decir ésto? Cuando no haya ninguna variable que contenga el objeto se llamará al destructor. Para
verlo vamos a modificar el ejemplo:

$miCoche = new Coche("gasolina");


echo "El coche usa " . $miCoche->mostrarTipoCombustible() . PHP_EOL;
$miCoche = null;
echo "Fin del programa.\n";

Aquí estamos haciendo que la variable $miCoche deje de "apuntar" al objeto. A partir de ese
momento la única "referencia" que había al objeto era esta variable. Así que el objeto ya no es
necesario y se destruye. Por tanto veremos que el mensaje "Objeto destruído" aparece antes que
"Fin del programa":

57
El coche usa gasolina
Objeto destruído.
Fin del programa.

Y por último es importante saber que los destructores no devuelven ningún valor ni se les pueden
pasar parámetros.

58
Un ejemplo paso a paso
Llegados a este punto hemos avanzado bastante y es hora de hacer un pequeño parón y practicar lo
aprendido. Todavía nos queda bastante por revisar (como por ejemplo los métodos y propiedades
privados) pero es conveniente afianzar conocimientos antes de seguir.

En este capítulo te propongo un sencillo ejemplo. Voy a aprovechar para comentar lo que se
consideran hoy en día buenas prácticas y algunos consejos para hacer tu código más legible.

Verás que insisto mucho en lo de "hacer tu código más legible" ¿por qué? Porque tu código lo
tendrán que leer otras personas y cuanto más fácil sea de entender menos esfuerzo costará
modificarlo y mejorarlo el día de mañana. Seguramente te habrá pasado que revisas tu código unas
semanas o meses más tarde y te preguntas ¿pero qué es lo que quería hacer aquí?

Esforzarse en hacer un código de calidad no cuesta mucho esfuerzo adicional pero te ahorrará
muchos quebraderos de cabeza en el futuro.

Seguramente algunos de estos consejos te parecerán una tontería pero medítalo antes de
rechazarlos. Algunos de ellos son fruto de años de experiencia y otros son recomendaciones de
gente de renombre. Ten la mente abierta al leer este capítulo.

Lo que sí es muy importante es que todos los miembros de un equipo (o todos los involucrados en
un proyecto) usen un mismo estilo de programación. Es conveniente ponerse todos de acuerdo
antes de comenzar un proyecto en las convenciones que se van a seguir. Y es muy recomendable
dejar por escrito en un documento todas las decisiones que se tomen, por si se incorporan nuevos
miembros al equipo.

El enunciado
Modifica la clase Coche para poder hacer lo siguiente:

• En el constructor de la clase podremos indicar el tipo de combustible que acepta el coche (no
uses una clase para el combustible, basta con una cadena de texto como "Gasolina"). Si no se
indica nada el combustible será Gasolina.

• El coche comienza estando parado y con el depósito vacío.

• Saber si el coche está parado o en marcha (si velocidad es mayor que cero estará en marcha).

• Si el coche está en la reserva (le quedan menos de 10 litros de combustible) no se puede acelerar
(es un coche muy exquisito, no acepta combustible de la reserva).

• Si se intenta echar un combustible que no es el correcto no aumentarán los litros de


combustible (también es un coche muy listo, si el combustible no le gusta te lo escupe en la
cara).

Una vez tengas la clase crea el objeto $miCoche con él y haz las siguientes operaciones:

59
$miCoche = new Coche('Gasoil');
echo "Velocidad después de acelerar con el depósito vacío: " . $miCoche->acelerar(10)
. "\n";
echo "Combustible después de repostar con el combustible equivocado: " . $miCoche-
>repostar("Gasolina", 40) . "\n";
echo "Combustible después de echar 40 litros de gasóleo: " . $miCoche->repostar(
"Gasoil", 40) . "\n";
echo "Velocidad después de acelerar con combustible en el depósito: " . $miCoche-
>acelerar(10) . "\n";
echo "Estado del coche: " . $miCoche->estado();

y el resultado debería ser:

Velocidad después de acelerar con el depósito vacío: 0


Combustible después de repostar con el combustible equivocado: 0
Combustible después de echar 40 litros de gasóleo: 40
Velocidad después de acelerar con combustible en el depósito: 10
Estado del coche: Moviéndose

Simplificaciones
Dado que estamos todavía empezando y nos faltan unos cuantos conceptos tendremos que hacer
unas cuantas simplificaciones. Por ejemplo, no vamos a usar clases para los tipos de combustible.
Esto ya lo veremos más adelante, por ahora usaremos una cadena para controlar el tipo de
combustible.

Si tienes algo de experiencia en programación orientada a objetos seguro que te llama la atención
el abuso de las propiedades public. Pero ten en cuenta que aún no hemos visto nada de herencia ni
encapsulación.

El constructor
Vamos a empezar por el constructor de la clase. El enunciado nos dice que "en el constructor
debemos indicar el tipo de combustible". Por lo tanto el constructor aceptará un parámetro que
será $tipoÇombustible.

"Si no se indica el tipo de combustible éste será Gasolina". Esto significa que el parámetro
$tipoCombustible es opcional. Y el valor por defecto será "Gasolina".

Si juntamos todo esto tenemos que el constructor quedaría algo así:

function __construct($tipoCombustible = 'Gasolina')

Como vamos a necesitar el valor $tipoCombustible lo mejor será almacenarlo en una propiedad a la
que llamaremos igual:

60
function __construct($tipoCombustible = 'Gasolina') {
  $this->tipoCombustible = $tipoCombustible;
}

La clase, por ahora, quedará así:

class Coche
{
  public $tipoCombustible;

  function __construct($tipoCombustible = 'Gasolina')


  {
  $this->tipoCombustible = $tipoCombustible;
  }
}

A continuación vemos que el enunciado dice "El coche comienza estando parado y con el depósito
vacío". Esto debería darnos la idea de que necesitamos dos propiedades adicionales: combustible
(porque lo necesitamos para saber si el coche tiene combustible) y velocidad (para controlar si está
parado o no). Ambas propiedades tendrán el valor inicial 0.

public $velocidad = 0;
public $combustible = 0;

¿Se está moviendo?


Lo siguiente que nos pide el enunciado es que debemos poder "saber si el coche está parado o en
marcha (si velocidad es mayor que cero estará en marcha)". Esto lo podemos conseguir con el
método estaEnMovimiento() que devolverá true si el vehículo está en marcha y false si está parado.
Podría ser algo así:

public function estaEnMovimiento()


{
  if( $this->velocidad > 0 ) {
  return true;
  }
  else {
  return false;
  }
}

Este método hace exactamente lo que queremos, pero creo que podemos mejorarlo. Vamos a echar
un vistazo más detallado a la condición que usamos en el if:

61
( $this->velocidad > 0 )

Si te fijas bien, cuando esta condición se cumple el resultado es true si no se cumple el resultado es
false. Entonces ¿para qué queremos el if? ¿No crees que nos lo podríamos ahorrar así?

public function estaEnMovimiento()


{
  return $this->velocidad > 0;
}

De esta forma conseguimos que nuestro método sea más corto y legible.

Volvamos un momento a la versión anterior. También podríamos haberlo planteado de esta otra
forma:

public function estaEnMovimiento()


{
  if( $this->velocidad == 0 ) {
  return false;
  }
  else {
  return true;
  }
}

Este método es correcto, hace exactamente lo que queremos. ¿Le ves algún fallo? Piénsalo.

El método se llama estaEnMovimiento pero lo que hacemos en el if es comprobar si está parado. No


está mal, pero no es muy intuitivo. Cuando entras en un método llamado estaEnMovimiento esperas
(sin darte cuenta) que el if compruebe si está en movimiento, no si está parado. Pequeños detalles
como éste hacen que tu código sea más fácil de entender.

Podríamos seguir dándole vueltas a la comprobación (¿qué pasa si la velocidad es menor que cero?
¿iría marcha atrás?, etc.) pero no vamos a complicarnos más.

Para rematar este método podríamos indicar el tipo de dato que devuelve; en este caso sería el tipo
bool (true o false):

public function estaEnMovimiento(): bool


{
  return $this->velocidad > 0;
}

62
Acelerando el coche
Lo siguiente que nos pide el enunciado es "Si el coche está en la reserva (le quedan menos de 10
litros de combustible) no se puede acelerar (es un coche muy exquisito, no acepta combustible de la
reserva)". Así que en el método acelerar() tenemos que comprobar si hay combustible suficiente
antes de aumentar la velocidad. Esto lo podríamos hacer así:

public function acelerar( $incremento )


{
  if ( $this->combustible > 10 ) {
  $this->velocidad += $incremento;
  }
}

Una vez más tenemos un método que hace exactamente lo que necesitamos. Pero imagina que es la
primera vez que ves este código. Lo primero que te peguntarás es ¿qué significa ese "10"? ¿Por qué
10 y no 20? Si haces un pequeño esfuerzo mental puede que se te ocurra que, quizá, haya un
problema si hay menos de 10 litros. Puede que hasta te des cuenta que se refiere a la reserva. Ese es
el peligro de usar números, que no se "auto explican". ¿Y si creamos, por ejemplo, una propiedad
llamada $reserva?

public $reserva = 10;

public function acelerar( $incremento )


{
  if ( $this->combustible > $this->reserva ) {
  $this->velocidad += $incremento;
  }
}

Ahora el esfuerzo mental para entender el código es menor. El código es mucho más claro. Por
supuesto queda el hecho de que tenemos que entender qué es eso de la reserva, pero ahora ya
tenemos una pista de lo que hace el código.

Podríamos, incluso, ir un poco más allá y hacer el código aún más expresivo usando un método
llamado estaEnLaReserva():

public function estaEnLaReserva()


{
  return $this->combustible < $this->reserva;
}

Este nuevo método comprueba que el combustible es menor que el límite de la reserva. Fíjate en
que he usado la misma estrategia que en estaEnMovimiento con los if.

Usando este nuevo método el código sería más expresivo:

63
public $reserva = 10;

public function acelerar( $incremento )


{
  if ( ! $this->estaEnLaReserva() ) {
  $this->velocidad += $incremento;
  }
}

Esto lo puedes leer así: "Si no está en la reserva aumentamos la velocidad" (recuerda que el ! es la
negación).

Otra opción cambiar el nombre del método estaEnLaReserva por hayCombustibleSuficiente:

public function acelerar( $incremento )


{
  if ( $this->hayCombustibleSuficiente() ) {
  $this->velocidad += $incremento;
  }
}

public function hayCombustibleSuficiente()


{
  return $this->combustible > $this->reserva;
}

Que, posiblemente, sea incluso más claro. Aunque, bien pensado, perdemos ese concepto de que "al
coche no le gusta coger combustible de la reserva". Así que mejor nos quedamos con la versión de
estaEnLaReserva.

Otras pequeñas mejoras serán añadir el int al parámetro $incremento y bool al método
estaEnLaReserva para saber que devuelve solo true o false. También podíamos añadir un valor por
defecto para el incremento de velocidad, por ejemplo 1:

public function acelerar(int $incremento = 1): int

Glu, glu, glu


Sigamos, a continuación se nos pide que "Si se intenta echar un combustible que no es el correcto
no aumentarán los litros de combustible (también es un coche muy listo, si el combustible no le
gusta te lo escupe en la cara)". Esto significa que necesitamos un método repostar en el que habrá
que comprobar el tipo de combustible. ¿Qué tal algo así?

64
public function repostar($tipoCombustible, $litros)
{
  if ($tipoCombustible == $this->tipoCombustible) {
  $this->combustible += $litros;
  }
}

Este es bastante fácil pero, quizás, podríamos ganar algo de legibilidad añadiendo un nuevo método
elCombustibleEsCorrecto().

public function elCombustibleEsCorrecto( $tipoCombustible )


{
  return $this->tipoCombustible == $tipoCombustible;
}

Con lo que el método quedaría:

public function repostar($tipoCombustible, $litros)


{
  if ($this->elCombustibleEsCorrecto( $tipoCombustible ) {
  $this->combustible += $litros;
  }
}

Si te fijas, el código ahora es más claro, ya no tienes que pararte a pensar por qué se está
comparando el combustible, lo estás leyendo claramente: "Si el combustible es correcto aumento el
número te litros".

Otra pequeña mejora: indicar el tipo de datos que acepta el método:

public function repostar(string $tipoCombustible, int $litros)

Vamos bien, ahora nos queda que el objeto $miCoche se comporte como se menciona al final del
enunciado:

65
$miCoche = new Coche('Gasoil');
echo "Velocidad después de acelerar con el depósito vacío: " . $miCoche->acelerar(10)
. "\n";
echo "Combustible después de repostar con el combustible equivocado: " . $miCoche-
>repostar("Gasolina", 40) . "\n";
echo "Combustible después de echar 40 litros de gasóleo: " . $miCoche->repostar(
"Gasoil", 40) . "\n";
echo "Velocidad después de acelerar con combustible en el depósito: " . $miCoche-
>acelerar(10) . "\n";
echo "Estado del coche: " . $miCoche->estado();

Tenemos un método acelerar() pero, ojo, en el enunciado vemos que este método tiene que devolver
la velocidad. Habrá que cambiarlo para que cumpla esta condición y, de paso, indicamos que el
método devuelve un valor de tipo int:

public function acelerar(int $incremento = 1): int


{
  if (!$this->estaEnLaReserva()) {
  $this->velocidad += $incremento;
  return $this->velocidad;
  }
  else {
  return $this->velocidad;
  }
}

Venga, seguro que ya se te ha ocurrido una forma de mejorar este método. Esta es muy fácil. Seguro
que has visto que tanto en el if como en el else hay código repetido. ¡Saquémoslo fuera!

public function acelerar(int $incremento = 1): int


{
  if (!$this->estaEnLaReserva()) {
  $this->velocidad += $incremento;
  }
  return $this->velocidad;
}

Con el método repostar() nos sucede lo mismo, hay que devolver los litros que hay después del
repostaje:

66
public function repostar($tipoCombustible, $litros): int
{
  if ($this->elCombustibleEsCorrecto( $tipoCombustible )) {
  $this->combustible += $litros;
  }
  return $this->combustible;
}

Por último vemos que nos falta un método para mostar el estado del coche ("parado" o "en
marcha"). Vamos a crear el método estado() para ésto:

public function estado()


{
  if ($this->estaEnMovimiento()) {
  return "Moviéndose";
  }
  else {
  return "Parado";
  }
}

Este método lo podríamos simplificar así:

public function estado()


{
  if ($this->estaEnMovimiento()) {
  return "Moviéndose";
  }
  return "Parado";
}

Puedes ver que si se cumple el if se sale del método así que el else podría ser innecesario. Hay
mucha gente a la que esta segunda forma le resulta menos clara. Si ese es tu caso y el de la mayoría
de la gente del proyecto podéis optar por la primera forma. Pero, como he mencionado al principio
del capítulo, hay que ponerse de acuerdo.

Mejoras en el diseño
Si lo pensamos bien esta clase tiene un par de puntos que no son del todo correcto.

Por ejemplo, fíjate en lo que hace el método acelerar(): aumenta la velocidad si hay combustible y
luego devuelve la velocidad. Pero ¿qué te puede hacer pensar que ese método nos va a devolver la
velocidad? ¿No sería más lógico pensar que debería devolver true o false dependiendo de si ha
acelerado o no?

Lo mismo sucede con el método repostar(), posiblemente, fuese más lógico que devolviese true o

67
false dependiendo de si ha conseguido repostar.

Quizá sería más correcto que esos dos métodos devolviesen el resultado de la operación y hubiese
otros métodos que nos dijesen la velocidad a la que va el coche o el nivel de combustible.

En este caso hemos tenido que hacerlo así por exigencias del enunciado. En la vida real te
encontrarás con situaciones así por exigencias del proyecto o del cliente. Si crees que lo que te
proponen es incorrecto coméntalo, seguramente ayudarás a mejorar el diseño.

Sigue jugando, hay miles de premios


Ya tienes una clase Coche que hace unas cuantas cosas. Ahora juega con ella, experimenta. Por
ejemplo, modifica el método repostar() para que no deje repostar si el coche está en marcha. O
poner una capacidad máxima al depósito y no permitir echar al depósito más litros de los que
quepan.

68
Encapsulación
Hasta ahora hemos visto que las propiedades y los métodos de nuestras clases eran todas public.
Esto es muy cómodo ya que podemos acceder de forma directa y sencilla a todas ellas.

Pero habrá casos en los que puede no interesar que algunos métodos y propiedades sean accesibles
desde el "exterior". ¿Y por qué podemos querer "ocultar" nuestros métodos y propiedades? Vamos a
verlo en el siguiente apartado.

Proteger nuestras variables


Imaginemos la clase Coche en la que, al crear un objeto tenemos que decidir el tipo de combustible
que usa (fichero Coche.php):

class Coche
{
  public $tipoCombustible;

  function __construct($tipoCombustible = 'Gasolina')


  {
  $this->tipoCombustible = $tipoCombustible;
  }
}

Un coche de gasolina tiene un motor diferente de uno de gasoil o de uno eléctrico. Cuando se decide
fabricar un coche se añadirán o quitarán elementos al vehículo en función del tipo de combustible
(o energía) que vaya a usar. Por eso, en nuestro ejemplo definimos el tipo de combustible en el
constructor; No debería poder cambiarse el combustible una vez creado el objeto.

Pero aquí hay un problema, nada nos impide hacer lo siguiente (fichero index.php):

require('Coche.php');

$miCoche = new Coche();


print_r($miCoche);
$miCoche->tipoCombustible = 'Gasoil';
print_r($miCoche);

¡Hemos cambiado el combustible con el que funciona el vehículo! Aquí tenemos un ejemplo de por
qué no debemos hacer todas nuestras propiedades públicas.

¿Cómo podemos evitar que se acceda a determinadas propiedades de un objeto? Para evitarlo
podemos definir estas propiedades como private. Vamos a modificar el ejemplo para restringir el
acceso a $tipoCombustible:

69
class Coche
{
  private $tipoCombustible;

  function __construct($tipoCombustible = 'Gasolina')


  {
  $this->tipoCombustible = $tipoCombustible;
  }
}

Si intentásemos modificar $tipoCombustible con esta nueva definición tendríamos un error:

PHP Fatal error: Uncaught Error: Cannot access private property


Coche::$tipoCombustible index.php

Hacer algo público conlleva una obligación


Imagina que participas en un proyecto y haces que tus propiedades o métodos sean públicos. Algún
otro miembro del equipo usa un determinado método o una propiedad. Si, más adelante, decides
modificar, el nombre o el tipo de valor que contiene esa propiedad el equipo tendrá un problema.
Al modificarla harás que el código que la usa deje de funcionar. Así que, si alguien usa alguna de
tus variables ya no podrás modificarlas.

Puedes pensar que bastaría con hablar con esa persona y hacer las modificaciones necesarias. Pero
¿y si tu clase se usa en muchos sitios? ¿O en más de un proyecto? ¿O la usa gente con la que ni
siquiera tienes contacto? El asunto se puede complicar mucho.

Por eso digo que hacer algo público conlleva una obligación. ¿La solución? Haz públicas solo
aquellas propiedades que tengas una certeza (más o menos certera) de que no van a cambiar.

Getters y Setters
Seguro que has oído hablar de los getters y los setters pero no les ves mucho sentido. Estos son
unos métodos que usamos para dar (o recuperar) un valor a una propiedad.

Por cambiar un poco vamos a dejar el coche aparcado un rato y vamos con otra clase. Vamos a
crear la clase Persona, una clase muy sencilla:

Class Persona
{
  public $edad;
}

Con esta clase podemos crear un objeto y asignarle un valor:

70
$yo = new Persona();
$yo->edad = '10 años';

Mmm, quizá este valor no sea correcto. ¿Y si lo que necesitamos es que esta propiedad tenga un
valor numérico? Esto podría causar problemas.

También podríamos hacer ésto:

$yo = new Persona();


$yo->edad = -5;

¿Conoces a alguien con menos cinco años?

Resulta que al dar libertad para acceder directamente a la propiedad $edad podemos tener
problemas. Ahí es donde entran los setters (del inglés set). Son unos métodos cuya única función es
dar un valor a una propiedad. Se les suele nombrar poniendo set delante del nombre de la
propiedad, en este caso sería setEdad():

Class Persona
{
  private $edad;

  public function setEdad($edad)


  {
  $this->edad = $edad;
  }
}

Ahora que tenemos un método para dar valor a la propiedad podemos hacerla private.

En este método podemos hacer las comprobaciones necesarias para ver si la edad que nos pasan es
correcta. Por ejemplo:

public function setEdad($edad)


{
  if (is_numeric($edad) && $edad >=0) {
  $this->edad = $edad;
  }
}

Como la propiedad $edad ahora es privada necesitaremos un método para recuperar su valor. A
este método se le llama getter (del inglés get):

71
public function getEdad()
{
  return $this->edad;
}

Aunque es práctica habitual llamar a estos métodos usan get y set no es obligatorio. Podríamos
llamarlas establecerEdad(), escribirEdad(), guardarEdad() o lo que queramos.

No son métodos propios de PHP, ni son reconocidos como tales. Simplemente son unos métodos que
nosotros estamos usando para dar valor a una propiedad o para recuperar su valor.

72
Herencia
La herencia consiste en crear una clase tomando otra como base. La nueva clase se llama clase hija
(o hijo o heredera o como prefieras) y la original madre (o padre o clase base).

La clase hija "hereda" todas las propiedades y métodos de la madre. De esta forma nos ahorramos
tener que volver a crear los métodos que ya estaban definidos en la madre. Esta es una de las bases
de la reutilización de código.

Imaginemos que en nuestro programa queremos tratar no solo con coches sino también con
camiones. La clase Coche podría controlar el número de pasajeros y los litros en el depósito. Podría
tener este aspecto:

class Coche {
  public $numeroPasajeros = 0;
  public $litros = 0;

  public function repostar($litros)


  {
  if ($litros>0) {
  $this->litros += $litros;
  }
  }

  public function litrosEnDeposito()


  {
  return $this->litros;
  }

  public function setNumeroPasajeros($numeroPasajeros)


  {
  $this->numeroPasajeros = $numeroPasajeros;
  }

  public function getNumeroPasajeros()


  {
  return $this->numeroPasajeros;
  }
}

La clase Camion no se preocuparía de los pasajeros (no suele haber pasajeros en un camión) pero
sería importante que tuviera un remolque para llevar mercancías. En el camión también es
importante controlar el combustible. Esta clase podría quedar algo así:

73
class Camion {
  private $litros = 0;
  private $tieneRemolque = false;

  public function repostar($litros)


  {
  if ($litros>0) {
  $this->litros += $litros;
  }
  }

  public function litrosEnDeposito()


  {
  return $this->litros;
  }

  public function setRemolque($tieneRemolque)


  {
  $this->tieneRemolque = $tieneRemolque;
  }

  public function getRemolque()


  {
  return $this->tieneRemolque;
  }

Se puede ver que cada una de las clases tiene sus propios métodos:

• Coche tiene métodos para controlar el número de pasajeros.

• Camion tiene métodos para controlar si hay remolque o no.

Aparte de estos métodos hay unos que son comunes: repostar() y litrosEnDeposito(). Hemos tenido
que crear estos métodos en las dos clases.

Para no repetir estos métodos podríamos hacer una "super-clase" que contuviese todos los métodos
de Coche, los de Camion y los comunes. El problema es que cuando tengamos un coche habrá
métodos que no estaremos usando (los del camión). Y viceversa.

En el ejemplo solo tenemos dos métodos "propios" para cada tipo de vehículo. En la realidad suelen
ser más. Esto hace que nuestras clases se vuelvan muy complejas y difíciles de mantener.

Heredar para reutilizar


Y aquí es donde la herencia puede echarnos una mano. Podemos crear una clase llamada Vehiculo
que contenga los métodos y las propiedades comunes:

74
class Vehiculo
{
  private $litros = 0;

  public function repostar($litros)


  {
  if ($litros>0) {
  $this->litros += $litros;
  }
  }

  public function litrosEnDeposito()


  {
  return $this->litros;
  }
}

Y, ahora, Coche y Camion podrían heredar estos métodos de la clase Vehiculo. Para que una clase
herede de otra se usa la palabra_reservada extends seguida del nombre de la clase madre:

class ClaseHija extends ClaseMadre

Usando herencia las clases Coche y Camion quedarían:

75
class Coche extends Vehiculo {
  private $numeroPasajeros = 0;

  public function setNumeroPasajeros($numeroPasajeros)


  {
  $this->numeroPasajeros = $numeroPasajeros;
  }

  public function getNumeroPasajeros()


  {
  return $this->numeroPasajeros;
  }
}

class Camion extends Vehiculo {


  private $tieneRemolque = false;

  public function setRemolque($tieneRemolque)


  {
  $this->tieneRemolque = $tieneRemolque;
  }

  public function getRemolque()


  {
  return $this->tieneRemolque;
  }

Vamos a probar nuestras clases. Puedes crear un fichero llamado index.php en el que meter todas
estas clases y añadiríamos el siguiente código:

$miCoche = new Coche();

$miCoche->repostar(10);
echo "Litros en mi coche: " . $miCoche->litrosEnDeposito() . PHP_EOL;
$miCoche->setNumeroPasajeros(3);
echo "Pasajeros en mi coche: " . $miCoche->getNumeroPasajeros() . PHP_EOL;

Y la salida sería:

Litros en mi coche: 10
Pasajeros en mi coche: 3

76
No abusar de la herencia
La programación orientada a objetos no difiere mucho de la vida real… no se debe abusar de la
herencia. Cuando descubrimos los beneficios de heredar de una clase todos solemos lanzarnos
como locos a usarla. Es algo normal. Pero tiene algunas desventajas. Hay una frase que se repite
mucho cuando se habla de la POO:

Dar preferencia a la composición sobre la herencia.

En un capítulo posterior entraré en más detalle y veremos qué significa esta frase. Por ahora
disfruta de la luna de miel con la herencia y úsala sin miedo.

Constructores y herencia
Los constructores también se heredan de la clase madre. Podemos verlo añadiendo un constructor
en la clase Vehiculo:

function __construct()
{
  echo "Constructor definido en Vehiculo.\n";
}

Y comprobamos que lo estamos llamando al crear un objeto de tipo Coche:

$miCoche = new Coche();

Al ejecutar este código veremos el mensaje:

Constructor definido en Vehiculo.

Ahora vamos a añadir un constructor para la clase hija (la clase Coche):

function __construct()
{
  echo "Constructor definido en Coche.\n";
}

Al ejecutar el script vemos que el mensaje ha cambiado, ahora es:

Constructor definido en Coche.

Te habrás dado cuenta de que no estamos llamando al constructor de la clase madre. Esto se debe a
que la clase hija tiene su propio constructor. Lo habitual suele ser que necesitemos llamar también

77
al de la madre. Para eso debemos añadir una llamada explícita:

parent::__construct();

Con lo que el constructor de la hija (clase Coche) quedaría:

function __construct()
{
  echo "Constructor definido en Coche.\n";
  parent::__construct();
}

Ahora sí, al ejecutar el código tendremos:

Constructor definido en Coche.


Constructor definido en Vehiculo.

El orden es importante

Ten en cuenta que el orden es importante. Si necesitas que la llamada al constructor de la madre se
haga antes que al de la hija deberías invertir el orden:

function __construct()
{
  parent::__construct();
  echo "Constructor definido en Coche.\n";
}

Ahora la salida cambiará:

Constructor definido en Vehiculo.


Constructor definido en Coche.

Sin madre

La llamada al constructor de la clase madre puede hacerse solo si la clase es derivada. En nuestro
caso la clase Vehiculo no tiene madre, no hereda de ninguna otra clase. Por lo tanto
parent::__construct(); no tiene sentido. Así que no lo añadas a todos tus constructores por si acaso,
tendrías un error si lo hicieras:

78
class Vehiculo
{
  private $litros = 0;

  function __construct()
  {
  parent::__construct();
  echo "Constructor definido en Vehiculo\n";
  }
  ...
}

Si intentas acceder al constructor de la madre de una clase sin madre verás un error como este:

PHP Fatal error: Uncaught Error: Cannot access parent:: when current class scope has
no parent in intro.php:44
...

Por último, si llamamos al constructor de la madre y ésta no lo tiene definido también tendremos
un error.

Resumen

• Si la clase madre tiene constructor y la hija no → se usa el de la madre.

• Si la clase hija tiene constructor se usa el suyo y se omite el de la madre.

• Si queremos que la hija llame al constructor de la madre hay que hacerlo con
parent::__construct();

Public, private y protected


En el capítulo de encapsulación vimos que es importante proteger nuestras variables; no es
conveniente hacerlas public salvo que sea estrictamente necesario. Si te fijas en el ejemplo que
hemos usado para la clase Vehiculo la propiedad $litros es privada (private). Aún así hemos podido
acceder a ella a través de los métodos repostar() y litrosEnDeposito().

Recuperemos el ejemplo anterior de la clase Vehiculo:

79
class Vehiculo
{
  private $litros = 0;

  public function repostar($litros)


  {
  if ($litros>0) {
  $this->litros += $litros;
  }
  }

  public function litrosEnDeposito()


  {
  return $this->litros;
  }
}

Y la clase Coche:

class Vehiculo
{
  private $litros = 0;

  public function repostar($litros)


  {
  if ($litros>0) {
  $this->litros += $litros;
  }
  }

  public function litrosEnDeposito()


  {
  return $this->litros;
  }
}

Imaginemos que queremos un nuevo método para tener un resumen del estado del coche. Este
método lo añadiríamos en la clase Coche y sería así:

public function estado()


{
  return [
  'pasajeros' => $this->numeroPasajeros,
  'litros' => $this->litros,
  ];
}

80
Este método no funcionaría porque no podemos acceder a la propiedad $litros. Esta propiedad está
definida en Vehículo como private. Eso quiere decir que solo se puede acceder a ella desde el
código de la propia clase Vehiculo. No podrán verla ni siquiera desde las clases hijas (como Coche en
este caso).

Podemos probarlo con el siguiente código:

$miCoche = new Coche();

$miCoche->repostar(10);
print_r($miCoche->estado());

Que al ejecutar el resultado sería:

PHP Notice: Undefined property: Coche::$litros

Entonces ¿Cómo podemos hacer que una propiedad o un método sean "inaccesibles" desde el
exterior pero que las clases hijas sí puedan verlas? Para eso tenemos la palabra_reservada
protected. Cuando una propiedad o un método son definidas como protected éstas quedan
accesibles para las clases hijas.

Podemos cambiar la propiedad $litros para hacerla protected:

class Vehiculo
{
  protected $litros = 0;
  ...
}

Y, ahora sí, Coche tendrá acceso a ella.

Sin embargo, esta propiedad sigue inaccesible desde el "exterior":

$miCoche->repostar(10);
echo $miCoche->litros;

Esto mostraría el error:

PHP Fatal error: Uncaught Error: Cannot access protected property Coche::$litros

Resumen public / private /protected

• A las propiedades y métodos public de una clase se puede acceder desde la propia clase, desde
clases heredadas y desde el “exterior” (desde código que use un objeto de esa clase).

81
• A las que son private solo se puede acceder desde esa propia clase.

• Y si son protected podemos acceder solo desde la propia clase y desde el código de las clases
heredadas.

Overriding - Sobrescritura de métodos


Hemos visto que cuando tanto la clase madre como la hija tiene un método __construct() el de la
hija sustituye al de la madre. Esto es lo que se llama overriding. Además de con el constructor esto
sucede con cualquier método que exista tanto en la madre como en la hija.

En nuestro ejemplo anterior puede que nos interese que la clase Coche use su propio método
repostar():

public function repostar($litros)


{
  echo "Esta es la función repostar de la clase Coche.\n";
}

Un dato curioso: Si los parámetos del método de la clase hija no coinciden con la de la madre
tendremos un warning. Por ejemplo, si definiésemos el método en la clase Coche así:

public function repostar($tipoCombustible, $litros)


{
  echo "Esta es la función repostar de la clase Coche.\n";
}

Veríamos un aviso como éste:

PHP Warning: Declaration of Coche::repostar($tipoCombustible, $litros) should be


compatible with Vehiculo::repostar($litros)

Recordemos que un warning no es un error. El programa puede funcionar sin errores, pero PHP no
avisa de una posible fuente de problemas.

El problema aquí es que nos estamos saltando el principio de sustitución de Liskov del que hablaré
en otro libro. Este principio es uno de los cinco pilares de los principios SOLID.

82
Introducción al desarrollo guiado por
pruebas
Ya hemos avanzado un poco y creo que podemos aprovechar para hacer una pequeña al desarrollo
guiado por pruebas. Seguramente ya lo conocerás o habrás oído hablar de ello. Quizá sea más
conocido como TDD (Test Driven Development). Este capítulo va a ser solo una pequeña
introducción, se podría escribir un libro entero solo de este tema.

Es una forma de programar en la que primero se escriben unas pruebas (o tests) que definen cómo
tiene que funcionar nuestra aplicación. El proceso, por contarlo de manera muy resumida, consiste
en:

• Definir un test y probar si pasa (es decir, que se ejecuta sin errores).

• Como aún no hemos escrito nada de código el test fallará.

• Escribimos el mínimo código necesario que haga que el test pase.

• Volvemos a ejecutar el test y, esta vez, pasará.

• Creamos un nuevo test (o ampliamos el que ya existe).

Esta forma de desarrollo (que tiene desde fanáticos seguidores a acérrimos enemigos) tiene una
serie de ventajas:

• Nos ayuda a desarrollar sin errores.

• Nos permite comprobar que toda nuestra aplicación funciona como esperamos en segundos.

• Permite hacer cambios a nuestra aplicación sin miedo a "romper" cosas (¿Cuántas veces te
habrá pasado que un pequeño cambio hace que otras partes de la aplicación dejen de
funcionar?).

• Podemos refactorizar sin miedo (ya hablaremos sobre ésto).

• Nos ayuda a que el código sea más limpio y legible.

• El mantenimiento de la aplicación deja de ser un dolor de cabeza.

• Cuando se añade un nuevo desarrollador al proyecto los test le ayudan a entender la aplicación.

Como puedes ver son unas cuantas ventajas así que ¿por qué no probarlo?

La herramienta: phpunit
En PHP tenemos varias herramientas para automatizar las pruebas. Una de las más usadas es
phpunit, aunque puede que no se la más sencilla de usar. Yo estoy acostumbrado a esta
herramienta y es la que utilizo en el 90% de los proyectos.

phpunit para cada proyecto

Para poder seguir los ejemplos de este capítulo necesitarás tener phpunit instalado en tu sistema o
en tu proyecto a través de Composer (echa un vistazo al capítulo de Herramientas donde se explica

83
cómo instalarlo). Dado que cada proyecto puede requerir una versión diferente de phpunit yo
tiendo a usar para cada proyecto su propia versión. Así que para ejecutar las pruebas tendrás que
hacer:

$ vendor/bin/phpunit

Si ejecutas el comando:

$ phpunit

El phpunit que se ejecutará será el del sistema y, puede ser, que sea una versión incompatible con
tu proyecto.

Por simplicidad, cuando veas escrito phpunit me referiré en realidad a vendor/bin/phpunit.

La clase Coche desde cero


Vamos a retomar el ejemplo del capítulo anterior (el de la clase Coche) y vamos a rehacerlo desde
cero usando la metodología TDD. Recordemos el enunciado:

• En el constructor de la clase podremos indicar el tipo de combustible que acepta el coche (no
uses una clase para el combustible, basta con una cadena de texto como "Gasolina"). Si no se
indica nada el combustible será Gasolina.

• El coche comienza estando parado y con el depósito vacío.

• Saber si el coche está parado o en marcha (si velocidad es mayor que cero estará en marcha).

• Si el coche está en la reserva (le quedan menos de 10 litros de combustible) no se puede acelerar
(es un coche muy exquisito, no acepta combustible de la reserva).

• Si se intenta echar un combustible que no es el correcto no aumentarán los litros de


combustible (también es un coche muy listo, si el combustible no le gusta te lo escupe en la
cara).

Una vez tengas la clase crea el objeto $miCoche con él y haz las siguientes operaciones:

$miCoche = new Coche('Gasoil');


echo "Velocidad después de acelerar con el depósito vacío: " . $miCoche->acelerar(10)
. "\n";
echo "Combustible después de repostar con el combustible equivocado: " . $miCoche-
>repostar("Gasolina", 40) . "\n";
echo "Combustible después de echar 40 litros de gasóleo: " . $miCoche->repostar(
"Gasoil", 40) . "\n";
echo "Velocidad después de acelerar con combustible en el depósito: " . $miCoche-
>acelerar(10) . "\n";
echo "Estado del coche: " . $miCoche->estado();

y el resultado debería ser:

84
Velocidad después de acelerar con el depósito vacío: 0
Combustible después de repostar con el combustible equivocado: 0
Combustible después de echar 40 litros de gasóleo: 40
Velocidad después de acelerar con combustible en el depósito: 10
Estado del coche: Moviéndose

¿Por dónde empezar?


Podemos empezar por donde queramos así que yo voy a empezar por un test que compruebe que
podamos repostar gasolina. El test diría algo como:

Comprobar que el coche puede repostar gasolina.

¿Y cómo hacemos un test para comprobar esto? El primer paso es crear una carpeta donde vamos a
almacenar todos nuestros test, por ejemplo, la carpeta tests dentro de nuestro proyecto. En esta
carpeta vamos a crear un fichero llamado: CocheTest.php con el siguiente contenido:

use PHPUnit\Framework\TestCase;

class CocheTest extends TestCase


{
}

Para que la clase CocheTest funcione con phpunit tenemos que hacer que la clase herede de una
clase llamada TestCase. Hablaremos sobre herencia más adelante, por ahora basta con saber que al
extender (de ahí lo de extends) lo que hacemos es heredar las propiedades y métodos de la clase
"padre".

En esta clase podemos añadir un test que "compruebe que el coche puede repostar gasolina". Este
método sería algo así:

use PHPUnit\Framework\TestCase;

class CocheTest extends TestCase


{
  public function test_repostar_gasolina()
  {
  $miCoche = new Coche();

  $this->assertEquals(10, $miCoche->repostar('Gasolina', 10));


  }
}

Lo primero que hacemos es crear un objeto de la clase Coche. El siguiente paso es la aserción,
donde decimos que al repostar gasolina el resultado debe ser 10.

85
Lo dice la RAE

 Según la RAE una aserción es una: "Proposición en que se afirma o da por cierto
algo."

Una aserción tiene el siguiente formato:

$this->assertEquals(valor_esperado, valor_real);

La aserción dice que el valor esperado debe ser igual (assertEquals) al valor real. En nuestro caso el
valor esperado es 10 y el valor real es el valor que devuelve la función repostar.

Si los valores no coinciden la aserción falla y el test no pasa.

Sobre los nombres de los test


Los nombres de los test deben cumplir las siguientes normas:

• Deben ser descriptivos.

• Deben comenzar con la palabra "test".

Curiosamente, para los test no se suele usar la notación Camel Case sino Snake Case.

Si no queremos tener que añadir el prefijo "test" a cada uno de nuestros test podemos otro sistema;
basta con añadir el comentario /** @test */ justo encima de la cabecera del test y con eso phpunit
sabrá que es un test:

/** @test */
public function repostar_gasolina()
{
  $miCoche = new Coche();
  $this->assertEquals(10, $miCoche->repostar('Gasolina', 10));
  }
}

Probamos phpunit por primera vez


Recuerda que todavía no tenemos creada la clase Coche; estamos empezando desde cero. Vamos a
ejecutar phpunit por primera vez. Tenemos que pasar como parámetro la carpeta donde tenemos
guardados nuestos test, en este caso la carpeta tests:

86
$ phpunit tests
PHPUnit 5.7.19 by Sebastian Bergmann and contributors.

E 1 / 1 (100%)

Time: 32 ms, Memory: 4.00MB

There was 1 error:

1) CocheTest::test_repostar_gasolina
Error: Class 'Coche' not found

/home/gorka/pruebas/tests/CocheTest.php:9

ERRORS!
Tests: 1, Assertions: 0, Errors: 1.

Usa el phpunit de tu proyecto

 Recuerda que donde pone phpunit debes poner vendor/bin/phpunit (salvo que
quieras usar el phpunit que tienes instalado en tu sistema).

Puedes ver que hay un error en nuestra prueba:

ERRORS!class Coche
{
  public function repostar()
  {
  }
}
Tests: 1, Assertions: 0, Errors: 1.

Y el error es:

1) CocheTest::test_repostar_gasolina
Error: Class 'Coche' not found

/home/gorka/pruebas/tests/CocheTest.php:9

Es lógico ya que aún no tenemos la clase Coche creada. Para corregir este error tendremos que
crear el fichero Coche.php en el que pondremos el siguiente contenido:

class Coche
{
}

87
y en nuestro fichero de test CocheTest.php tendremos que añadir una línea para cargar el fichero
que contiene la clase Coche:

require('Coche.php');

Si volvemos a ejecutar phpunit vemos que el error ha cambiado. En esta ocasión se quejará de que
no existe el método repostar() en la clase Coche:

There was 1 error:

1) CocheTest::test_repostar_gasolina
Error: Call to undefined method Coche::repostar()

/home/gorka/pruebas/CocheTest.php:12

ERRORS!
Tests: 1, Assertions: 0, Errors: 1.

Así que vamos a la clase coche y añadimos este método:

class Coche
{
  public function repostar()
  {
  }
}

Una vez más cambia el error:

1) CocheTest::test_repostar_gasolina
Failed asserting that null matches expected 10.

/home/gorka/pruebas/tests/CocheTest.php:12

FAILURES!
Tests: 1, Assertions: 1, Failures: 1.

Vaya, parece que ya no tenemos errores; ahora tenemos fallos (Failures). Los fallos se producen
cuando no hay errores en el código pero el resultado del test no es el esperado. En este caso el fallo
se produce porque el método repostar() no devuelve ningún valor y esperábamos que el valor
devuelto fuese un 10:

$this->assertEquals(10, $miCoche->repostar('Gasolina', 10));

Al principio del capítulo he comentado que la filosofía de este tipo de desarrollo es "escribir el

88
mínimo código que haga que el test pase". En este caso el mínimo código sería hacer que el método
devuelva el valor 10:

public function repostar()


{
  return 10;
}

Y el resultado, por fin, será el deseado. Se ejecuta el test y ya no tenemos ni errores ni fallos:

$ phpunit tests
OK (1 test, 1 assertion)

Es evidente que ese método no va a pasar un test que intente echar, por ejemplo, 20 litros. Así que,
ahora que todos nuestros test funcionan, vamos a modificar el método repostar() para que recoja
los datos que se le pasen:

public function repostar($tipoCombustible, $litros)


{
  return $litros;
}

Si lo comprobamos vemos que todo sigue sin errores:

$ phpunit tests/
OK (1 test, 1 assertion)

El segundo test
Como el test pasa correctamente ya podemos crear un segundo test. Ahora vamos a usar un test
para que el método repostar no sume los litros si el combustible es Gasoil:

public function test_no_repostar_si_echamos_gasoil()


{
  $miCoche = new Coche();

  $this->assertEquals(0, $miCoche->repostar('Gasoil', 10));


}

Si ejecutamos phpunit tenemos un error:

89
$ phpunit tests
There was 1 failure:

1) CocheTest::test_no_repostar_si_echamos_gasoil
Failed asserting that 10 matches expected 0.

/home/gorka/pruebas/CocheTest.php:19

FAILURES!
Tests: 2, Assertions: 2, Failures: 1.

Tenemos dos tests, en los que hacemos dos aserciones (que son las veces que estamos usando
assertEquals) y un fallo. ¿Cómo podemos arreglarlo? Si nos fijaos en la respuesta de phpunit vemos
que el fallo está en el test test_no_repostart_si_echamos_gasoil:

1) CocheTest::test_no_repostar_si_echamos_gasoil
Failed asserting that 10 matches expected 0.

Para arreglarlo podemos añadir una comprobación en el método repostar():

if ( $tipoCombustible == 'Gasolina' ) {
  return $litros;
}

Y esta vez sí que pasarán los dos test:

$ phpunit tests
OK (2 tests, 2 assertions)

Vamos a repasar la clase Coche:

class Coche
{
  public function repostar( $tipoCombustible, $litros )
  {
  if ( $tipoCombustible == 'Gasolina' ) {
  return $litros;
  }
  }
}

Mejoras en el código
En el capítulo anterior hemos visto que esto podría ser más expresivo si usamos el método

90
elCombustibleEsCorrecto(). Como todos nuestros test pasan las pruebas podemos empezar a hacer
cambios. Añadamos el nuevo método:

class Coche
{
  public function repostar( $tipoCombustible, $litros )
  {
  if ( $this->elCombustibleEsCorrecto( $tipoCombustible ) ) {
  return $litros;
  }
  }

  public function elCombustibleEsCorrecto( $tipoCombustible )


  {
  return $tipoCombustible == 'Gasolina';
  }
}

y veamos que todas las pruebas siguen siendo correctas:

OK (2 tests, 2 assertions)

Cambios en el código que rompen los test


¿Cuál puede ser el siguiente paso? ¿Tenemos este método ya comprobado? Mmm, no, si recuerdas
el enunciado se nos pedía que en el constructor se pudiese indicar el tipo de combustible que usa el
coche. Así que hagamos un nuevo test para comprobarlo:

/** @test */
public function poder_respostar_gasoil_si_el_coche_es_de_gasoil()
{
  $miCoche = new Coche('Gasoil');

  $this->assertEquals(10, $miCoche->repostar('Gasoil', 10));


}

Ojo con la cabecera


Solo para ver si prestabas atención he usado la otra forma de escribir el nombre
 de un test (usando /** @test */). Recuerda, si no añades el comentario el nombre
debe empezar por _test.

Al intentar pasar las pruebas tendremos un fallo:

91
$ phpunit tests
There was 1 failure:

1) CocheTest::poder_respostar_gasoil_si_el_coche_es_de_gasoil
Failed asserting that null matches expected 10.

/home/gorka/pruebas/tests/CocheTest.php:27

FAILURES!
Tests: 3, Assertions: 3, Failures: 1.

El fallo se debe a que a nuestra clase Coche no podemos decirle qué tipo de combustible usa el
coche. Así que vamos a modificar la clase para que acepte el tipo de combustible añadiendo un
constructor y modificando el método elCombustibleEsCorrecto:

class Coche
{
  public $tipoCombustible;

  function __construct( $tipoCombustible )


  {
  $this->tipoCombustible = $tipoCombustible;
  }

  public function repostar( $tipoCombustible, $litros )


  {
  if ( $this->elCombustibleEsCorrecto( $tipoCombustible ) ) {
  return $litros;
  }
  }

  public function elCombustibleEsCorrecto( $tipoCombustible )


  {
  return $tipoCombustible == $this->tipoCombustible;
  }
}

Ponemos en marcha phpunit y:

92
There were 2 errors:

1) CocheTest::test_repostar_gasolina
Missing argument 1 for Coche::__construct(), called in tests/CocheTest.php on line 10
and defined

/home/gorka/pruebas/Coche.php:6
/home/gorka/pruebas/tests/CocheTest.php:10

2) CocheTest::test_no_repostar_si_echamos_gasoil
Missing argument 1 for Coche::__construct(), called in tests/CocheTest.php on line 17
and defined

/home/gorka/pruebas/Coche.php:6
/home/gorka/pruebas/tests/CocheTest.php:17

ERRORS!
Tests: 3, Assertions: 1, Errors: 2.

¡Maldición! Ahora tenemos dos errores. ¡Y los que fallan son los que antes eran correctos! Vaya,
vaya, mira por dónde. Hemos hecho un cambio que ha "roto" nuestro código pero gracias a que
teníamos unas pruebas automáticas definidas hemos descubierto el fallo al momento. ¿No es
maravilloso esto del TDD?

Bueno, ¿dónde está el problema? El problema está en que ahora la clase Coche pide un parámetro
al declarar un objeto. Podemos solucionarlo dando un valor por defecto al parámetro
tipoCombustible:

function __construct( $tipoCombustible = 'Gasolina' )

Si probamos de nuevo vemos que nuestros test pasan la prueba:

OK (3 tests, 3 assertions)

Podrías pensar que es una tontería, que el problema estaba en que los test estaban mal porque les
faltaba un parámetro. Pero como puedes ver el problema no era ese sino que la clase Coche no se
comportaba como debía. El enunciado (y los test) nos decía "Si no se indica nada el combustible
será Gasolina", pero nuestra clase no lo estaba cumpliendo.

Un test por si acaso


Para asegurarnos de que todo funciona correctamente vamos a añadir un nuevo test. Esta vez
comprobaremos que si echamos gasolina a un coche que funciona con gasóleo tampoco se produce
el repostaje:

93
/** @test */
public function no_repostar_si_echamos_gasolina_a_un_choche_gasoil()
{
  $miCoche = new Coche('Gasoil');

  $this->assertEquals(0, $miCoche->repostar('Gasolina', 10));


}

Este test pasará las pruebas sin problemas, no será necesario cambiar nada en el código.

Repostando dos veces


Este método repostar() parece que funciona a la perfección ¿no? Antes de seguir vamos a ver qué
pasa si repostamos dos veces:

/** @test */
public function el_coche_reposta_dos_veces()
{
  $miCoche = new Coche();

  $this->assertEquals(10, $miCoche->repostar('Gasolina', 10));


  $this->assertEquals(20, $miCoche->repostar('Gasolina', 10));
}

¡Vaya hombre! Tenemos un fallo:

There was 1 failure:

1) CocheTest::el_coche_reposta_dos_veces
Failed asserting that 10 matches expected 20.

/home/gorka/webs/phpunit-prueba/tests/CocheTest.php:44

FAILURES!
Tests: 5, Assertions: 6, Failures: 1.

Si revisamos el código del método repostar vemos que nos falta algo:

public function repostar( $tipoCombustible, $litros )


{
  if ( $this->elCombustibleEsCorrecto( $tipoCombustible ) ) {
  return $litros;
  }
}

94
¡El método devuelve el número de litros pero luego no almacena ese valor en ningún sitio! Así que
vamos a usar la propiedad $combustible:

public function repostar( $tipoCombustible, $litros )


{
  if ( $this->elCombustibleEsCorrecto( $tipoCombustible ) ) {
  $this->combustible += $litros;
  }

  return $this->combustible;
}

Ahora sí debería funcionar:

$ phpunit tests

1) CocheTest::test_repostar_gasolina
Undefined property: Coche::$combustible

/home/gorka/webs/phpunit-prueba/Coche.php:15
/home/gorka/webs/phpunit-prueba/tests/CocheTest.php:12

2) CocheTest::test_no_repostar_si_echamos_gasoil
Undefined property: Coche::$combustible

...

ERRORS!
Tests: 5, Assertions: 0, Errors: 5.

¿Cómo? ¡cinco errores! Ah no, que no tenemos definida la propiedad $combustible. La añadimos y
volvemos a probar:

$ phpunit tests
OK (5 tests, 6 assertions)

Ahora sí, ya podemos seguir.

Acelerando el coche
Ahora vamos a hacer que el coche pueda acelerar. Primero vamos a comprobar que el coche pueda
acelerar (por ahora no nos vamos a preocupar del combustible):

95
/** @test */
public function el_coche_puede_acelerar()
{
  $miCoche = new Coche();

  $miCoche->repostar('Gasolina', 100);

  $this->assertEquals(10, $miCoche->acelerar(10));
}

Esta prueba va a dar un error porque aún no tenemos creado el método acelerar():

There was 1 error:

1) CocheTest::el_coche_puede_acelerar_cuando_hay_combustible_suficiente
Error: Call to undefined method Coche::acelerar()

/home/gorka/webs/phpunit-prueba/tests/CocheTest.php:45

ERRORS!
Tests: 5, Assertions: 4, Errors: 1.

Depués de crear el método:

public function acelerar( $incremento ) {

Tendremos un fallo porque el método acelerar() no hace lo que se espera:

1) CocheTest::el_coche_puede_acelerar
Failed asserting that null matches expected 10.

Aquí podríamos seguir el proceso de antes de usar return 10; para que pase el test e ir haciendo
modificaciones. Por simplificar voy a poner ya el código "correcto" para que pase el test (y a definir
la propiedad $velocidad):

public function acelerar( $incremento ) {


  $this->velocidad += $incremento;

  return $this->velocidad;
}

Este código ya pasa el test así que podemos añadir uno nuevo.

96
Comprobar el combustible
Podemos empezar comprobando que el coche no pueda acelerar sin combustible ¡lógico!

/** @test */
public function no_puede_acelerar_si_no_tiene_combustible()
{
  $miCoche = new Coche();

  $this->assertEquals(0, $miCoche->acelerar(10));
}

Esta prueba va a fallar porque no hacemos ninguna comprobación de combustible en el método


acelerar(). Así que vamos a modificar el método:

public function acelerar( $incremento ) {


  if ( $this->combustible > 0 ) {
  $this->velocidad += $incremento;
  }

  return $this->velocidad;
}

Ahora todo debería funcionar… ¿o no?

There was 1 failure:

1) CocheTest::el_coche_puede_acelerar
Failed asserting that null matches expected 10.

/home/gorka/webs/phpunit-prueba/tests/CocheTest.php:52

FAILURES!
Tests: 7, Assertions: 8, Failures: 1.

¿Y ahora por qué falla ese test si antes funcionaba bien? Vamos a revisarlo:

/** @test */
public function el_coche_puede_acelerar()
{
  $miCoche = new Coche();

  $this->assertEquals(10, $miCoche->acelerar(10));
}

Si te fijas, al hacer este test no repostábamos pero no pasaba nada porque no hacíamos la

97
comprobación de combustible. Esto pasa a menudo, cuando añadimos alguna nueva característica
a nuestro programa algún test puede dejar de funcionar. Pero eso está bien porque vemos que ese
test era incorrecto. Vamos a modificarlo para que muestre el comportamiento correcto:

/** @test */
public function el_coche_puede_acelerar_si_hay_suficiente_combustible()
{
  $miCoche = new Coche();

  $miCoche->repostar('Gasolina', 100);

  $this->assertEquals(10, $miCoche->acelerar(10));
}

Ahora sí pasará el test. Antes no tenía sentido, ¿cómo va a acelerar un coche sin combustible?

El método estado()
Y ya solo nos queda un método que probar, el que nos devuelve el estado del coche. En este caso
vamos a necesitar dos test:

/** @test */
public function el_coche_esta_parado()
{
  $miCoche = new Coche();

  $this->assertEquals('parado', $miCoche->estado());
}

y el que comprueba si está en marcha:

/** @test */
public function el_coche_esta_en_marcha()
{
  $miCoche = new Coche();

  $miCoche->repostar('Gasolina', 100) ;
  $miCoche->acelerar( 10 );

  $this->assertEquals('en marcha', $miCoche->estado());


}

Este segundo test es más complejo porque tenemos que llamar a repostar y acelerar para que el
coche se ponga en movimiento. Después de hacer todo el proceso deberíamos terminar con un
método estado() parecido a éste:

98
public function estado()
{
  if ( $this->velocidad > 0 ) {
  return "en marcha";
  }
  else {
  return "parado";
  }
}

AssertSame
Ya tenemos una clase Coche en la que tenemos bastante confianza porque pasa todas las pruebas
que hemos definido. Así que vamos a probarla en una aplicación "real". Crea un fichero llamado
index.php y mete el siguiente código (el del enunciado):

require('Coche.php');

$miCoche = new Coche('Gasoil');


echo "Velocidad después de acelerar con el depósito vacío: " . $miCoche->acelerar(10)
. "\n";
echo "Combustible después de repostar con el combustible equivocado: " . $miCoche-
>repostar("Gasolina", 40) . "\n";
echo "Combustible después de echar 40 litros de gasóleo: " . $miCoche-
>repostar("Gasoil", 40) . "\n";
echo "Velocidad después de acelerar con combustible en el depósito: " . $miCoche-
>acelerar(10) . "\n";
echo "Estado del coche: " . $miCoche->estado() . "\n";

ahora ejecuta este fichero y veamos el resultado:

$ php index.php
Velocidad después de acelerar con el depósito vacío:
Combustible después de repostar con el combustible equivocado:
Combustible después de echar 40 litros de gasóleo: 40
Velocidad después de acelerar con combustible en el depósito: 10
Estado del coche: en marcha

¿Ves algo raro? El resultado de las dos primeras líneas sale en blanco en lugar de mostrar un cero.
¿A qué se debe ésto? Si te fijas en las declaraciones de las propiedades $velocidad y $combustible
verás que estaban así:

public $velocidad;
public $combustible;

99
Recordemos que si no damos un valor inicial a una propiedad ésta tendrá el valor null (nulo). Vaya,
nuestros test no han localizado este "fallo". Eso es porque el test solo comprueba que los valores
sean iguales:

$this->assertEquals(0, $miCoche->repostar('Gasoil', 10));

y recordemos que, en una comparación, 0 y null son iguales. Pero en este caso no queremos
comprobar que sean iguales, queremos comprobar que sean idénticos. La diferencia entre igual e
idéntico es que para que sean idénticos se comprobaría que fuesen del mismo tipo (es decir, que
ambos fueran números enteros). En este caso deberíamos cambiar la aserción por assertSame:

$this->assertSame(0, $miCoche->repostar('Gasoil', 10));

Al hacer este cambio los test fallarán hasta que cambiemos la propiedad $combustible por:

public $combustible = 0;

Otros métodos assert


Además de assertEquals y assertSame tenemos un montón de métodos para usar en nuestras
pruebas. Echa un vistazo a la documentación de phpunit para descubrirlos.

No ejecutar todas las pruebas


Filtrar ficheros Filtrar métodos

Conclusión
Hay que usar los test frecuentemente.

Los test deben ser rápidos. Tenemos que fiarnos de nuestros test. Nuestro test deben pasar.

100
Constantes
Las constantes no tienen mucho secreto. El valor de una constantes es… constante. Esto quiere
decir que no puede cambiar y solo podemos darle valor en el momento de "definirla". Si en algún
momento vemos que debemos cambiar el valor de una constante eso quiere decir que debemos
usar una variable (o propiedad) en su lugar.

En este capítulo vamos a ver cómo se usan las constantes dentro de una clase. Voy a aprovechar
este capítulo para mostrar cómo el uso de constantes hace que el código sea más legible.

Definir constantes
En PHP podemos definir constantes usando define:

define('CONSTANTE', 10);

Y podemos acceder a esta constante directamente con su nombre sin comillas:

echo CONSTANTE;

En una clase también podemos definir constantes:

Class NombreClase
{
  const CONSTANTE = 10;
}

Es importante recordar que una constante no puede cambiar nunca de valor (de
 ahí que se llame constante).

Para acceder a una constante tenemos que usar el nombre de la clase y el operador de resolución
de ámbito (::).

echo NombreClase::CONSTANTE;

También podemos acceder a la constante a través de un objeto. Por ejemplo:

$variableObjeto = new NombreClase;


echo $variableObjeto::CONSTANTE;

Podemos usar una constante, por ejemplo, para los impuestos:

101
Class Precio
{
  const IVA = 16;
}

echo Precio::IVA;

Nombres de constantes
A una constante podemos darle el nombre que queramos pero hay que recordar que no es una
variable así que no puede comenzar con el simbolo $.

Los nombres de constantes se suelen escribir en mayúsculas aunque no es obligatorio. Es


costumbre que, si la constante está compuesta de varias palabras, usar el guión bajo (_) para
separar cada palabra (lo que se llama snake case). Por ejemplo:

• NOMBRE_DE_CONSTANTE

• SEGUNDOS_EN_UN_DIA

¿Para qué una constante dentro de una clase?


Si podemos crear las constantes con define ¿para qué queremos definirlas dentro de una clase?

Metiendo las constantes dentro de una clase podemos tenerlas más ordenadas. Un uso interesante
puede ser para controlar el resultado de alguna operación. Vamos a crear una clase para enviar el
email de registro en una web.

Vamos a suponer tres posibles resultados:

• Que falte el email.

• Que el email tenga un formato incorrecto.

• Que el email se envíe correctamente.

Para cada uno de los posibles resultados podemos crear una constante:

Class EmailRegistro
{
  const ERROR_FALTA_EMAIL = 1;
  const ERROR_FORMATO_ERRONEO = 2;
  const EMAIL_ENVIADO = 3;

  ...
}

La clase completa podría ser algo así:

102
Class EmailRegistro
{
  const ERROR_FALTA_EMAIL = 1;
  const ERROR_FORMATO_ERRONEO = 2;
  const EMAIL_ENVIADO = 3;

  private $email;
  private $error;

  function __construct($email)
  {
  $this->email = $email;
  }

  private function comprobar()


  {
  if (!$this->email) {
  $this->error = self::ERROR_FALTA_EMAIL;
  return false;
  }

  if (!filter_var($this->email, FILTER_VALIDATE_EMAIL)) { ①
  $this->error = self::ERROR_FORMATO_ERRONEO;
  return false;
  }

  return true;
  }

  public function enviar()


  {
  if(!$this->comprobar()) {
  return $this->error;
  }

  // Código para enviar el email


  ...
  return self::EMAIL_ENVIADO;
  }
}

① La función filter_var() permite comprobar si una variable pasa un determinado filtro. En este
caso comprobamos si pasa el filtro FILTER_VALIDATE_EMAIL. Más información en:
http://php.net/manual/es/function.filter-var.php

El método enviar() se encarga de comprobar el email a través del método comprobar(). Si éste es
correcto el email se envía (bueno, se enviaría si tuviésemos el código para hacerlo). Este método
devolverá un valor que podemos usar para saber si el email se ha enviado o no. Gracias a que las
constantes están definidas dentro de la clase podemos comprobar el resultado haciendo algo así:

103
$email = new EmailRegistro('info@phpsensei.es');

$resultado = $email->enviar();
if ($resultado == EmailRegistro::EMAIL_ENVIADO) {
  echo "Email enviado";
}

¿Todavía no ves utilidad a las constantes?


Si todavía no te parece que usar constantes es una buena idea compara el código anterior con éste:

Class EmailRegistro
{
  private $email;
  private $error;

  function __construct($email)
  {
  $this->email = $email;
  }

  private function comprobar()


  {
  if (!$this->email) {
  $this->error = 1;
  return false;
  }

  if (!filter_var($this->email, FILTER_VALIDATE_EMAIL)) {
  $this->error = 2;
  return false;
  }

  return true;
  }

  public function enviar()


  {
  if(!$this->comprobar()) {
  return $this->error;
  }

  // Código para enviar el email


  ...
  return 3;
  }
}

104
Si te fijas el código ahora es más difícil de entender. Para saber qué significan ese 1, 2 y 3 tienes que
investigar el código. Y la cosa empeora al usar la clase:

$email = new EmailRegistro('info@phpsensei.es');

$resultado = $email->enviar();
if ($resultado == 3) {
  echo "Email enviado";
}

Ahora es incluso menos evidente. Hasta que no vemos el texto "Email enviado" no es evidente qué
significa ese 3. Comparemos de nuevo esa condición con ésta:

if ($resultado == EmailRegistro::EMAIL_ENVIADO)

¿Qué valores podemos usar en una constante?


Podemos usar números y cadenas de texto:

Class SurtidoDeConstantes
{
  const NUMERO_ENTERO = 16;
  const NUMERO_DECIMAL = 10.20;
}

Y podemos hacer operaciones para calcular el valor de una constante (esto es posible desde la
versión 5.6 de PHP):

Class SurtidoDeConstantes
{
  const CADENA_DE_TEXTO = 'Hola';
  const CADENAS_CONCATENADAS = 'Hola' . ' mundo';
}

También podemos usar otras constantes para crear nuevas constantes:

Class SurtidoDeConstantes
{
  const CONSTANTE_COMPUESTA = self::NUMERO_ENTERO + 10;
}

Y, desde la versión 7 se pueden usar arrays:

105
const ARRAY = [ 1, 2, 3 ];s

Lo que no podemos hacer es:

Class SurtidoDeConstantes
{
  // Aunque existiera la propiedad $variable esto no se podría
  const CON_VARIABLE = self::$variable;
  // Esto tampoco:
  const CON_VARIABLE_2 = $this->$variable;
}

¿Y si no le doy un valor?
Prueba el siguiente código:

Class SurtidoDeConstantes
{
  const CONSTANTE;
}

Si lo haces verás un error:

PHP Parse error: syntax error, unexpected ';', expecting '=' in index.php

Este error se debe a que una constante no se puede dejar sin un valor. Si fuera una variable sí
podríamos hacerlo, porque a una variable podemos asignarle un valor más adelante. Pero una
constante no puede cambiar de valor. Así que, si no va a tener un valor ¿para qué la podemos
querer?

¿Public y private?
Si queremos proteger nuestras constantes podemos hacerlo desde la versión 7.1. A partir de esa
versión las constantes de una clase se pueden definir como privadas o públicas.

Class SurtidoDeConstantes
{
  public const CONSTANTE_PUBLICA = 10;
  private const CONSTANTE_PRIVADA = 20;
}

Como seguramente habrás imaginado, si no indicas nada la constante será public.

106
Interfaces
¿Qué es un Interface?

Un Interface en PHP es una entidad que te permite especificar los métodos


que debe implementar una clase.

Ya tenemos una definición, que queda muy bonita, pero que, seguro, te ha dejado igual que estabas.
Vamos a ver unos ejemplos que te ayudarán a entender qué es un interface y para qué sirve.

Vamos a montar un garaje


Imagina que has ahorrado un dinero y decides montar un parking como negocio. Sin entrar en
detalles de lo buen o mal negocio que puede ser vamos a crear una aplicación para tu negocio.

Esta aplicación te permitirá saber los coches que están aparcados y el dinero que has ganado. Para
eso vamos a crear una clase Garaje que será la encargada de gestionarlo todo:

class Garaje
{
  private $cochesAparcados = [];
  private $ingresos = 0;

  public function aparcar( Coche $coche )


  {
  $this->cochesAparcados[] = $coche;
  }

  public function calcularIngresos()


  {
  foreach( $this->cochesAparcados as $coche ) {
  $this->ingresos += $coche->importeCobrado();
  }
  return $this->ingresos;
  }
}

Garaje.php
 La clase Garaje se guardará en el fichero Garaje.php.

Esta clase tiene un método aparcar() que lo único que hace es añadir un objeto Coche a un array. El
objeto que le pasemos a este método debe ser obligatoriamente del tipo Coche.

También tiene un método llamado calcularIngresos() que recorre el array de coches aparcados y,
para cada uno, calcula el importe que se le ha cobrado. Como puedes ver en el ejemplo usamos el
método importeCobrado() de cada coche para saber qué se le ha cobrado a cada uno.

107
Ya te habrás dado cuenta de que vamos a necesitar una clase Coche. Esta clase no va a ser la misma
que en capítulos anteriores. Vamos a hacerle unos pequeños (grandes) cambios:

class Coche
{
  private $longitud = 0;
  const EUROS_POR_METRO = 10;

  function __construct( $longitud )


  {
  $this->longitud = $longitud;
  }

  public function importeCobrado()


  {
  return $this->longitud * self::EUROS_POR_METRO;
  }
}

Esta clase tendrá el método importeCobrado que calculará el importe que se le ha cobrado al
vehículo. En este caso estamos calculando el importe multiplicando la longitud por 10 (estamos
cobrando a 10 euros el metro de vehículo; sí es una tarifa muy abusiva).

Estas clases las usaríamos así:

include('Coche.php');
include('Garaje.php');

$miCoche = new Coche(3);


$tuCoche = new Coche(4);

$garaje = new Garaje();

$garaje->aparcar($miCoche);
$garaje->aparcar($tuCoche);

echo "Ingresos totales: " . $garaje->calcularIngresos() . " euros.\n";

Ampliamos el negocio
El negocio marcha bien y decidimos ampliarlo. Vamos a permitir la entrada también de camiones.
Pero a los camiones les vamos a cobrar diferente. Como hay pocos aparcamientos cerca vamos a
aprovecharnos; les vamos a cobrar 15 euros por metro cuadrado. Y, además, le vamos a sumar 5
metros a la longitud del camión. La clase Camion quedaría así:

108
class Camion
{
  private $longitud = 0;
  const EUROS_POR_METRO = 15;

  function __construct( $longitud )


  {
  $this->longitud = $longitud;
  }

  public function importeCobrado()


  {
  return ($this->longitud + 5) * self::EUROS_POR_METRO;
  }
}

Ahora nos vamos al fichero index.php e intentamos meter un camión en el garaje añadiendo estas
líneas al final del fichero:

$miCamion = new Camion(10);


$garaje->aparcar($miCamion);

y añadimos el include al principio:

include('Camion.php');

Al ejecutar este código vamos a tener un problema:

PHP Catchable fatal error: Argument 1 passed to Garaje::aparcar() must be an instance


of Coche, instance of Camion given, called in index.php on line 14 and defined in
Garaje.php on line 8

El método aparcar() espera que se le envíe un objeto Coche pero le hemos pasado un objeto Camion.
Oh, oh, ¿y ahora qué hacemos?

Puede que se te ocurra modificar el método aparcar() para que pueda aceptar cualquier tipo de
parámetro. Pero esto te puede causar problemas ya que se le podría pasar un objeto que no tuviese
el método importeCobrado() y tendríamos un error.

Entonces podrías pensar: ¿Y si dejo que entre cualquier tipo de valor pero luego uso un if para
controlar que los objetos son de tipo Coche o Camion. Podría ser una solución, pero tu código
empieza a ser complicado. Además, si quieres aceptar motos tendrás que modificar la clase Garaje.

¿Y cómo arreglamos este desaguisado?

Pues aquí es donde pueden ayudarnos los interfaces. ¿Qué tienen en común los coches y los

109
camiones? ¡Ambos son vehículos!

¿No sería maravilloso que aparcar() pudiese aceptar como parámetro un vehículo, sin importar que
sea Coche o Camion? Pues sí, gracias a un interface se puede.

Vamos a verlo. Lo primero es crear el interfaz Vehiculo (en el fichero Vehiculo.php):

interface Vehiculo
{
  public function importeCobrado();
}

Un interface se crea usando la palabra reservada interface. Podemos añadirle métodos pero no
propiedades. Y en los métodos de un interface no puede haber código (ya veremos más adelante
para qué sirve este método en blanco).

Vamos a ver cómo podemos usarlo para solucionar nuestro problema. Empezamos por modificar la
clase Coche para que implemente el interface Vehiculo:

class Coche implements Vehiculo

¿Implementar?
 A esto que estamos haciendo se llama implementar un interface.

Y hacemos lo mismo con la clase Camion:

class Camion implements Vehiculo

Ahora, ambas clases son vehículos. Por último modificamos el método aparcar() de la clase Garaje
para que acepte como parámetros objetos del tipo Vehiculo:

public function aparcar( Vehiculo $vehiculo )


{
  $this->vehiculosAparcados[] = $vehiculo;
}

Los objetos del tipo Coche y los del tipo Camion son todos del tipo Vehiculo (porque implementan el
interfaz Vehiculo). El método aparcar() ahora acepta objetos del tipo Vehiculo así que podremos
enviarle objetos Coche y Camion tranquilamente. ¡Problema solucionado!

¿Para qué tiene métodos el interfaz?


Volvamos a echar un vistazo al interface que hemos creado:

110
interface Vehiculo
{
  public function importeCobrado();
}

¿Para qué queremos este método si no puede tener nada de código dentro? Esta es otra de las
maravillas de los interfaces; las clases que lo implementen deberán definir este método.

Si a la clase Camion o a la clase Coche les faltase el método importeCobrado() tendríamos


problemas; el método calcularIngresos() de la clase Garaje necesita ese método. Si no existe vamos a
tener un error.

Gracias a que hemos añadido ese método en el interface tanto Coche como Camion deben definir el
método importeCobrado(). Si este método faltase en la clase Camion veríamos este error:

PHP Fatal error: Class Camion contains 1 abstract method and must therefore be
declared abstract or implement the remaining methods (Vehiculo::importeCobrado) in
Camion.php on line 13

¿Y por qué no crear una clase Vehiculo en lugar de un


interfaz?
Es cierto, podríamos hacer que Vehiculo fuese una clase normal y que Coche y Camion heredasen de
ella. El programa funcionaría correctamente.

Sin embargo ya no estaremos obligados a tener el método importeCobrado(). En este caso podría
haber problemas porque alguien (quizá nosotros) que use esta clase en una aplicación podría
intentar pasar un objeto sin el método importeCobrado().

Características de los interfaces en PHP


Se pueden extender

Al igual que hemos visto con las clases "normales" donde podíamos usar extendes para crear clases
heredadas, en los interfaces también podemos extender otros interfaces.

Se pueden añadir constantes

Antes hemos visto que a un interface no se le pueden añadir propiedades. Pero sí podemos añadirle
constantes. En nuestro ejemplo podríamos añadir una constante que fije el máximo de vehículos
que pueden entrar:

111
interface Vehiculo
{
  const MAX_VEHICULOS = 100;

  public function importeCobrado();


}

Lo que no podemos hacer es definir una constante en un interfaz y redefinirla en una clase que la
implementa. Por ejemplo, este código daría problemas:

interface Vehiculo
{
  const EUROS_POR_METRO = 5;

  public function importeCobrado();


}

Al ejecutar el index.php tendríamos un error similar a éste:

PHP Fatal error: Cannot inherit previously-inherited or override constant


EUROS_POR_METRO from interface Vehiculo in Coche.php on line 3

Todos los métodos de un interface son públicos

Hemos visto que un interface se usa, entre otras cosas, para obligar a que las clases que lo
implementan tengan unos determinados métodos. Es como si hubiera un "contrato" entre el
interface y la clase: si quieres implementarme tendrás que cumplir estas condiciones (crear unos
métodos determinados). Estos métodos son los que la clase debe dejar "expuestos" al mundo
exterior, así que deben ser públicos.

112
Clases abstractas
Por resumir mucho diremos que una clase abstracta es un interface al que se le pueden definir
métodos y propiedades. Al igual que un interface no se puede Instanciar, es decir, no podemos crear
un objeto a partir de una clase abstracta.

Una clase abstracta se define:

abstract class NombreClase {

¿Para qué una clase abstracta?


Hemos visto que podemos usar un interface para obligar a los objetos que lo implementan a que
tengan usa serie de métodos. Pero es responsabilidad de los objetos definir esos métodos.

Pero hay situaciones en las que absolutamente todos los objetos que usan un mismo interfaz tienen
partes del código en común. Volvamos a nuestras clases Coche y Camion.

En el capítulo anterior teníamos una clase Garage para gestionar el negocio de un parking.
Imaginemos que queremos ampliar el negocio y ofrecemos un servicio de limpieza de vehículos.
Nos gustaría añadir un método llamado limpiar en todas las clases Vehiculo. Y también una
propiedad llamada limpio para controlar si el vehículo está limpio.

Este método y la propiedad serían algo así (es un método simplón que lo único que hace es cambiar
el valor de la propiedad limpio a true):

public $limpio = false;

public function limpiar()


{
  if( !$this->limpio ) {
  $this->limpio = true;
  }
}

Tendríamos que añadir el método y la propiedad en todas las clases Vehiculo que tenemos (Coche y
Camion) y todas las que creemos en el futuro. Además, para asegurarnos de que las nuevas clases
de tipo Vehiculo tengan este método tendríamos que añadirlo al interface. Y, claro, cuando creemos
una nueva clase Vehiculo puede que nos olvidemos de crear la propiedad limpio.

¿Cómo solucionamos todos estos problemas? Seguro que ya conoces la respuesta ¡con una clase
abstracta!

Tenemos que empezar por modficar el interface Vehiculo:

113
interface Vehiculo
{
  public function importeCobrado();
}

y convertirlo en una clase abstracta:

abstract class Vehiculo


{
  public abstract function importeCobrado();
}

Como puedes ver hay dos cambios importantes:

• Hemos cambiado interface por abstract class.

• Hemos añadido abstract a la definición del método importeCobrado(). Si queremos que un


método no tenga código debemos declararlo como abstract.

Al declarar el método importeCobrado() como abstract obligamos a todas las clases hijas a que
definan este método (igual que pasaba con los métodos de un interface).

Ahora que hemos convertido Vehiculo en una clase abstracta ya podemos añadirle el nuevo método
y la nueva propiedad:

abstract class Vehiculo


{
  private $limpio = false;

  public abstract function importeCobrado();

  public function limpiar()


  {
  if( !$this->limpio ) {
  $this->limpio = true;
  }
  }
}

Todavía no hemos terminado, si ejecutamos el fichero index.php tendremos un error:

PHP Fatal error: Coche cannot implement Vehiculo - it is not an interface in


Coche.php on line 3

Se nos está quejando porque Vehiculo ya no es un interface sino una clase abstracta. Para
solucionarlo basta con cambiar la clase Coche de ésto:

114
class Coche implements Vehiculo

a ésto:

class Coche extends Vehiculo

Y haríamos lo mismo con la clase Camion.

Usar el nuevo método


Este nuevo método que hemos creado podemos usarlo de esta forma para limpiar todos los
vehículos aparcados:

public function limpiar()


{
  foreach($this->vehiculosAparcados as $vehiculo ) {
  $vehiculo->limpiar();
  }
}

Ojo, la propiedad limpio es privada


Si te fijas en la definición de la clase abstracta Vehiculo verás que está declarada como private. Lo
he hecho así porque las clases hijas no la utilizan. Si éstas tuvieran que acceder a esa propiedad
podrían hacerlo a través de un Getter o un Setter. También podrías hacer la propiedad protected o
public, dependiendo de las exigencias del proyecto.

¿Qué métodos deben ser abstractos y cuáles no?


En una clase abstracta los métodos pueden ser abstractos o no. Sería conveniente entender cuándo
hacer un método abstracto y cuándo no.

Si absolutamente todas las clases hijas van a usar un determinado método con el mismo código éste
no debería ser abstracto. Así todas las clases que lo heredan podrán reutilizarlo.

Si el método puede variar de una clase a otra deberías definirlo como abstracto y dejar que cada
clase hija defina su propio código.

¿Por qué no se puede instanciar una clase abstracta?


Hay un ejemplo que suelo usar para estos casos, la clase Fruta:

115
abstract class Fruta
{
  abstract public function color();

  public function tipo()


  {
  return "Este producto es una fruta";
  }
}

class Limon extends Fruta


{
  public function color()
  {
  return 'El limón es Amarillo';
  }
}

class Naranja extends Fruta


{
  public function color()
  {
  return 'La naranja es Naranja';
  }
}

Aquí tenemos una clase abstracta Fruta y dos clases hijas: Limon y Naranja. Cada una tiene un
método color() que devuelve el color de la fruta.

Viendo este código ¿piensas que tendría sentido crear un objeto del tipo Fruta? Tendrías que definir
el método color(), pero ¿de qué color es la fruta? La fruta no tiene un color determinado, dependerá
del tipo de fruta que sea. Por eso, en este caso, no tendría sentido crear un objeto _Fruta.

Y como no hay que creerse todo lo que nos cuentan vamos a comprobar que fruta, efectivamente,
no se puede instanciar. Mete este código en el fichero index.php (y crea el fichero Fruta.php con la
clase Fruta):

include('Fruta.php');
$fruta = new Fruta();

Este es el error que vamos a tener si ejecutamos este código:

PHP Fatal error: Uncaught Error: Cannot instantiate abstract class prueba in
index.php:21

116
¿Cuándo usar una clase abstracta?
Una clase abstracta será conveniente cuando necesitemos compartir código entre clases del mismo
tipo y, además, queramos obligar a que estas clases deban definir determinados métodos.

¿Por qué no usar siempre clases abstractas?


Una buena pregunta que se suele plantear la gente cuando descubre los interfaces y las clases
abstractas es ¿Por qué no usar siempre clases abstractas?

La pregunta es buena porque una clase abstracta parece que nos da más flexibilidad que un
interface; nos permite definir qué métodos deben tener las clases de ese tipo y, además, nos permite
meter código en ellos.

Hay varias buenas razones por las que no debemos usar siempre una clase abstracta. La más
importante es que una clase solo puede heredar de otra clase. En cambio una clase puede
implementar más de un interface.

¿Qué otras opciones hay?


Habrá casos en los que no estará claro si usar un interface o una clase abstracta… o ninguno de los
dos. En los siguientes capítulos vamos a ver otras opciones que son los trait y otro sistema que es el
de inyección de dependencias.

117
Traits
Hagamos un pequeño resumen de lo que tenemos hasta ahora:

• Cuando hay varias clases similares que tienen varios métodos iguales podemos usar herencia.
Definimos una clase base con sus propios métodos y éstos los heredan las clases hijas.

• Si la clase base no tiene sentido instanciarla nunca y queremos obligar a que las clases hijas
tengan que definir determinados métodos podemos usar una clase abstracta.

• Si tenemos varias clases con un comportamiento parecido y queremos obligarlas a que definan
determinados métodos podemos usar interfaces.

El problema de las clases abstractas y de la herencia es que una clase solo puede tener una madre
(como en la vida real). Por contra, una clase sí que puede implementar varios interfaces. Pero,
claro, en los interfaces no podemos meter nada de código. Entonces ¿qué podemos hacer cuando
una clase va a implementar dos interfaces y, además, tiene código en común. ¡Qué dilema!

¿Qué es un trait?
Para solucionar la limitación que tiene PHP con la herencia (solo puede heredar de una clase) se
introdujeron los trait. Resumiendo mucho podemos decir que un trait permite añadir
funcionalidad a una clase. Es una forma de hacer "copia/pega" de código sin tener que hacer
"copia/pega" de verdad.

Vamos a crear un trait para ver cómo funciona. En este caso vamos a crear dos métodos para
activar y desactivar el Wifi en un vehículo (que está muy de moda esto del Wifi en los coches):

trait Wifi {
  public function activarWifi() {
  return true;
  }

  public function desactivarWifi() {


  return false;
  }
}

 Un trait no se puede instanciar; no podemos crear un objeto a partir de un trait.

Para añadir el código del trait en una clase basta con usar:

use Wifi;

Por ejemplo, en la clase Camion que hemos usado anteriormente:

118
class Camion extends Vehiculo {
  use Wifi;

  private $tieneRemolque = false;

  public function setRemolque($tieneRemolque)


  {
  $this->tieneRemolque = $tieneRemolque;
  }

  public function getRemolque()


  {
  return $this->tieneRemolque;
  }
}

Ahora, si creamos un objeto Camion tendremos disponibles los métodos activarWifi() y


desactivarWifi():

$camion = new Camion();


$camion->activarWifi();

¿Por qué usar un Trait?


Ahora que hemos visto lo que son deberíamos preguntarnos ¿debo usarlos? ¿cuándo hay que
usarlos?

Vamos a ver dos ejemplos, uno en el que podemos usar herencia para solucionar el problema y otro
en el que la herencia tiene limitaciones.

Para empezar imaginemos un fabricante de vehículos que hace camiones y coches. En su catálogo
hay dos modelos de coches y dos modelos de camiones. Los dos modelos camiones tienen
características comunes (por ejemplo ambos tienen un remolque). Los modelos de coches también
tienen características comunes entre ellos (por ejemplo pueden llevar pasajeros).

Para representar esto podemos usar una clase base llamada Vehiculo que puede contener métodos
y propiedades comunes a todos los vehículos. Luego podemos crear una clase Camion, que será hija
de la clase Vehículo que incluierá todas las características propias de un camión. Por último
podemos tener una clase para cada modelo de camión con sus características únicas. Con los coches
haríamos algo similar. Hemos resuelto el problema con herencia.

Ahora imaginemos que el fabricante quiere añadir Wifi solo en un modelo de camión y en uno de
sus coches. Como no todos los vehículos tendrán Wifi está claro que estas nuevas características no
pueden ir dentro de la clase Vehiculo. Tampoco podemos meter el Wifi en la clase Camion ni en la
clase Coche porque sus hijas pueden no necesitarlo.

Así que los métodos relacionados con el Wifi tendremos que meterlos dentro de los modelos de

119
camión y coche que usan Wifi. En esta situación nos encontraríamos con un modelo de coche que
tiene unos métodos para controlar el wifi y un modelo de camión que tendrá esos mismos métodos
repetidos. En principio no hay nada malo en eso, pero ya tenemos código duplicado (los mismos
métodos en dos sitios diferentes). ¿Y no habíamos dicho que una de las bases de la programación
orientada a objetos era precisamente evitar la duplicidad de código?

Pues en esta situación es donde nos viene muy bien un trait. Bastaría con añadir el trait Wifi a los
modelos de coche y camión que lo usan y listo.

Ventajas de los Traits

Con los traits podemos evitar la duplicidad de código, que es algo que está muy bien. Acabamos
de ver un ejemplo donde nos ahorra escribir el mismo código dos veces.

Para solucionar ese mismo problema también podríamos haber usado clases intermedias. Por
ejemplo podríamos haber creado una clase Vehiculo para los vehículos "normales" sin Wifi y otra
llamada VehiculoWifi para los modelos con Wifi. Los métodos del Wifi irían en la clase VehiculoWifi
Y claro, habría que crear las clases Camion y CamionWifi y Coche y CocheWifi. ¡Menuda
complicación!

¿Y qué pasaría si además del Wifi hay alguna otra característica similar que está presente en unos
modelos y en otros no? ¡Puede ser un auténtico infierno!

Así que esta es otra ventaja de los trait nos ayuda a evitamos la herencia compleja.

Problemas de los Traits

Un trait nos puede ser muy útil cuando tenemos que añadir una serie de métodos iguales a clases
que no pueden heredar de una clase base común. Pero, como casi todo en la vida, hay partes
buenas y partes malas.

Para empezar lo de los traits se introdujo en PHP para solucionar el problema que había con la
herencia. En PHP una clase solo puede tener una clase madre (no existe la herencia múltiple) y los
traits son un pequeño "apaño" que nos permite reutilizar código de manera sencilla.

Uno de los problemas de los traits es que es muy fácil añadir nuevas características a una clase. Y
cuando hacemos eso nos estamos saltando uno de los cinco principios SOLID: el de responsabilidad
única. El principio de responsabilidad única dice que una clase (o un método) debe cumplir con
una única función (o, como dijo Robert C. Martin "debería tener una única razón para cambiar"). En
otro libro hablaré sobre los principios SOLID aplicados a PHP.

De forma similar, al ser tan fácil añadir más y más traits a una clase podemos ir añadiendo
complejidad casi sin darnos cuenta.

Por otro lado podemos estar duplicando código. Como, cuando escribimos código, no tenemos
fácilmente accesibles los métodos de los traits que usa nuestra clase, es fácil duplicar los métodos.
Es posible que no seamos conscientes de que un trait tiene un método que necesitamos y creemos
uno similar. Con los editores de código modernos no es gran problema porque podemos ver los
métodos que hereda una clase. Aún así, siempre existe el riesgo.

120
No abuses de los Traits

Los traits pueden ser algo fantánsitico pero, insisto, no abuses de ellos.

En programación orientada a objetos se suele decir que siempre debes dar prioridad a la
composición sobre la herencia. De este concepto de la composición voy a hablar en un próximo
capítulo. Es algo muy importante así que se merece su propio capítulo.

Características de los traits


¿Puede tener métodos abstractos?

Sí, un trait puede incluir métodos abstractos para obligar a las clases que lo usan a definirlos.
Funcionan igual que los métodos abstractos "normales".

Por ejemplo, en nuestro trait Wifi podríamos querer obligar a que la clase que lo use tenga un
método llamado fuenteAlimentacionWifi() que podría indicar cómo se enchufa el router wifi al
vehículo. Este método abstracto sería algo así:

abstract public function fuenteAlimentacionWifi();

con lo que Wifi quedaría:

trait Wifi {
  public function activarWifi() {
  return true;
  }

  public function desactivarWifi() {


  return false;
  }

  abstract public function fuenteAlimentacionWifi();


}

Y la clase que use el trait estaría obligada a definir este método.

¿Se puede usar más de un Trait?

Sí, en una clase podemos añadir tantos trait como queramos.

¿Puede tener propiedades?

Sí, un trait puede tener propiedades public, private y protected. Pero recordemos que al usar un
trait en una clase es como hacer copia/pega.

¿Qué quiere decir ésto? Vamos a ver lo que implica esto del copiar/pegar con un ejemplo. Mira este

121
código muy simplificado:

trait Wifi
{
  private $estadoWifi = false;
}

class Camion extends Vehiculo


{
  use Wifi;

  public function estadoWifi() {


  return $this->estadoWifi;
  }
}

$camion = new Camion();


echo $camion->estadoWifi();

Como sabemos ya por lo que hemos visto en herencia las propiedades private no son accesibles
desde el código de una clase hija. Teniendo eso en mente es habitual pensar que $estadoWifi no
será accesible desde la clase Camion. Sin embargo, si ejecutas el código anterior verás que no hay
ningún problema.

La razón es el copia/pega que he mencionado. El código del trait hay que verlo como si estuviera
incrustado dentro de la clase Camion; no hay herencia ni nada similar.

Así que si tenemos dos traits que incluyen dos propiedades con el mismo nombre vamos a tener un
problema. Veamos un ejemplo:

trait Wifi
{
  private $conectadoInternet = false;
}

trait Internet4G
{
  private $conectadoInternet = true;
}

class Camion extends Vehiculo


{
  use Wifi, Internet4G;
}

Esto nos va a provocar un error fatal. PHP se quejará de que existen dos propiedades con el mismo
nombre pero incompatibles (en un trait el valor es true y en el otro false).

122
Si ambas propiedades tuviesen el mismo valor no se produciría un error. Podrías pensar que así
queda todo solucionado. Pero ahora imagina que, casualidades de la vida, dos traits usan una
propiedad a la que ambos llaman igual pero que se usa para cosas diferentes. Aquí tendríamos
problemas y, seguro, que difíciles de detectar.

La moraleja de esta historia es que los traits están bien pero, como ves, tienen bastante peligro.

¿Y si la clase y el trait tienen un método con el mismo nombre?

Si una clase usa un trait ésta puede definir métodos cuyos nombres coinciden con los del trait. Por
ejemplo:

class Camion extends Vehiculo


{
  use Wifi;

  public function estadoWifi()


  {
  echo "Método de la clase Camion";
  }
}

trait Wifi
{
  public function estadoWifi()
  {
  echo "Método del trait Wifi";
  }
}

$camion = new Camion();


$camion->estadoWifi();

En este caso se usaría el método estadoWifi() de la clase. Pero antes de que te lances a usar esto
debes tener en cuenta dos cosas:

• La declaración de ambos métodos debe ser igual. Si no son iguales PHP se quejará lanzando un
aviso.

• Salvo que quieras reemplazar el método existente en el trait hacer ésto puede ser muy mala
idea. Que no tengas un error o un aviso no quiere decir que tu código no este mal.

¿Y si se usan dos traits que tienen un método con el mismo nombre?

Esto ya empieza a ser más complicado. Para empezar, si hay dos traits con una función que tiene el
mismo nombre se va a producir un error fatal. Vamos a verlo con un ejemplo:

123
class Camion extends Vehiculo
{
  use Wifi, Internet4G;
}

trait Wifi
{
  public function estadoInternet()
  {
  echo "Método del trait Wifi";
  }
}

trait Internet4G
{
  public function estadoInternet()
  {
  echo "Método del trait Internet4G";
  }
}

$camion = new Camion();


$camion->estadoInternet();

La ejecución de este código producirá el siguiente resultado:

PHP Fatal error: Trait method estadoWifi has not been applied, because there are
collisions with other trait methods on Camion in index.php

Es normal, PHP se encuentra con un problema que no sabe cómo resolver ¿cuál debe escoger? (es lo
que se llama un conflicto). Así que tenemos que echarle una mano usando insteadOf. Lo haremos
cambiando un poco el use:

use Wifi, Internet4G {


  Wifi::estadoInternet insteadof Internet4G;
}

Aquí le estamos diciendo que use el método estadoInternet() del trait Wifi en lugar del que hay en el
trait Internet4G. Si hacemos ésto… ¡conflicto resuleto! Ojará los conflictos del mundo real se
solucionaran así.

Precedencia

Ya que estamos mareando la perdiz siguamos viendo posibilidades ¿Qué pasaría si un método
existe en la clase madre, en la hija y en el trait? Por ejemplo:

124
class Vehiculo
{
  public function estadoInternet()
  {
  echo "Método de la clase Vehiculo";
  }
}

class Camion extends Vehiculo


{
  use Wifi;

  public function estadoInternet()


  {
  echo "Método de la clase Camión";
  }
}

trait Wifi
{
  public function estadoInternet()
  {
  echo "Método del trait Wifi";
  }
}

$camion = new Camion();


$camion->estadoInternet();

Aquí, y sin que sirva de precedente, PHP sigue una cierta coherencia. Al igual que ocurre en la
herencia "normal" si la clase hija tiene el método definido se usa éste en lugar del de la madre.
También tiene preferencia el de la clase hija con respecto al del trait.

Si la clase hija no tiene el método definido se usa el del trait. Y si el método no existe en el trait se
usa el de la madre.

Resumiendo:

• Primero se busca el método en la clase hija.

• Luego en el trait.

• Por último en la clase madre.

Y, si no quieres tener feos warnings será mejor que las definiciones de todas ellas coincidan.

Cambiar la visibilidad de un método

En ocasiones, un trait puede tener métodos que no estén definido como nos gustaría. Por ejemplo,
podría haber un método public que nos gustaría ocultar. Esto es lo que se llama cambiar la
visibilidad de un método.

125
Podríamos modificar el trait directamente pero esto no siempre es una buena idea. Es posible que
se esté usando en alguna otra parte de tu aplicación y ahí sí sea necesaria la visibilidad que tiene. Si
solo queremos cambiarlo en un lugar de nuestra aplicación (o la clase está en la carpeta vendor)
existe una forma más correcta de hacer el cambio.

 Recuerda que modificar código dentro de la carpeta vendor es pecado.

Para hacer que un método sea private haríamos algo así:

use NombreTrait { nombreMetodo as private; }

De igual forma haríamos esto para hacer un método public:

use NombreTrait { nombreMetodo as public; }

Y, por supuesto, también podríamos hacerlo private.

Por si acaso no queda del todo claro vamos a verlo en un ejemplo algo más completo. Cambiamos la
visibilidad de un método privado para que sea público:

class Camion
{
  use Wifi { estadoInternet as public; }
}

trait Wifi
{
  private function estadoInternet()
  {
  return true;
  }
}

$camion = new Camion();


$camion->estadoInternet();

Si hacemos public un método que ya es public no tendremos ningún problema. Lo mismo ocurre
con los métodos private y protected.

Traits compuestos

Un trait puede hacer uso de otros traits. Podemos complicarlo tanto como queramos pero,
recuerdo, usar un trait es añadir código a una clase directamente. Procura no abusar.

En un ejemplo más arriba teníamos dos traits: Wifi e Internet4G. Imagina que estos van siempre de
la mano. Podrías crear un trait adicional para ahorrarte el trabajo. Algo así:

126
trait Internet
{
  use Wifi, Internet4G;
}

trait Wifi
{
  ...
}

trait Internet4G
{
  ...
}

class Camion
{
  use Internet;
}

Métodos estáticos

Todavía no hemos visto cómo funcionan los métodos y propiedades estáticos. Vamos a dejarlo para
el próximo capítulo. Por ahora basta con saber que los traits admiten métodos y propiedades
estáticos pero estas últimas funcionan un poco diferente de lo normal.

Recapitulando
Recuerda siempre que un trait es equivalente a copiar/pegar, no tiene nada que ver con herencia.
También es importante tener en cuenta que son un parche que se añadió porque PHP no permite la
herencia múltiple.

Algunos principiantes se lían con esto (sobre todo con métodos protected y private) porque se
imaginan que funciona como la herencia. Cuando pienses en traits imagína que su código está
dentro de la clase que lo usa y seguro que así lo ves mejor.

127
Métodos y propiedades Static
En PHP tenemos la palabra reservada static que nos permite acceder a una propiedad o a un
método de una clase sin necesidad de crear un objeto.

Antes de usar esta palabrita mágica mira bien a tu izquierda y tu derecha. Y ten cuidado si vas a
compartir el código con otros desarrolladores… Hay mucho odio y resentimiento hacia todo lo que
sea static.

A lo largo del capítulo hablaré sobre las razones por las que hay gente que desprecia todo lo static
con tanto ahínco.

Métodos estáticos
Cuando un método lo definimos usando static podemos acceder a él sin necesidad de crear un
objeto. Como ya sabes muy bien, para poder usar una clase tenemos que crear un objeto. Por
ejemplo, imaginemos una clase llamada Mates que contiene algunas funciones matemáticas:

class Mates {
  public function sumar($num1, $num2)
  {
  return $num1 + $num2;
  }
}

$mates = new Mates;


echo $mates->sumar(1, 2) . PHP_EOL;

Esta clase es de un nivel de "absurdidez" de nivel máximo. Es solo un ejemplo.


 Espero que nunca definas una clase para hacer sumas y restas.

Para usar esta clase hemos tenido que crear el objeto $mates.

Pero, si usamos la palabra static se produce la magia:

class Mates {
  public static function sumar($num1, $num2)
  {
  return $num1 + $num2;
  }
}

echo Mates::sumar(1, 2) . PHP_EOL;

Como ves en una sola línea tenemos la llamada al método sumar sin andar creando objetos.
Seguramente te preguntarás cuál es el problema con usar static si hace que nuestro código sea más

128
corto.

El problema viene cuando trabajamos con propiedades estáticas y cuando queremos hacer pruebas
unitarias. Pero no nos adelantemos, luego hablaremos de ésto.

Si no es estático no es estático

El mensaje es claro, si un método no está definido como static no podemos llamarlo como si lo
fuera. En versiones anteriores de PHP se permitía. En la versión 7 aún se permite pero verás un
warning avisando de que es un comportamiento obsoleto. En próximas versiones llamar a un
método no static como si lo fuera puede dar un error. Así que ¡no lo hagas!

Esto te puede dar problemas en futuras versiones de PHP porque sumar() no está definida como
static:

class Mates {
  public function sumar($num1, $num2)
  {
  return $num1 + $num2;
  }
}

echo Mates::sumar(1, 2) . PHP_EOL;

Otra forma de ahorrar código

Si lo que quieres es ahorrarte una línea de código o crear una variable puedes usar este sistema:

echo (new Mates)->sumar(1, 2) . PHP_EOL;

Esta es una forma de crear un objeto de manera temporal si solo vamos a hacer uso de él una vez.
Es fácil de recordar, donde antes hemos puesto $mates ahora hemos puesto (new Mates).

¿Qué pasa con $this?

Un problema que tienen los métodos estáticos es que no pueden acceder a las propiedades
"normales" de la clase. No puede acceder a ellas porque las propiedades solo tienen sentido cuando
hemos creado un objeto.

Tampoco podemos acceder a los métodos que no sean static.

En realidad el problema que tenemos es que la variable $this no está disponible dentro de un
método static.

Veamos un ejemplo:

129
class Mates {
  private $pi = 3.1416;

  public static function circunferencia($radio)


  {
  return 2 * $this->pi * $radio;
  }
}

echo Mates::circunferencia(1) . PHP_EOL;

El método circunferencia() está definido como static así que no podrá acceder a la variable interna
$this. Si lo intentamos vamos a tener este resultado:

PHP Fatal error: Uncaught Error: Using $this when not in object context in
intro.php:11

Como puedes ver se está quejando de que estás intentando usar $this pero no hay un objeto de por
medio (porque es un método static).

En la siguiente sección vamos a ver cómo acceder a otras propiedades y métodos estáticos.

Propiedades estáticas
Antes hemos visto que un método static no tiene acceso a $this y, por tanto, no puede usar
propiedades que no sean static. Para definir una propiedad como static basta con hacer:

private static $pi = 3.1416;

Y para acceder a esta propiedad, en lugar de usar $this usaremos self:::

class Mates
{
  private static $pi = 3.1416;

  public static function circunferencia($radio)


  {
  return 2 * self::$pi * $radio;
  }

}
echo Mates::circunferencia(1) . PHP_EOL;

Ahora sí.

130
Pero debemos tener mucho cuidado con las propiedades static… a éstas las carga el diablo. Si no
tenemos cuidado pueden ser una fuente inagotable de problemas.

A las propiedades static las carga el dibablo


El problema con las propiedades static está en que todos los objetos que de la clase Mates van a
compartir la propiedad $pi. Seguramente esto no te habrá llamado mucho la atención ni piensas
que pueda ser un problema. Así que vamos a verlo con calma.

Olvidemos por un momento esto de lo estático y volvamos a las clases "normales". Vamos a usa una
clase llamada Corredor que vamos a usar para contar las vueltas que da un corredor en un circuito
de atletismo (aparquemos los coches y vamos a hacer un poco de ejercicio):

class Corredor
{
  private $vueltas = 0;

  public function contarVuelta()


  {
  $this->vueltas++;
  return $this->vueltas;
  }
}

$corredor = new Corredor();

echo $corredor->contarVuelta() . PHP_EOL;


echo $corredor->contarVuelta() . PHP_EOL;
echo $corredor->contarVuelta() . PHP_EOL;

Esta clase usa una variable $vueltas que se incrementa cada vez que el corredor da una vuelta. El
método contarVuelta() es el encargado de incrementar la variable y devolver su valor.

El resultado será:

1
2
3

Usando esa clase Corredor podemos controlar a otro atleta:

131
class Corredor
{
  private $vueltas = 0;

  public function contarVuelta()


  {
  $this->vueltas++;
  return $this->vueltas;
  }
}

$corredor = new Corredor();

echo $corredor->contarVuelta() . PHP_EOL;


echo $corredor->contarVuelta() . PHP_EOL;
echo $corredor->contarVuelta() . PHP_EOL;

$otroCorredor = new Corredor();

echo $otroCorredor->contarVuelta() . PHP_EOL;


echo $otroCorredor->contarVuelta() . PHP_EOL;
echo $otroCorredor->contarVuelta() . PHP_EOL;

Aquí tenemos dos objetos que son instancias de la clase Corredor cada uno con su propiedad
$vueltas. Cuano llamamos a contarVuelta() se actualiza el valor de $vueltas de ese corredor y no
afecta al otro. Esto sucede así porque cada objeto almacena sus propios valores.

El resultado será:

1
2
3
1
2
3

¡La vida es maravillosa! Pero un buen día descubres las propiedades static. Creo que a todos nos
pasa que cuando descubrimos algo nuevo queremos probarlo a todo correr. ¡Y además con un
método static me puedo ahorrar un par de línea de código! ¡Allá voy!

132
class Corredor
{
  private static $vueltas = 0;

  public static function contarVuelta()


  {
  self::$vueltas++;
  return self::$vueltas;
  }
}

echo Corredor::contarVuelta() . PHP_EOL;


echo Corredor::contarVuelta() . PHP_EOL;
echo Corredor::contarVuelta() . PHP_EOL;

Todo bien. Con un corredor el resultado es el esperado:

1
2
3

Pero ahora añadimos un nuevo corredor. Mmm, habrá que usar objetos para controlar a cada uno
de los corredores ¿no? Veamos:

class Corredor
{
  private static $vueltas = 0;

  public static function contarVuelta()


  {
  self::$vueltas++;
  return self::$vueltas;
  }
}

$corredor = new Corredor();

echo $corredor::contarVuelta() . PHP_EOL;


echo $corredor::contarVuelta() . PHP_EOL;
echo $corredor::contarVuelta() . PHP_EOL;

$otroCorredor = new Corredor();

echo $otroCorredor::contarVuelta() . PHP_EOL;


echo $otroCorredor::contarVuelta() . PHP_EOL;
echo $otroCorredor::contarVuelta() . PHP_EOL;

133
Vaya, ya no nos ahorramos nada de código. Pero no importa, nos ha gustado esto de las cosas static.
Vamos a probarlo:

1
2
3
4
5
6

¿Como es posible? ¡Pero si estamos usando dos objetos independientes! Pues aquí es donde está el
problema; todos los objetos de una misma clase comparten los valores de sus propiedades static.
Así que, mucho cuidado cuando las uses.

Este es un ejemplo un poco absurdo, pero hay otro que seguro que ves más claro. Imagina una clase
Contador más general que podemos usar en cualquier parte de nuestro código para contar alguna
cosa que nos interese:

class Contador
{
  private static $cuenta = 0;

  public static function incrementar()


  {
  self::$cuenta++;
  return self::$cuenta;
  }
}

Lo bueno que tiene es que en cualquier parte del código podemos hacer:

Contador::incrementar()

Y no tenemos que preocuparnos del valor de $cuenta porque se mantiene entre una llamada y otra.
Podemos llamar al método incrementar() desde un método de una clase, desde una función… desde
cualquier sitio. Es como si fuese una variable global.

El problema, otra vez, está en que ese valor se comparte siempre. Imagina que añader la llamada a
incrementar() en un lugar que has olvidado. O que otro que trabaja en el mismo proyecto la añade
en otra parte del código.

Tienes casi garantizado que en algún momento vas a tener resultados inesperados. Por ejemplo
empezarás a buscar en el código que tú controlas las llamadas a incrementar() Imagina que le
llamas solo dos veces, pero el resultado es tres. Seguro que hay alguna llamada con la que no habías
contado.

Esta es una de las razones por las que hay tanto odio hacia las propiedades static.

134
El gran problema con las propiedades static (y con las variables globales)

Seguro que has oído que las variables globales son malas. Pero ¿por qué son malas? El problema es
que, como todo el código que se ejecuta tiene acceso a ellas, su valor puede cambiar en cualquier
lugar. Si tienes unas pocas líneas de código no es un problema. Pero ¿cuántos proyectos de verdad
tienen unas pocas líneas de código?

Vamos a ver una hipotética situación (que sucede muy a menudo). Imagina que tienes esta función
que usa una variable global.

$titulo = "¡Qué gran noticia!";

function mostrarTitulo()
{
  global $titulo;

  echo "<h1>$titulo</h1>";
}

mostrarTitulo();

La pruebas y todo funciona correctamente. Como ya has adivinado el resultado de esta función es:

<h1>¡Qué gran noticia!</h1>

Y funciona correctamente durante mucho tiempo.

Pero un buen día, sin razón aparente, deja de funcionar como esperabas. De repente empieza a
mostrar el resultado:

<h1>"¡Qué gran noticia!"</h1>

¿De dónde han salido esas comillas? Revisas tu código y no ves comillas por ningún lado. Por
supuesto, para este ejercicio de imaginación, hay que suponer que estamos en un proyecto de,
digamos, diez mil líneas de código. Usas tu editor de textos y buscas $titulo y ves que aparece en un
montón de sitios. Empiezas a revisar uno por uno y, después de una largo y desesperado rato,
descubres que hay una función llamada procesarTitulo():

function procesarTitulo()
{
  global $titulo;

  $titulo = '"$titulo"';
}

Investigas un poco y descubres que otra persona del proyecto añadió esa función porque la

135
necesitaba. Hablas con esa persona y, después de una larga discusión, llegaís a un acuerdo para no
modificar esa variable. Con suerte llegáis a ese acuerdo sin derramamiento de sangre.

Este sencillo ejemplo de la vida diaria ilustra a la perfección el problema que suponen las variables
globales.

Las propiedades static son, de alguna manera, variables globales. Así que el problema es el mismo.

Por decirlo de manera más elegante, el problema con una propiedad static es que los objetos ya no
tienen control total sobre ella. Un objeto puede cambiar el valor de sus propiedades a su gusto y en
todo momento se sabe qué valor van a tener. Pero una propiedad static puede ser modificada por
cualquier otro objeto de la misma clase y esto podría llevar a errores difíciles de localizar.

¿Qué utilidad tienen las propiedades static?


Si son tan malas las propiedades static ¿por qué existen? Claro que tienen utilidad, tan solo hay que
tener cuidado al usarlas.

Por ejemplo, se usan en el patrón de diseño Singleton. Este patrón se puede usar, por ejemplo, para
crear una conexión a una base de datos. Estableces la conexión la primera vez que quieras
conectarte y en el resto del código reutilizas esa conexión.

Otra utilidad es usarlas como variables globales (si es que necesitas usarlas).

También se puede usar por ejemplo si queremos crear objetos cada uno con un identificador único:

class Coche {
  private static $ultimoId = 0;

  public $id;

  public function __construct() {


  $this->id = self::$ultimoId;
  self::$ultimoId++;
  }
}

$coche = new Coche;


echo "Este objeto tiene el Id: " . $coche->id . PHP_EOL;

$otroCoche = new Coche;


echo "Este objeto tiene el Id: " . $otroCoche->id . PHP_EOL;

Fusilado (con algunas modificaciones) de Stack Overflow:


 https://stackoverflow.com/questions/43114376/php-when-to-use-private-static-
properties-in-the-class

Al ejecutar el código veremos el siguiente resultado:

136
Este objeto tiene el Id: 0
Este objeto tiene el Id: 1

Vamos a ver cómo funciona. En esta clase tenemos dos propiedades: $ultimoId e $id. $ultimoId es
una propiedad estática que va a funcionar a modo de contador de objetos. $id es una propiedad
"normal" que guardará el identificador de cada objeto.

Cada vez que creamos un nuevo objeto éste tendrá su propio $id pero recordará $ultimoId porque
es una propiedad común a todos los objetos de esa clase.

public function __construct() {


  $this->id = self::$ultimoId;
  self::$ultimoId++;
}

Cuando creamos el primer objeto $ultimoId tiene el valor cero. Así que $this→id recibirá el valor 0.
Por último el valor de $ultimoId se incrementa, así que cogerá el valor 1.

Cuando creamos el segundo objeto éste compartirá $ultimoId con los demás objetos de la clase
Coche. Es decir, que $ultimoId tiene ahora el valor 1. Así que el $id del segundo objeto será 1.

Si siguiésemos creando objetos cada uno tendría su propio $id sin repetirse ninguno.

Por supesto, cada vez que ejecutemos el código los objetos empezarán con el id
 cero porque no lo estamos almacenando en ningún sitio.

¿Es mejor usar una constante o una propiedad static?


En los lenguajes de programación compilados hay una diferencia de velocidad considerable entre
usar constantes o variables (ganan las constantes por goleada). En un lenguaje interpretado como
PHP la diferencia es mucho menor.

La ventaja de usar una constante frente a una propiedad static es que las constantes no se pueden
modificar y así estamos protegidos frente a errores o despistes.

¿Librería de métodos estáticos?


Usar métodos estáticos es una forma de tener funciones globales agrupadas por clases. Todos
usamos las funciones globales de PHP continuamente. También usamos las nuestras propias.

Por ejemplo, si necesitamos una función global para sumar números enteros y otra que sume
cadenas de texto podríamos hacer:

• sumarEnteros($numero1, $numero2);

• sumarCadenas($numero1, $numero2);

137
Esto está bien, pero también podríamos crear, por ejemplo, una clase Numeros y meter ahí todas las
funciones globales relacionados con los números. Y otra clase Cadenas en la que meter las
funciones relacionadas con cadenas. De esta forma podríamos tener todas nuestras funciones bien
ordenadas y disponibles de forma global.

Pero ojo, para que nuestro código no se convierta rápidamente en espagueti deberías seguir una
serie de normas:

• No abuses de los métodos estáticos.

• Intenta que sean lo más cortos posibles y que no llamen a otros métodos.

• Que las variables que use un método estático sean sus parámetros.

• Y, sobre todo y relacionado con el punto anterior, que no usen propiedades estáticas.

El ejemplo mencionado podría quedar algo así:

class Numeros
{
  public static function sumar($numero1, $numero2)
  {
  return $numero1 + $numero2;
  }
}

class Cadenas
{
  const NUMEROS = [
  'uno' => 1,
  'dos' => 2,
  'tres' => 3,
  ];

  public static function sumar($numero1, $numero2)


  {
  return self::NUMEROS[$numero1] + self::NUMEROS[$numero2];
  }
}

echo "2 + 3 = " . Numeros::sumar(2, 3). PHP_EOL;


echo "dos + tres = " . Cadenas::sumar('dos', 'tres') . PHP_EOL;

Este es un ejemplo sencillo pero creo que sirve para hacerse una idea de cómo podemos agrupar
métodos estáticos.

Llamar a una propiedad estática usando una variable


Seguramente recordarás que podemos usar una variable para llama a una propiedad no estática:

138
Class Coche
{
  public $combustible = 10;
  public $kilometraje = 20000;
}

$nombrePropiedad = 'combustible';
$coche = new Coche;
echo "El $nombrePropiedad es " . $coche->$nombrePropiedad;

El resultado será:

El combustible es 10

Lo que ocuerre es que al encontrarse una variable donde debería estar el nombre de una
propiedad:

$coche->$nombrePropiedad

lo primero que hace PHP es sustituir esa variable por su valor. De tal forma que, en este caso, ese
trozo de código sería equivalente a:

$coche->combustible

Con las propiedades static podemos hacer lo mismo pero con una pequeña modificación. Si intentas
usar el mismo sistema, por ejemplo así:

Class Coche
{
  public static $combustible = 10;
  public static $kilometraje = 20000;
}

$nombrePropiedad = 'combustible';
echo "El $nombrePropiedad es " . Coche::$nombrePropiedad;

Vas a tener un error:

PHP Fatal error: Access to undeclared static property: Coche::$nombrePropiedad in


index.php

¿Por qué? Pues porque a las propiedades static se accede usando el símbolo $ (con las "normales"
no se usa el $).

139
Recuerda que si usaras Coche::combustible PHP esperaría que combustible fuese
 una constante.

¿Y cómo hacemos para usar este "truco" de acceder a una propiedad static con una variable? Pues
como en la vida real; "Si un dolar no te abre las puertas… prueba con dos". Sería así:

Coche::$$nombrePropiedad

Y el código completo quedaría:

Class Coche
{
  public static $combustible = 10;
  public static $kilometraje = 20000;
}

$nombrePropiedad = 'combustible';
echo "El $nombrePropiedad es " . Coche::$nombrePropiedad;

Y, ahora sí, el resultado será el esperado:

El combustible es 10

Llamar a un método estático usando una variable


Vamos a recuperar el ejemplo que usábamos al principio del capítulo de la clase Mates:

class Mates {
  public static function sumar($num1, $num2)
  {
  return $num1 + $num2;
  }
}

echo Mates::sumar(1, 2) . PHP_EOL;

Si queremos acceder, como hemos hecho con las propiedades static, al método sumar() usando una
variable podemos hacerlo así:

$operacion = 'sumar';
echo Mates::$operacion(1, 2) . PHP_EOL;

En este caso no necesitamos usar el símbolo doble $. Imagino que es así porque al llamar a un

140
método usamos el paréntesiso al final y no hay "métodos constantes" con los que pueda haber
duda.

Repaso
Es posible que te hayas hecho un cierto jaleo mental con tanto public, private, protected, static,
constantes, métodos, etc. Para poner un poco de orden en el siguiente capítulo tienes un rápido
repaso de todas las posibilidades.

141
Repaso a los elementos de una clase
Como seguramente ya te habrás liado con tantas posibilidades que hay para acceder a los métodos,
propiedades y constantes vamos a hacer un pequeño repaso rápido.

Empecemos con esta sencilla clase:

Class Clase
{
  public $propiedad = 10;
  private $propiedadPrivada = 20;
  protected $propiedadProtected = 30;
  public static $propiedadStatic = 40;
  const CONSTANTE = 50;

  public function metodo()


  {
  echo "Método normal.\n";
  }

  public static function metodoStatic()


  {
  echo "Método estático.\n";
  }
}

Vamos a crear un objeto con esta clase:

$objeto = new Clase;

$nombrePropiedad = 'propiedad';
$nombrePropiedadStatic = 'propiedadStatic';

$nombreMetodo = 'metodo';
$nombreMetodoStatic = 'metodoStatic';

Acceder a una propiedad:

echo $objeto->propiedad;

También podemos usar una variable en lugar del nombre de la propiedad:

echo $objeto->$nombrePropiedad;

Como $nombrePropiedad tiene el valor propiedad es equivalente a la versión anterior. También

142
podemos usar llaves:

echo $objeto->{$nombrePropiedad};

Aquí no tiene mucho sentido pero las llaves son necesarias cuando usamos algo más complejo como
otro objeto o un array:

echo $objeto->{$otroObjeto->nombrePropiedad};
echo $objeto->{$unArray['nombrePropiedad']}

 Esto falla porque no existen ni $otroObjeto ni $unArray.

También podemos usar una cadena en la que puede haber o no variables:

echo $objeto->{"propiedad"};

Para acceder a las propiedades estáticas:

echo $objeto::$propiedadStatic;

O también podemos usar una variable como hemos hecho con la propiedad no estática (pero aquí
hay que poner dos $):

echo $objeto::$$nombrePropiedadStatic;

Para acceder a una constante:

echo $objeto::CONSTANTE;

A las propiedades static y las constantes podemos acceder sin usar un objeto solo con el nombre de
la clase:

echo Clase::$propiedadStatic;
echo Clase::$$nombrePropiedadStatic;
echo Clase::CONSTANTE;

A un método "normal" podemos llamarlo así:

$objeto->metodo();
$objeto->$nombreMetodo();

143
Y a un método estático:

$objeto::metodoStatic();
Clase::metodoStatic();

Y también podemos usar una variable, pero aquí no hace falta el dolar extra:

$objeto::$nombreMetodoStatic();

144
Clases y métodos Final
Final es una palabra reservada de PHP que evita que un método se pueda sobrescribir o una clase
se pueda heredar.

Métodos final
Sabemos que a una clase normal podemos extenderla y modificar sus métodos:

class Vehiculo
{
  public function arrancar()
  {
  echo "¡Brrrummm!";
  }
}

class Coche extends Vehiculo


{
  public function arrancar()
  {
  echo "Mi choche hace ¡brrummm!";
  }
}

$coche = new Coche;


$coche->arrancar();

Si, por alguna razón queremos evitar que el método arrancar() se pueda modificar en una clase
heredada bastará con añadir la palabra final:

final public function arrancar()


{
  echo "¡Brrrummm!";
}

Si añades final e intentas ejecutar el código de antes verás un error:

PHP Fatal error: Cannot override final method Vehiculo::arrancar()

Podrías pensar que si el método es privado esto no debería afectarle (al fin y al cabo una clase hija
no tiene acceso a los métodos privados de la madre):

145
class Vehiculo
{
  final private function arrancar()
  {
  echo "¡Brrrummm!";
  }
}

Pero esto también te va a mostrar el error de antes.

Propiedades final
No, las propiedades no se pueden declarar como final.

Clases final
Aquí es donde viene lo realmente interesante. Cuando declaramos una clase como final esa clase no
se puede heredar. Por ejemplo:

final class Vehiculo


{
  private function arrancar()
  {
  echo "¡Brrrummm!";
  }
}

class Coche extends Vehiculo


{
  public function arrancar()
  {
  echo "Mi choche hace ¡brrummm!";
  }
}

$coche = new Coche;

Si ejecutamos el código veremos el error:

PHP Fatal error: Class Coche may not inherit from final class (Vehiculo)

¿Cuándo usar final para una clase?


Hasta aquí toda la teoría sobre final, no tiene mucho secreto. Como muchas otras cosas en
programación las cosas sencillas no lo son tanto. También hay mucha pelea con respecto a hacer

146
clases final o no. Hay argumentos en ambos bandos (sí, en esto también hay bandos).

El problema es el siguiente: Si definimos una clase como final no podrá tener clases hijas. Esto
quiere decir que si la clase no hace lo que queremos no podremos extenderla y perdemos
flexibilidad.

Entonces ¿por qué querría alguien hacer una clase final?

Pues hay varios (y buenos motivos):

La herencia es muy cómoda y es fácil abusar de ella. Cada vez que heredamos de una clase estamos
metiendo más y más métodos en ella. Al final acabamos con una clase con un montón de métodos y
propiedades que se convierte en un monstruo difícil de mantener.

No es raro ver cinco o diez niveles de herencia. Si decidimos cambiar un método en la clase
principal podemos estar afectando a todas las clases hijas. Probablemente nos veríamos obligados a
modificar todas esas clases.

Así que, para evitar estos problemas, podemos definir nuestra clase como final.

Hay quienes, por defecto, hacen todas sus clases final. Si en un momento determinado necesitan
extender esa clase simplemente quitan la etiqueta final y listo. De esta forma se obligan a buscar
soluciones que no sean herencia.

¿Y si una clase final no hace todo lo que necesito?


Hemos visto que con final una clase no se puede extender. Pero habrá situaciones en las que nos
encontraremos con una clase final que nos gustaría ampliar. ¿Qué podemos hacer en estas
situaciones en las que necesitamos extender una clase final? En estos casos podemos hacer uso de
algo llamado composición.

Vamos a ver qué es la composición en el siguiente capítulo.

147
Composición
La composición es un concepto muy simple y fácil de usar; Consiste en usar un objeto dentro de
una clase.

Un sencillo ejemplo
Lo vas a entender bien con un sencillo ejemplo: Imagina una clase Vehiculo a la que queremos
añadir métodos que se encarguen de todo lo relacionado con el motor. Podríamos crear todos esos
métodos o podríamos crear una clase aparte llamada Motor que se encargue de todo eso. También
podría suceder que la clase Motor ya esté escrita (por nosotros o por algún otro desarrollador) y
queramos aprovecharla (por eso de reutilizar código y no reinventar la rueda). Esta clase Motor
podría ser algo así:

class Motor
{
  public function cambiarAceite()
  {
  echo "Aceite cambiado";
  }
}

¿Cómo usarías esta clase dentro de la clase Vehiculo? Podrías pensar, por ejemplo, que bastaría con
hacer que la clase Vehiculo herede de la clase Motor. Esa podría ser una solución. Pero ahora
imagina que también quieres hacer lo mismo para el chasis (con una clase llamada Chasis).

Con herencia tendríamos un problema ya que en PHP una clase solo puede tener una madre. Una
posible opción sería modificar la clase Motor para que herede de Chasis pero hay varias pegas:

• Tenemos que modificar la clase Motor para que extienda la clase Chasis.

• La clase Motor tendría unos cuantos métodos que no tienen nada que ver con ella.

• La clase final, Vehiculo, sería una clase monstruosa con un montón de métodos.

Para casos como éste es mejor usar la lo que se llama composición. Sería algo así:

148
class Vehiculo
{
  private $motor;

  public function __construct($motor)


  {
  $this->motor = $motor;
  }

  public function hacerMantenimiento()


  {
  $this->motor->cambiarAceite();
  }
}

Lo que hacemos es pasar un objeto Motor al constructor de Vehiculo y guardarlo en una propiedad.
De esta forma podremos acceder al motor desde cualquier método de Vehículo a través de
$this→motor. Por ejemplo podríamos crear el método hacerMantenimiento() que, entre otras cosas,
se encargaría de cambiar el aceite del motor. Este método solo tendría que llamar a $this→motor
así:

$this->motor->cambiarAceite();

Dentro de una aplicación esta clase se usaría así:

$motor = new Motor();


$vehiculo = new Vehiculo($motor);
$vehiculo->hacerMantenimiento();

Como puedes ver hay que crear un objeto $motor para pasarlo como parámetro al crear el Vehiculo.
Seguro que te parece una complicación innecesaria, no le ves muchas ventajas y se te ocurren
mejores formas de solucionar el problema. Así que vamos primero a ver un ejemplo de
composición con dos objetos y luego vamos a ver qué otra soluciones podríamos usar.

Composición con más de un objeto


Vamos a poner asientos en nuestro vehículo (así será más cómodo para sus ocupantes). Para los
asientos usamos otra clase a la que llamaremos Asientos:

149
class Asientos
{
  public function limpiar()
  {
  echo "Asientos limpitos";
  }
}

Y la clase Vehiculo quedaría:

class Vehiculo
{
  private $motor;
  private $asientos;

  public function __construct($motor, $asientos)


  {
  $this->motor = $motor;
  $this->asientos = $asientos;
  }

  public function hacerMantenimiento()


  {
  $this->motor->cambiarAceite();
  $this->asientos->limpiar();
  }
}

En esta segunda versión al constructor de Vehiculo le pasamos dos objetos, uno para el $motor y
otro para los $asientos. Ahora el mantenimiento lo hacemos llamando a cambiarAceite() del objeto
$motor y al método limpiar() del objeto $asientos.

Esta nueva versión de Vehiculo se puede usar así:

$motor = new Motor();


$asientos = new Asientos();

$vehiculo = new Vehiculo($motor, $asientos);


$vehiculo->hacerMantenimiento();

En esta ocasión necesitamos dos objetos para poder crear el vehículo.

Solucionar el problema con herencia


Quizá todavía piensas que la herencia es mucho más fácil de usar. Antes he comentado algunos
problemas que supone usar herencia pero por si no te lo crees vamos a verlo con más

150
detenimiento. Vamos a verlo cuando solo tenemos motor. Empezaríamos haciendo que Vehiculo
herede de la clase Motor:

class Vehiculo extends Motor


{
  public function hacerMantenimiento()
  {
  $this->cambiarAceite();
  }
}

$vehiculo = new Vehiculo();


$vehiculo->hacerMantenimiento();

Pues sí, el código nos ha quedado aparentemente mucho más corto, no necesitamos un constructor,
ni declarar objetos adicionales. Parece que la cosa pinta bien por ahora.

Pero ahora tenemos que añadir los asientos. Recuerda que en PHP una clase no puede tener más
que una madre así que no podemos hacer esto:

class Vehiculo extends Motor, Asientos // ¿Haciendo trampas?


{
}

Así que tendríamos que empezar a hacer chapucillas. Algo como:

class Asientos extends Motor


{}

class Vehiculo extends Asientos


{}

¿Empiezas a ver el problema? Aquí se ve el absurdo rápidamente, no tiene sentido que Asientos
herede de Motor, pero en el software real esto se ve muy a menudo.

¿Por qué no usar traits?


Seguro que ya lo has visto claro ¡podemos usar traits! No parece una mala idea, vamos a verlo.
Convertimos la clase Motor en un trait:

151
trait Motor
{
  public function cambiarAceite()
  {
  echo "Aceite cambiado";
  }
}

Y si hacemos lo mismo con Asientos la clase Vehiculo puede coger este aspecto:

class Vehiculo
{
  use Motor, Asientos;

  public function hacerMantenimiento()


  {
  $this->cambiarAceite();
  $this->limpiar();
  }
}

No está mal, tenemos un código más corto en nuestra clase Vehiculo, no hace falta constructor, sin
objetos adicionales ¡y funciona! Pero no te escapes tan rápido que no todo es tan bonito. Sigue
leyendo que hay más sorpresas.

Motores y asientos de varios tipos


Como nuestro jefe/cliente nunca está contento ahora quiere poder usar distintos tipos de asientos y
de motores. Nos dicen que todos los motores deben tener la posibilidad de cambiar aceite (así que
todos necesitan el método cambiarAceite()) y que habrá dos tipos de motores: gasolina y eléctrico
(aunque los eléctricos en realidad no necesitan cambios de aceite). Así que creamos una clase para
cada tipo de motor:

152
class MotorGasolina
{
  public function cambiarAceite()
  {
  echo "Aceite de coche eléctrico cambiado";
  }
}

class MotorElectrico
{
  public function cambiarAceite()
  {
  echo "Este coche no necesita cambios de aceite";
  }
}

Usando la composición ¿Sabes qué habría que cambiar en la clase Vehículo de antes? ¡Nada! Solo
tendríamos que indicar qué tipo de motor queremos al crear el objeto Vehiculo y listo:

$motor = new MotorElectrico(); // Esta es la única línea que cambia


$asientos = new Asientos();

$vehiculo = new Vehiculo($motor, $asientos);


$vehiculo->hacerMantenimiento();

Y si creamos dos vehículos a cada uno de ellos le podemos poner un motor diferente sin tener que
modificar la clase Vehiculo. ¿A que ahora sí te gusta más esto de la composición?

Sin embargo, la solución de los traits ya no nos vale aquí. Habría que crear un tipo de trait para
cada tipo de motor y una clase vehículo diferente para cada tipo de motor. Si quieres, puedes
probar a solucionar el problemas con traits a modo de ejercicio.

Aquí viene de perlas un interface


Como nos estamos empezando a volver adeptos al código bien hecho vamos a hacer un pequeño
cambio. Para que nuestro código sea más correcto y evitar problemas en el futuro podríamos
modificar Vehiculo para obligar a que el parámetro $motor sea del tipo Motor:

public function __construct(Motor $motor, $asientos)

Y Motor podría ser un interface:

153
interface Motor
{
  public function cambiarAceite();
}

Por último habría que hacer que MotorElectrico y MotorGasolina implementasen el interface:

class MotorGasolina implements Motor


{
  public function cambiarAceite()
  {
  echo "Aceite de coche de gasolina cambiado";
  }
}

class MotorElectrico implements Motor


{
  public function cambiarAceite()
  {
  echo "Aceite de coche eléctrico cambiado";
  }
}

¿Es imprescindible usar un interface? No, no es imprescindible. Ya has visto que la versión anterior
de Vehiculo funcionaba sin problemas (la versión donde no decíamos que $motor tuviese que ser
del tipo Motor). Sin embargo, al usar el interface obligamos a los las clases "motor" que se vayan a
usar incluyan el método cambiarAceite(). Así que cuando vayamos a crear una clase motor
sabremos que lo único que necesitamos para que funcione bien es que disponga de los métodos
indicados en el interface.

Chapuzas Acme, S.A.


Es posible que por la cabeza ya se te ha pasado una solución similar a ésta:

154
class Motor
{
  private $tipo;

  public function __construct($tipo)


  {
  $this->tipo = $tipo;
  }

  public function cambiarAceite() {


  switch($this->tipo) {
  case 'gasolina':
  echo 'Aceite de coche de gasolina cambiado.\n';
  break;
  case 'electrico':
  echo 'Aceite de coche eléctrico cambiado.\n';
  break;
  }
  }
}

Y pensarás ¿qué tiene de malo? ¿acaso no funciona? Pues sí, funciona a la perfección pero podría
tener varios problemas. Imagina, por ejemplo, que esta clase tiene dos métodos:

• cambiarAceite().

• limpiar().

Habría que añadir el método limpiar que sería algo así:

public function limpiar() {


  switch($this->tipo) {
  case 'gasolina':
  echo 'Motor de gasolina limpio.\n';
  break;
  case 'electrico':
  echo 'Motor eléctrico limpio.\n';
  break;
  }
}

Perfecto, sin problemas. Ahora nos piden un tercer tipo de motor: uno de diesel. Tendremos que
modificar los dos métodos para añadir la opción del diesel.

¿Qué problema hay? Todo el que tiene un poco de experiencia en desarrollo sabe que es muy fácil
olvidarse añadir alguno de los case (¿a quién no le ha pasado?). Y no nos daremos cuenta hasta que
el programa falle. Si, además, no estamos usando pruebas automatizadas seguramente
descubriremos el fallo cuando llame el usuario de la aplicación muy enfadado.

155
Usando un interface basta con crear una nueva clase que ya nos avisará de que le faltan métodos.
Además, con los nuevos IDE el propio editor te "chiva" que ese interface necesita algunos métodos
que has olvidado.

Por curiosidad, este ejemplo violaría dos de los principios SOLID: el de Responsabilidad Única y el
de OCP (Open Closed Principle). Los principios SOLID es muy conveniente conocerlos y aplicarlos
siempre que se pueda.

156
Métodos mágicos
Ya hemos visto el método __construct() al que se llama automáticamente cuando creamos un objeto.
Este es lo que se llama un método mágico. Tenemos disponibles varios métodos de este tipo:

• __construct(),

• __destruct(),

• __call(),

• __callStatic(),

• __get(),

• __set(),

• __isset(),

• __unset(),

• __sleep(),

• __wakeup(),

• __toString(),

• __invoke(),

• __set_state(),

• __clone()

• __debugInfo().

__get() y \__set()
Si ya conoces estos dos métodos posiblemente te suceda como a mí y te preguntes ¿para qué
existen? Vamos a ver primero qué es lo que hacen y cómo funcionan.

Como ya sabes no es posible acceder a las propiedades definidas como privadas desde fuera:

class Coche
{
  private $combustible;
}

$coche = new Coche();


$coche->combustible = 'Gasolina';

Si ejecutas este código verás el error:

PHP Fatal error: Uncaught Error: Cannot access private property Coche::$combustible

Pero con un método __set() podemos dar valor a $gasolina:

157
class Coche
{
  private $combustible;

  public function __set($propiedad, $valor)


  {
  $this->$propiedad = strtoupper($valor);
  }
}

$coche = new Coche();


$coche->combustible = 'Gasolina';

print_r($coche);

El resultado sería:

Coche Object
(
  [combustible:Coche:private] => GASOLINA
)

Este método funciona de la siguiente forma; cuando se intenta acceder a una propiedad inaccesible
se llama a este método. ¿Y qué es una propiedad inaccesible? Pues es una propiedad a la que no se
puede acceder bien por que es privada o protegida o porque no existe.

Podemos comprobar que se ha llamado a __set() porque la propiedad se ha guardado en


mayúsculas (que es lo que hace __set()).

En el ejemplo anterior la propiedad era inaccesible por ser protegida. Pero podría ser que la
propiedad no existiese:

class Coche
{
  public function __set($propiedad, $valor)
  {
  $this->$propiedad = strtoupper($valor);
  }
}

$coche = new Coche();


$coche->combustible = 'Gasolina';

print_r($coche);

El resultado sería:

158
Coche Object
(
  [combustible] => GASOLINA
)

Pero si la propiedad ya existe y es public entonces no se llamará a este método:

class Coche
{
  public $combustible;

  public function __set($propiedad, $valor)


  {
  $this->$propiedad = strtoupper($valor);
  }
}

$coche = new Coche();


$coche->combustible = 'Gasolina';

print_r($coche);

Y al ejecutarlo podemos comprobar que el combustible no aparece en mayúsculas porque no se ha


hecho la llamada a __set().

Coche Object
(
  [combustible] => Gasolina
)

Un ejemplo que nos puede dar una idea de la utilidad de estos métodos podría ser éste:

159
class Pizza
{
  private $ingredientes = [];

  public function anadirIngrediente($ingrediente)


  {
  if (!$this->tieneIngrediente($ingrediente)) {
  $this->ingredientes[] = $ingrediente;
  }
  }

  public function __get($ingrediente)


  {
  if ($this->tieneIngrediente($ingrediente)) {
  return true;
  }
  return false;
  }

  private function tieneIngrediente($ingrediente)


  {
  return in_array($ingrediente, $this->ingredientes);
  }
}

$pizza = new Pizza();


$pizza->anadirIngrediente('cebolla');
if($pizza->cebolla) {
  echo "Tiene cebolla";
}

De esta forma podríamos ver si un determinado ingrediente existe solo con usar:

if($pizza->cebolla)

Personalmente prefieriría usar:

if($pizza->tieneIngrediente($cebolla))

porque creo que se entiende mejor lo que se pretende hacer.

¿Puedo usar __get() y __set()?

Ahora que ya sabes cómo funcionan te recomiendo que, en principio, no lo uses. En lugar de usar
éstos yo prefiero usar getters y setters por los siguientes motivos:

• Son más lentas que usar getters y setters.

160
• El código más claro usando getters y setters. Si, por ejemplo, una propiedad ve modificado su
valor en el _set() o _get() puede que no nos demos cuenta y hacernos perder mucho tiempo
buscando fallos.

• Las ayudas que nos dan los IDE modernos no nos sirven si los usamos.

Sin embargo hay ocasiones en las que pueden ser convenientes. Por ejemplo si el objeto tiene un
montón de propiedades y necesitamos un montón de getters y setters similares pueden ser una
buena opción.

__isset() y __unset()
Estos dos métodos van de la mano con get() y set(). Cuando queremos saber si existe una
determinada propiedad usamos isset(). Si la propiedad no existe esta función devolverá false.

El método isset() se ejecutará cuando la propiedad no exista. Lo mismo sucerá con unset().

__call() y __callStatic()
__call()

Con los métodos tenemos algo parecido a get() y set(): el método __call(). Este método mágico se
"activa" cuando llamamos a un método que no es accesible desde un objeto.

Como se acerca la hora de comer vamos a seguir con la clase pizza. En esta clase vamos a añadir un
método mágico:

class Pizza
{
  public function __call($nombre, $parametros)
  {
  echo "No entiendo lo que significa: $nombre\n";
  echo "Ni qué hacer con esta información:\n";
  print_r($parametros);
  }
}

Vamos a crear un objeto $pizza y a llamar al método echarIngrediente() que no existe:

$pizza = new Pizza;


$pizza->echarIngrediente('piña');

 Sí, hay gente a la que le gusta la piña en la pizza.

Al ejecutar ese código veremos el resultado:

161
No entiendo lo que significa: echarIngrediente
Ni qué hacer con esta información:
Array
(
  [0] => piña
)

Como el método echarIngrediente() no existe se llamará automáticamente a __call(). Veamos qué


pasa si creamos este método:

class Pizza
{
  public function __call($nombre, $parametros)
  {
  echo "No entiendo lo que significa: $nombre\n";
  echo "Ni qué hacer con esta información:\n";
  print_r($parametros);
  }

  private function echarIngrediente($ingrediente)


  {
  echo "Voy a añadir el ingrediente: $ingrediente.";
  }
}

Ahora el resultado será:

Voy a añadir el ingrediente: piña.

Antes he dicho que __call() entra en acción cuando el método al que llamamos es inaccesible. Esto
ocurrirá cuando el método llamado no exista o cuando sea privado o protegido:

class Pizza
{
  public function __call($nombre, $parametros)
  {
  echo "No entiendo lo que significa: $nombre\n";
  echo "Ni qué hacer con esta información:\n";
  print_r($parametros);
  }

  private function echarIngrediente($ingrediente)


  {
  echo "Voy a añadir el ingrediente: $ingrediente.";
  }
}

162
En este ejemplo echarIngrediente() existe pero no es accesible desde el objeto $pizza. Por lo tanto
entrará en juego __call().

__callStatic()

Este método (añadido en la versión 5.3.0 de PHP) es similar a __call pero se activa cuando llamamos
a un método inaccesible de forma estática. Bonita frase ¿qué quiere decir?

Vamos a verlo con el ejemplo de la pizza. Añadamos este nuevo método:

public static function __callStatic($nombre, $parametros)


{
  echo "Vaya, vaya, no se lo que significa $nombre ";
  echo "y veo que ni siquiera te has dignado a crear un objeto.\n";
  echo "¿Y qué quieres que haga con ésto?\n";
  print_r($parametros);
  echo "¿Es que tengo que hacerlo yo todo?";
}

Como puedes ver es un método muy quejica.

¿Cuál es la diferencia con call()? Antes hemos visto que call() se ejecuta cuando llamamos a un
método que no existe o que es privado o protegido. __callStatic() se ejecuta cuando hacemos una
llamada de manera estática, por ejemplo:

Pizza::echarIngrediente('cebolla');

Si ejecutamos el código ahora tendremos:

Vaya, vaya, no se lo que significa echarIngrediente y veo que ni siquiera te has


dignado a crear un objeto.
¿Y qué quieres que haga con ésto?
Array
(
  [0] => cebolla
)
¿Es que tengo que hacerlo yo todo?

¿Es recomendable usar estos dos métodos mágicos?

En mi opinión, al igual que los métodos mágicos get() y set(), creo que estos dos no deberían usarse
en la mayoría de las ocasiones. Existen situaciones en las que es conveniente usarlas pero creo que
este tipo de métodos hace que el código sea más difícil de entender y mantener.

163
__toString()
Este es un método bastante curioso. Nos permite decidir cómo se va a comportar un objeto cuando
lo tratamos como una cadena. Si tratar de mostrar un objeto con echo tendrás un error. Por
ejemplo:

class Pizza
{
  private $ingredientes;

  public function __construct($ingredientes)


  {
  $this->ingredientes = $ingredientes;
  }
}

$pizza = new Pizza(['cebolla', 'piña']);

echo $pizza;

Al ejecutar esto tendrás un error:

PHP Catchable fatal error: Object of class Pizza could not be converted to string.

Pero si usamos __toString():

class Pizza
{
  private $ingredientes;

  public function __construct($ingredientes)


  {
  $this->ingredientes = $ingredientes;
  }

  public function __toString()


  {
  return "Esta pizza tiene: " . implode(", ", $this->ingredientes) . "." .
PHP_EOL;
  }
}

$pizza = new Pizza(['cebolla', 'piña']);

echo $pizza;

El resultado ahora será:

164
Esta pizza tiene: cebolla, piña.

No son muchas las situaciones en las que necesitaremos esto pero hay veces en las que viene muy
bien.

serialize() y unserialize()
Estas dos son funciones de PHP y no métodos mágicos. Pero las pongo aquí porque son útiles para
entender para qué se usan los métodos sleep() y wakeup().

Según la documentación de PHP serialize() "genera una representación apta para el


almacenamiento de un valor". Vale ¿y qué quiere decir ésto?

Hay ocasiones en las que nos puede interesar guardar un objeto en una base de datos, o enviarlo a
otra aplicación. Sabiendo la clase a la que pertenece el objeto y conociendo los valores de sus
propiedades podemos "reconstruir" el objeto.

En este ejemplo podemos ver a las dos funciones en acción:

class Pizza
{
  private $ingredientes;

  public function __construct($ingredientes)


  {
  $this->ingredientes = $ingredientes;
  }
}

$pizza = new Pizza(['cebolla', 'piña']);

var_dump($pizza);

$pizzaEmpaquetada = serialize($pizza);

echo $pizzaEmpaquetada;

$nuevaPizza = unserialize($pizzaEmpaquetada);

var_dump($nuevaPizza);

La salida de este programa sería algo así:

165
object(Pizza)#1 (1) {
  ["ingredientes":"Pizza":private]=>
  array(2) {
  [0]=>
  string(7) "cebolla"
  [1]=>
  string(5) "piña"
  }
}

O:5:"Pizza":1:{s:19:" Pizza ingredientes";a:2:{i:0;s:7:"cebolla";i:1;s:5:"piña";}}

object(Pizza)#2 (1) {
  ["ingredientes":"Pizza":private]=>
  array(2) {
  [0]=>
  string(7) "cebolla"
  [1]=>
  string(5) "piña"
  }
}

serialize() va a convertir el objeto $pizza en una "representación apta para el almacenamiento", es


decir:

O:5:"Pizza":1:{s:19:" Pizza ingredientes";a:2:{i:0;s:7:"cebolla";i:1;s:5:"piña";}}

Esto significa más o menos:

• O:5:"Pizza". Esto indica que es un objeto (O) del tipo "Pizza" y que su nombre tiene 5 caracteres.

• s:19:" Pizza ingredientes";a:2{…}. Esto es una propiedad llamada "ingredientes" que es un array
(a) de dos elementos.

• {i:0;s:7:"cebolla";i:1;s:5:"piña";} Este es el contenido del array, que son dos cadenas de texto.

Con esta información podemos crear un objeto igual que el objeto $pizza. Y eso es precisamente lo
que hacemos aquí:

$nuevaPizza = unserialize($pizzaEmpaquetada);

Si te fijas en lo que muestra var_dump() en ambas ocasines verás que son dos objetos igualitos.
Pero, ojo, no son el mismo objeto; Si te fijas cada uno tiene un identificador diferente:

object(Pizza)#1
object(Pizza)#2

166
Es decir, que con estas funciones podemos crear un objeto a partir de su representación pero será
un nuevo objeto.

__sleep() y __wakeup()
__sleep()

Hemos visto que al usar serialize() se guardan los valores de todas las propiedades del objeto. Pero
hay ocasiones en las que esto no es deseable; La operación "unserialize() es bastante "cara" (en
términos de consumo de procesador). Así que las propiedades que no sean necesarias mejor no
guardarlas. En esas ocasiones nos viene muy bien el método __sleep().

Por ejmplo, cuando tenemos un objeto en el que guardamos la sesión de un usuario no tiene
sentido guardar los campos que sean nulos. Podemos verlo aquí en acción:

167
class Sesion
{
  private $propiedades;

  private $nombre = 'Gorka';


  private $apellidos = 'Urrutia';
  private $ip;

  function __sleep()
  {
  $this->propiedades = $this->arrayPropiedades();

  $this->eliminarPropiedadesConValorNulo();

  return $this->listadoPropiedadesValidas();
  }

  private function arrayPropiedades()


  {
  return (array) $this;
  }

  private function eliminarPropiedadesConValorNulo()


  {
  foreach ($this->propiedades as $clave => $valor)
  {
  $this->eliminarElementoSiNulo($valor, $clave);
  }
  }

  private function eliminarElementoSiNulo($valor, $clave)


  {
  if (is_null($valor)) {
  unset($this->propiedades[$clave]);
  }
  }

  private function listadoPropiedadesValidas()


  {
  return array_keys($this->propiedades);
  }
};

$sesion = new Sesion();

var_dump($sesion);

var_dump(serialize($sesion));

Al ejecutar el código vemos que en la serialización del objeto ha desaparecido el campo $ip:

168
object(Sesion)#1 (4) {
  ["propiedades":"Sesion":private]=>
  NULL
  ["nombre":"Sesion":private]=>
  string(5) "Gorka"
  ["apellidos":"Sesion":private]=>
  string(7) "Urrutia"
  ["ip":"Sesion":private]=>
  NULL
}
string(139) "O:6:"Sesion":4:{s:19:" Sesion propiedades";N;s:14:" Sesion
nombre";s:5:"Gorka";s:17:" Sesion apellidos";s:7:"Urrutia";s:10:" Sesion ip";N;}"

Cuando hagamos el unserialize() no va a haber problema, todos aquellos campos que no estén
disponibles se crearán como nulos.

__wakeup()

Este método va de la mano con __sleep() y está pensado para realizar operaciones al restaurar el
objeto. Por ejemplo, dar determinados valores iniciales.

__invoke()
Este es un método muy curioso. Nos permite usar un objeto como si de una función de tratase. Por
ejemplo:

class Pizza
{
  public function __invoke()
  {
  echo "Soy una clase pero funciono como una función.";
  }
}

$pizza = new Pizza();


$pizza();

Al ejecutar este código veremos el resultado:

Soy una clase pero funciono como una función.

Si no existiese este método el resultado sería un error:

169
PHP Fatal error: Uncaught Error: Function name must be a string in index.php:8
Stack trace:
#0 {main}
  thrown in index.php on line 8

¿Qué utilidad tiene __invoke()?

Para la mayoría de programadores en PHP no es fácil encontrar una utilidad real para este método.
Hay quienes opinan que hace que el código sea más legible en algunos casos. En muchas ocasiones
una clase sólo tiene un método público para realizar una acción determinada. Por ejemplo una
clase que se encarga de enviar mensajes:

class Mensaje
{
  public function enviar()
  {
  // Código para enviar un mensaje
  }
}

$mensaje = new Mensaje();


$mensaje->enviar();

Para algunos este código sería más legible usando __invoke(). Sería algo así:

class Mensaje
{
  public function __invoke()
  {
  // Código para enviar un mensaje
  }
}

$mensaje = new Mensaje();


$mensaje();

Quienes opinan así dicen que:

$mensaje();

es más claro que:

$mensaje->enviar();

170
Hay que insistir en que ven más legible el código con __invoke() cuando la clase
 tiene un único método y está clara cuál sería su acción. En el caso de un mensaje
su acción más evidente sería enviarlo.

En este momento de mi vida no veo que $mensaje() de mayor claridad al código (aunque me
reservo el derecho a cambiar de opinión).

Otra posible utilidad sería en el caso que tuviésemos una función como ésta:

function ejecutar($sistemaEnvioMensaje) {
  $sistemaEnvioMensaje();
}

Esta función enviaría un mensaje usando el sistema que le pasemos como parámetro. Y podemos
usarla así:

class Mensaje
{
  public function __invoke()
  {
  echo "Enviando el mensaje desde la clase Mensaje.\n";
  }
}

function funcionMensaje() {
  echo "Enviando mensaje desde la función funcionMensaje.\n";
}

function ejecutar($sistemaEnvioMensaje) {
  $sistemaEnvioMensaje();
}

ejecutar('funcionMensaje');
ejecutar(new Mensaje);

Podemos usar tanto una función "normal" como una clase que tenga el método __invoke().

171
Excepciones
¿Qué es una excepción?

Una excepción es una condición especial que cambia el flujo normal de un


programa.

Las excepciones se producen, por ejemplo, cuando falla una conexión a una base de datos. En
nuestra aplicación podemos añadir código que se encargue de manejar estas excepciones. Esto nos
permite que el código "normal" quede separado del código que se encarga de solucionar los
"problemas".

Excepciones en lugar de if/else para controlar fallos


Vamos a ver la ventaja de usar excepciones con un sencillo ejemplo. Empezaremos con una función
que duplica el valor de un número. Si el parámetro que recibe la función no es un número ésta
devolverá false:

function duplicar($numero)
{
  if (!is_numeric($numero)) {
  return false;
  }

  return $numero * 2;
}

Y esta función podríamos usarla así:

echo duplicar( 10 ) . PHP_EOL;

¿Pero qué ocurre si el valor que pasamos no es un número? Deberíamos comprobarlo y mostrar un
mensaje:

$numero = duplicar(10);
if ($numero!==false) {
  echo $numero;
}
else {
  echo "No es un número";
}

Donde pone "10" imagina que es un valor que ha introducido un usuario o es un


 dato que leemos de un fichero o una base de datos.

172
Prueba a ejecutar el código usando != en lugar de !== y usa duplicar(0) para ver el
 por qué de !==.

Ya se nos está complicando el código. Si lo lees te preguntarás ¿por qué puede ser false? No es muy
intuitivo.

Vamos a cambiar ligeramente la función usando una excepción:

function duplicar($numero)
{
  if (!is_numeric($numero)) {
  throw new InvalidArgumentException("No es un número");
  }

  return $numero * 2;
}

Si el valor no es un número lanzamos una excepción. Si es un número lo duplicamos.

Esta función ahora podríamos usarla así:

echo duplicar(10);

y funcionaría correctamente. Pero si pasamos un texto en lugar de un número:

echo duplicar("Diez");

Ahora el resultado será éste:

PHP Fatal error: Uncaught InvalidArgumentException: No es un número in index.php


Stack trace:
#0 index.php(10): duplicar('Diez')
#1 {main}
  thrown in index.php on line 5

Este mensaje nos está diciendo que se ha producido una excepción y que no hemos hecho nada con
ella. En este caso la excepción que hemos lanzado es InvalidArgumentException, que es una
excepción que viene "de fábrica" con PHP.

Esto es una excepción porque lo esperado en esa parte de la aplicación sería que el valor fuese un
número. Si nos encontramos con una cadena de texto es porque el usuario ha introducido mal un
dato o los datos que hay en el fichero son erróneos.

Las excepciones no es conveniente dejarlas "abandonadas", nuestro código debería ser capaz de
manejarlas. No queda bien mostrar un mensaje de error al usuario sin más.

173
Para gestionar una posible excepción hay que envolver el código que puede producirla dentro de
un bloque try:

try {
  echo duplicar(10);
}

y luego debemos añadir a continuación un bloque catch que es donde gestionamos la excepción:

catch (InvalidArgumentException $e) {


  echo $e->getMessage();
}

En este caso lo único que vamos a hacer es comprobar si se produce la excepción


InvalidArgumentException. En caso afirmativo mostrarmos el mensaje de la excepción. El código
completo quedaría así:

function duplicar($numero)
{
  if (!is_numeric($numero)) {
  throw new InvalidArgumentException("No es un número");
  }

  return $numero * 2;
}

try {
  echo duplicar("Hola");
}
catch (InvalidArgumentException $e) {
  echo $e->getMessage();
}

Así vemos que el código que queremos ejecutar:

echo duplicar("Hola");

queda separado del código que maneja excepciones. Este último solo lo miraremos si tenemos
curiosidad por saber qué pasa en caso de fallo.

Podríamos complicar un poco más el ejemplo haciendo que la función solo admita números pares y
lance excepciones diferentes:

174
function esPar( $numero )
{
  return !($numero % 2);
}

function duplicarNumeroPar($numero)
{
  if (!is_numeric($numero)) {
  throw new InvalidArgumentException("No es un número.");
  }

  if (!esPar($numero)) {
  throw new InvalidArgumentException("No es un número par.");
  }

  return $numero * 2;
}

try {
  echo duplicarNumeroPar(11);
}
catch (InvalidArgumentException $e) {
  echo $e->getMessage();
}

Ver el mensaje de error de la excepción


En este primer ejemplo que hemos visto usamos el método getMessage() para ver el mensaje de
error que hemos "metido" en la excepción:

echo $e->getMessage();

Este mensaje es el que habíamos pasado al constructor de la excepción:

 throw new InvalidArgumentException("No es un número");

En este caso es el mensaje "No es un número".

Estos mensajes no suelen estar pensados para mostrarlos al usuario sino más bien para ayudar al
desarrollador a localizar errores. Cuando estamos desarrollando la aplicación y metemos datos de
pruebas y nos aparece una excepción para la que todavía no nos hemos ocupado nos da pistas de lo
que tenemos que hacer.

Un ejemplo de excepciones
Supongamos que nos pasan un array con datos que contiene el precio de coste de distintos

175
productos. Nos piden que calculemos el precio de venta al público de esos artículos usando una
clase llamada Precio que ya existe.

Esta clase podría ser algo así:

class Precio
{
  private $precioBase;

  public function __construct($precioBase)


  {
  $this->precioBase = $precioBase;
  }

  public function aplicarMargen()


  {
  $this->validarPrecio();

  return $this->precioBase * 2;
  }

  private function validarPrecio()


  {
  if (!is_numeric($this->precioBase)) {
  throw new InvalidArgumentException("No es un número.");
  }

  if ($this->precioBase <= 0) {
  throw new InvalidArgumentException("No se admiten precios nulos o
negativos.");
  }
  }
}

La responsabilidad de esta clase es tomar un precio base y aplicarle el margen (con el método
aplicarMargen()). Esta clase espera que el precio base sea un número positivo. Si se le pasa un dato
no válido no sabrá qué hacer con él y provocará una excepción.

Esta clase no sabe qué es lo que vas a querer hacer cuando el dato no sea válido. Solo sabe que no
puede hacer nada con él y por eso lanza una excepción. Cuando uses esta clase en tu código, tú
deberás decidir qué hacer cuando ocurre una excepción.

Una posible forma de usar la clase sería la siguiente; partimos de un array de productos y precios
de coste a los que vamos a aplicar el margen. En nuestro caso decidimos que si un precio no es
válido ese producto no podrá ir en el listado de precios finales. Para poder solucionar ese dato
erróneo vamos a crear una lista de productos con precio erróneo y, por ejemplo, se la podemos
enviar a alguien por email para que la revise.

176
$preciosCoste = [
  'zapatos' => 10,
  'camisa' => 20,
  'pantalon' => 0,
  'calcetines' =>'Cuarenta',
  'guantes' => '50'
];

$preciosFinales = [];
$preciosErroneos = [];

foreach ($preciosCoste as $articulo => $coste) {


  $precio = new Precio($coste);
  try {
  $preciosFinales[$articulo] = $precio->aplicarMargen();
  }
  catch (InvalidArgumentException $e) {
  $preciosErroneos[] = $articulo;
  }
}

print_r($preciosFinales);
print_r($preciosErroneos); // Esta lista podríamos enviarla por email a alguien para
que la revise.

El resultado será:

Array
(
  [zapatos] => 20
  [camisa] => 40
  [guantes] => 100
)
Array
(
  [0] => pantalon
  [1] => calcetines
)

Las excepciones son para situaciones excepcionales


Como su propio nombre indica una excepción debe usarse para situaciones excepcionales, no para
controlar el flujo de la aplicación.

No siempre es fácil ver si las estamos usando bien. En la clase Precio de antes se podría haber
resuelto el problema de los precios erróneos usando otra función que nos indicara si el precio es
correcto. O comprobando si el precio calculado es mayor que cero.

177
Sin excepciones el código anterior podría ser así:

class Precio
{
  private $precioBase;

  public function __construct($precioBase)


  {
  $this->precioBase = $precioBase;
  }

  public function aplicarMargen()


  {
  return $this->precioBase * 2;
  }

  public function validarPrecio()


  {
  if (!is_numeric($this->precioBase)) {
  return false;
  }

  if ($this->precioBase <= 0) {
  return false;
  }

  return true;
  }
}

$preciosCoste = [
  'zapatos' => 10,
  'camisa' => 20,
  'pantalon' => 0,
  'calcetines' =>'Cuarenta',
  'guantes' => '50'
];

$preciosFinales = [];
$preciosErroneos = [];

foreach ($preciosCoste as $articulo => $coste) {


  $precio = new Precio($coste);
  if ($precio->validarPrecio()) {
  $preciosFinales[$articulo] = $precio->aplicarMargen();
  }
  else {
  $preciosErroneos[] = $articulo;
  }
}

178
print_r($preciosFinales);
print_r($preciosErroneos); // Esta lista podríamos enviarla por email a alguien para
que la revise.

Si ejecutas esta otra versión verás que el código sin excepciones funciona igual de bien. Hay
muchos desarrolladores a los que no les gustan nada las excepciones. A mí personalmente me gusta
usarlas aunque sin abusar de ellas.

Algunas ventajas de las excepciones:

• Nos obligan a cuidar más los errores. Si usamos una clase que lanza una excepción tendremos
que encargarnos de ella. En el caso de la versión sin excepciones podríamos coger el precio
directamente sin preocuparnos si es válido (sin llamar a _validarPrecio()). En cambio, con la
versión con excepciones tenemos que hacer algo o tendremos un error.

• Queda separado el código del flujo "normal" del código que se encarga del control de errores.

• Nos puede ahorrar un montón de if-else. En mi opinión, bien usadas, las excepciones pueden
hacer el código más claro.

Algunas desventajas:

• Al estar en sitios diferentes el punto donde se produce el error (donde se lanza la excepción) y
donde se manera hacer que sea algo más complejo esa parte del programa.

• Si no se usan correctamente hacen que el código sea más complejo.

También hay cierta discusión sobre si son más lentas que usar un if/else equivalente. Por un lado
son más lentas por el extra que supone su gestión. Por otro lado pueden ser algo más rápidas
porque solo se ejecutan cuando hay una excepción. En la versión sin excepciones, para cada
elemento del array, tenemos un if extra (el de if ($precio→validarPrecio())). Yo creo que sí son algo
más lentas, pero esa lentitud extra (mínima) se compensa con las ventajas que ofrecen las
excepciones.

Todas las excepciones son hijas de Exception


En PHP hay varias excepciones predefinidas. Cuando usamos la clase que haya creado un tercero
seguramente éste habrá definido otras excepciones. Y, por supuesto, podemos crear las nuestras.

Pero todas, todas, las excepciones son hijas de la clase Exception. Si intentamos lanzar una
excepción que no sea derivada de Exception tendremos un error.

Así que, en el ejemplo de los precios de antes, podríamos haber usado Exception en lugar de
InvalidArgumentException:

179
try {
  $preciosFinales[$articulo] = $precio->aplicarMargen($coste);
}
catch (Exception $e) {
  $preciosErroneos[] = $articulo;
}

Esto seguirá funcionando del modo esperado. La razón de usar InvalidArgumentException es que
podemos usar distintas excepciones dependiendo del problema que se haya producido y
gestionarlas de manera diferente.

Las excepciones hay que tratarlas


Cuando se produce una excepción no basta con meterla dentro de un try-catch y listo. Debemos
hacer algo con ella, aunque solo sea mostrar un bonito mensaje al usuario.

No es recomendable hacer algo así:

try {
  // Código que podría lanzar una excepción.
} catch (Exception $e) {
  // Meto este catch para no tener que ocuparme de las malditas excepciones.
}

Al usar Exception ese catch va a "capturar" cualquier excepción que se produzca. De esta forma no
vamos a ver feos mensajes de error si se produce una excepción. Pero nuestra aplicación será, y lo
sabes, una basura.

Códigos de error adicionales


En ocasiones nos puede interesar añadir un código adicional a la excepción para tener más
información de lo que ha ocurrido. Esto se hace añadiendo un parámetro extra al crear la
excepción:

throw new Exception("Mensaje", número_de_error);

Vamos a verlo con el ejemplo que duplicaba números pares:

180
define('ERROR_NO_ES_NUMERO', 1000);
define('ERROR_NO_ES_PAR', 1001);

function esPar( $numero )


{
  return !($numero % 2);
}

function duplicarNumeroPar($numero)
{
  if (!is_numeric($numero)) {
  throw new InvalidArgumentException("No es un número.", ERROR_NO_ES_NUMERO);
  }

  if (!esPar($numero)) {
  throw new InvalidArgumentException("No es un número par.", ERROR_NO_ES_PAR);
  }

  return $numero * 2;
}

try {
  echo duplicarNumeroPar(11);
}
catch (InvalidArgumentException $e) {
  echo $e->getMessage() . PHP_EOL;
  echo "El código de error ha sido: " . $e->getCode() . PHP_EOL;
}

Así, en el catch podríamos realizar acciones diferentes dependiendo del código de error.

Crear nuevas excepciones


Usar códigos de error como hemos visto en el apartado está bien pero es posible que nos obligue a
añadir if/else o lógica adicional. Una alternativa a usar los códigos de error es usar nuestras propias
excepciones. Para definir una nueva excepción tenemos que crear una nueva clase que herede de
otra excepción:

class MiExcepcion extends Exception


{}

No tiene por qué heredar directamente de Exception; nuestra clase puede ser hija de otra excepción
(pero esa otra excepción debe heredar de forma directa o indirecta de Exception).

181
class NoEsNumeroException extends InvalidArgumentException
{
}

class NoEsParException extends InvalidArgumentException


{
}

function esPar( $numero )


{
  return !($numero % 2);
}

function duplicarNumeroPar($numero)
{
  if (!is_numeric($numero)) {
  throw new NoEsNumeroException();
  }

  if (!esPar($numero)) {
  throw new NoEsParException();
  }

  return $numero * 2;
}

try {
  echo duplicarNumeroPar(11);
}
catch (NoEsNumeroException $e) {
  echo "No, no es un número.";
}
catch (NoEsParException $e) {
  echo "No, no es par.";
}

Esto puede hacer que el código quede más claro. De esta forma, además, podemos añadir métodos
extra en nuestras excepciones que pueden ayudarnos a gestionarlas.

Múltiples catch
Como se puede ver en el ejemplo anterior podemos añadir tantos catch como queramos. En cada
catch podemos gestionar cada tipo de excepción.

No se si existe alguna limitación al número de catch que se pueden usar pero si alguna vez llegas al
límite creo que sería mejor revisar tu código. Lo que no está permitido es usar dos bloques catch
para una misma excepción.

182
El orden de los catch es importante

Ojo con el orden de los catch. Esto podemos verlo en el siguiente ejemplo:

class NoEsNumeroException extends InvalidArgumentException {}

function duplicarNumero($numero)
{
  if (!is_numeric($numero)) {
  throw new NoEsNumeroException();
  }

  return $numero * 2;
}

try {
  echo duplicarNumero("Texto");
}
catch (NoEsNumeroException $e) {
  echo "No, no es un número.";
}
catch (Exception $e) {
  echo "Otra excepción.";
}

El resultado será:

No, no es un número.

Pero si probamos a cambiar el orden de los catch:

catch (Exception $e) {


  echo "Otra excepción.";
}
catch (NoEsNumeroException $e) {
  echo "No, no es un número.";
}

Ahora el resultado será:

Otra excepción.

La razón es que primero hemos mirado si se ha producido una excepción del tipo Exception.
Resulta que NoEsNumeroException es también del tipo Exception así que la recoge el primer catch.
Como ya está recogida la excepción ésta no llega al segundo catch.

183
finally
Esta es la última parte de un bloque try-catch y es opcional. Se añade al final del bloque:

try {
  // Código que puede lanzar una excepción
}
catch (MiExcepcion $e){
  // Código que se ejecuta si se produce MiExcepcion
}
finally {
  // Código que se ejecuta siempre, haya o no haya excepciones
}

A mí me costó un poco pillar su utilidad. Te voy a poner un ejemplo para que lo veas claro:

function duplicarNumero($numero)
{
  if (!is_numeric($numero)) {
  throw new Exception();
  }

  return $numero * 2;
}

try {
  echo duplicarNumero("Diez");
}
catch (Exception $e) {
  echo "Se ha producido una excepción.\n";
}
finally {
  echo "Esta línea se ejecutará aunque se lance una excepción que quede sin
capturar.\n";
}
echo "Este texto solo se mostrará sin no se produce una excepción.\n";

El resultado será:

Se ha producido una excepción.


Esta línea se ejecutará aunque se lance una excepción que quede sin capturar.
Este texto solo se mostrará sin no se produce una excepción.

Y te preguntarás ¿para qué quiero el finally si también se muestra el último echo? Pues porque el
echo no se va a ejecutar si queda alguna excepción sin capturar. Vamos a probar quitando el catch:

184
function duplicarNumero($numero)
{
  if (!is_numeric($numero)) {
  throw new Exception();
  }

  return $numero * 2;
}

try {
  echo duplicarNumero("Diez");
}
finally {
  echo "Esta línea se ejecutará aunque se lance una excepción que quede sin
capturar.\n";
}
echo "Este texto solo se mostrará sin no se produce una excepción.\n";

Ahora el resultado será diferente. Como queda una excepción sin capturar la ejecución del
programa se detiene y no se muestra el último echo. Sin embargo el mensaje del finally sí que se
mostrará:

Esta línea se ejecutará aunque se lance una excepción que quede sin capturar.
PHP Fatal error: Uncaught Exception in index.php:6
Stack trace:
#0 index.php(13): duplicarNumero('Diez')
#1 {main}
  thrown in index.php on line 6

Esto podemos usarlo si necesitamos que se realice una acción siempre, se haya producido un error
o no. Por ejemplo esta acción podría ser cerrar un fichero.

185
Introducción a Git
Se dice "guit".

¿Qué es git?
Cuando desarrollas una aplicación es muy importante usar un sistema de control de versiones
(VCS). Un sistema de control de versiones nos permite, experimentar hacer y deshacer cambios sin
peligro a "romper" nada. Hay unos cuantos VCS disponibles pero uno de los más usadoa, y el que
más me gusta a día de hoy, es el git.

En esta sección vamos a usar git como VCS.

No confundir VCS (Version Control System) con CSV que es un formato de archivos
 llamado "Comma Separated Values".

El repositorio

Un repositorio es el conjunto de archivos y carpetas que contiene todo lo relacionado con un


proyecto controlado por Git; incluyendo el historial. Cada uno de los participantes del proyecto
tendrá una copia en su ordenador de todo el historial de cambios completo desde su inicio.

No es necesario un servidor

No es lo mismo git que GitHub

bla bla

Aprende a usarlo en la línea de comandos


Git es una herramienta fantástica pero hay que saber bien cómo funciona. Hoy en día casi todos los
IDE incluyen un asistente para git. El problema es que ninguno tiene todas las funciones que ofrece
git y cada uno puede funcionar de manera diferente. Algunos usuarios, al cambiar de IDE se
sienten un poco perdidos enlo que a git se refiere. Por esta razón yo suelo recomendar que,
primero, se aprenda a manejar git desde la línea de comandos. Luego, al usar un IDE entenderemos
mucho mejor lo que estamos haciendo.

Instalar git
bla

Comenzar con git


Comenzar a usar git en un proyecto ya existente

El primer paso para trabajar con git es inicializar el proyecto:

186
$ git init

El resultado de este comando será:

Initialized empty Git repository in /home/gorka/pruebasgit/.git/

Clonar un repositorio

Si trabajamos en un proyecto que ya use git seguramente comenzaremos clonándolo (usando el


comando git clone). Cuando clonamos un proyecto lo que hacemos es descargar los archivos del
proyecto y todo el historial de cambios del mismo.

Para clonar un respositorio debemos abrir un terminal e ir a la carpeta donde queremos descargar
el proyecto. En esa carpeta ejecutaremos el comando git clone:

$ git clone https://url_del_proyecto_en_github_o_donde_sea/nombre_proyecto.git

Por ejemplo:

$ git clone https://github.com/LaraDock/laradock.git

Esto creará una nueva carpeta llamada laradock donde se descargará el proyecto.

Si ya tenemos un proyecto creado podemos añadirlo como un submódulo (es muy poco probable
que hagas esto en tus comienzos con git). En el ejemplo anterior usaríamos:

$ git submodule add https://github.com/LaraDock/laradock.git

Configurar git para nuestro proyecto


bla, bla

Git, el ojo que todo lo ve (si tú quieres)


Git va a hacer un seguimiento de todos los ficheros de nuestro proyecto que queramos. Cada vez
que hagamos un cambio en alguno de nuestros ficheros git lo va a saber. Pero solo va a controlar
los ficheros que le pidamos.

Cuando comenzamos a controlar un proyecto con git éste no hará seguimiento de ningún fichero
hasta que se lo digamos. Vamos a usar un proyecto imaginario en el que tenemos dos ficheros:

187
$ ls
funciones.php
index.php

Si en la carpeta del proyecto ejecutamos el comando git status veremos algo como esto:

$ git status
En la rama master

Commit inicial

Archivos sin seguimiento:


  (use «git add <archivo>...» para incluir en lo que se ha de confirmar)

  funciones.php
  index.php

no se ha agregado nada al commit pero existen archivos sin seguimiento (use «git add»
para darle seguimiento)

Podemos ver que tenemos dos ficheros a los que no se está haciendo seguimiento.

Como se puede apreciar git suele dar pistas de lo que tenemos que hacer a continuación. En este
caso ya nos está diciendo que podemos añadir los archivos usando el comando git add.

Pedirle a git que controle nuestros ficheros


Como hemos visto git no va a controlar nigún archivo hasta que se lo pidamos. Para indicarle que lo
haga podemos usar el comando git add nombre_fichero. Podemos añadir los ficheros uno a uno:

$ git add funciones.php


$ git add index.php

varios en una única línea:

$ git add index.php funciones.php

o todos de golpe:

$ git add *

188
¿Todos a la vez?
Ojo con esta última opción. Es muy habitual que no queramos añadir todos los
 archivos al Git. Por ejemplo, los ficheros temporales, archivos compilados, etc no
suele ser interesante añadirlos.

Una vez hemos añadido ambos archivos al Git vamos a ver cómo ha quedado el proyecto:

$ git status
En la rama master

Commit inicial

Cambios para hacer commit:


  (use «git rm --cached <archivo>...» para sacar del stage)

  nuevo archivo: funciones.php


  nuevo archivo: index.php

El fichero .gitignore
Hay muchos archivos a los que no nos interesa hacer seguimiento. Por ejemplo, los ficheros
temporales, los de caché, o los ficheros resultado de compilar un programa. Para evitar añadir
accidentalmente alguno de estos archivos tenemos el fichero .gitignore. Este es el lugar donde
indicamos qué ficheros quedarán excluidos del control de git.

TODO: Poner un ejemplo de gitignore.

Guardar un estado del proyecto. El commit


Cada vez que queramos guardar una "foto" del estado de nuestro proyecto deberemos hacer lo que
se llama un commit. El formato de este comando es:

$ git commit -m "Un mensaje descriptivo de los cambios realizados"

Cuando hagamos un commit debemos incluir un mensaje que describa los cambios que hemos
hecho. En nuestro caso podemos usar el mensaje "Comienzo del proyecto":

$ git commit -m "Comienzo del proyecto"


[master (root-commit) f1b65e0] Comienzo del proyecto
 2 files changed, 9 insertions(+)
 create mode 100644 funciones.php
 create mode 100644 index.php

Si no añadimos el parámetro -m "mensaje" se nos abrirá un editor de texto en el que tendremos que
escribir el mensaje. Si lo dejamos en blaco el commit fallará:

189
$ git commit -m ""
Aborting commit due to empty commit message.

Comitear

 Hay mucha gente que a la acción de enviar los datos a un repositorio le llama
comitear. Yo también a veces peco de usar este palabrejo.

Las tres zonas de Git


Aquí viene un concepto de vital importancia en Git: las tres zonas. En Git los archivos pueden estar:

• Modificados.

• En la staging_area (a veces traducida como el área de ensayo).

• Enviados al repositorio. O como se suele decir: comiteados.

Staging area

Esta es una zona "intermedia" donde vamos a ir metiendo los ficheros que queremos enviar en el
próximo commit. seguramente pensarás que es un incordio ¿por qué no enviar todo al commit
directamente y listo? Bueno, eso se puede hacer pero, en muchísimas ocasiones no querrás hacerlo.

Ficheros modificados

190
Términos y definiciones
Esta sección no está pensada para leerla como el resto del libro. Es tan solo una referencia rápida
de algunos conceptos que se repiten varias veces en los distintos capítulos. Aún así, si te apetece
leerla… ¡adelante!

Símbolos
Símbolo "::"

Este es el llamado operador de resolución de ámbito.

array

Buenas prácticas
En todo lenguaje de programación existe lo que se denominan "buenas prácticas". Éstas describen
la forma que la mayoría de la gente que usa ese lenguaje de programación considera correctas.

Las buenas prácticas no son caprichosas, suele haber una razón por detrás. Por eso suele ser
recomendable seguirlas aunque, a veces, parezcan no tener mucho sentido.

Algunas buenas prácticas lo son dependiendo del grupo en el que estés. En un determinado
Framework (o en un grupo) puede que algo se considere una buena práctica y en otro mala.

Las buenas prácticas no son inamovibles y pueden evolucionar con el tiempo. Lo que ayer era
correcto hoy puede no serlo y al revés.

Camel Case

Código espagueti
Es una forma peyorativa de referirse a un código enrevesado, confuso y de mala calidad. Imagino
que el origen del nombre viene porque es un código que está entrelazado como los espaguetis y
seguir su flujo es tan complicado como intentar ver por dónde va un espagueti en un plato.

Control de versiones

Framework

Getter
Son métodos normales y corrientes que se usan para recoger el valor de una variable. Se usan para
evitar el acceso directo a una variable privada o protected y lograr así la encapsulación.

191
Ver también Setter.

IDE (Integrated Develompent Environment)


Un entorno de desarrollo integrado es un programa que consiste en un editor de código fuente con
una serie de herramientas que hacen la vida más fácil al desarrollador. Entre estas herramientas
suelen estar:

• Autocompletado inteligente.

• Herramientas de búsqueda rápida.

• Integración con un servidor.

• Integración con control de verisones (Git o similar).

Instanciar
Es la acción de crear una variable objeto a partir de una clase.

Es un "préstamo" del inglés, la RAE no reconoce esta palabrita aunque en la Wikipedia sí que la
encontrarás.

JSon

Lenguaje no tipado
Un lenguaje de programación se dice que es no tipado o débilmente tipado cuando no hay un control
sobre los tipos de datos en las variables. En PHP podemos meter en una variable cualquier tipo de
dato sin problemas. En otros lenguajes fuertemente tipados no podemos meter un texto en una
variable que se ha definido para usar con números enteros.

Notice / Aviso (mensajes de error en PHP)


Es un tipo de error leve. Advierte de algo que podría ser un error o que podría dar problemas.

Ver también Warning / Advertencia (mensajes de error en PHP).

Operador de resolución de ámbito


Esta palabreja se refiera al símbolo ::. Algunos se refieren a él como doble dos puntos, que es un
nombre más evidente. Hay otros muy frikis que se refieren a este símbolo como Paamayim
Nekudotayim (que parece ser que es así como se dice doble dos puntos en hebreo).

Este símbolo permite acceder a las constantes de una clase y a los métodos y propiedades estáticos.

192
Palabra reservada
Es una palabra que la tiene reservada PHP para uso interno y no puede haber métodos o constantes
con ese mismo nombre.

Patrón de diseño
Los patrones de diseño en programación son soluciones que alguien ha buscado para un problema
que se repite mucho. Alguien se dio cuenta hace un tiempo que se encontraba una y otra vez con
los mismos problemas. En lugar de buscar una solución cada vez se le ocurrió reutilizar las
soluciones que había encontrado.

Así que un patrón no es más que una forma de solucionar un problema.

Lo bueno de los patrones es que mucha gente los ha probado y son muy efectivos.

En PHP (y en todos los lenguajes) hay un montón de patrones, como son el Singleton, Factory,
Command, etc.

Setter
Ver también: Getter.

Snake Case

Principios SOLID
Son cinco reglas que estableció el gran Robert Cecil Martin (también conocido como Uncle Bob)
hace unos cuantos años que ayudan a escribir código fácil de mantener.

Si sigues estos cinco principios tu software debería poder mantenerse y ampliarse sin mayor
dificultad. Es muy recomendable conocerlos si quieres ser un buen desarrollador. Además, queda
bien ponerlo en el curriculum y puede que hasta te paguen más.

SOLID es un acrónimo y cada letra se refiere a uno de los principios (te pongo las traducciones en
lugar de sus nombres en inglés):

• S - Principio de responsabilidad única. Cada clase debe tener una única razón para cambiar (o
lo que es lo mismo debe hacer una única cosa). Nada de clases para todo.

• O - Principio de cerrado/abierto. Una clase debe poder ampliarse sin modificarla. Parece magia
pero no.

• L - Principio de sustitución de Liskov. Una clase puede sustituirse por una subclase y el
programa debe seguir funcionando.

• I - Principio de segregación de los interfaces. No intentes usar un interface o una clase abstracta
para todo.

• D - Principio de inversión de dependencias. Mejor usar muchos interfaces y clases abstractas. Y

193
los interfaces deben definirlos las clases de nivel más alto. Vamos, que donde manda capitán no
manda marinero.

Terminal

Warning / Advertencia (mensajes de error en PHP)


Es una advertencia en tiempo de ejecución. Es un error no fatal (el script puede continuar) pero es
algo que debemos arreglar.

Ver también Notice / Aviso (mensajes de error en PHP).

194

Вам также может понравиться