Apprendre PHP & MySQL : Programmation Orientée Objet (POO)

La Programmation Orientée Objet (POO) est un paradigme de programmation très structuré, centré sur le principe de Classes.

Icône de calendrier
Intermédiaire
12 chapitres

Deux grands paradigmes de programmation

Programmation Procédurale

La programmation procédurale est la « programmation école » par excellence. Il s’agit d’un sous-ensemble de la programmation impérative.

C’est une manière de programmer très répandue, reposant sur une séquence d’instructions informatiques ordonnées et finies. Les instructions du programme sont découpées en fonctions successives appelées procédures. Grossièrement, c’est de l’algorithmie organisée !

Une fonction peut manipuler de manière flexible les données globales du programme, mais aussi ses propres données (= données locales).

La programmation procédurale est intuitive et spontanée à l’écriture, ce qui rend le code généralement léger. En revanche, son aspect chronologique et interdépendant complexifie son exploitation, à mesure qu’un projet se développe : maintenance et scalabilité (simplicité d’évolution) s’y verront plus complexes.

Programmation Orientée Objet

Dans la programmation orientée objet le programme ne s’exécute pas via des fonctions, mais bien des objets. Un objet est une structure de données organisée contenant ses propres données appelées attributs, mais aussi fonctions appelées méthodes.

De par sa nature, un objet possède une structure rigide, formant une unité, capable d’interagir avec d’autres objets au besoin.

La programmation objet nécessite un gros travail d’analyse et de conception en amont pour créer ces fameux modèles de données. Cela induit logiquement un code plus lourd, mais facilite son exploitation : maintenance et scalabilité simplifiées.

Programmation Procédurale VS Programmation Objet

Comparons ces 2 paradigmes pour bien comprendre leurs différences.

CritèreProgrammation ProcéduraleProgrammation Objet
ProgrammeDécoupé en fonctions successivesObjets interagissant entre eux
CodeLéger et moins structuréLourd et structuré
DonnéesLes fonctions agissent sur leurs propres données mais aussi sur des données globalesLes objets manipulent leurs propres données
Domaine d’utilisationLorsque la mécanique a plus d’importance que la structure de données (traitement spécifique)Lorsque la structure de données a plus d’importance que la mécanique (traitement classique - CRUD)

Ce qu’il faut retenir :

Dans la programmation procédurale, les programmes sont exécutés avec des fonctions, et les données peuvent être facilement accessibles et modifiables, alors qu’en programmation orientée objet, chaque programme est constitué d’entités appelées objets, plus difficilement accessibles et modifiables.

Un monde de classes et d’objets

Classes

Une classe est un modèle de données définissant la structure commune à tous les objets qui seront créés à partir d’elle. C’est un concept.

À chaque classe sont associées des données (= propriétés ou attributs) et des actions (méthodes).

Exemple de classe : une voiture

  • Attributs : marque, modèle, couleur, vitesse maximale, kilométrage…
  • Méthodes : accélérer, freiner, tourner…

Objets

Un objet, aussi appelé instance de classe, est une représentation particulière de cette classe. Il peut représenter une chose matérielle ou immatérielle.

Exemples d’objets :

  • La Seat Ibiza noire de cousin Kévin.
  • Le Kangoo blanc de tonton José.

Création d’une classe

Mot-clé class

On déclare une classe PHP avec le mot clé class, suivi du nom de la classe, dont la première lettre doit systématiquement être écrite en majuscule (en PascalCase). Généralement cette classe est créée dans un fichier spécifique, portant le nom de la classe en question (en conservant toujours la majuscule au début).

Car.php
copié !
class Car {

	// ...

}

Attributs

Les attributs sont les caractéristiques d’un objet.

Un attribut n’est ni plus ni moins qu’une variable PHP que l’on préfixera par un mot clé spécifiant sa visibilité : public, private ou protected.

Nous reviendrons sur le concept de visibilité un peu plus tard dans ce chapitre.

Car.php
copié !
class Car {

	// Attributs
	public string $brand;
	public string $model;
	public string $color;
	public int $max_speed;
	public float $mileage;

}

On ne définit généralement pas de valeur à ces variables car nous sommes ici sur la classe (le modèle). En revanche, chaque objet instancié de cette classe va venir hydrater les propriétés de cet objet avec ses propres valeurs.

Constantes

