UT03 PHP Orientat a Objectes¶
Criteris d'avaluació
Resultat d'aprenentatge:
- Desenvolupa aplicacions Web identificant i aplicant mecanismes per separar el codi de presentació de la lògica de negoci.
Criteris d'avaluació:
-
S'han identificat els avantatges de separar la lògica de negoci dels aspectes de presentació de l'aplicació.
-
S'han analitzat tecnologies i mecanismes que permeten realitzar aquesta separació i les seves característiques principals.
-
S'han utilitzat objectes i controls en el servidor per generar l'aspecte visual de l'aplicació web en el client.
-
S'han utilitzat formularis generats de forma dinàmica per respondre als esdeveniments de l'aplicació Web.
-
S'han escrit aplicacions Web amb manteniment d'estat i separació de la lògica de negoci.
-
S'han aplicat els principis de la programació orientada a objectes.
-
S'ha provat i documentat el codi.
Classes i Objectes¶
PHP segueix un paradigma de programació orientada a objectes (POO) basada en classes.
Una classe és una plantilla que defineix les propietats i mètodes per poder crear objectes. D'aquesta manera, un objecte és una instància d'una classe.
Tant les propietats com els mètodes es defineixen amb una visibilitat (qui pot accedir)
- Privat -
private: Només hi pot accedir la pròpia classe. - Protegit -
protected: Només hi pot accedir la pròpia classe o els seus descendents. - Públic -
public: Pot accedir qualsevol altra classe.
Per declarar una classe, s'utilitza la paraula clau class seguit del nom de la classe. Per a un objecte a partir de la classe, s'utilitza new:
<?php
class NombreClase {
// propietats
// i mètodes
}
$ob = new NombreClase();
Classes amb majúscula
Totes les classes comencen per lletra majúscula.
Quan un projecte creix, és normal modelar les classes mitjançant UML (recordeu Entorns de Desenvolupament?). Les classes es representen mitjançant un quadrat, separant el nom, de les propietats i els mètodes:
Un cop hem creat un objecte, s'utilitza l'operador -> per accedir a una propietat o un mètode:
$objecte->propietat;
$objecte->mètode(paràmetres);
Si des de dins de la classe, volem accedir a una propietat o mètode de la mateixa classe, utilitzarem la referència $this;
$this->propietat;
$this->mètode(paràmetres);
Persona.php com:
<?php
class Persona {
private string $nom;
public function setNom(string $nom) {
$this->nom= $nom;
}
public function imprimir(){
echo $this->nom;
echo '<br>';
}
}
$bruno = new Persona(); // creem un objecte
$bruno->setNom("Bruno Díaz");
$bruno->imprimir();
Tot i que es poden declarar diverses classes al mateix arxiu, és una mala pràctica. Així doncs, cada fitxer contedrà una sola classe, i es nomenarà amb el nom de la classe.
Encapsulació¶
Les propietats es defineixen privades o protegides (si volem que les classes heretades hi puguin accedir).
Per a cada propietat, s'afegeixen mètodes públics (getter/setter):
public setPropietat(tipus $param)
public getPropietat() : tipus
Les constants es defineixen públiques perquè siguin accessibles per tots els recursos.
<?php
class MajorMenor {
private int $major;
private int $menor;
public function setMajor(int $maj) {
$this->major = $maj;
}
public function setMenor(int $men) {
$this->menor = $men;
}
public function getmajor() : int {
return $this->major;
}
public function getMenor() : int {
return $this->menor;
}
}
Rebent i enviant objectes¶
És recomanable indicar-ho en el tipus de paràmetres. Si l'objecte pot retornar nuls es posa ? davant del nom de la classe.
Objectes per referència
Els objectes que s'envien i reben com a paràmetres sempre es passen per referència.
<?php
function majmen(array $numeros) : ?MajorMenor {
$a = max($numeros);
$b = min($numeros);
$result = new MajorMenor();
$result->setMajor($a);
$result->setMenor($b);
return $result;
}
$resultat = majmen([1,76,9,388,41,39,25,97,22]);
echo "<br>Major: ".$resultat->getMajor();
echo "<br>Menor: ".$resultat->getMenor();
Constructor¶
El constructor dels objectes es defineix mitjançant el mètode màgic __construct. Pot o no tenir paràmetres, però només hi pot haver un únic constructor.
<?php
class Persona {
private string $nom;
public function __construct(string $nom) {
$this->nom = $nom;
}
public function imprimir(){
echo $this->nom;
echo '<br>';
}
}
$bruno = new Persona("Bruno Díaz");
$bruno->imprimir();
Constructors en PHP 8¶
Una de les grans novetats que ofereix PHP 8 és la simplificació dels constructors amb paràmetres, el que es coneix com a promoció de les propietats del constructor.
Per a això, en comptes d'haver de declarar les propietats com a privades o protegides, i després dins del constructor haver d'assignar els paràmetres a aquestes propietats, el mateix constructor promociona les propietats.
Vegem-ho millor amb un exemple. Imaginem una classe Punt on vulguem emmagatzemar les seves coordenades:
<?php
class Punt {
protected float $x;
protected float $y;
protected float $z;
public function __construct(
float $x = 0.0,
float $y = 0.0,
float $z = 0.0
) {
$this->x = $x;
$this->i = $y;
$this->z = $z;
}
}
A PHP 8, quedaria de la següent manera (molt més curt, cosa que facilita la seva llegibilitat):
<?php
class Punt {
public function __construct(
protected float $x = 0.0,
protected float $y = 0.0,
protected float $z = 0.0,
) {}
}
L'ordre importa
A l'hora de codificar l'ordre dels elements ha de ser:
<?php
declare( strict_types = 1);
class NomClasse {
// propietats
// constructor
// getters - setters
// resta de mètodes
}
?>
Classes estàtiques¶
Una classe estàtica en PHP és una classe que no necessita ser instanciada per utilitzar els seus mètodes o propietats. Tota la funcionalitat d’aquest tipus de classe s’accedeix directament a través del nom de la classe, utilitzant l'operador de resolució d’àmbit ::. Les classes estàtiques serveixen per a crear mètodes i propietats que són comuns a totes les instàncies i que, per tant, no depenen d’un objecte específic.
Particularitats de les classes estàtiques en PHP¶
- Mètodes i propietats estàtiques: S’han de declarar amb la paraula clau static.
- Accés a membres estàtics: Es fa mitjançant el nom de la classe, seguit de l’operador :: i el nom del membre estàtic. Per exemple: Classe::metodeEstatic().
- No necessiten instanciar-se: No cal crear un objecte de la classe per a utilitzar els seus membres estàtics, cosa que fa la seva utilitat en funcionalitats que no requereixen informació d’un objecte en concret.
- Limitacions: Un mètode estàtic no pot accedir a les propietats ni a methods no estàtics directament, ja que aquestes requereixen una instància de la classe per ser accedides.
Exemples pràctics
- Una funció que calcula un valor i que és útil des de qualsevol lloc sense necessitat d’instanciar, com una calculadora o utilitat general.
- Comptadors globals que comparteixen una variable entre totes les instàncies, com en el cas de comptar quantes vegades s’ha cridat un mètode.
En resum les classes estàtiques són útils quan es tracta de grups de funcions o dades que són globals per a tota la aplicació, no associades a un objecte concret. Tot i això, s’han d’utilitzar amb prudència, ja que poden complicar el manteniment del codi si s’abusa d’elles.
Es declaren amb static i es referencia amb ::.
Recordau!
- Si volem accedir a un mètode estàtic, s'anteposa el nom de la classe:
Producte::nouProducte(). - Si des d'un mètode volem accedir a una propietat estàtica de la mateixa classe, s'utilitza la referència
self:self::$numProductes
<?php
class Producte {
const IVA = 0.23;
private static $numProductes = 0;
public static function nouProducte() {
self::$numProductes++;
}
}
Producte:: nouProducte();
$impost = Producte::IVA;
També podem tenir classes normals que tinguin alguna propietat estàtica:
<?php
class Producte {
const IVA = 0.23;
private static $numProductes = 0;
private $codi;
public function __construct(string $cod) {
self::$numProductes++;
$this->codi = $cod;
}
public function mostrarResum() : string {
return "El producte ".$this->codi." és el número ".self::$numProductes;
}
}
$prod1 = new Producte("PS5");
$prod2 = new Producte("XBOX Sèries X");
$prod3 = new Producte("Nintendo Switch");
echo $prod3->mostrarResum();
Introspecció¶
En treballar amb classes i objectes, existeixen un conjunt de funcions ja definides pel llenguatge que permeten obtenir informació sobre els objectes:
instanceof: permet comprovar si un objecte és d'una determinada classeget_class: retorna el nom de la classeget_declared_class: retorna un vectoramb els noms de les classes definidesclass_alias: crea un àliesclass_exists/method_exists/property_exists:truesi la classe / mètode / propietat està definidaget_class_methods/get_class_vars/get_object_vars: Retorna un vectoramb els noms dels mètodes / propietats d'una classe / propietats d'un objecte que són accessibles des d'on es fa la trucada.
Un exemple d'aquestes funcions pot ser el següent:
<?php
$p = new Producte("PS5");
if ($p instanceof Producte) {
echo "És un producte";
echo "La classe és ".get_class($p);
class_alias("Producte", "Article");
$c = new Article("Nintendo Switch");
echo "Un article és un ".get_class($c);
print_r(get_class_methods("Producte"));
print_r(get_class_vars("Producte"));
print_r(get_object_vars($p));
if (method_exists($p, "mostrarResumen")) {
$p->mostrarResumen();
}
}
Clonat
En assignar dos objectes no es copien, es crea una nova referència. Si volem una còpia, cal clonar-lo mitjançant el mètode clone(object) : object
Si volem modificar el clonat per defecte, cal definir el mètode màgic '__clone()'que s'anomenarà després de copiar totes les propietats.
Més informació a https://www.php.net/manual/es/language.oop5.cloning.php
Herència¶
PHP suporta herència simple, de manera que una classe només pot heretar d'una altra, no de dues classes alhora. Per a això s'utilitza la paraula clau extends. Si volem que la classe A hereta de la classe B farem:
class A extends B
El fill hereta els atributs i mètodes públics i protegits.
Cada classe en un arxiu
Com ja hem comentat, hauríem de col·locar cada classe en un arxiu diferent per posteriorment utilitzar-lo mitjançant include. En els següent exemple els hem col·locat al costat per facilitar la seva llegibilitat.
Per exemple, tenim una classe Producte i una Tv que hereta de Producte:
<?php
class Producte {
public $codi;
public $nombre;
public $nombreCorto;
public $PVP;
public function mostrarResumen() {
echo "<p>Prod:".$this->codi." </p>";
}
}
class Tv extends Producte {
public $polzades;
public $tecnologia;
}
Podem utilitzar les següents funcions per esbrinar si hi ha relació entre dues classes:
get_parent_class( object): stringis_subclass_of( object, string): bool
<?php
$t = new Tv();
$t->codi = 33;
if ($t instanceof Producte) {
echo $t->mostrarResumen();
}
$pare = get_parent_class($t);
echo "<br>La classe pare és: " . $pare;
$objectePare = new $pare;
echo $objectePare->mostrarResum();
if (is_subclass_of($t, 'Producte')) {
echo "<br>Som un fill de Producte";
}
Sobreescriure mètodes¶
Podem crear mètodes en els fills amb el mateix nom que el pare, canviant el seu comportament. Per invocar els mètodes del pare -> parent::nomMetodo()
<?php
class Tv extends Producte {
public $polzades;
public $tecnologia;
public function mostrarResum() {
parent::mostrarResum();
echo "<p>TV ".$this->tecnologia." de ".$this->polzades." </p>";
}
}
Constructor en fills¶
En els fills no es crea cap constructor de manera automàtica. Per la qual cosa si no n'hi ha, s'invoca automàticament el del pare. En canvi, si el definim en el fill, hem d'invocar el del pare de manera explícita.
<?php
class Producte {
public string $codi;
public function __construct(string $codi) {
$this->codi = $codi;
}
public function mostrarResum() {
echo "<p>Prod:".$this->codi." </p>";
}
}
class Tv extends Producte {
public $polzades;
public $tecnologia;
public function __construct(string $codi, int $polzades, string $tecnologia) {
parent::__construct($codi);
$this->polzades = $polzades;
$this->tecnologia = $tecnologia;
}
public function mostrarResum() {
parent::mostrarResum();
echo "<p>TV ".$this->tecnologia." de ".$this->polzades." </p>";
}
}
<?php
class Producte {
public function __construct(private string $codi) { }
public function mostrarResum() {
echo "<p>Prod:".$this->codi." </p>";
}
}
class Tv extends Producte {
public function __construct(
string $codi,
private int $polzades,
private string $tecnologia)
{
parent::__construct($codi);
}
public function mostrarResum() {
parent::mostrarResum();
echo "<p>TV ".$this->tecnologia." de ".$this->polzades." </p>";
}
}
Classes abstractes¶
Les classes abstractes obliguen a heretar d'una classe, ja que no es permet la seva instància. Es defineix mitjançant abstract class NombreClase {. Una classe abstracta pot contenir propietats i mètodes no-abstractes, i/o mètodes abstractes.
<?php
// Classe abstracta
abstract class Producte {
private $codi;
public function getCodi() : string {
return $this->codi;
}
// Mètode abstracte
abstract public function mostrarResum();
}
Quan una classe hereta d'una classe abstracta, obligatòriament ha d'implementar els mètodes que té el pare marcats com a abstractes.
<?php
class Tv extends Producte {
public $polzades;
public $tecnologia;
public function mostrarResum() { //obligat a implementar-lo
echo "<p>Codi ".$this->getCodi()." </p>";
echo "<p>TV ".$this->tecnologia." de ".$this->polzades." </p>";
}
}
$t = new Tv();
echo $t->getCodi();
Classes finals¶
Són classes oposades a abstractes, ja que eviten que es pugui heretar una classe o mètode per sobreescriure'l.
<?php
class Producte {
private $codi;
public function getCodi() : string {
return $this->codi;
}
final public function mostrarResum() : string {
return "Producte ".$this->codi;
}
}
// No podrem heretar de Microones
final class Microones extends Producte {
private $potencia;
public function getPotencia() : int {
return $this->potència;
}
// No podem implementar mostrarResum()
}
Interfícies¶
Permet definir un contracte amb les firmes dels mètodes a complir. Així doncs, només conté declaracions de funcions i totes han de ser públiques.
Es declaren amb la paraula clau interfície i després les classes que compleixin el contracte el realitzen mitjançant la paraula clau implements.
<?php
interface Nombrable {
// declaració de funcions
}
class NombClasse implements NomInterface {
// codi de la classe
Es permet l'herència d'interfícies. A més, una classe pot implementar diversos interfícies (en aquest cas, sí que suporta l'herècia múltiple, però només d'interfícies).
<?php
interface Mostrable {
public function mostrarResum() : string;
}
interface MostrableTot extends Mostrable {
public function mostrarTot() : string;
}
interface Facturable {
public function generarFactura() : string;
}
class Producte implements MostrableTot, Facturable {
// Implementacions dels mètodes
// Obligatòriament haurà d'implementar public function mostrarResum, mostrarTot i generarFactura
}
Mètodes encadenats¶
Segueix el plantejament de la programació funcional, i també es coneix com a method chaining. Planteja que sobre un objecte es realitzen diverses trucades.
<?php
$p1 = new Llibre();
$p1->setNom("Harry Potter");
$p1->setAutor("JK Rowling");
echo $p1;
// Method chaining
$p2 = new Llibre();
$p2->setNom("Patria")->setAutor("Aramburu");
echo $p2;
Per facilitar-ho, anam modificar tots els seus mètodes mutadors (que modifiquen dades, setters, ...) perquè retornin una referència a $this:
<?php
class Llibre {
private string $nom;
private string $autor;
public function getNom() : string {
return $this->nom;
}
public function setNomb(string $nombre) : Llibre {
$this->nom = $nom;
return $this;
}
public function getAutor() : string {
return $this->autor;
}
public function setAutor(string $autor) : Llibre {
$this->autor = $autor;
return $this;
}
public function __toString() : string {
return $this->nom." de ".$this->autor;
}
}
Mètodes màgics¶
Totes les classes PHP ofereixen un conjunt de mètodes, també coneguts com a magic methods que es poden sobreescriure per substituir el seu comportament. Alguns d'ells ja els hem utilitzat.
Davant de qualsevol dubte, és convenient consultar la documentació oficial.
Els més destacables són:
__construct()__destruct()→ s'invoca en perdre la referència. S'utilitza per tancar una connexió a la BD, tancar un fitxer, ...__toString()→ representació de l'objecte com a cadena. És a dir, quan femesclat $objectes'executa automàticament aquest mètode.__get(propietat),__set(propietat, valor)→ Permetria accedir a les propietats privades, tot i que sempre és més llegible/mantenible codificar els getter/setter.__isset(propietat),__unset(propietat)→ Permet esbrinar o treure el valor a una propietat.__sleep(),__wakeup()→ S'executen en recuperar (unserialize) o emmagatzemar un objecte que se serialitza (serialize), i s'utilitzen per a definir quines propietats se serialitzen.__call(),__callStatic()→ S'executen en cridar a un mètode que no és públic. Permeten sobrecarreguen mètodes.
Espai de noms¶
Des de PHP 5.3 i també coneguts com a Namespaces, permeten organitzar les classes/interfícies, funcions i/o constants de forma similar als paquets a Java.
Recomanació
Un sol namespace per arxiu i crear una estructura de carpetes respectant els nivells/subnivells (igual que es fa a Java)
Es declaren en la primera línia mitjançant la paraula clau namespace seguida del nom de l'espai de noms assignat (cada subnivell se separa amb la barra invertida \):
Per exemple, per col·locar la classe Producte dins del namespace Dwes\Exemples ho faríem així:
<?php
namespace DWES\Exemples;
const IVA = 0.21;
class Producte {
public $nombre;
public function mostra() : void {
echo"<p>Prod:" . $this->nom . "</p>";
}
}
Accés¶
Per referenciar un recurs que conté un namespace, primer hem de tenir-lo disponible fent ús d'include o require. Si el recurs és al mateix namespace, es realitza un accés directe (es coneix com a accés sense qualificar).
Realment hi ha tres tipus d'accés:
- sense qualificar:
recurs - qualificat:
rutaRelativa\recurs→ no cal posar el namespace complet - totalment qualificat:
\rutaAbsoluta\recurs
<?php
namespace DWES\Exemples;
include_once("Producte.php");
echo IVA; // sense qualificar
echo Utilidades\IVA; // accés qualificat. Donaria error, no existeix \DWES\Exemples\Utilitats\IVA
echo \DWES\Exemples\IVA; //totalment qualificat
$p1 = new Producte(); // el busca al mateix namespace i troba \DWES\Exemples\Producte
$p2 = new Model\Producte(); // donaria error, no existeix el namespace Model. Està buscant \DWES\Exemples\Model\Producte
$p3 =new \DWES\Exemples\Producte(); // \DWES\Exemples\Producte
Per evitar la referència qualificada podem declarar l'ús mitjançant use (similar a fer import a Java). Es fa a la capçalera, després del namespace:
Els tipus possibles són:
use const nomQualificatConstantuse function nomQualificatFunciouse nomQualificatClasseuse nomQualificatClasse as NouNom// per renomenar elements
Per exemple, si volem utilitzar la classe \DWES\Exemples\Producte des d'un recurs que es troba a l'arrel, per exemple a inici.php, faríem:
<?php
include_once("DWES\Exemple\Producte.php");
use const DWES\Exemples\IVA;
use \DWES\Exemples\Producte;
echo IVA;
$p1 = new Producte();
To use or not to use
En resum, use permet accedir sense qualificar recursos que estan en un altre namespace. Si estem en el mateix espai de nom, no necessitem use.
Organització¶
Tot projecte, a mesura que creix, necessita organitzar el seu codi font. Es planteja una organització en la qual els arxius que interactuen amb el navegador es col·loquen en l'arrel, i les classes que defensem van dins d'un namespace (i dins de la seva pròpia carpeta o app).
Organització, includes i usos
- Col·locarem cada recurs en un fitxer a part.
- En la primera línia indicarem el seu namespace (si no està en l'arrel).
- Si utilitzem altres recursos, farem un
include_onced'aquests recursos (classes, interfícies, etc...). - Cada recurs ha d'incloure tots els altres recursos que referenciï: la classe de la qual hereta, interfícies que implementa, classes utilitzades/rebudes com paràmetres, etc...
- Si els recursos estan en un espai de noms diferent al que estem, emprarem
useamb la ruta completa per a després utilitzar referències sense qualificar.
Autoload¶
No és tediós haver de fer l 'include de les classes? L 'autoload ve al rescat.
Així doncs, permet carregar les classes (no les constants ni les funcions) que s'utilitzaran i evitar haver de fer el include_once de cadascuna d'elles. Per a això, s'utilitza la funció spl_autoload_register
<?php
spl_autoload_register( function( $nomClasse ) {
include_once $nomClasse.'. php';
} );
?>
Per què s'anomenen autoload?
Perquè abans es realitzava mitjançant el mètode màgic __autoload(), el qual està deprecated des de PHP 7.2
I com organitzem ara el nostre codi aprofitant l 'autoload?
Per facilitar la recerca dels recursos a incloure, és recomanable col·locar totes les classes dins d'una mateixa carpeta. Nosaltres les col·locarem dins d 'app. Altres carpetes que podem crear són test per col·locar les proves PhpUnit que després realitzarem, o la carpeta vendor on s'emmagatzemaran les llibreries del projecte (aquesta carpeta és un estàndard dins de PHP, ja que Composer la crea automàticament).
Com hem col·locat tots els nostres recursos dins d 'app, ara el nostre autoload.php (el qual col·loquem a la carpeta arrel) només va a buscar dins d'aquesta carpeta:
<?php
spl_autoload_register( function( $nomClasse ) {
include_once "app/".$nomClasse.'. php';
} );
?>
Autoload i rutes errònies
A Ubuntu en fer l'include de la classe que rep com a paràmetre, les barres dels namespace (\) són diferents a les de les rutes (/). Per això, és millor que utilitzem el fitxer autoload:
<?php
spl_autoload_register( function( $nomClasse ) {
$ruta = "app\\".$nomClasse.'. php';
$ruta = str_replace("\\", "/", $ruta); // Substituïm les barres
include_once $ruta';
} );
?>
Gestió d'Errors¶
PHP classifica els errors que ocorren en diferents nivells. Cada nivell s'identifica amb una constant. Per exemple:
E_ERROR: errors fatals, no recuperables. S'interromp l'script.E_WARNING: advertències en temps d'execució. L'script no s'interromp.E_NOTICE: avisos en temps d'execució.
Podeu comprovar el llistat complet de constants de https://www.php.net/manual/es/errorfunc.constants.php
Per a la configuració dels errors podem fer-ho de dues formes:
- A nivell de
php.ini:error_reporting: indica els nivells d'errors a notificarerror_reporting = E_ALL & ~E_NOTICE-> Tots els errors menys els avisos en temps d'execució.
display_errors: indica si mostrar o no els errors per pantalla. En entorns de producció és comú posar-lo aoff
- mitjançant codi amb les funcions següents:
error_reporting(codi)-> Controla quins errors notificarset_error_handler(nomManejador)-> Indica que s'invocarà cada vegada que es trobi un error. El manejador rep com a paràmetres el nivell de l'error i el missatge
A continuació tenim un exemple mitjançant codi:
<?php
error_reporting(E_ALL &E_NOTICE &E_WARNING);
$resultat = $divident / $divisor;
error_reporting(E_ALL &E_NOTICE);
set_error_handler("manejadorErrors");
$resultat = $divident / $divisor;
restore_error_handler(); // torna a l'anterior
function manejadorErrors($nivell, $missatge) {
switch($nivell) {
case E_WARNING:
echo "<strong>Warning</strong>: $missatge.<br/>";
break;
default:
echo "Error de tipus no especificat: $missatge.<br/>";
}
}
Error de tipus no especificat: Undefined variable: dividend.
Error de tipus no especificat: Undefined variable: divisor.
Error de tipo Warning: Division by zero.
Excepcions¶
La gestió d'excepcions forma part des de PHP 5. El seu funcionament és similar a Java, fent ús d'un bloc try / catch / finally. Si detectem una situació anòmala i volem llançar una excepció, haurem de realitzar throw new Exception (adjuntant el missatge que l'ha provocat).
<?php
try {
if ($divisor == 0\) {
throw new Exception("Divisió per zero.");
}
$resultat = $divident / $divisor;
} catch (Exception $e) {
echo "S'ha produït el següent error: ".$e->getMessage();
}
La classe Exception és la classe pare de totes les excepcions. El seu constructor rep missatge[,codiError][,excepcionPrevia].
A partir d'un objecte Exception, podem accedir als mètodes getMessage()i getCode() per obtenir el missatge i el codi d'error de l'excepció capturada.
El propi llenguatge ofereix un conjunt d'excepcions ja definides, les quals podem capturar (i llançar des de PHP 7). Es recomana la seva consulta en la documentació oficial.
Creant excepcions¶
Per crear una excepció, la forma més curta és crear una classe que únicament hereti d 'Exception.
<?php
class HolaExcepcio extends Exception {}
Si volem, i és recomanable depenent dels requisits, podem sobrecarregar els mètodes màgics, per exemple, sobrecarregant el constructor i trucant al constructor del pare, o rescriure el mètode __toString per canviar el seu missatge:
<?php
class Excepcio extends Exception {
public function __construct($msj, $codi = 0, Exception $previa = null) {
// codi propi
parent::__construct$msj, $codi, $previa);
public function __toString() {
return __CLASS__ . ": [{$this->codxxe}]: {$this->message}\n";
}
public function laMevaFuncio() {
echo "Una funció personalitzada per a aquest tipus d'excepció\n";
}
}
Si definim una excepció d'aplicació dins d'un namespace, quan referenciem a Exception, haurem de referenciar-la mitjançant el seu nom totalment qualificat (\Exception), o utilitzant use:
<?php
namespace \DWES\Exemples;
class AppExcepcio extends Exception {}
<?php
namespace \DWES\Exemples;
use Exception;
class AppExcepcion extends Exception {}
Excepcions múltiples¶
Es poden fer servir excepcions múltiples per comprovar diferents condicions. A l'hora de capturar-les, es fa de més específica a més general.
<?php
$email = "exemple@exemple.com";
try {
// Comprova si l'email és vàlid
if(filter_var($email, FILTER_VALIDATE_EMAIL) === FALSE) {
throw new Excepcio($email);
}
// Comprova la paraula exemple en la direcció email
if(strpos($email, "exemple") !== FALSE) {
throw new Exception("$email és un email d'exemple no vàlid");
}
} catch (Excepcio $e) {
echo $e->laMevaFuncio();
} catch(Exception $e) {
echo $e->getMessage();
}
Autoavaluació
Què passaria en executar el següent codi?
<?php
class MainException extends Exception {}
class SubException extends MainException {}
try {
throw new SubException("Llançada SubException");
} catch (MainException $e) {
echo "Capturada MainException " . $e->getMessage();
} catch (SubException $e) {
echo "Capturada SubException " . $e->getMessage();
} catch (Exception $e) {
echo "Capturada Exception " . $e->getMessage();
}
Si en el mateix 'catch' volem capturar diverses excepcions, hem d'utilitzar l'operador '|':
<?php
class MainException extends Exception {}
class SubException extends MainException {}
try {
throw new SubException("Llançada SubException");
} catch (MainException | SubException $e ) {
echo "Capturada Exception " . $e->getMessage();
}
Des de PHP 7, existeix el tipus Throwable, el qual és un interfície que implementen tant els errors com les excepcions, i ens permet capturar els dos tipus alhora:
<?php
try {
// el teu codi
} catch (Throwable $e) {
echo 'Forma de capturar errors i excepcions alhora';
}
Si només volem capturar els errors fatals, podem fer ús de la classe Error:
<?php
try {
// Genera una notificació que no es captura
echo $variableNoAsignada;
// Error fatal que es captura
funcionQueNoExiste();
} catch (Error $e) {
echo "Error capturat: " . $e->getMessage();
}
Rellançar excepcions¶
En les aplicacions reals, és molt comú capturar una excepció de sistema i llançar-n'una d'aplicació que hem definit nosaltres. També podem llançar les excepcions sense necessitat d'estar dins d'un try/catch.
<?php
class AppException extends Exception {}
try {
// Codi de negoci que falla
} catch (Exception $e) {
throw new AppException("AppException: ".$e->getMessage(), $e->getCode(), $e);
}
SPL¶
Standard PHP Library és el conjunt de funcions i utilitats que ofereix PHP, com:
- Estructures de dades
- Pila, cua, cua de prioritat, llista doblement enllaçada, etc...
- Conjunt d'iteradors dissenyats per recórrer estructures agregades
- arrays, resultats de bases de dades, arbres XML, llistats de directoris, etc.
Podeu consultar la documentació a https://www.php.net/manual/es/book.spl.php o veure alguns exemples a https://diego.com.es/tutorial-de-la-libreria-spl-de-php
També defineix un conjunt d'excepcions que podem utilitzar perquè les llancin les nostres aplicacions:
LogicException(extends Exception)BadFunctionCallExceptionBadMethodCallExceptionDomainExceptionInvalidArgumentExceptionLengthExceptionOutOfRangeException
RuntimeException(extends Exception)OutOfBoundsExceptionOverflowExceptionRangeExceptionUnderflowExceptionUnexpectedValueException
També podeu consultar la documentació d'aquestes excepcions a https://www.php.net/manual/es/spl.exceptions.php.
