DWES05. - Programación Orientada A Objetos en PHP
DWES05. - Programación Orientada A Objetos en PHP
Caso práctico
Carlos empieza a darse cuenta de que, con lo
que lleva aprendido de PHP, ya es capaz de
hacer bastantes cosas. Ha acabado de
programar la aplicación web para gestionar su
colección de comics, y está satisfecho con el
resultado obtenido. De vez en cuando ha
tenido que echar mano de la documentación
del lenguaje, para buscar información sobre
cómo hacer algo, o los parámetros que
requiere cierta función, pero siempre ha
podido solucionarlo por sí mismo.
Caso práctico
Carlos comenta a Juan su problema, y éste le
dice que seguramente gran parte del problema
se arregle si utiliza programación orientada a
objetos en sus aplicaciones. Le explica por
encima de qué se trata y cuáles son sus
ventajas, pero no puede ayudarlo mucho más.
La última vez que programó en PHP, intentó
utilizar objetos en su aplicación, pero las
características de orientación a objetos del lenguaje dejaban mucho que
desear, por lo que acabó haciéndola como siempre.
Sin embargo, por lo que ha oído, parece ser que en las últimas versiones de
PHP eso ha cambiado considerablemente. El lenguaje evoluciona, y él lleva
bastante tiempo sin utilizarlo, así que tendrá que actualizarse.
La estructura de los objetos se define en las clases. En ellas se escribe el código que define
el comportamiento de los objetos y se indican los miembros que formarán parte de los
objetos de dicha clase. Entre los miembros de una clase puede haber:
A la creación de un objeto basado en una clase se le llama instanciar una clase y al objeto
obtenido también se le conoce como instancia de esa clase.
Las ventajas más importantes que aporta la programación orientada a objetos son:
Métodos estáticos.
Métodos constructores y destructores.
Herencia.
Interfaces.
Clases abstractas.
Traits (A partir de la versión 5.4.0).
Entre las características que no incluye PHP, y que puedes conocer de otros lenguajes de
programación, están:
Herencia múltiple.
Sobrecarga de métodos.(incluidos los métodos constructores).
Sobrecarga de operadores.
Autoevaluación
Antes de PHP5, el comportamiento cuando se pasaba una variable a una
función era siempre el mismo, independientemente de si la variable
fuera un objeto o de cualquier otro tipo: siempre se creaba una nueva
variable copiando los valores de la original.
Verdadero.
Falso.
Solución
1. Opción correcta
2. Incorrecto
1.2.- Creación de clases.
La declaración de una clase en PHP se hace utilizando la palabra
class. A continuación y entre llaves, deben figurar los miembros de la
clase. Conviene hacerlo de forma ordenada, primero las propiedades
o atributos, y después los métodos, cada uno con su código
respectivo.
Como comentábamos antes, es preferible que cada clase figure en su propio fichero
(producto.php). Además, muchos programadores prefieren utilizar para las clases nombres que
comiencen por letra mayúscula, para, de esta forma, distinguirlos de los objetos y otras
variables.
Una vez definida la clase, podemos usar la palabra new para instanciar objetos de la
siguiente forma:
$p = new Producto();
Para que la línea anterior se ejecute sin error, previamente debemos haber
declarado la clase. Para ello, en ese mismo fichero tendrás que incluir la clase
poniendo algo como:
require_once('producto.php');
Los atributos de una clase son similares a las variables de PHP. Es posible indicar un valor
en la declaración de la clase. En este caso, todos los objetos que se instancien a partir de
esa clase, partirán con ese valor por defecto en el atributo.
Para acceder desde un objeto a sus atributos o a los métodos de la clase, debes utilizar el
operador flecha (fíjate que sólo se pone el símbolo $ delante del nombre del objeto):
$p->nombre = 'Samsung Galaxy A6';
$p->muestra();
Cuando se declara un atributo, se debe indicar su nivel de acceso. Los principales niveles
son:
public. Los atributos declarados como public pueden utilizarse directamente por los
objetos de la clase. Es el caso del atributo $nombre anterior.
private. Los atributos declarados como private sólo pueden ser accedidos y modificados
por los métodos definidos en la clase, no directamente por los objetos de la misma. Es
el caso del atributo $codigo.
1.2.1.- Creación de clases (I).
Uno de los motivos para crear atributos privados es que su valor
forma parte de la información interna del objeto y no debe formar
parte de su interface. Otro motivo es mantener cierto control sobre
sus posibles valores.
private $codigo;
public function setCodigo($nuevo_codigo) {
if (noExisteCodigo($nuevo_codigo)) {
$this->codigo = $nuevo_codigo;
return true;
}
return false;
}
public function getCodigo() { return $this->codigo; }
Aunque no es obligatorio, el nombre del método que nos permite obtener el valor de un
atributo suele empezar por get, y el que nos permite modificarlo por set, y a continuación el
nombre del atributo con la primera letra en mayúsculas.
Debes conocer
En PHP5 se introdujeron los llamados métodos mágicos, entre ellos __set y
__get. Si se declaran estos dos métodos en una clase, PHP los invoca
automáticamente cuando desde un objeto se intenta usar un atributo no
existente o no accesible. Por ejemplo, el código siguiente simula que la clase
Producto tiene cualquier atributo que queramos usar.
class Producto {
private $atributos = array();
public function __get($atributo) {
return $this->atributos[$atributo];
}
public function __set($atributo, $valor) {
$this->atributos[$atributo] = $valor;
}
}
En la documentación de PHP tienes más información sobre los métodos
mágicos.
Métodos mágicos.
Autoevaluación
En lugar de programar un método set para modificar el valor de los
atributos privados en que sea necesario, puedo utilizar el método
mágico __set.
Verdadero.
Falso.
Sí, pero tendrías que comprobar el nombre del atributo usado y asignar
el valor al adecuado.
Solución
1. Incorrecto
2. Opción correcta
1.2.2.- Creación de clases (II).
Dentro de la clase para acceder a sus métodos o atributos propios usaremos $this->nombre,
(salvo que el atributo o el método sea estático) Fíjate que al atributo se le quita "$". Veamos
un ejemplo:
class Persona{
private $nombre;
private $perfil;
public function getNombre(){
return $this->nombre;
}
public function setNombre($n){
$this->nombre=$n;
}
public function saludo(){
//Fijate como hacemos referencia al método getNombre
echo "Hola {$this->getNombre()}, Buenos dias";
}
}
$persona1=new Persona();
$persona1->setNombre("Luis");
$persona1->saludo();
Debes conocer
Una referencia es una forma de utilizar distintos nombres de variables para
acceder al mismo contenido. En los puntos siguientes aprenderás a crearlas y
a utilizarlas.
Referencia.
Además de métodos y propiedades, en una clase también se pueden definir constantes,
utilizando la palabra const. Es importante que no confundas los atributos con las constantes.
Son conceptos distintos: las constantes no pueden cambiar su valor (obviamente, de ahí su
nombre), no usan el carácter $ y, además, su valor va siempre entre comillas y está
asociado a la clase, es decir, no existe una copia del mismo en cada objeto. Por tanto, para
acceder a las constantes de una clase, se debe utilizar el nombre de la clase y el operador
::, llamado operador de resolución de ámbito (que se utiliza para acceder a los elementos
de una clase).
class DB {
const USUARIO = 'gestor';
…
}
echo DB::USUARIO;
Es importante resaltar que no es necesario que exista ningún objeto de una clase para
poder acceder al valor de las constantes que defina. Además, sus nombres suelen
escribirse en mayúsculas.
Tampoco se deben confundir las constantes con los miembros estáticos de una clase. En
PHP, una clase puede tener atributos o métodos estáticos, también llamados a veces
atributos o métodos de clase. Se definen utilizando la palabra clave static.
class Producto {
private static $num_productos = 0;
public static function nuevoProducto() {
self::$num_productos++;
}
…
}
Los atributos y métodos estáticos no pueden ser llamados desde un objeto de la clase
utilizando el operador "->". Si el método o atributo es público, deberá accederse utilizando el
nombre de la clase y el operador de resolución de ámbito.
Producto::nuevoProducto();
self::$num_productos ++;
Los atributos estáticos de una clase se utilizan para guardar información general sobre la
misma, como puede ser el número de objetos que se han instanciado. Sólo existe un valor
del atributo, que se almacena a nivel de clase.
Los métodos estáticos suelen realizar alguna tarea específica o devolver un objeto
concreto. Por ejemplo, las clases matemáticas suelen tener métodos estáticos para realizar
logaritmos o raíces cuadradas. No tiene sentido crear un objeto si lo único que queremos es
realizar una operación matemática.
Por motivos de retrocompatibilidad con PHP3 y PHP4, si PHP no David Vignoni (GNU/GPL)
puede encontrar una función __construct() de una clase dada, se
buscará la función constructora del estilo antiguo, por el nombre de la
clase.
class Persona{
public static $id;
private $nombre;
private $perfil;
public function __construct(){
$perfil="Público";
}
}
//creamos persona1 que tiene inicializado su atributo $perfil a Público.
$persona1=new Persona();
// Podemos comprobarlo
var_dump($persona1);
class Persona{
public static $id;
private $nombre;
private $perfil;
public function __construct($n, $p){
$this->nombre=$n;
$this->perfil=$p;
}
}
// Creamos un objeto de la clase Persona e inicializamos sus atributos;
// Fíjate que ya NO podremos usar: $persona1=new Persona(); ya que el constructor espera dos
$persona1=new Persona("Juan", "Privado");
//Podemos comprobarlo
var_dump($persona1);
Los constructores del estilo antiguo (llamados como el nombre de la clase) están
OBSOLETOS desde PHP 7.0, por lo que serán eliminados en futuras versiones.
Se debería utilizar siempre "__construct()" en código nuevo.
También es posible definir un método destructor, que debe llamarse "__destruct" y permite
definir acciones que se ejecutarán cuando se elimine el objeto.
class Producto {
private static $num_productos = 0;
private $codigo;
public function __construct($codigo) {
$this->$codigo = $codigo;
self::$num_productos++;
}
public function __destruct() {
self::$num_productos--;
}
…
}
$p = new Producto('GALAXYS');
Autoevaluación
¿Cuál es la utilidad del operador de resolución de ámbito ::?
Nos permite hacer referencia a la clase del objeto actual.
Se utiliza para acceder a los elementos de una clase, como constantes y
miembros estáticos.
Sí; y por tanto debe usarse precedido por el nombre de una clase, o por
una referencia a una clase como self.
Solución
1. Incorrecto
2. Opción correcta
1.3.- Utilización de objetos.
Ya sabes cómo instanciar un objeto utilizando new, y cómo
acceder a sus métodos y atributos públicos con el operador
flecha:
$p = new Producto();
$p->nombre = 'Samsung Galaxy A6';
$p->muestra(); Pixabay (Dominio público)
Una vez creado un objeto, puedes utilizar el operador instanceof para comprobar si es o no
una instancia de una clase determinada.
Además, a partir de PHP5 se incluyen una serie de funciones útiles para el desarrollo de
aplicaciones utilizando POO.
Devuelve el nombre
get_class() echo "La clase es: " . get_class($p); de la clase del
objeto.
Devuelve un array
get_declared_classes() print_r(get_declared_classes()); con los nombres de
las clases definidas.
Función Ejemplo Significado
Devuelve un array
con los nombres de
los métodos de una
get_class_methods() print_r(get_class_methods('Producto')); clase que son
accesibles desde
dónde se hace la
llamada.
Devuelve true si
existe el método en
if (method_exists('Producto', 'vende') {
el objeto o la clase
…
que se indica, o false
method_exists()
en caso contrario,
}
independientemente
de si es accesible o
no.
Devuelve un array
con los nombres de
los atributos de una
get_class_vars() print_r(get_class_vars('Producto')); clase que son
accesibles desde
dónde se hace la
llamada.
Devuelve un array
con los nombres de
los métodos de un
get_object_vars() print_r(get_object_vars($p)); objeto que son
accesibles desde
dónde se hace la
llamada.
Devuelve true si
existe el atributo en
if (property_exists('Producto', 'codigo') {
el objeto o la clase
. . .
que se indica, o false
property_exists()
}
en caso contrario,
independientemente
de si es accesible o
no.
Desde PHP5, puedes indicar en las funciones y métodos de qué clase deben ser los
objetos que se pasen como parámetros así como el tipo del dato devuelto (caso que lo
haya). Para ello, debes especificar el tipo antes del parámetro. Para el dato devuelto poner
":tipoDato", despues de la declaración de la función o el método y antes de las llaves.
public function precioProducto(Producto $p) :float {
. . .
return $precio;
}
$p = new Producto();
$p->nombre = 'Samsung Galaxy S';
$a = $p;
Negative Space (Dominio público)
En PHP4, la última línea del código anterior crea un nuevo objeto con los mismos valores
del original, de la misma forma que se copia cualquier otro tipo de variable. Si después de
hacer la copia se modifica, por ejemplo, el atributo 'nombre' de uno de los objetos, el otro
objeto no se vería modificado.
Sin embargo, a partir de PHP5 este comportamiento varía. El código anterior simplemente
crearía un nuevo identificador del mismo objeto. Esto es, en cuanto se utilice uno
cualquiera de los identificadores para cambiar el valor de algún atributo, este cambio se
vería también reflejado al acceder utilizando el otro identificador. Recuerda que, aunque
haya dos o más identificadores del mismo objeto, en realidad todos se refieren a la única
copia que se almacena del mismo.
Debes conocer
Para crear nuevos identificadores en PHP a un objeto ya existente, se utiliza
el operador "=". Sin embargo, como ya sabes, este operador aplicado a
variables de otros tipos, crea una copia de la misma. En PHP puedes crear
referencias a variables (como números enteros o cadenas de texto), utilizando
el operador & , que ya vimos en el paso de parámetros por referencia:
function suma(&$v) {
$v ++;
}
$a = 3;
suma ($a);
echo $a; // Muestra 4
Referencias en PHP.
Por tanto, a partir de PHP5 no puedes copiar un objeto utilizando el operador "=". Si
necesitas copiar un objeto, debes utilizar clone. Al utilizar clone sobre un objeto existente, se
crea una copia de todos los atributos del mismo en un nuevo objeto.
$p = new Producto();
$p->nombre = 'Samsung Galaxy A6';
$a = clone($p);
Además, existe una forma sencilla de personalizar la copia para cada clase particular. Por
ejemplo, puede suceder que quieras copiar todos los atributos menos alguno. En nuestro
ejemplo, al menos el código de cada producto debe ser distinto y, por tanto, quizás no tenga
sentido copiarlo al crear un nuevo objeto. Si éste fuera el caso, puedes crear un método de
nombre __clone en la clase. Este método se llamará automáticamente después de copiar
todos los atributos en el nuevo objeto.
class Producto {
…
public function __clone($atributo) {
$this->codigo = nuevo_codigo();
}
…
}
Autoevaluación
¿Cuál es el nombre de la función que se utiliza para hacer una copia de
un objeto?
clone.
__clone.
Solución
1. Opción correcta
2. Incorrecto
1.3.2.- Utilización de objetos (II).
A veces tienes dos objetos y quieres saber su relación
exacta. Para eso, en PHP puedes utilizar los operadores
"==" y "===".
$p = new Producto();
$p->nombre = 'Samsung Galaxy A6';
$a = clone($p);
// El resultado de comparar $a == $p da verdadero
// pues $a y $p son dos copias idénticas
Sin embargo, si utilizas el operador "===", el resultado de la comparación será true sólo
cuando las dos variables sean referencias al mismo objeto.
$p = new Producto();
$p->nombre = 'Samsung Galaxy A6';
$a = clone($p);
// El resultado de comparar $a === $p da falso
// pues $a y $p no hacen referencia al mismo objeto
$a = & $p;
// Ahora el resultado de comparar $a === $p da verdadero
// pues $a y $p son referencias al mismo objeto.
A veces puede ser útil mostrar el contenido de un objeto sin tener que usar var_dump() para
ello podemos usar el método mágico __toString(). Este método siempre debe devolver un String.
class Persona{
public $nombre;
public $apellidos;
public $perfil;
public function __toString() :String{
return "{$this->apellidos}, {$this->nombre}, Tu pérfil es: {$this->perfil}";
}
}
$p = new Producto();
$a = serialize($p);
Esta cadena se puede almacenar en cualquier parte, como puede ser la sesión del usuario,
o una base de datos. A partir de ella, es posible reconstruir el objeto original utilizando la
función unserialize().
$p = unserialize($a);
Debes conocer
Las funciones serialize y unserialize se utilizan mucho con objetos, pero sirven para
convertir en una cadena cualquier tipo de dato, excepto el tipo resource. Cuando
se aplican a un objeto, convierten y recuperan toda la información del mismo,
incluyendo sus atributos privados. La única información que no se puede
mantener utilizando estas funciones es la que contienen los atributos estáticos
de las clases.
Pero en PHP esto aún es más fácil. Los objetos que se añadan a la sesión del usuario son
serializados automáticamente. Por tanto, no es necesario usar serialize() ni unserialize().
session_start();
$_SESSION['producto'] = $p;
Para poder deserializar un objeto, debe estar definida su clase. Al igual que antes, si lo
recuperamos de la información almacenada en la sesión del usuario, no será necesario
utilizar la función unserialize.
session_start();
$p = $_SESSION['producto'];
Debes conocer
Como ya viste en el tema anterior, el mantenimiento de los datos en la sesión
del usuario no es perfecta; tiene sus limitaciones. Si fuera necesario, es
posible almacenar esta información en una base de datos. Para ello tendrás
que usar las funciones serialize() y unserialize(), pues en este caso PHP ya no
realiza la serialización automática.
Autoevaluación
Si serializas un objeto utilizando serialize, ¿puedes almacenarlo en una
base de datos MySQL?
Verdadero.
Falso.
Solución
1. Opción correcta
2. Incorrecto
1.5.- Herencia.
La herencia es un mecanismo de la POO que nos permite
definir nuevas clases en base a otra ya existente. Las
nuevas clases que heredan también se conocen con el
nombre de subclases. La clase de la que heredan se
llama clase base o superclase.
class Producto {
public $codigo;
public $nombre;
public $nombre_corto;
public $pvp;
public function muestra() {
echo "<p>" . $this->codigo . "</p>";
}
}
Esta clase es muy útil si la única información que tenemos de los distintos productos es la
que se muestra arriba. Pero, si quieres personalizar la información que vas a tratar de cada
tipo de producto (y almacenar, por ejemplo para los televisores, las pulgadas que tienen o
su tecnología de fabricación), puedes crear nuevas clases que hereden de Producto. Por
ejemplo, TV, PC, Movil.
Como puedes ver, para definir una clase que herede de otra, simplemente tienes que
utilizar la palabra extends indicando la superclase. Los nuevos objetos que se instancien a
partir de la subclase son también objetos de la clase base; se puede comprobar utilizando
el operador instanceof().
$t = new TV();
if ($t instanceof Producto) {
// Este código se ejecuta pues la condición es cierta.
. . .
}
Antes viste algunas funciones útiles para programar utilizando objetos y clases. Las de la
siguiente tabla están además relacionadas con la herencia.
Existe una forma de evitar que las clases heredadas puedan redefinir el comportamiento de
los métodos existentes en la superclase: utilizar la palabra final. Si en nuestro ejemplo
hubiéramos hecho:
class Producto {
public $codigo;
public $nombre;
public $nombre_corto;
public $pvp;
public final function muestra() {
echo "<p>" . $this->codigo . "</p>";
}
}
Incluso se puede declarar una clase utilizando final. En este caso no se podrían crear clases
heredadas utilizándola como base.
Opuestamente al modificador final, existe también abstract. Se utiliza de la misma forma, tanto
con métodos como con clases completas, pero en lugar de prohibir la herencia, obliga a que
se herede. Es decir, una clase con el modificador abstract no puede tener objetos que la
instancien, pero sí podrá utilizarse de clase base y sus subclases sí podrán utilizarse para
instanciar objetos.
Y un método en el que se indique abstract, debe ser redefinido obligatoriamente por las
subclases, y no podrá contener código.
class Producto {
. . .
abstract public function muestra();
}
Autoevaluación
La función is_subclass_of recibe como primer parámetro:
Un objeto.
Un objeto o una clase.
Solución
1. Incorrecto
2. Opción correcta
1.5.2.- Herencia (II).
Vamos a hacer una pequeña modificación en nuestra clase
Producto. Para facilitar la creación de nuevos objetos,
crearemos un constructor al que se le pasará un array con
los valores de los atributos del nuevo producto.
class Producto {
public $codigo; Captura de pantalla BlueJ (Elaboración Propia)
public $nombre;
public $nombre_corto;
public $pvp;
public function __construct($row) {
$this->codigo = $row['cod'];
$this->nombre = $row['nombre'];
$this->nombre_corto = $row['nombre_corto'];
$this->PVP = $row['pvp'];
}
public function muestra() {
echo "<p>" . $this->codigo . "</p>";
}
}
¿Qué pasa ahora con la clase TV, qué hereda de Producto? Cuando crees un nuevo objeto de
esa clase, ¿se llamará al constructor de Producto? ¿Puedes crear un nuevo constructor
específico para TV que redefina el comportamiento de la clase base?
Empezando por esta última pregunta, obviamente puedes definir un nuevo constructor para
las clases heredadas que redefinan el comportamiento del que existe en la clase base, tal y
como harías con cualquier otro método. Y dependiendo de si programas o no el constructor
en la clase heredada, se llamará o no automáticamente al constructor de la clase base.
Ya viste con anterioridad cómo se utilizaba la palabra clave self para tener acceso a la clase
actual. La palabra parent es similar. Al utilizar parent haces referencia a la clase base de la
actual, tal y como aparece tras extends.
Autoevaluación
Si una subclase no tiene método constructor, y su clase base sí lo tiene,
cuando se instancie un nuevo objeto de la subclase:
Se llamará automáticamente al constructor de la clase base.
No se llamará automáticamente al constructor de la clase base.
Solución
1. Opción correcta
2. Incorrecto
1.6.- Interfaces.
Un interface es como una clase vacía que solamente
contiene declaraciones de métodos. Se definen utilizando
la palabra interface.
interface iMuestra {
public function muestra();
}
Y cuando crees las subclases deberás indicar con la palabra implements que tienen que
implementar los métodos declarados en este interface.
Todos los métodos que se declaren en un interface deben ser públicos. Además
de métodos, los interfaces podrán contener constantes pero no atributos.
Un interface es como un contrato que la clase debe cumplir. Al implementar todos los
métodos declarados en el interface se asegura la interoperabilidad entre clases. Si sabes
que una clase implementa un interface determinado, sabes qué nombre tienen sus
métodos, qué parámetros les debes pasar y, probablemente, podrás averiguar fácilmente
con qué objetivo han sido escritos.
Countable {
abstract public int count ( void )
}
Si creas una clase para la cesta de la compra en la tienda web, podrías implementar este
interface para contar los productos que figuran en la misma.
Antes aprendiste que en PHP una clase sólo puede heredar de otra, que no existe la
herencia múltiple. Sin embargo, sí es posible crear clases que implementen varios
interfaces, simplemente separando la lista de interfaces por comas después de la palabra
"implements".
La única restricción es que los nombres de los métodos que se deban implementar en los
distintos interfaces no coincidan. Es decir, en nuestro ejemplo, el interface iMuestra no podría
contener un método count, pues éste ya está declarado en Countable.
Debes conocer
En PHP también se pueden crear nuevos interfaces heredando de otros ya
existentes. Se hace de la misma forma que con las clases, utilizando la
palabra "extends".
1.6.1.- Interfaces (I).
Una de las dudas más comunes en POO, es qué solución
adoptar en algunas situaciones: interfaces o clases
abstractas. Ambas permiten definir reglas para las clases
que los implementen o hereden respectivamente. Y
ninguna permite instanciar objetos. Las diferencias
principales entre ambas opciones son:
Por ejemplo, en la tienda online va a haber dos tipos de usuarios: clientes y empleados.
Si necesitas crear en tu aplicación objetos de tipo Usuario (por ejemplo, para manejar de
forma conjunta a los clientes y a los empleados), tendrías que crear una clase no abstracta
con ese nombre, de la que heredarían Cliente y Empleado.
class Usuario {
. . .
}
class Cliente extends Usuario {
. . .
}
class Empleado extends Usuario {
. . .
}
Pero si no fuera así, tendrías que decidir si crearías o no Usuario, y si lo harías como una
clase abstracta o como un interface.
Si por ejemplo, quisieras definir en un único sitio los atributos comunes a Cliente ya Empleado,
deberías crear una clase abstracta Usuario de la que hereden.
Pero esto no podrías hacerlo si ya tienes planificada alguna relación de herencia para una
de estas dos clases.
Para finalizar con los interfaces, a la lista de funciones de PHP relacionadas con la POO
puedes añadir las siguientes.
Funciones de utilidad para interfaces en PHP
Autoevaluación
Si en tu código utilizas un interface, y quieres crear uno nuevo
basándote en él:
Puedes utilizar la herencia para crear el nuevo constructor extendiendo
al primero.
No puedes hacerlo, pues no se puede utilizar herencia con los
interfaces; solo con las clases.
Sí; se hace utilizando extends, de la misma forma que con las clases.
Solución
1. Opción correcta
2. Incorrecto
1.7.- Ejemplo de POO en PHP.
Es hora de llevar a la práctica lo que has aprendido.
Vamos a aplicar los principios de la POO para realizar un
"CRUD" a la tabla "productos" de la base de datos
"proyecto" usada en unidades anteriores. Recuerda que
las credenciales para acceder a la base de datos eran
"gestor::secreto" y en la tabla usuarios habíamos creado
los usuarios "admin::secreto" y "gestor::pass". En los
enlaces siguientes puedes ver un vídeo en "youtube" de
la aplicación ya terminada y un resumen textual del vídeo. Soumil Kumar (Dominio público)
Enlace al Vídeo.
Resumen textual.
Vamos a utilizar una estructura para esta práctica muy común, en el directorio donde
guardemos los archivos de la práctica crearemos una carpeta "class" donde guardaremos
todas las clases y un carpeta "public" donde guardaremos todas las páginas "php, html...", que
vayamos a necesitar visualizar en el navegador.
Crearemos una clase "Conexion" y una clase para todas las tablas que
vayamos a usar. El convenio es poner el nombre del archivo exactamente
igual al nombre de la clase que implementa, es decir, el archivo
"Usuario.php" implementará la clase "Usuario".
Para no tener que estar haciendo el "require" de cada uno de los archivos
de clase que vayamos a necesitar, vamos a utilizar la "autocarga de las
clases", que es básicamente un mecanismo por el que cada vez que
Captura de pantalla instanciamos un objeto de una clase hace el "require" del archivo donde se
terminal Ubuntu
(Elaboración propia)
encuentra implementada. Por eso es muy importante que respete las
convenciones de nombres. El código para ello sería el siguiente:
spl_autoload_register(function ($class) {
require "../class/" . $class . ".php";
});
function existeNombreCorto($nc){
if ($this->id == null) {
$consulta = "select * from productos where nombre_corto=:nc";
} else {
$consulta = "select * from productos where nombre_corto = :nc AND id != :i"
}
$nc = func_get_arg(0);
$stmt = $this->conexion->prepare($consulta); //podemos acceder a conexion al se
try {
if ($this->id == null)
$stmt->execute([':nc' => $nc]);
else
$stmt->execute([':nc' => $nc, ':i' => $this->id]);
} catch (PDOException $ex) {
die("Error al comprobar el nombre corto: " . $ex->getMessage());
}
if ($stmt->rowCount() == 0) return false; //No existe
return true; //existe
}
Fíjate que en este método tenemos que controlar si estemos en crear (compruebo
que no existe ese nombre_corto en toda la base de datos) o actualizar (no
comprobaremos el nombre_corto del código del producto a actualizar ya que ese
nombre_corto ya existe).
Para todas las clases que heredan de la clase Conexión el constructor será:
$nombre = trim($_POST['usuario']);
Captura de pantalla de Firefox
$pass = trim($_POST['pass']); (Elaboración propia)
if (strlen($nombre) == 0 || strlen($pass) == 0) {
error("Error, El nombre o la contraseña no pueden cont
}
$usuario = new Usuario();
if (!$usuario->isValido($nombre, $pass)) {
$usuario = null;
error("Credenciales Inválidas");
}
$usuario = null;
$_SESSION['nombre'] = $nombre;
header('Location:listado.php');
En "crear.php" por una parte para comprobamos que no existe el nombre corto:
if ($producto->existeNombreCorto($nc)) {
error("Error ya existe un nombre corto con ese valor.");
}
Solución
1. Incorrecto
2. Opción correcta
2.- Programación en capas.
Caso práctico
Después de unas semanas, Carlos ha
conseguido reprogramar su aplicación de
catalogación utilizando orientación a objetos. Le
ha costado bastante esfuerzo, pero reconoce
que el resultado merece la pena. El código
resultante es mucho más limpio, y cada clase de
las que ha creado tiene un cometido concreto y
bien definido.
De igual manera el encabezado de las páginas era repetido una y otra vez (importar
Bootstrap, FontAwesome...). Ademas existe un problema añadido: si el proyecto es extenso y en
distintos archivos tenemos clases de igual nombre podremos tener colisión entre ellas.
Existen varios métodos que permiten separar la lógica de presentación (en nuestro caso, la
que genera las etiquetas HTML) de la lógica de negocio, donde se implementa la lógica
propia de cada aplicación. El más extendido es el patrón de diseño Modelo – Vista –
Controlador (MVC). Este patrón pretende dividir el código en tres partes, dedicando cada
una a una función definida y diferenciada de las otras.
La gran ventaja de este patrón de programación es que genera código muy estructurado,
fácil de comprender y de mantener. En la web puedes encontrar muchos ejemplos de
implementación del modelo MVC en PHP.
Aunque puedes programar utilizando MVC por tu cuenta, es más habitual utilizar el patrón
MVC en conjunción con un framework o marco de desarrollo. Existen numerosos
frameworks disponibles en PHP, muchos de los cuales incluyen soporte para MVC como
Laravel y Symfony. En esta unidad no profundizaremos en la utilización de un framework
específico, pero utilizaremos Blade que es el sistema de plantillas de Laravel, el cual nos
permite generar HTML dinámico con una sintaxis mucho más limpia que si usáramos PHP
plano.
2.1.- Namespaces en PHP.
Los namespaces o espacios de nombres permiten crear proyectos
complejas con mayor flexibilidad evitando problemas de colisión
entre clases, métodos, funciones y mejorando la legibilidad del
código. aparecen a partir de PHP5.3
Podemos comprarlo con el sistema de archivos, los archivos están dentro de carpetas y
dentro de éstas hay a su vez otras carpetas con otros archivos. Una carpeta se comporta
como si fuera un namespace, por ejemplo, no puede haber dos archivos con el mismo nombre
en la misma carpeta, pero sí puede haber dos archivos con el mismo en distintas.
<?php
namespace Proyecto;
//la declaración del namespace debe ser la primera línea si no obtendremos un error
Constantes.
Funciones.
Clases, interfaces, traits, clases abstractas.
<?php
namespace Proyecto;
const E = 2.7182;
function saludo(){
echo "Buenos dias";
}
class Prueba{
private $nombre;
public function probando(){
echo "Esto es el método probando de la clase Prueba";
}
}
Para pode usar este archivo en otra parte podemos hacerlo de varias formas:
<?php
include "ejemploNamespace.php";
echo Proyecto\E; // accedemos a la constante
Proyecto\saludo(); // accedemos a la función
$prueba=new Proyecto\Prueba();
$prueba->probando();
O bien :
<?php
include "ejemploNamespace.php";
use const Proyecto\E;
use function Proyecto\saludo;
use Proyecto\Prueba;
// ahora ya podemos usarlos
echo E;
saludo();
$prueba = new Prueba();
$prueba->probando();
Autoevaluación
En un namespace solo guardaremos las clases para evitar colisiones entre ellas.
Sugerencia
Verdadero Falso
Falso
Podemos guardar también funciones y constantes.
2.2.- Gestionar Dependencias.
A la hora de empezar el desarrollo de un proyecto en PHP, es
necesario conocer todas las librerías que necesitaremos. La
instalación de estas librerías puede ser una tarea trabajosa si lo
hacemos a mano, pero existen gestores de dependencias que se
encargan de realizarla de forma automática, gestionando esas
librerías de forma sencilla y eficaz.
Es simple de utilizar
Cuenta con un repositorio propio donde podemos encontrar casi de todo (Packagist)
Disminuye significativamente problemas de cambio de entorno de ejecución (Mediante
su funcionalidad de congelar dependencias)
Actualiza las librerías a nuevas versiones fácilmente si queremos mediante "composer
update".
Vemos a empezar a trabajar con Composer, para ello veremos la instalación y los primeros
pasos:
Instalación
Nos descargamos el archivo desde la página oficial (acceder a Composer), desde esa
página nos recomiendan ejecutar las siguientes instrucciones:
Para comprobar que todo funciona teclea desde la terminal el comando "composer" te
debe aparecer algo como lo siguiente:
Captura de pantalla Ubuntu (Elaboración propia)
Primeros pasos
Veamos su uso, en la raíz del directorio donde vaya a estar nuestro proyecto y desde
la terminal tecleamos "composer init" a continuación se nos hacen unas preguntas y
vamos contestando:
Al final nos muestra un archivo JSON , se nos pregunta que si queremos generarlo,
contestamos que si y nos genera el archivo "composer.json". Una vez generado el archivo
tecleando "composer install" nos instalará las dependencias.
Una de las cosas que hace composer es gestionarnos el tema tema de la autocarga de
clases/librerías (acuérdate que en apartados anteriores ya habíamos visto una forma de
hacerlo). Para ello debemos crearnos, en el raíz del proyecto que vayamos a usar, un
directorio de nombre por ejemplo "src" y modificar el archivo "composer.json". Para el
"autoload" de clases y librerías se hace uso de los namespaces.
1 "name": "usuario/usuario",
2 "description": "Ejemplo Blade",
3 "type": "project",
4 "config": {
5 "optimize-autoloader": true
6 },
7 "autoload": {
8 "psr-4": {
9 "Clases\\": "src"
10 }
11 },
12 "license": "GNU/GPL",
13 "authors": [
14 {
15 "name": "usuario",
16 "email": "[email protected]"
17 }
18 ]
19 }
Fíjate de la línea 7 a la 10 lo que estamos indicando es que vamos a tener un namespace de
nombre Clases y que su directorio real va a ser "src". En este directorio meteremos todas las
clases y comenzaremos el archivo como ya vimos "namespace Clases". Las tres lineas anteriores
es para que se optimize la autocarga de las clases, no es necesario pero si recomendable.
El nombre que se le suele dar al directorio de clases de un proyecto es "src" de "source".
Cada vez que modifiquemos el fichero "composer.json" hay que hacer un "composer install" para
instalar las nuevas dependencias que hayamos puesto.
Blade es un sistema de plantillas que nos permite generar HTML dinámico con una sintaxis
mucho más limpia que si usáramos PHP plano. Veamos como instalarlo, configurarlo y unos
primeros pasos
Blade necesita una carpeta "cache" (donde se guardara una cache de las vistas para su
carga) y una carpeta "views" que será la carpeta donde guardaremos los archivos de plantilla
que creeemos. Todas las vistas Blade tienen que tener la extensión ".blade.php". Para
configurar un proyecto donde vayamos a utilizar Blade, haremos lo siguiente:
En la carpeta del proyecto creamos las carpetas "public" (será la parte accesible desde el
servidor web), "src" (guardaremos las clases que vayamos a crear si es el caso), "caches" y
"views". En la carpeta "cache" el servidor web (Apache) necesita permiso de escritura, si
estamos en Windows no hay problema, si estamos en Linux y sabes como hacerlo lo más
seguro es que pongas como grupo de estaa carpetas a Apache y des los permisos
apropiados. Para no complicarnos desde la terminal y en la carpeta de nuestro proyecto
una vez creadas las carpetas tecleamos: "chmod 777 cache" .
Una vez creada la estructura de nuestro proyecto iniciamos composer acuérdate que esto lo
hacíamos con "composer init" contestamos a las preguntas iniciales como ya vimos y las
siguientes así:
"Would you like to define your dependencies (require) interactively [yes]?" en vez escribir no, le damos a
intro ("yes" es la opción por defecto)
"Search for a package:" ponemos "laravel-blade" y elegimos la opción: "philo/laravel-blade". A veces
el paquete puede no aparecer (puede haber cambiado el nombre, el desarrollador...),
no pasa nada, vete a Packagist , desde su buscador localiza el paquete y sigue las
instrucciones, que básicamente son poner unas líneas en tu archivo "composer.json" y
hacer "composer install". O generar el archivo y hacer el "composer require" después como ya
vimos.
Para las dependencias de desarrollo (dev) contestamos que no.
"Do you confirm generation [yes]?" le damos a intro ("yes" es la opción por defecto)
"Would you like to install dependencies now [yes]?" le damos a intro ("yes" es la opción por defecto) .
En este paso se descargan las librerías seleccionadas, en este caso: "philo/laravel-blade"
en la carpeta "vendor" que si no existe se creará.
{
"name": "usuario/usuario",
"description": "Ejemplo Blade",
"type": "project",
"require": {
"philo/laravel-blade": "^3.1"
},
"config": {
"optimize-autoloader": true
},
"autoload": {
"psr-4": {
"Clases\\": "src"
}
},
"license": "GNU/GPL",
"authors": [
{
"name": "usuario",
"email": "[email protected]"
}
]
}
Autoevaluación
¿ Necesitamos usar el Framework Laravel pasar sacar ventajas del
gestor de plantillas Blade ?
Verdadero
Falso.
Incorrecto. Con Composer podemos hacer uso de Blade sin tener que
utilizar Laravel.
Solución
1. Incorrecto
2. Opción correcta
2.3.1.- Separación de la lógica de negocio
(II).
<?php
require 'vendor/autoload.php';
use Philo\Blade\Blade;
$views = '../views'; OpenClipart-Vectors (Dominio
público)
$cache = '../cache';
$blade = new Blade($views, $cache);
echo $blade->view()->make('vista')->render();
Esto me cargaría la vista "vista.blade.php" de la carpeta "views". Fíjate que solo hace falta poner
el nombre de la vista y no la extensión ".blade.php".
Para entender esto mejor desarrollaremos un proyecto con dos páginas, una para ver el
listado de productos de la base de datos proyecto y otra para ver el listado de familias.
A las clases Conexion, Productos y Familias que ya la hemos visto a lo largo del tema,
vamos a modificarlas para usar namespaces en todo el proyecto. La estructura de nuestro
proyecto de ejemplo podría ser la siguiente:
Captura de pantalla
Visual Studio Code
(Elaboración propia)
En "src" tendremos las clases para conectarnos a la base de dato y recuperar los datos
de familias y productos.
En "public" Las dos páginas que realmente mostraremos
En "views" crearemos una carpeta "plantillas" (pondremos las plantillas que reusaremos
las veces que queramos) y las dos vistas, la que me generará el listado de productos
y la que me generará el listado de familias.
Vamos a ello:
Las clases ya sabemos como hacerlas simplemente vamos a poner en el encabezado de
las mismas, lo siguiente. Ten en cuenta que el nombre del namespace viene dado por lo
que pusimos en el "autoload" del composer y la carpeta de las mismas también. Si los has
cambiado debes reflejarlo en "composer.json"
<?php
namespace Clases;
use PDO;
use PDOException;
Al usar namespace es necesario poner "use" a los métodos y la clase PDO. Si no lo hacemos
para no tener errores tendríamos que andar poniendo "$conexion = new \PDO(. . . ),
catch(\PDOException) . . ."
Las páginas que vamos a ver (familias y productos) comporten una estructura como el
esqueleto html, la carga de bootstrap o las hojas de estilos, todo esto lo podemos meter
en la plantilla y así nos evitamos tener que repetir el mismo código una y otra vez. Veamos
la plantilla
1 <!doctype html>
2 <html lang="es">
3 <head>
4 <meta charset="UTF-8">
5 <meta name="viewport"
6 content="width=device-width, user-scalable=no, initial-scale=1.0, maximum-scal
7 <meta http-equiv="X-UA-Compatible" content="ie=edge">
8 <!-- css para usar Bootstrap -->
9 <link rel="stylesheet" href="https://stackpath.bootstrapcdn.com/bootstrap/4.4.1/css/
10 <title>@yield('titulo')</title>
11 </head>
12 <body style="background:#0277bd">
13 <div class="container mt-3">
14 <h3 class="text-center mt-3 mb-3">@yield('encabezado')</h3>
15 @yield('contenido')
16 </div>
17 </body>
18 </html>
cada instrucción de Blade viene precedida de "@". En las líneas 10, 14 y 15 verás "@yield('. .
.')" se usa para mostrar el contenido de una sección determinada. Para cada página
cambiaremos: contenido, título y cabecera.
Vista: "vistaProductos.blade.php"
@extends('plantillas.plantilla1')
@section('titulo')
{{$titulo}}
@endsection
6 @section('encabezado')
7 {{$encabezado}}
8 @endsection
9 @section('contenido')
10 <table class="table table-striped">
11 <thead>
12 <tr class="text-center">
13 <th scope="col">Código</th>
14 <th scope="col">Nombre</th>
15 <th scope="col">Nombre Corto</th>
16 <th scope="col">Precio</th>
17 </tr>
18 </thead>
19 <tbody>
20 @foreach($productos as $item)
21 <tr class="text-center">
22 <th scope="row">{{$item->id}}</th>
23 <td>{{$item->nombre}}</td>
24 <td>{{$item->nombre_corto}}</td>
25 @if($item->pvp>100)
26 <td class='text-danger'>{{$item->pvp}}</td>
27 @else
28 <td class='text-success'>{{$item->pvp}}</td>
29 @endif
30 </tr>
31 @endforeach
32 </tbody>
33 </table>
@endsection
Vista "vistaFamilias.blade.php"
@extends('plantillas.plantilla1')
@section('titulo')
{{$titulo}}
@endsection
@section('encabezado')
{{$encabezado}}
@endsection
@section('contenido')
<table class="table table-striped">
<thead>
<tr class="text-center">
<th scope="col">Código</th>
<th scope="col">Nombre</th>
</tr>
</thead>
<tbody>
@foreach($familias as $item)
<tr class="text-center">
<th scope="row">{{$item->cod}}</th>
<td>{{$item->nombre}}</td>
</tr>
@endforeach
25 </tbody>
26 </table>
27 @endsection
Lo primero que hacemos con "@extends('...')" es llamar a la plantilla, para ello indicamos la ruta
(fíjate que la extensión ".blade.php" no hace falta ponerla). Acuérdate que creamos tres "yields"
uno llamado título, otro encabezado y otro contenido, para rellenarlos ponemos el bloque
"@section('...'), @endsection" con el nombre que pusimos en los "yields".
A las vistas mandamos unas variables (en el apartado siguiente veremos como). A la vista
"vistaProductos.blade.php" mandamos las variables "$titulo, $encabezado y $productos", $producto trae los
productos de las base de datos (los mandamos con un "fetchAll()" de una consulta a la tabla
productos). Cuando usamos Blade no hace falta poner "<?php echo $titulo; ?>" que es como lo
hemos hecho hasta ahora, nos basta con poner "{{...}}"
Puedes observar ademas que también podemos poner un bucle para recorrer, en este
caso, los productos "@foreach(...), endforeach".
De igual manera con un bloque "@if, @else, @endif" pintamos los precios de mas de 100€ de
rojo y el resto de verde.
El código HTML lo ponemos tal cual, si necesitásemos poner código PHP en una pagina
Blade lo pondríamos entre las directivas "@php, @endphp"
Manual Blade
2.4.- Generación del interface de usuario.
En el apartado anterior configuramos las vistas y
una plantilla para ahorrarnos repetir código, a las
vistas las pasamos unas variables como titulo
encabezado, las listas de datos que queramos
mostrar . . . Veamos como podemos llamar a estas
vistas y pasar variables a las mismas.
Página: "productos.php"
1 <?php
2
3 require '../vendor/autoload.php';
4
5 use Philo\Blade\Blade;
6 use Clases\Producto;
7
8 $views = '../views';
9 $cache = '../cache';
10
11 $blade = new Blade($views, $cache);
12 $titulo='Productos';
13 $encabezado='Listado de Productos';
14 $productos=(new Producto())->recuperarProductos();
15 echo $blade->view()->make('vistaProductos', compact('titulo', 'encabezado', 'productos')
Las líneas del "use" son para poder usar nuestras Clases y las de "Philo\Blade" (acuérdate
del namespace de las mismas que era Clase).
Las siguientes son para indicar la ruta relativa de las carpetas "views" y "cache" y
guardarla en sendas variables.
En la siguiente nos creamos un objeto de la clase "Blade", le pasamos las rutas de las
carpetas "views" y "cache" e inicializamos las variables que pasaremos a la vista: $titulo,
$encabezado y $productos. Esta última línea la hemos simplificado, en realidad podíamos
haber puesto:
$productos = new Productos();
$productos->recuperarProductos();
Página: "familias.php"
1 <?php
2
3 require '../vendor/autoload.php';
4
5 use Philo\Blade\Blade;
6 use Clases\Familia;
7
8 $views = '../views';
9 $cache = '../cache';
10
11 $blade = new Blade($views, $cache);
12 $titulo='Familias';
13 $encabezado='Listado de Familias';
14 $familias=(new Familia())->recuperarFamilias();
15 echo $blade->view()->make('vistaFamilias', compact('titulo', 'encabezado', 'familias'))-
A continuación se deja un enlace con los archivos del proyecto. Para poder ver el mismo
funcionando tendréis que tener Composer instalado hacer "composer init" instalar la librería de
Blade y autoload y hacer "composer install". Descargar archivos. (zip - 5,71 KB)
Ejercicio resuelto
A partir de la página de "login" que ya vimos en el ejemplo 1.7.1 modifica el
ejemplo que acabamos de ver para poder acceder a listar "productos" y
"familias" solo si estamos validados.
Usa Blade para crear la vista que cargue la página de "login" y modifica listar
usuarios y productos para que muestren el usuario con el que estamos
logeados y el botón cerrar sesión.
Se recomienda abordar este ejercicio desde cero para repasar todos los
conceptos.
Mostrar retroalimentación
Aquí se deja una posible solución, recuerda que tienes que montar el
proyecto para que este funcione, es decir, inicializar Composer e
instalar con él las dependencias necesarias.
Descargar solución propuesta. (zip - 9,96 KB)
Autoevaluación
Las plantillas que crees en Blade es preferible alojarlas:
No, pues en este caso podrían acceder a ellas los usuarios de tu web.
Recuerda que es el código PHP el que utiliza las plantillas para generar
las páginas que ven los usuarios, y por tanto éstos no necesitan acceder
a ellas a través del servidor web.
Solución
1. Opción correcta
2. Incorrecto
Anexo 1
<?php
class Persona
{
private $nombre;
private $perfil;
}
}