En POO, les constantes vont nous permettre de définir des valeurs rattachées à une classe toute entière, cela signifie qu’elles seront communes à l’ensemble des objets instanciés de cette classe.

Leur utilisation est donc naturellement plus occasionnelle au sein de nos classes. Si en programmation procédurale on peut déclarer une constante avec le mot-clé const ou avec la fonction define(), dans le contexte d’une classe, nous n’avons pas ce luxe et cela se fera uniquement avec const.

Car.php
copié !
class Car {

	// Attributs

	// Constantes
	const MAX_MILEAGE = 500000;

}

Méthodes

Les méthodes sont les actions applicables à un objet. Elles ne sont ni plus ni moins que des fonctions classiques et peuvent avoir ou non des paramètres ainsi que retourner ou non des données.

Au même titre que les attributs, on spécifie un niveau de visibilité pour chaque méthode.

On distingue 2 grands types de méthodes : méthodes magiques et personnelles.

Méthodes magiques

Les méthodes magiques sont des fonctions déjà prévues par le langage. Nous n’évoquerons ici que les 3 méthodes magiques principales, à savoir les getter (accesseurs), setter (mutateurs) et construct (constructeur), mais il en existe de nombreuses autres.

Constructeur

Le constructeur d’une classe est une méthode bien particulière, car elle est appelée automatiquement à chaque instanciation de la classe (lors de la création d’un objet - nous verrons ceci un peu plus tard dans ce chapitre). Il porte systématiquement le nom __construct et son rôle est de construire l’objet en hydratant ses attributs avec les données transmises via les arguments.

Car.php
copié !
class Car {

	// Attributs
	// Constantes

	// Constructeur
	public function __construct(string $brand, string $model, string $color, int $max_speed, float $mileage = 0) {
		$this->brand = $brand;
		$this->model = $model;
		$this->color = $color;
		$this->max_speed = $max_speed;
		$this->mileage = $mileage;
	}

}

La pseudo-variable $this est disponible lorsqu’une méthode est appelée depuis un contexte objet. Elle fait référence à l’objet appelant. $this->brand fait donc référence à l’attribut $brand de l’objet qui est instancié.

Accesseurs

Un accesseur (aussi appelé getter) est une méthode spécifique dont le rôle est d’accéder aux attributs d’un objet. Il porte par convention le préfixe get, suivi du nom de l’attribut auquel il fait référence, en camelCase.

Car.php
copié !
class Car {

	// Attributs
	// Constantes
	// Constructeur

	// Accesseurs
	public function getBrand(): string {
		return $this->brand;
	}
	public function getModel(): string {
		return $this->model;
	}
	// ...

}
Mutateurs

Un mutateur (aussi appelé setter) est une méthode spécifique dont le rôle est de modifier les attributs d’un objet. Il porte par convention le préfixe set, suivi du nom de l’attribut auquel il fait référence, en camelCase.

Car.php
copié !
class Car {

	// Attributs
	// Constantes
	// Constructeur
	// Accesseurs

	// Mutateurs
	public function setBrand(string $brand): void {
		$this->brand = $brand;
	}
	public function setModel(string $model): void {
		$this->model = $model;
	}

}

Méthodes personnelles

Les méthodes personnelles sont des fonctions sur mesure qu’il est possible de définir à l’intérieur de nos classes.

On nomme généralement ces méthodes par un verbe à l’infinitif, en camelCase et en anglais of course.

Car.php
copié !
class Car {

	// Attributs...
	// Constantes...
	// Constructeur
	// Accesseurs / Mutateurs

	// Méthodes persos
	public function move(float $km): void {
		if ($this->mileage + $km < self::MAX_MILEAGE) {
			$this->mileage += $km;
		} else {
			echo "Le moteur vient de lâcher... :(";
		}
	}

}

Dans la méthode suivante, nous accédons à la constante MAX_MILEAGE non pas avec $this mais bien avec le mot-clé self et l’opérateur de résolution de portée ::. Cet opérateur permet de cibler la classe elle-même et non une de ses instances.

self est utilisé au lieu de $this étant donné qu’une constante appartient à la classe toute entière et n’est pas propre à un objet spécifique.

Visibilité et principe d’encapsulation

Visibilité

