TODO:
* traits
* namespaces
* stdclass ?
* iterable
* closure
Affichage debug d'un objet
<?php
class Titi {
private bool $x = false;
protected string $y = "";
public $z = null;
function methode() { }
}
echo "---- var_export ----\n";
var_export(new Titi());
echo "---- print_r ----\n";
print_r(new Titi());
echo "---- var_dump ----\n";
var_dump(new Titi());
?>
---- var_export ----
\Titi::__set_state(array(
'x' => false,
'y' => '',
'z' => NULL,
))---- print_r ----
Titi Object
(
[x:Titi:private] =>
[y:protected] =>
[z] =>
)
---- var_dump ----
/users/ensweb/www-prod/TW3/pres/oophp/demo/exPrintDebug.php:17:
class Titi#3 (3) {
private bool $x =>
bool(false)
protected string $y =>
string(0) ""
public $z =>
NULL
}
Mot clé final
final
pour une méthode : la méthode ne peut pas être redéfinie dans une classe dérivée
final
pour une classe : la classe ne peut pas être dérivée
<?php
class finalEx {
private string $donnee = "exemple";
final function maj(): string {
return strtoupper($this->donnee);
}
}
final class finalExtend extends finalEx {
/*
* Produira une erreur fatale :
* Fatal error: Cannot override final method finalEx::maj()
*/
/*
public function maj(): string {
return $this->donnee;
}
*/
}
/*
* Produira une erreur fatale :
* Fatal error: Class erreur may not inherit from final class (finalExtend)
*/
/*
class erreur extends finalExtend {
// impossible
}
*/
$class = new finalEx();
echo $class->maj();
echo "\n";
$classExtend = new finalExtend();
echo $classExtend->maj();
?>
EXEMPLE
EXEMPLE
Classe et méthode abstraite
- Mot clé
abstract
- Une classe abstraite ne peut pas être instanciée
- Une méthode abstraite définit sa signature, pas son implémentation
- Une méthode abstraite doit être dérivée avec une visibilité supérieure
<?php
abstract class AbstractClass
{
// Force la classe étendue à définir cette méthode
abstract protected function getValue(): string;
// Méthode commune
public function printOut(): void
{
echo $this->getValue();
}
}
class ConcreteClass1 extends AbstractClass
{
protected function getValue(): string
{
return "ConcreteClass1";
}
}
class ConcreteClass2 extends AbstractClass
{
protected function getValue(): string
{
return "ConcreteClass2";
}
}
$class1 = new ConcreteClass1;
$class1->printOut();
$class2 = new ConcreteClass2;
$class2->printOut();
ConcreteClass1ConcreteClass2
Interfaces
- Une interface fournit un plan général en spécifiant les méthodes publiques qu’une classe doit implémenter,
sans impliquer la complexité et les détails de l’implémentation des méthodes.
- Définir avec le mot clé
interface
- L’interface ne contient pas de propriétés ou de variables comme le cas dans une classe.
- Une classe implémente une interface :
class maClasse implements monInterface
- Existence d'interfaces "internes" prédéfinies dans PHP5
- Traversable dont le but est d’être l’interface de base de toutes les classes permettant de parcourir des objets
- Iterator qui définit des signatures de méthodes pour les itérateurs
- IteratorAggregate qui est une interface qu’on va pouvoir utiliser pour créer un itérateur externe
- Throwable qui est l’interface de base pour la gestion des erreurs et des exceptions
- ArrayAccess qui permet d’accéder aux objets de la même façon que pour les tableaux
- Serializable qui permet de personnaliser la linéarisation d’objets.
Différences entre les interfaces et les classes abstraites sont :
- Une interface ne peut contenir que les signatures des méthodes ainsi qu’éventuellement des constantes mais pas de propriétés.
Cela est dû au fait qu’aucune implémentation n’est faite dans une interface : une interface n’est véritablement qu’un plan ;
- Une classe ne peut pas étendre plusieurs autres classes à cause des problèmes d’héritage. En revanche, une classe peut
tout à fait implémenter plusieurs interfaces.
Exemple
<?php
interface Abonne {
public const ABONNEMENT = 15;
public function getNom(): string;
public function setPrixAbo(): void;
public function getPrixAbo(): float;
}
class Etudiant implements Abonne {
protected string $nom;
protected string $persopass;
protected string $region;
protected float $prix_abo;
public function __construct(string $n, string $p, string $r) {
$this->nom = $n;
$this->persopass = $p;
$this->region = $r;
}
public function getNom(): string {
return $this->nom;
}
public function getPrixAbo(): float {
return $this->prix_abo;
}
public function setPrixAbo(): void {
if ($this->region === 'Normandie') {
$this->prix_abo = Abonne::ABONNEMENT / 2;
} else {
$this->prix_abo = Abonne::ABONNEMENT;
}
}
}
/*
class Etudiant implements Abonne, Stagiaire {
// code...
}
class Salarie implements Abonne{
// code...
}
*/
$valentin = new Etudiant('Pierre', 'abcdef', 'Normandie');
$sophie = new Etudiant('Sophie', '123456', 'Bretagne');
$gael = new Etudiant('Gael', 'flotri', 'Occitanie');
$valentin->setPrixAbo();
$sophie->setPrixAbo();
$gael->setPrixAbo();
echo 'Prix de l\'abonnement pour ' . $valentin->getNom() . ' : ' . $valentin->getPrixAbo() . '<br>';
echo 'Prix de l\'abonnement pour ' . $sophie->getNom() . ' : ' . $sophie->getPrixAbo() . '<br>';
echo 'Prix de l\'abonnement pour ' . $gael->getNom() . ' : ' . $gael->getPrixAbo();
?>
Prix de l'abonnement pour Pierre : 7.5
Prix de l'abonnement pour Sophie : 15
Prix de l'abonnement pour Gael : 15
Une classe peut implémenter plusieurs interfaces, séparées par des virgules.
Type Nullable
Dans PHP 8, l'introduction des types nullables offre une flexibilité supplémentaire lors de la déclaration des types pour les paramètres de fonction, les valeurs de retour et les propriétés de classe. Un type nullable est déclaré en ajoutant un point d'interrogation (?) avant le type.
//Types Nullable pour les Valeurs de Retour
function diviser(?float $a, ?float $b): ?float {
if ($b === 0.0) {
return null;
}
return $a / $b;
}
// Exemple d'utilisation
$resultat = diviser(10.0, 2.0);
//Types Nullable pour les Paramètres de Fonction
function exemple(?string $parametre) {
if ($parametre === null) {
echo "Le paramètre est null.";
} else {
echo "Le paramètre est : " . $parametre;
}
}
// Exemple d'utilisation
exemple("valeur");
exemple(null);
//Types Nullable pour les Propriétés de Classe
class Exemple {
public ?string $propriete;
public function __construct(?string $valeur) {
$this->propriete = $valeur;
}
}
// Exemple d'utilisation
$objet = new Exemple("valeur");
echo $objet->propriete;
Espaces de noms
Les namespaces permettent de définir un nom de package que l'on pourra ensuite charger de manière automatique gràce à un autoloading.
Pour déclarer un namespace, il faut utiliser la commande namespace
suivi du nom du namespace à créer.
On prendra l'habitude de créer nos classes dans un namespace pour éviter les collisions !
Par exemple, nous allons placer notre class Exemple dans un namespace MonNamespace.
Exemple
<?php
namespace Tutoriel\MonNamespace;
function strlen()
{
echo 'Hello world ! <br/>';
}
strlen();
echo \strlen('Hello world !');
?>
Hello world !
13
Héritage vs composition
Un principe important en POO est de favoriser la composition
plutôt que l'héritage pour mutualiser du code
En effet l'héritage a des conséquences pénibles, notamment le fait qu'à partir du moment où on a sous-classé une classe, il devient difficile de la faire évoluer (tout changement a des conséquences sur les classes filles)
cela contrevient à la modularisation du code — les classes sont trop dépendantes les unes des autres, alors que le but de la POO est plutôt d'essayer de créer des « boîtes noires » dont le comportement interne n'a pas d'influence à l'extérieur
En utilisant la composition, on garde l'indépendance entre les classes
le prix à payer est une syntaxe un peu plus lourde dans la classe, et l'obligation parfois d'écrire des méthodes de « délégation »
ex autom cell: comportement en fonction de la règle, en fonction de l'affichage voulu
Traits
Les traits de PHP sont (principalement) un moyen de faire de la composition,
sans les inconvénients
trait MonTrait {
function uneMethodeUtile() {
}
class Toto {
use MonTrait;
}
Dans l'exemple ci-dessus, la classe
Toto
contient la méthode
uneMethodeUtile
, comme si elle avait hérité de
MonTrait
, sauf qu'il n'y a pas eu d'héritage :
- Toto peut très bien étendre une autre classe
- Toto peut utiliser d'autres traits, pas de problème d'héritage multiple (en cas de conflit de nommage, il faut obligatoirement définir explicitement la méthode concernée dans
Toto
- On peut utiliser une même méthode dans plusieurs classes, sans qu'elles soient liées par le typage
Les inconvénients de la composition disparaissent, puisque tout se passe comme si la méthode était définie dans la classe :
- syntaxe simple
- pas besoin d'écrire des méthodes de délégation
Méthodes magiques
- Méthodes
__sleep
et __wakeup
: code à exécuter lors de la sérialisation et désérialisation d'un objet
- Utile pour les ressources (connection BDD, ressource fichier)
- Méthodes
__get __set __call
pour gérer la surcharge objet (voir le manuel)
- à utiliser pour gérer les erreurs, pas « pour de vrai » ! peu efficace + code moins lisible, moins analysable par des outils + leaky abstraction, voir un exemple dans la doc
Linéarisation / Délinéarisation
En PHP, la linéarisation est effectuée nativement avec les deux fonctions suivantes :
Linéarisation :
avec la fonction serialize()
qui prend en paramètre la donnée à sérialiser et vous retourne une chaîne de caractère,
Délinéarisation
avec la fonction unserialize()
qui effectue l’opération inverse en prenant une chaîne de caractère et vous fournissant les données correspondantes.
<?php
class Personne {
public string $nom;
public function __construct(string $nom) {
$this->nom = $nom;
}
public function qui(): string {
return $this->nom;
}
public function salut(Personne $autre): string {
return "Ravi de vous rencontrer " . $autre->nom . ", <br/>". " Je suis " . $this->nom . "<br/>";
}
}
// Création de deux objets
$youssef = new Personne("Youssef");
$jean = new Personne("Jean");
echo $youssef->salut($jean);
echo serialize($youssef);
echo "</br>";
$test = unserialize(serialize($youssef));
echo "</br>";
echo ($test->qui());
?>
Ravi de vous rencontrer Jean,
Je suis Youssef
O:8:"Personne":1:{s:3:"nom";s:7:"Youssef";}Youssef
Itérables
- Les Itérables peuvent être utilisés comme type d'argument pour indiquer qu'une fonction a besoin d'un ensemble de valeurs
Un iterator doit avoir ces methodes:
- current() : Renvoie l'élément courant
- key() : Renvoie la clé associée à l'élément actuel .
- next() : Element suivant
- rewind() : 1er element
- valid() : S'il ne pointe sur aucun élémenf
<?php
// Create an Iterator
class MyIterator implements Iterator {
private array $items = [];
private int $pointer = 0;
public function __construct(array $items) {
// array_values() makes sure that the keys are numbers
$this->items = array_values($items);
}
public function current(): ?string {
return $this->items[$this->pointer];
}
public function key() : int{
return $this->pointer;
}
public function next() : void {
$this->pointer++;
}
public function rewind() : void {
$this->pointer = 0;
}
public function valid() : bool {
return $this->pointer < count($this->items);
}
}
function affiche(Traversable $myIterable): void{
foreach($myIterable as $item) {
echo $item . "<br/>";
}
}
// Use the iterator as an iterable
$iterator = new MyIterator(["a", "b", "c"]);
affiche($iterator);
?>
a
b
c
Conventions de nommage en PHP
Les conventions de nommage sont un ensemble de «lignes directrices» définissant le cas à utiliser pour nommer les éléments
de votre code. C'est à dire comment créer des noms de variables, de fonctions et classes en PHP ?
Elles ne sont que des lignes directrices (rien d'obligatoire mais juste une bonne pratique).
Ceci est fait pour garder votre code uniforme, ce qui le rend plus facile à suivre et à comprendre.
Les deux cas utilisés le plus souvent sont le
Pascal Case
et le
Camel Case
.
- Pascal case -> NomEnPascalCase (aussi appelée Upper Camel Case)
Pascal case c'est quand la première lettre de chaque mot commence par une majuscule sans espaces entre les mots.
- Camel Case -> nomEnCamelCase (aussi appelé Lower Camel Case)
Camel cas c'est la même chose, sauf que la première lettre du mot commence par une lettre minuscule.
Les bases et les généralités
- Les mots clefs abstract ou static doivent passer avant la visibilité d'une variable de classe
- Déclarez une classe par fichier
- Les propriétés de classe avant les méthodes
- Les méthodes dans cet ordre : publiques, protégées, privées.
Classe :
MaClasse (Upper Camel Case) //Le nom de la classe doit indiquer l'objet créé (User, Article...)
Fichier
(script tout bête sans classe dedans) : mon_script.php
Fichier de classe :
MaClasse.php (Upper Camel Case)
Fonction :
maFonction() (camel Case) //Le nom doit décrire le comportement de la fonction ou de la méthode
variable :
$maVariable, $monArray, $maString, $monEntier (camel Case) Les noms doivent décrire les données dans la variable.
constante :
NOM_DE_CONSTANTE (ALL_CAPS -> tout en majuscule). Il faut utiliser le signe '_' pour séparer les mots dans des constantes
-
bool :
true, false en minscule
- Une difficile : un fichier doit soit contenir des déclarations de fichiers, mais pas de code executable.
-
Entendre par là, vous pouvez déclarer des variables et des fonctions dans un fichier, mais rien qui ne s'exécute à
l'inclusion de ce fichier
- A l'exception du contenu des classes bien entendu
Autoload et espace de noms
- Les espaces de noms et les classes doivent utiliser l'autoload
- Il n'y a pas de limitation quant à la quantité de niveaux pour les espaces de nom
- Dans l'espace de nom, chaque séparateur est converti en constante DIRECTORY_SEPARATOR
- Même chose pour le caractère '_' dans le nom de la classe. Par contre, aucune signification dans l'espace de nom
- L'espace de nom doit comporter l'extension .php quand il est chargé par le système
- Les
Use
doivent apparaitre après les espaces de nom
- Une ligne vide après la fin des déclarations de vos espaces de nom