Recherche…


Introduction

Les classes et les objets sont utilisés pour rendre votre code plus efficace et moins répétitif en regroupant des tâches similaires.

Une classe est utilisée pour définir les actions et la structure de données utilisées pour créer des objets. Les objets sont ensuite construits en utilisant cette structure prédéfinie.

Syntaxe

  • class <ClassName> [ extends <ParentClassName> ] [ implements <Interface1> [, <Interface2>, ... ] { } // Déclaration de classe
  • interface <InterfaceName> [ extends <ParentInterface1> [, <ParentInterface2>, ...] ] { } // Déclaration d'interface
  • use <Trait1> [, <Trait2>, ...] ; // utilise des traits
  • [ public | protected | private ] [ static ] $<varName>; // Déclaration d'attribut
  • const <CONST_NAME>; // déclaration constante
  • [ public | protected | private ] [ static ] function <methodName>([args...]) { } // Déclaration de méthode

Remarques

Classes et composants d'interface

Les classes peuvent avoir des propriétés, des constantes et des méthodes.

  • Les propriétés contiennent des variables dans la portée de l'objet. Ils peuvent être initialisés sur déclaration, mais seulement s'ils contiennent une valeur primitive.
  • Les constantes doivent être initialisées lors de la déclaration et ne peuvent contenir qu'une valeur primitive. Les valeurs constantes sont fixées au moment de la compilation et peuvent ne pas être affectées au moment de l'exécution.
  • Les méthodes doivent avoir un corps, même vide, à moins que la méthode ne soit déclarée abstraite.
class Foo {
    private $foo = 'foo'; // OK
    private $baz = array(); // OK
    private $bar = new Bar(); // Error!
}

Les interfaces ne peuvent pas avoir de propriétés, mais peuvent avoir des constantes et des méthodes.

  • Les constantes d' interface doivent être initialisées lors de la déclaration et ne peuvent contenir qu'une valeur primitive. Les valeurs constantes sont fixées au moment de la compilation et peuvent ne pas être affectées au moment de l'exécution.
  • Les méthodes d' interface n'ont pas de corps.
interface FooBar {
    const FOO_VALUE = 'bla';
    public function doAnything();
}

Interfaces

introduction

Les interfaces sont des définitions des API publiques que les classes doivent implémenter pour satisfaire l'interface. Ils fonctionnent comme des "contrats", en spécifiant ce que fait un ensemble de sous-classes, mais pas comment ils le font.

La définition d'interface est très proche de la définition de classe, transformant la class mots-clés en interface :

interface Foo {

}

Les interfaces peuvent contenir des méthodes et / ou des constantes, mais aucun attribut. Les constantes d'interface ont les mêmes restrictions que les constantes de classe. Les méthodes d'interface sont implicitement abstraites:

interface Foo {
    const BAR = 'BAR';

    public function doSomething($param1, $param2);
}

Remarque: les interfaces ne doivent pas déclarer de constructeurs ou de destructeurs, car ce sont des détails d'implémentation au niveau de la classe.

La concrétisation

Toute classe devant implémenter une interface doit le faire en utilisant le mot-clé implements . Pour ce faire, la classe doit fournir une implémentation pour chaque méthode déclarée dans l'interface, en respectant la même signature.

Une seule classe peut implémenter plusieurs interfaces à la fois.

interface Foo {
    public function doSomething($param1, $param2);
}

interface Bar {
    public function doAnotherThing($param1);
}


class Baz implements Foo, Bar {
    public function doSomething($param1, $param2) {
        // ...
    }

    public function doAnotherThing($param1) {
        // ...
    }
}

Lorsque les classes abstraites implémentent des interfaces, elles n'ont pas besoin d'implémenter toutes les méthodes. Toute méthode non implémentée dans la classe de base doit alors être implémentée par la classe concrète qui l'étend:

abstract class AbstractBaz implements Foo, Bar {
    // Partial implementation of the required interface...
    public function doSomething($param1, $param2) {
        // ...
    }
}

class Baz extends AbstractBaz {
    public function doAnotherThing($param1) {
        // ...
    }
}

Notez que la réalisation de l'interface est une caractéristique héritée. Lors de l'extension d'une classe qui implémente une interface, il n'est pas nécessaire de la redéclarer dans la classe concrète, car elle est implicite.

Note: Avant PHP 5.3.9, une classe ne pouvait pas implémenter deux interfaces spécifiant une méthode avec le même nom, car cela provoquerait une ambiguïté. Les versions plus récentes de PHP permettent cela tant que les méthodes en double ont la même signature [1] .

Héritage

Comme les classes, il est possible d'établir une relation d'héritage entre les interfaces, en utilisant le même mot extends clé extends . La principale différence est que l'héritage multiple est autorisé pour les interfaces:

interface Foo {

}

interface Bar {

}

interface Baz extends Foo, Bar {

}

Exemples

Dans l'exemple ci-dessous, nous avons un exemple simple d'interface pour un véhicule. Les véhicules peuvent avancer et reculer.

interface VehicleInterface {
    public function forward();

    public function reverse();

    ...
}

class Bike implements VehicleInterface {
    public function forward() {
        $this->pedal();
    }

    public function reverse() {
        $this->backwardSteps();
    }

    protected function pedal() {
        ...
    }

    protected function backwardSteps() {
        ...
    }

    ...
}

class Car implements VehicleInterface {
    protected $gear = 'N';

    public function forward() {
        $this->setGear(1);
        $this->pushPedal();
    }

    public function reverse() {
        $this->setGear('R');
        $this->pushPedal();
    }

    protected function setGear($gear) {
        $this->gear = $gear;
    }

    protected function pushPedal() {
        ...
    }

    ...
}

Ensuite, nous créons deux classes qui implémentent l'interface: Bike et Car. Bike and Car en interne sont très différents, mais les deux sont des véhicules et doivent implémenter les mêmes méthodes publiques que VehicleInterface.

Typehinting permet aux méthodes et fonctions de demander des interfaces. Supposons que nous ayons une classe de parking qui contient des véhicules de toutes sortes.

class ParkingGarage {
    protected $vehicles = [];

    public function addVehicle(VehicleInterface $vehicle) {
        $this->vehicles[] = $vehicle;
    }
}

Comme addVehicle nécessite un $vehicle de type VehicleInterface pas d'implémentation concrète), nous pouvons entrer Bikes et Cars, que le ParkingGarage peut manipuler et utiliser.

Constantes de classe

Les constantes de classe fournissent un mécanisme permettant de conserver des valeurs fixes dans un programme. C'est-à-dire qu'ils fournissent un moyen de donner un nom (et une vérification au moment de la compilation associée) à une valeur comme 3.14 ou "Apple" . Les constantes de classe ne peuvent être définies qu'avec le mot-clé const - la fonction define ne peut pas être utilisée dans ce contexte.

À titre d'exemple, il peut être utile d'avoir une représentation abrégée de la valeur de π dans un programme. Une classe avec des valeurs const fournit un moyen simple de conserver ces valeurs.

class MathValues {
    const PI = M_PI;
    const PHI = 1.61803;
}

$area = MathValues::PI * $radius * $radius;

On peut accéder aux constantes de classe en utilisant l’opérateur à deux points (appelé opérateur de résolution de portée) sur une classe, un peu comme les variables statiques. Contrairement aux variables statiques, cependant, les constantes de classe ont leurs valeurs fixées au moment de la compilation et ne peuvent pas être réaffectées (par exemple, MathValues::PI = 7 produirait une erreur fatale).

Les constantes de classe sont également utiles pour définir des éléments internes à une classe pouvant nécessiter une modification ultérieure (mais ne changent pas assez souvent pour justifier le stockage, par exemple, d'une base de données). Nous pouvons référencer cela en interne en utilisant le resolutor du self scope (qui fonctionne à la fois dans les implémentations instanciées et statiques)

class Labor {
    /** How long, in hours, does it take to build the item? */
    const LABOR_UNITS = 0.26;
    /** How much are we paying employees per hour? */
    const LABOR_COST = 12.75;

    public function getLaborCost($number_units) {
         return (self::LABOR_UNITS * self::LABOR_COST) * $number_units;
    }
}

Les constantes de classe ne peuvent contenir que des valeurs scalaires dans les versions <5.6

Depuis PHP 5.6, nous pouvons utiliser des expressions avec des constantes, ce qui signifie que les instructions mathématiques et les chaînes avec concaténation sont des constantes acceptables.

class Labor {
    /** How much are we paying employees per hour? Hourly wages * hours taken to make */
    const LABOR_COSTS = 12.75 * 0.26;

    public function getLaborCost($number_units) {
         return self::LABOR_COSTS * $number_units;
    }
}

Depuis PHP 7.0, les constantes déclarées avec define peuvent maintenant contenir des tableaux.

define("BAZ", array('baz'));

Les constantes de classe sont utiles pour stocker plus que des concepts mathématiques. Par exemple, si vous préparez une tarte, il peut être utile d’avoir une seule classe de Pie capable de prendre différents types de fruits.

class Pie {
    protected $fruit;

    public function __construct($fruit) {
        $this->fruit = $fruit;
    }
}

On peut alors utiliser la classe Pie comme ça

$pie = new Pie("strawberry");

Le problème qui se pose ici est que, lors de l'instanciation de la classe Pie , aucune indication n'est fournie quant aux valeurs acceptables. Par exemple, lorsqu’on fait une tarte au "boysenberry", elle pourrait être mal orthographiée "boisenberry". En outre, nous pourrions ne pas supporter une tarte aux prunes. Au lieu de cela, il serait utile d'avoir une liste de types de fruits acceptables déjà définis quelque part, il serait logique de les rechercher. Dites une classe nommée Fruit :

class Fruit {
    const APPLE = "apple";
    const STRAWBERRY = "strawberry";
    const BOYSENBERRY = "boysenberry";
}

$pie = new Pie(Fruit::STRAWBERRY);

La liste des valeurs acceptables en tant que constantes de classe fournit un indice précieux quant aux valeurs acceptables acceptées par une méthode. Cela garantit également que les fautes d'orthographe ne peuvent pas dépasser le compilateur. Alors que new Pie('aple') et new Pie('apple') sont tous deux acceptables pour le compilateur, new Pie(Fruit::APLE) produira une erreur de compilation.

Enfin, utiliser des constantes de classe signifie que la valeur réelle de la constante peut être modifiée à un seul endroit et que tout code utilisant la constante a automatiquement les effets de la modification.

Si la méthode la plus courante pour accéder à une constante de classe est MyClass::CONSTANT_NAME , elle peut également être accédée par:

echo MyClass::CONSTANT;

$classname = "MyClass";
echo $classname::CONSTANT; // As of PHP 5.3.0

Les constantes de classe dans PHP sont classiquement nommées toutes en majuscules avec des traits de soulignement comme séparateurs de mots, bien que tout nom de label valide puisse être utilisé comme nom de constante de classe.

Depuis PHP 7.1, les constantes de classe peuvent maintenant être définies avec différentes visibilités par rapport à la portée publique par défaut. Cela signifie que les constantes protégées et privées peuvent désormais être définies pour éviter que les constantes de classe ne fuient inutilement dans la portée publique (voir Visibilité de la méthode et de la propriété ). Par exemple:

class Something {
    const PUBLIC_CONST_A = 1;
    public const PUBLIC_CONST_B = 2;
    protected const PROTECTED_CONST = 3;
    private const PRIVATE_CONST = 4;
}

définir vs constantes de classe

Bien que ce soit une construction valide:

function bar() { return 2; };

define('BAR', bar());

Si vous essayez de faire la même chose avec les constantes de classe, vous obtenez une erreur:

function bar() { return 2; };

class Foo {
    const BAR = bar(); // Error: Constant expression contains invalid operations
}

Mais vous pouvez faire:

function bar() { return 2; };

define('BAR', bar());

class Foo {
    const BAR = BAR; // OK
}

Pour plus d'informations, voir les constantes dans le manuel .

Utiliser :: class pour récupérer le nom de la classe

PHP 5.5 a introduit la syntaxe ::class pour récupérer le nom complet de la classe, en prenant en compte les instructions de portée et d' use espaces de noms.

namespace foo;
use bar\Bar;
echo json_encode(Bar::class); // "bar\\Bar"
echo json_encode(Foo::class); // "foo\\Foo"
echo json_encode(\Foo::class); // "Foo"

Ce qui précède fonctionne même si les classes ne sont même pas définies (c.-à-d. Cet extrait de code fonctionne seul).

Cette syntaxe est utile pour les fonctions qui nécessitent un nom de classe. Par exemple, il peut être utilisé avec class_exists pour vérifier l' class_exists d'une classe. Aucune erreur ne sera générée, quelle que soit la valeur de retour dans cet extrait de code:

class_exists(ThisClass\Will\NeverBe\Loaded::class, false);

Reliure statique tardive

En PHP 5.3+ et ci-dessus, vous pouvez utiliser une liaison statique tardive pour contrôler la classe à partir de laquelle une propriété ou une méthode statique est appelée. Il a été ajouté pour résoudre le problème inhérent au résolver self:: scope. Prenez le code suivant

class Horse {
    public static function whatToSay() {
         echo 'Neigh!';
    }

    public static function speak() {
         self::whatToSay();
    }
}

class MrEd extends Horse {
    public static function whatToSay() {
         echo 'Hello Wilbur!';
    }
}

Vous vous attendez à ce que la classe MrEd remplace la fonction parent whatToSay() . Mais quand nous courons cela nous obtenons quelque chose d'inattendu

Horse::speak(); // Neigh!
MrEd::speak(); // Neigh!

Le problème est que self::whatToSay(); ne peut se référer qu'à la classe Horse , ce qui signifie qu'il n'obéit pas à MrEd . Si nous basculons sur le static:: scope, nous n’avons pas ce problème. Cette nouvelle méthode indique à la classe d'obéir à l'instance qui l'appelle. Ainsi, nous obtenons l'héritage attendu

class Horse {
    public static function whatToSay() {
         echo 'Neigh!';
    }

    public static function speak() {
         static::whatToSay(); // Late Static Binding
    }
}

Horse::speak(); // Neigh!
MrEd::speak(); // Hello Wilbur!

Classes abstraites

Une classe abstraite est une classe qui ne peut pas être instanciée. Les classes abstraites peuvent définir des méthodes abstraites, qui sont des méthodes sans corps, mais seulement une définition:

abstract class MyAbstractClass {
    abstract public function doSomething($a, $b);
}

Les classes abstraites devraient être étendues par une classe enfant qui peut alors fournir l'implémentation de ces méthodes abstraites.

L'objectif principal d'une classe comme celle-ci est de fournir une sorte de modèle permettant aux classes d'enfants d'hériter, en forçant une structure à y adhérer. Permet de développer à ce sujet avec un exemple:

Dans cet exemple, nous allons implémenter une interface de Worker . Tout d'abord, nous définissons l'interface:

interface Worker {
    public function run();
}

Pour faciliter le développement d'autres implémentations Worker, nous allons créer une classe de travail abstraite qui fournit déjà la méthode run() partir de l'interface, mais spécifie des méthodes abstraites qui doivent être remplies par toute classe enfant:

abstract class AbstractWorker implements Worker {
    protected $pdo;
    protected $logger;

    public function __construct(PDO $pdo, Logger $logger) {
        $this->pdo = $pdo;
        $this->logger = $logger;
    }

    public function run() {
        try {
            $this->setMemoryLimit($this->getMemoryLimit());
            $this->logger->log("Preparing main");
            $this->prepareMain();
            $this->logger->log("Executing main");
            $this->main();
        } catch (Throwable $e) {
            // Catch and rethrow all errors so they can be logged by the worker
            $this->logger->log("Worker failed with exception: {$e->getMessage()}");
            throw $e;
        }
    }

    private function setMemoryLimit($memoryLimit) {
        ini_set('memory_limit', $memoryLimit);
        $this->logger->log("Set memory limit to $memoryLimit");
    }

    abstract protected function getMemoryLimit();

    abstract protected function prepareMain();

    abstract protected function main();
}

Tout d'abord, nous avons fourni une méthode abstraite getMemoryLimit() . Toute classe s'étendant depuis AbstractWorker doit fournir cette méthode et renvoyer sa limite de mémoire. AbstractWorker définit alors la limite de mémoire et la connecte.

Deuxièmement, AbstractWorker appelle les prepareMain() et main() , après avoir enregistré leur appel.

Enfin, tous ces appels de méthodes ont été regroupés dans un bloc try - catch . Donc, si l'une des méthodes abstraites définies par la classe enfant génère une exception, nous intercepterons cette exception, la connecterons et la renverrons. Cela empêche toutes les classes enfants de l'implémenter elles-mêmes.

Définissons maintenant une classe enfant qui s'étend depuis AbstractWorker :

class TranscactionProcessorWorker extends AbstractWorker {
    private $transactions;

    protected function getMemoryLimit() {
        return "512M";
    }

    protected function prepareMain() {
        $stmt = $this->pdo->query("SELECT * FROM transactions WHERE processed = 0 LIMIT 500");
        $stmt->execute();
        $this->transactions = $stmt->fetchAll();
    }

    protected function main() {
        foreach ($this->transactions as $transaction) {
            // Could throw some PDO or MYSQL exception, but that is handled by the AbstractWorker
            $stmt = $this->pdo->query("UPDATE transactions SET processed = 1 WHERE id = {$transaction['id']} LIMIT 1");
            $stmt->execute();
        }
    }
}

Comme vous pouvez le voir, le TransactionProcessorWorker était plutôt facile à mettre en œuvre, car il suffisait de spécifier la limite de mémoire et de s'inquiéter des actions réelles à effectuer. Aucune gestion des erreurs n'est requise dans TransactionProcessorWorker car elle est gérée dans AbsractWorker .

Note importante

Lors de l'héritage d'une classe abstraite, toutes les méthodes marquées abstract dans la déclaration de classe du parent doivent être définies par l'enfant (ou le fils lui-même doit également être marqué abstract); de plus, ces méthodes doivent être définies avec la même visibilité (ou moins restreinte). Par exemple, si la méthode abstraite est définie comme protégée, l'implémentation de la fonction doit être définie comme étant protégée ou publique, mais pas privée.

Extrait de la documentation PHP pour l'abstraction de classes .

Si vous ne définissez pas les méthodes des classes abstraites parent dans la classe enfant, vous obtiendrez une erreur PHP fatale comme celle-ci.

Erreur fatale: la classe X contient 1 méthode abstraite et doit donc être déclarée abstraite ou implémenter les méthodes restantes (X :: x) dans

Espacement des noms et chargement automatique

Techniquement, le chargement automatique fonctionne en exécutant un rappel lorsqu'une classe PHP est requise mais non trouvée. De tels rappels tentent généralement de charger ces classes.

En général, l'autoloading peut être compris comme une tentative de chargement de fichiers PHP (en particulier des fichiers de classe PHP, où un fichier source PHP est dédié à une classe spécifique) à partir des chemins appropriés selon le nom complet de la classe lorsqu'une classe est nécessaire .

Supposons que nous ayons ces classes:

Fichier de classe pour application\controllers\Base :

<?php
namespace application\controllers { class Base {...} }

Fichier de classe pour application\controllers\Control :

<?php
namespace application\controllers { class Control {...} }

Fichier de classe pour application\models\Page :

<?php
namespace application\models { class Page {...} }

Sous le dossier source, ces classes doivent être placées sur les chemins en tant que leurs noms de domaine complets respectivement:

  • Dossier d'origine
    • applications
      • controllers
        • Base.php
        • Control.php
      • models
        • Page.php

Cette approche permet de résoudre par programmation le chemin du fichier de classe en fonction du FQN, en utilisant cette fonction:

function getClassPath(string $sourceFolder, string $className, string $extension = ".php") {
    return $sourceFolder . "/" . str_replace("\\", "/", $className) . $extension; // note that "/" works as a directory separator even on Windows
}

La fonction spl_autoload_register nous permet de charger une classe en cas de besoin en utilisant une fonction définie par l'utilisateur:

const SOURCE_FOLDER = __DIR__ . "/src";
spl_autoload_register(function (string $className) {
    $file = getClassPath(SOURCE_FOLDER, $className);
    if (is_readable($file)) require_once $file;
});

Cette fonction peut être étendue pour utiliser des méthodes de chargement de secours:

const SOURCE_FOLDERS = [__DIR__ . "/src", "/root/src"]);
spl_autoload_register(function (string $className) {
    foreach(SOURCE_FOLDERS as $folder) {
        $extensions = [
            // do we have src/Foo/Bar.php5_int64?
            ".php" . PHP_MAJOR_VERSION . "_int" . (PHP_INT_SIZE * 8),
            // do we have src/Foo/Bar.php7?
            ".php" . PHP_MAJOR_VERSION,
            // do we have src/Foo/Bar.php_int64?
            ".php" . "_int" . (PHP_INT_SIZE * 8),
            // do we have src/Foo/Bar.phps?
            ".phps"
            // do we have src/Foo/Bar.php?
            ".php"
        ];
        foreach($extensions as $ext) {
            $path = getClassPath($folder, $className, $extension);
            if(is_readable($path)) return $path;
        }
    }
});

Notez que PHP ne tente pas de charger les classes à chaque fois qu'un fichier utilisant cette classe est chargé. Il peut être chargé au milieu d'un script, ou même dans des fonctions d'arrêt. C'est l'une des raisons pour lesquelles les développeurs, en particulier ceux qui utilisent l'autoloading, devraient éviter de remplacer les fichiers source en cours d'exécution dans le runtime, notamment dans les fichiers phar.

Reliure dynamique

La liaison dynamique, également appelée substitution de méthode, est un exemple de polymorphisme d' exécution qui se produit lorsque plusieurs classes contiennent des implémentations différentes de la même méthode, mais l'objet auquel la méthode sera appelée est inconnu jusqu'au moment de l'exécution .

Ceci est utile si une certaine condition dicte quelle classe sera utilisée pour effectuer une action, où l'action est nommée de la même manière dans les deux classes.

interface Animal {
    public function makeNoise();
}

class Cat implements Animal {
    public function makeNoise
    {
        $this->meow();
    }
    ...
}

class Dog implements Animal {
    public function makeNoise {
        $this->bark();
    }
    ...
}

class Person {
    const CAT = 'cat';
    const DOG = 'dog';

    private $petPreference;
    private $pet;

    public function isCatLover(): bool {
        return $this->petPreference == self::CAT;
    }

    public function isDogLover(): bool {
        return $this->petPreference == self::DOG;
    }

    public function setPet(Animal $pet) {
        $this->pet = $pet;
    }

    public function getPet(): Animal {
        return $this->pet;
    }
}

if($person->isCatLover()) {
    $person->setPet(new Cat());
} else if($person->isDogLover()) {
    $person->setPet(new Dog());
}

$person->getPet()->makeNoise();

Dans l'exemple ci-dessus, la classe Animal ( Dog|Cat ) qui fera makeNoise est inconnue jusqu'à l'exécution en fonction de la propriété de la classe User .

Visibilité de méthode et de propriété

Il existe trois types de visibilité que vous pouvez appliquer aux méthodes (fonctions de classe / objet ) et aux propriétés ( variables de classe / objet ) dans une classe, qui fournissent un contrôle d'accès à la méthode ou à la propriété à laquelle elles sont appliquées.

Vous pouvez en savoir plus à ce sujet dans la documentation PHP pour la visibilité OOP .

Publique

La déclaration d'une méthode ou d'une propriété comme public permet à la méthode ou à la propriété d'être accessible par:

  • La classe qui l'a déclaré.
  • Les classes qui étendent la classe déclarée.
  • Tout objet externe, classe ou code externe à la hiérarchie de classe.

Un exemple de cet accès public serait:

class MyClass {
    // Property
    public $myProperty = 'test';

    // Method
    public function myMethod() {
        return $this->myProperty;
    }
}

$obj = new MyClass();
echo $obj->myMethod();
// Out: test

echo $obj->myProperty;
// Out: test

Protégé

La déclaration d'une méthode ou d'une propriété comme protected permet d'accéder à la méthode ou à la propriété par:

  • La classe qui l'a déclaré.
  • Les classes qui étendent la classe déclarée.

Cela n'autorise pas les objets externes, les classes ou le code en dehors de la hiérarchie de classes à accéder à ces méthodes ou propriétés. Si quelque chose utilisant cette méthode / propriété n'y a pas accès, il ne sera pas disponible et une erreur sera générée. Seules les instances du self déclaré (ou de ses sous-classes) y ont accès.

Un exemple de cet accès protected serait:

class MyClass {
    protected $myProperty = 'test';

    protected function myMethod() {
        return $this->myProperty;
    }
}

class MySubClass extends MyClass {
    public function run() {
        echo $this->myMethod();
    }
}

$obj = new MySubClass();
$obj->run(); // This will call MyClass::myMethod();
// Out: test

$obj->myMethod(); // This will fail.
// Out: Fatal error: Call to protected method MyClass::myMethod() from context ''

L'exemple ci-dessus indique que vous ne pouvez accéder qu'aux éléments protected de sa propre portée. Essentiellement: "Ce qui se trouve dans la maison ne peut être que l’accès depuis l’intérieur de la maison."


Privé

La déclaration d'une méthode ou d'une propriété comme private permet à la méthode ou à la propriété d'être accessible par:

  • La classe qui l'a déclaré seulement (pas les sous-classes).

Un private méthode ou la propriété est uniquement visible et accessible dans la classe qui l'a créé.

Notez que les objets du même type auront accès à chacun des membres privés et protégés même s'ils ne sont pas les mêmes.

class MyClass {
    private $myProperty = 'test';

    private function myPrivateMethod() {
        return $this->myProperty;
    }

    public function myPublicMethod() {
        return $this->myPrivateMethod();
    }

    public function modifyPrivatePropertyOf(MyClass $anotherInstance) {
        $anotherInstance->myProperty = "new value";
    }
}

class MySubClass extends MyClass {
    public function run() {
        echo $this->myPublicMethod();
    }

    public function runWithPrivate() {
        echo $this->myPrivateMethod();
    }
}

$obj = new MySubClass();
$newObj = new MySubClass();

// This will call MyClass::myPublicMethod(), which will then call
// MyClass::myPrivateMethod();
$obj->run(); 
// Out: test

    
$obj->modifyPrivatePropertyOf($newObj);

$newObj->run();
// Out: new value

echo $obj->myPrivateMethod(); // This will fail.
// Out: Fatal error: Call to private method MyClass::myPrivateMethod() from context ''

echo $obj->runWithPrivate(); // This will also fail.
// Out: Fatal error: Call to private method MyClass::myPrivateMethod() from context 'MySubClass'

Comme indiqué précédemment, vous ne pouvez accéder à la méthode / propriété private qu'à partir de sa classe définie.

Appeler un constructeur parent lors de l'instanciation d'un enfant

Un écueil courant des classes enfants est que, si votre parent et votre enfant contiennent tous deux une méthode constructeur ( __construct() ), seul le constructeur de classe enfant sera exécuté . Il peut arriver que vous deviez exécuter la __construct() parent __construct() depuis son fils. Si vous devez le faire, vous devrez alors utiliser le parent:: scope:

parent::__construct();

Exploiter maintenant cela dans une situation réelle ressemblerait à quelque chose comme:

class Foo {

    function __construct($args) { 
        echo 'parent'; 
    }

}

class Bar extends Foo {

    function __construct($args) {
        parent::__construct($args);
    }
}

Ce qui précède exécutera le parent __construct() ce qui entraînera l'exécution de l' echo .

Mot-clé final

Def: le mot-clé final empêche les classes enfant de remplacer une méthode en préfixant la définition avec final. Si la classe elle-même est définie comme définitive, elle ne peut pas être étendue

Méthode finale

class BaseClass {
   public function test() {
       echo "BaseClass::test() called\n";
   }
   
   final public function moreTesting() {
       echo "BaseClass::moreTesting() called\n";
   }
}

class ChildClass extends BaseClass {
   public function moreTesting() {
       echo "ChildClass::moreTesting() called\n";
   }
}
// Results in Fatal error: Cannot override final method BaseClass::moreTesting()

Classe finale:

final class BaseClass {
   public function test() {
       echo "BaseClass::test() called\n";
   }

   // Here it doesn't matter if you specify the function as final or not
   final public function moreTesting() {
       echo "BaseClass::moreTesting() called\n";
   }
}

class ChildClass extends BaseClass {
}
// Results in Fatal error: Class ChildClass may not inherit from final class (BaseClass)

Constantes finales: Contrairement à Java, le mot clé final n'est pas utilisé pour les constantes de classe en PHP. Utilisez le mot-clé const place.

Pourquoi dois-je utiliser la final ?

  1. Prévenir la chaîne de transmission de l'héritage massif
  2. Composition encourageante
  3. Forcer le développeur à réfléchir à l'API publique utilisateur
  4. Forcer le développeur à réduire l'API publique d'un objet
  5. Un final cours peut toujours être rendu extensible
  6. extends encapsulation des pauses
  7. Vous n'avez pas besoin de cette flexibilité
  8. Vous êtes libre de changer le code

Quand éviter la final : Les classes finales ne fonctionnent efficacement que sous les hypothèses suivantes:

  1. Il y a une abstraction (interface) que la classe finale implémente
  2. Toutes les API publiques de la classe finale font partie de cette interface

$ this, self et static plus le singleton

Utilisez $this pour faire référence à l'objet actuel. Utilisez vous- self pour vous référer à la classe en cours. En d'autres termes, utilisez $this->member pour les membres non statiques, utilisez self::$member pour les membres statiques.

Dans l'exemple ci-dessous, sayHello() et sayGoodbye() utilisent self et $this différence peut être observée ici.

class Person {
    private $name;

    public function __construct($name) {
        $this->name = $name;
    }

    public function getName() {
        return $this->name;
    }

    public function getTitle() {
        return $this->getName()." the person";
    }

    public function sayHello() {
        echo "Hello, I'm ".$this->getTitle()."<br/>";
    }

    public function sayGoodbye() {
        echo "Goodbye from ".self::getTitle()."<br/>";
    }
}

class Geek extends Person {
    public function __construct($name) {
        parent::__construct($name);
    }

    public function getTitle() {
        return $this->getName()." the geek";
    }
}

$geekObj = new Geek("Ludwig");
$geekObj->sayHello();
$geekObj->sayGoodbye();

static fait référence à toute classe de la hiérarchie sur laquelle vous avez appelé la méthode. Il permet une meilleure réutilisation des propriétés de classe statiques lorsque les classes sont héritées.

Considérez le code suivant:

class Car {
    protected static $brand = 'unknown';
    
    public static function brand() {
         return self::$brand."\n";
    }
}

class Mercedes extends Car {
    protected static $brand = 'Mercedes';
}

class BMW extends Car {
    protected static $brand = 'BMW';
}

echo (new Car)->brand();
echo (new BMW)->brand();
echo (new Mercedes)->brand();

Cela ne produit pas le résultat souhaité:

inconnu
inconnu
inconnu

C'est parce que self fait référence à la classe Car chaque fois que la méthode brand() est appelée.

Pour faire référence à la classe correcte, vous devez utiliser à static place static :

class Car {
    protected static $brand = 'unknown';
    
    public static function brand() {
         return static::$brand."\n";
    }
}

class Mercedes extends Car {
    protected static $brand = 'Mercedes';
}

class BMW extends Car {
    protected static $brand = 'BMW';
}

echo (new Car)->brand();
echo (new BMW)->brand();
echo (new Mercedes)->brand();

Cela produit la sortie souhaitée:

inconnu
BMW
Mercedes

Voir aussi Liaison statique tardive

Le singleton

Si vous avez un objet qui est coûteux à créer ou représente une connexion à une ressource externe que vous souhaitez réutiliser, soit une connexion de base de données où il n'y a pas mise en commun de connexion ou d' une prise à un autre système, vous pouvez utiliser les static et self mots - clés dans un classe pour en faire un singleton. Il existe de fortes opinions quant à savoir si le modèle singleton devrait ou ne devrait pas être utilisé, mais il a ses utilisations.

class Singleton {
    private static $instance = null;

    public static function getInstance(){
        if(!isset(self::$instance)){
            self::$instance = new self();
        }
        
        return self::$instance;
    }
    
    private function __construct() {
        // Do constructor stuff
    }
}

Comme vous pouvez le voir dans l'exemple de code, nous définissons une propriété $instance statique privée pour contenir la référence de l'objet. Comme c'est statique, cette référence est partagée entre tous les objets de ce type.

La méthode getInstance() utilise une méthode connue sous le nom d'instanciation paresseuse pour retarder la création de l'objet jusqu'au dernier moment possible, car vous ne souhaitez pas que des objets inutilisés restent en mémoire et ne soient jamais destinés à être utilisés. Cela permet également d'économiser du temps et de la charge du processeur sur la page sans avoir à charger plus d'objets que nécessaire. La méthode vérifie si l'objet est défini, le crée sinon et le renvoie. Cela garantit qu'un seul objet de ce type est créé.

Nous configurons également le constructeur pour qu'il soit privé afin de garantir que personne ne le crée avec le new mot-clé depuis l'extérieur. Si vous devez hériter de cette classe, changez simplement les mots-clés private en protected .

Pour utiliser cet objet, écrivez simplement ce qui suit:

$singleton = Singleton::getInstance();

Maintenant, je vous implore d'utiliser l'injection de dépendance où vous le pouvez et de viser des objets faiblement couplés, mais parfois cela n'est tout simplement pas raisonnable et le modèle singleton peut être utile.

Chargement automatique

Personne ne veut require ou include chaque fois qu'une classe ou un héritage est utilisé. Parce qu'il peut être douloureux et facile à oublier, PHP propose le chargement automatique. Si vous utilisez déjà Composer, lisez l'article sur le chargement automatique à l'aide de Composer .

Qu'est-ce que le chargement automatique?

Le nom dit tout. Vous n'avez pas à obtenir le fichier dans lequel la classe demandée est stockée, mais PHP le charge automatiquement.

Comment puis-je faire cela en PHP de base sans code tiers?

Il y a la fonction __autoload , mais il est préférable d'utiliser spl_autoload_register . Ces fonctions seront considérées par PHP chaque fois qu'une classe n'est pas définie dans l'espace donné. L'ajout de chargement automatique à un projet existant ne pose donc aucun problème, car les classes définies (via require ie) fonctionneront comme avant. Par souci de précision, les exemples suivants utiliseront des fonctions anonymes, si vous utilisez PHP <5.3, vous pouvez définir la fonction et transmettre son nom comme argument à spl_autoload_register .

Exemples

spl_autoload_register(function ($className) {
    $path = sprintf('%s.php', $className);
    if (file_exists($path)) {
        include $path;
    } else {
        // file not found
    }
});

Le code ci-dessus essaie simplement d'inclure un nom de fichier avec le nom de la classe et l'extension ".php" ajoutée à l'aide de sprintf . Si FooBar doit être chargé, il semble que FooBar.php existe et si oui, l'inclut.

Bien sûr, cela peut être étendu pour répondre aux besoins individuels du projet. Si _ dans un nom de classe est utilisé pour grouper, par exemple User_Post et User_Image deux font référence à User , les deux classes peuvent être conservées dans un dossier appelé "User" comme ceci:

spl_autoload_register(function ($className) {
    //                        replace _ by / or \ (depending on OS)
    $path = sprintf('%s.php', str_replace('_', DIRECTORY_SEPARATOR, $className) );
    if (file_exists($path)) {
        include $path;
    } else {
        // file not found
    }
});

La classe User_Post va maintenant être chargée depuis "User / Post.php", etc.

spl_autoload_register peut être adapté à divers besoins. Tous vos fichiers avec des classes sont nommés "class.CLASSNAME.php"? Aucun problème. Divers imbrication ( User_Post_Content => "User / Post / Content.php")? Pas de problème non plus.

Si vous voulez un mécanisme de chargement automatique plus élaboré et que vous ne voulez toujours pas inclure Composer, vous pouvez travailler sans ajouter de bibliothèques tierces.

spl_autoload_register(function ($className) {
    $path = sprintf('%1$s%2$s%3$s.php',
        // %1$s: get absolute path
        realpath(dirname(__FILE__)),
        // %2$s: / or \ (depending on OS)
        DIRECTORY_SEPARATOR,
        // %3$s: don't wory about caps or not when creating the files
        strtolower(
            // replace _ by / or \ (depending on OS)
            str_replace('_', DIRECTORY_SEPARATOR, $className)
        )
    );

    if (file_exists($path)) {
        include $path;
    } else {
        throw new Exception(
            sprintf('Class with name %1$s not found. Looked in %2$s.',
                $className,
                $path
            )
        );
    }
});

En utilisant des chargeurs automatiques comme celui-ci, vous pouvez écrire du code comme ceci:

require_once './autoload.php'; // where spl_autoload_register is defined

$foo = new Foo_Bar(new Hello_World());

Utiliser des classes:

class Foo_Bar extends Foo {}
class Hello_World implements Demo_Classes {}

Ces exemples seront des classes include de foo/bar.php , foo.php , hello/world.php et demo/classes.php .

Classes anonymes

Des classes anonymes ont été introduites dans PHP 7 pour permettre de créer facilement des objets uniques rapides. Ils peuvent prendre des arguments de constructeur, étendre d'autres classes, implémenter des interfaces et utiliser des traits comme le font les classes normales.

Dans sa forme la plus simple, une classe anonyme ressemble à ceci:

new class("constructor argument") {
    public function __construct($param) {
        var_dump($param);
    }
}; // string(20) "constructor argument"

L'imbrication d'une classe anonyme dans une autre classe ne lui donne pas accès aux méthodes ou propriétés privées ou protégées de cette classe externe. L'accès aux méthodes et propriétés protégées de la classe externe peut être obtenu en étendant la classe externe de la classe anonyme. L'accès aux propriétés privées de la classe externe peut être obtenu en les transmettant au constructeur de la classe anonyme.

Par exemple:

class Outer {
    private $prop = 1;
    protected $prop2 = 2;

    protected function func1() {
        return 3;
    }

    public function func2() {
        // passing through the private $this->prop property
        return new class($this->prop) extends Outer {
            private $prop3;

            public function __construct($prop) {
                $this->prop3 = $prop;
            }

            public function func3() {
                // accessing the protected property Outer::$prop2
                // accessing the protected method Outer::func1()
                // accessing the local property self::$prop3 that was private from Outer::$prop
                return $this->prop2 + $this->func1() + $this->prop3;
            }
        };
    }
}

echo (new Outer)->func2()->func3(); // 6

Définir une classe de base

Un objet en PHP contient des variables et des fonctions. Les objets appartiennent généralement à une classe, qui définit les variables et les fonctions que tous les objets de cette classe contiendront.

La syntaxe pour définir une classe est la suivante:

class Shape {
    public $sides = 0;
    
    public function description() {
        return "A shape with $this->sides sides.";
    }
}

Une fois qu'une classe est définie, vous pouvez créer une instance en utilisant:

$myShape = new Shape();

Les variables et les fonctions de l'objet sont accessibles comme ceci:

$myShape = new Shape();
$myShape->sides = 6;

print $myShape->description(); // "A shape with 6 sides"

Constructeur

Les classes peuvent définir une méthode __construct() spéciale, exécutée dans le cadre de la création d'objet. Ceci est souvent utilisé pour spécifier les valeurs initiales d'un objet:

class Shape {
    public $sides = 0;
    
    public function __construct($sides) {
        $this->sides = $sides;
    }
    
    public function description() {
        return "A shape with $this->sides sides.";
    }
}

$myShape = new Shape(6);

print $myShape->description(); // A shape with 6 sides

Extension d'une autre classe

Les définitions de classes peuvent étendre les définitions de classes existantes, en ajoutant de nouvelles variables et fonctions, ainsi qu'en modifiant celles définies dans la classe parente.

Voici une classe qui étend l'exemple précédent:

class Square extends Shape {
    public $sideLength = 0;
    
    public function __construct($sideLength) {
       parent::__construct(4);
       
       $this->sideLength = $sideLength;
    }
    
    public function perimeter() {
        return $this->sides * $this->sideLength;
    }

    public function area() {
        return $this->sideLength * $this->sideLength;
    }
}

La classe Square contient des variables et un comportement pour la classe Shape et la classe Square :

$mySquare = new Square(10);

print $mySquare->description()/ // A shape with 4 sides

print $mySquare->perimeter() // 40

print $mySquare->area() // 100


Modified text is an extract of the original Stack Overflow Documentation
Sous licence CC BY-SA 3.0
Non affilié à Stack Overflow