Un attribut, une constante ou une méthode peuvent être définis selon 3 degrés de visibilité :

  • public : Les attributs, constantes ou méthodes définies avec le mot clé public vont être accessibles partout, c’est-à-dire depuis l’intérieur ou l’extérieur de la classe.
  • private : Les attributs, constantes ou méthodes définies avec le mot clé private ne vont être accessibles que depuis l’intérieur de la classe qui les a définies.
  • protected : Les attributs, constantes ou méthodes définies avec le mot clé protected ne vont être accessibles que depuis l’intérieur de la classe qui les a définies et depuis les classes qui en héritent. Nous reviendrons en fin de chapitre sur ce concept élémentaire d’héritage.

Encapsulation

Mais pourquoi définir plusieurs niveaux de visibilité ? Et bien pour que chacun n’ait connaissance que de ce qui l’intéresse ; c’est le principe d’encapsulation.

Pour rester dans le monde automobile, imaginez-vous avoir reçu un caillou sur votre pare-brise lorsque vous rouliez. Vous allez chez le garagiste pour lui demander une réparation.

Chez le garagiste, il faut considérer qu’il y a 2 zones :

  • Une zone publique : le guichet.
  • Une zone privée : l’atelier.

En tant que client vous allez simplement formuler votre requête auprès du guichet et allez attendre que la réparation se fasse. Comment va procéder le mécanicien pour la réparation ? On s’en fiche, c’est son rôle et seul le résultat vous importe.

  • Dans cet exemple, le guichet est ce qu’on appelle l’interface ; c’est la partie publique avec laquelle on interagit.
  • Dans cet exemple, l’atelier est ce qu’on appelle l’implémentation ; c’est la partie privée qui travaille en arrière-plan.

Chaque acteur va jouer un rôle dans un processus plus global, et dans ce besoin de déléguer les rôles, il y a ce besoin de séparer l’interface de l’implémentation.

Le bon fonctionnement de notre société est garanti par le principe d’encapsulation, cela vaut aussi pour notre développement.

L’encapsulation permet d’éviter des problèmes de développement potentiels via des contrôles d’accès (pas d’interférences extérieures) mais aussi de faciliter l’évolution de notre code.

En POO, l’encapsulation consiste concrètement à donner une visibilité à des attributs et méthodes afin d’en contrôler leur accès.

Attributs et encapsulation

Pour respecter le principe d’encapsulation, les attributs sont systématiquement manipulés à l’intérieur des classes ; il convient donc de spécifier une visibilité private (ou protected - cf. héritage) sur les attributs des classes.

Un objet instancié d’une classe ne doit pas manipuler (lecture - écriture) ses attributs directement, il doit passer par l’intermédiaire d’accesseurs et de mutateurs.

Car.php
copié !
class Car {

	// Attributs
	private string $brand;
	private string $model;
	private string $color;
	private int $max_speed;
	private float $mileage;

}

Méthodes et encapsulation

Constructeur

Pour être accessible à l’extérieur de la classe et rendre une instanciation possible, le constructeur se doit de toujours être en visibilité public.

Car.php
copié !
class Car {

	// Constructeur
	public function __construct() { ... }

}
Accesseurs et mutateurs

Les accesseurs et mutateurs jouent le rôle d’interface pour accéder / modifier les propriétés private d’un objet.

Imaginons que les voitures d’une marque ne puissent être peintes que de certaines couleurs. Si on pouvait directement accéder à l’attribut $color en public à l’extérieur de la définition de la classe, cela permettrait de lui affecter n’importe quelle valeur.

En passant par un mutateur, nous allons pouvoir définir des contrôles.

Car.php
copié !
class Car {

	// Mutateurs
	public function setColor(string $color): void {
		$color = strtolower($color);
		$allowed_colors = [
			"noir",
			"blanc",
			"rouge",
			"vert",
			"bleu"
		];
		// Check si la couleur existe bien dans le catalogue
		if (in_array($color, $allowed_colors)) {
			$this->color = $color;
		} else {
			$this->color = "noir"; // Couleur par défaut
		}
	}

}
Méthodes perso et encapsulation

Dans le cas d’une méthode perso, tout dépendra de son contexte d’utilisation. Les 3 visibilités peuvent être définies.

Ex : Une méthode vouée à n’être utilisée qu’au sein d’une classe pourrait tout à fait être private.

Voici le code complet de la classe Car précédente :

Car.php
copié !
class Car {

	private string $brand;
	private string $model;
	private string $color;
	private int $max_speed;
	private float $mileage;

	const MAX_MILEAGE = 500000;

	public function __construct(string $brand, string $model, string $color, int $max_speed, float $mileage = 0) {
		$this->brand = $brand;
		$this->model = $model;
		$this->color = $color;
		$this->max_speed = $max_speed;
		$this->mileage = $mileage;
	}

	public function getBrand(): string {
		return $this->brand;
	}
	public function getModel(): string {
		return $this->model;
	}
	public function getColor(): string {
		return $this->color;
	}
	public function getMaxSpeed(): int {
		return $this->max_speed;
	}
	public function getMileage(): float {
		return $this->mileage;
	}

	public function setBrand(string $brand): void {
		$this->brand = $brand;
	}
	public function setModel(string $model): void {
		$this->model = $model;
	}
	public function setColor(string $color): void {
		$this->color = $color;
	}
	public function setMaxSpeed(int $max_speed): void {
		$this->max_speed = $max_speed;
	}
	public function setMileage(float $mileage): void {
		$this->mileage = $mileage;
	}

	public function move(float $km): void {
		if ($this->mileage + $km < self::MAX_MILEAGE) {
			$this->mileage += $km;
		} else {
			echo "Oups, le moteur vient de lâcher... :(";
		}
	}

	public function goToJackyTuning(): void {
		$this->max_speed += 20;
	}

}

Instanciation d’une classe

Mot-clé new

Une classe seule, n’a pas vraiment d’utilité tant qu’elle n’a pas été instanciée.

Instancions donc notre classe Car pour créer des bolides flambant neufs.

Pour instancier une classe, nous devons :

  • Importer la classe avec la fonction require() (require() et non include() car une classe est « vitale » pour le bon fonctionnement de la page).
  • Faire appel au constructeur avec le mot-clé new et le nom de la classe à instancier. Cette instruction appelle la méthode __construct() qui construit l’objet et le place en mémoire.
copié !
require 'class/Car.php';
$car1 = new Car("Volkswagen", "T-ROC", "Blanc", 200);
$car2 = new Car("Peugeot", "3008", "Bleu", 190, 10000);

Autoload

L’autoload consiste à inclure automatiquement des fichiers de classes utilisées. Pour cela on utilise la fonction PHP spl_autoload_register() dont le rôle est de déclencher la fonction portant le nom spécifié en argument.

copié !
function loadClass(string $class){
	require $class . '.php';
}

spl_autoload_register('loadClass');

La fonction loadClass() de l’extrait de code précédent récupère dans le paramètre $class le nom de la classe utilisée (instanciation ou extension - concept abordé plus tard) en question pour inclure le fichier la contenant.

En savoir plus sur l’autoload en PHP.

Exploitation d’un objet

Une fois créés, il est possible d’accéder aux attributs, constantes et méthodes d’un objet.

Accéder aux attributs

Accès en lecture

Pour récupérer la valeur d’un attribut, on procède selon la syntaxe suivante $objet->attribut.

copié !
echo $car->brand;

Accès en écriture

Pour modifier la valeur d’un attribut, on procède selon la syntaxe suivante $objet->attribut = valeur.

copié !
$car->brand = "BMW";

Accéder aux constantes

L’accès aux constantes se fait bien entendu en lecture seule. Il est impossible de les redéfinir en dehors de la classe, c’est le principe même d’une constante : sa valeur ne doit pas changer, elle reste constante.

Afficher une constante depuis un objet s’apparente à la manière de le faire depuis l’intérieur de la classe (self::CONSTANTE), si ce n’est qu’ici le préfixe sera le nom de la classe, car on se trouve à l’extérieur de cette dernière. On utilisera ainsi la syntaxe Classe::CONSTANTE.

copié !
echo 'Le kilométrage maximal des voitures est de ' . Car::MAX_MILEAGE . ' km.';

Accéder aux méthodes

L’exécution d’une méthode s’apparente à l’exécution d’une fonction classique, si ce n’est qu’elle est rattachée à un objet, de la même manière que les attributs. On utilisera ainsi la syntaxe $objet->methode().

copié !
$car->move(120);

Héritage

Définition

Le concept d’héritage est un des principes fondamentaux de la programmation orientée objet. Il consiste à factoriser du code commun (attributs, constantes et méthodes) à plusieurs classes au sein d’une seule et unique classe, appelée « classe mère ».

Chaque classe rattachée à une classe mère est appelée « classe fille ». Une classe fille peut disposer des caractéristiques (attributs, constantes et méthodes déclarées en public ou protected) de son parent, les redéfinir pour en modifier le comportement (polymorphisme) et ajouter ses propres fonctionnalités.

On dit qu’une classe fille « étend » une classe mère : c’est le principe d’héritage.

Pour reprendre notre classe Car, en réalité les attributs et méthodes que nous avons définis pourraient être communs à de nombreux véhicules… motos et voitures ont tous deux une marque, un modèle, une couleur, une vitesse maximale et un kilométrage. Ces deux types de véhicules sont d’ailleurs tous deux en capacité de rouler…

Il serait alors très maladroit de créer 2 classes distinctes MotorBike et Car avec toutes deux les attributs $brand, $model, $color, $max_speed, $mileage ainsi que la méthode move(). Pourquoi ? Car ce n’est pas très DRY tout ça…

Classe parente

Imaginons que notre ancienne classe Car devienne Vehicle.

Mot-clé abstract

Si je vous demande de penser à un véhicule. Qu’avez-vous en tête ? Une voiture ? Une moto ? Un camion ? Un scooter ? Bref, dans tous les cas, il est impossible pour vous de visualiser la notion abstraite d’un véhicule, vous visualiserez toujours un type de véhicule concret. Votre cerveau ne peut donc pas créer une instance concrète de véhicule mais bien une instance de moto par exemple.

En POO la logique est la même. Certaines classes ne sont vouées qu’à être des définitions abstraites, pensées pour être étendues mais pas pour être directement instanciées. C’est le cas de la classe Vehicle qui ne pourra jamais être instanciée, on peut seulement en hériter : on parle d’abstraction.

On définit une classe comme abstraite en lui ajoutant simplement le mot clé abstract au moment de sa définition.

Vehicle.php
copié !
abstract class Vehicle {

	// Atributs et constantes

	// Constructeur...

	// Accesseurs et mutateurs...

	// Méthodes move() et goToJackyTuning()

	}

Toute tentative d’instanciation par new Vehicle() lèvera une erreur.

Visibilité : protected

Lorsque nous créons une classe mère, il peut être utile de déclarer la visibilité des attributs à protected.

Pourquoi ? Car cela va désormais permettre aux méthodes des classes enfants qui vont l’étendre, d’avoir accès aux attributs parents $brand, $model, $color, $max_speed et $mileage.

Vehicle.php
copié !
abstract class Vehicle {

	protected string $brand;
	protected string $model;
	protected string $color;
	protected int $max_speed;
	protected float $mileage;

	const MAX_MILEAGE = 500000;

	// Constructeur...

	// Accesseurs et mutateurs...

	// Méthodes move() et goToJackyTuning()

	}

Classe fille

Une voiture va avoir un nombre de portes et une moto pourra, quant à elle, être éligible au permis A2. On créera donc 2 classes filles avec leurs propres propriétés.

Le mot-clé extends

Concrètement, la relation d’héritage est symbolisée en PHP par le mot-clé extends du côté de la classe fille.

Car.php
copié !
class Car extends Vehicle {

	// ...

}
Motorbike.php
copié !
class Motorbike extends Vehicle {

	// ...

}

Le mot-clé parent

Le constructeur d’une classe fille est un peu particulier puisqu’il nécessite de construire un objet constitué des attributs de la classe fille mais aussi de sa classe parente.

En ce sens, le mot-clé parent suivi de l’opérateur :: nous permettra de faire référence au constructeur parent afin d’en initialiser ses attributs globaux. Plus globalement, parent fournit un moyen d’accéder aux membres statiques (abordés à la fin de cette leçon) ou constants, ainsi qu’aux propriétés ou méthodes surchargées d’une classe (de la même manière que self fait référence aux membres statiques ou constants de la classe elle-même).

Car.php
copié !
class Car extends Vehicle {

	private int $nb_doors;

	public function __construct(
		int $nb_doors,
		string $brand,
		string $model,
		string $color,
		int $max_speed,
		float $mileage = 0
	) {
		$this->nb_doors = $nb_doors;
		parent::__construct(
			$brand,
			$model,
			$color,
			$max_speed,
			$mileage
		);
	}

	public function getNbDoors(): int {
		return $this->nb_doors;
	}

	public function setNbDoors(int $nb_doors): void {
		$door_options = [3, 5];
		if (in_array($nb_doors, $door_options)) {
			$this->nb_doors = $nb_doors;
		}
	}

}
Motorbike.php
copié !
class Motorbike extends Vehicle {

	private bool $a2;

	public function __construct(
		bool $a2,
		string $brand,
		string $model,
		string $color,
		int $max_speed,
		float $mileage = 0
	) {
		$this->a2 = $a2;
		parent::__construct(
			$brand,
			$model,
			$color,
			$max_speed,
			$mileage
		);
	}

	public function getA2(): bool {
		return $this->a2;
	}

	public function setA2(bool $a2): void {
		if (is_bool($a2)) {
			$this->a2 = $a2;
		}
	}

}

Le constructeur prend ici en paramètre des variables qui vont permettre d’initialiser l’ensemble des attributs de la classe en question (attributs personnels + attributs du parent).

Profondeur

Une classe fille peut elle-même être parent d’une autre classe (héritage multi-niveaux).

Vehicle.php
copié !
abstract class Vehicle { ... }
Bike.php
copié !
abstract class Bike extends Vehicle { ... }
Motorbike.php
copié !
class Motorbike extends Bike { ... }
Scooter.php
copié !
class Scooter extends Bike { ... }

Polymorphisme

Polymorphisme, signifiant « qui peut prendre plusieurs formes » en grec est un concept de POO qui permet à des méthodes de même nom d’avoir des comportements différents. En programmation, on distingue 2 types de polymorphisme : la surcharge et la redéfinition

Redéfinition

La redéfinition permet de choisir entre différentes implémentations d’une même méthode selon le nombre et le type des arguments fournis.

Vehicle.php
copié !
class Vehicle {

	public function foo(): void {
		echo 'bar';
	}

	public function foo(string $bar): void {
		echo $bar;
	}

}

Surcharge

Liée à l’héritage, elle permet de surcharger une méthode dans une classe héritant d’une classe parente pour adapter son comportement à ses propres besoins.

Une méthode surchargée doit avoir le même nom que la méthode de base ainsi que, contrairement à la surcharge, des paramètres identiques à ceux de la méthode parente (nombre + type).

Vehicle.php
copié !
abstract class Vehicle {

	public function sound(): void {
		echo 'Vroum';
	}

}
Motorbike.php
copié !
class Motorbike extends Vehicle {

	public function sound(): void {
		echo 'Braaaoum';
	}

}

Manipuler un objet issu d’une classe enfant

Instanciation de la classe fille

Rien de nouveau en ce qui concerne l’instanciation d’une classe héritant d’une autre. On utilise aussi le mot-clé new suivi du nom de la classe fille :

copié !
$motorbike1 = new Motorbike();

Exploitation de l’objet enfant

Après avoir instancié une classe fille, depuis notre objet, nous pouvons accéder aux propriétés et méthodes partagées par la classe parente (si leur visibilité est public ou protected).

copié !
$motorbike1 = new Motorbike(true, "Kawasaki", "ER6-n", "Noir", 220);

// Accès à une méthode "public" du parent
echo $motorbike1->getMaxSpeed(); 
// Accès à une méthode "public" de lui-même (enfant)
echo $motorbike1->getA2();

Typage statique

Il est possible de déclarer des attributs ou méthodes comme statiques. Cela se fait avec le mot-clé static, et permet d’y accéder sans avoir besoin d’instancier la classe.

copié !
class Car {

	// ...

	private static int $counter = 0;

	public function __construct(string $brand, string $model, string $color, int $max_speed, float $mileage = 0) {
		$this->brand = $brand;
		$this->model = $model;
		$this->color = $color;
		$this->max_speed = $max_speed;
		$this->mileage = $mileage;
		self::$counter++;
	}

	// ...

	public static function getCounter(): int {
		return self::$counter;
	}

}

Étant donné que cette méthode peut être appelée sans créer d’objet, on y fait référence avec self et non $this. Pour rappel, dans la définition d’une classe, $this se réfère à l’objet actuel, tandis que self se réfère à la classe actuelle.

Pour y accéder de l’extérieur, la syntaxe est similaire aux constantes :

copié !
$car1 = new Car("Volkswagen", "T-ROC", "Blanc", 200);
$car2 = new Car("Peugeot", "3008", "Bleu", 190, 10000);
$car3 = new Car("Citroën", "C3", "Vert", 165, 90000);

echo Car::getCounter(); // Affiche 3