Déchiffrer des méthodes magiques en PHP

PHP fournit un certain nombre de méthodes "magiques" qui vous permettent de faire de très astuces en programmation orientée objet. Ces méthodes, identifiées par un préfixe de soulignement deux (__), fonctionnent comme des intercepteurs appelés automatiquement lorsque certaines conditions sont remplies. Les méthodes magiques fournissent des fonctionnalités extrêmement utiles, et ce tutoriel montrera l'utilisation de chaque méthode.


Avant que nous commencions

Pour bien comprendre les méthodes magiques, il est utile de les voir en action. Commençons donc avec un ensemble de base de classes très simples. Ici nous définissons deux classes: Device et Battery.

connexion = 'ressource'; echo $ this-> name. ' connecté' . PHP_EOL;  protected function disconnect () // déconnecter le réseau en toute sécurité $ this-> connection = null; echo $ this-> name. ' débranché' . PHP_EOL;  class Battery private $ charge = 0; fonction publique setCharge ($ charge) $ charge = (int) $ charge; si ($ charge < 0)  $charge = 0;  elseif($charge > 100) charge = 100;  $ this-> charge = $ charge; ?>

Si des mots comme "méthode" et "propriété" vous semblent étrangers, vous voudrez peut-être lire ceci en premier.

Les objets de périphérique contiendront un nom, un objet Batterie, un tableau de données et un descripteur de ressource externe. Ils ont également des méthodes pour connecter et déconnecter la ressource externe. Les objets de batterie stockent simplement une charge dans une propriété privée et disposent d'une méthode pour définir la charge.

Ce tutoriel suppose que vous maîtrisiez les bases de la programmation orientée objet. Si des mots comme "méthode" et "propriété" vous semblent étrangers, vous voudrez peut-être vous renseigner sur cette première.

Ces classes sont plutôt inutiles, mais elles constituent un bon exemple pour chacune des méthodes magiques. Alors maintenant que nous avons créé nos classes simples, nous pouvons essayer les méthodes magiques.


Constructeurs et destructeurs

Les constructeurs et les destructeurs sont appelés lorsqu'un objet est créé et détruit, respectivement. Un objet est "détruit" lorsqu'il n'y a plus de références, soit parce que la variable qui le contient n'a pas été définie / réaffectée ou que l'exécution du script est terminée..

__construction()

le __construction() méthode est de loin la méthode magique la plus utilisée. C'est là que vous effectuez l'initialisation dont vous avez besoin lorsqu'un objet est créé. Vous pouvez définir ici un nombre illimité d'arguments, qui seront transmis lors de la création d'objets. Toute valeur de retour sera transmise à travers le Nouveau mot-clé. Toute exception levée dans le constructeur arrêtera la création d'objet.

class Device //… fonction publique __construct (Battery $ battery, $ name) // $ battery ne peut être qu'un objet Battery valide $ this-> battery = $ battery; $ this-> name = $ name; // se connecter au réseau $ this-> connect ();  //…

Déclarer la méthode constructeur 'private' empêche le code externe de créer directement un objet.

Ici, nous avons déclaré un constructeur qui accepte deux arguments, une batterie et un nom. Le constructeur affecte chacune des propriétés requises par les objets pour fonctionner et exécute le relier() méthode. Le constructeur vous permet de vous assurer qu'un objet a toutes les pièces nécessaires avant qu'il puisse exister.

Pointe: La déclaration de la méthode de constructeur 'private' empêche le code externe de créer directement un objet. C'est pratique pour créer des classes singleton qui limitent le nombre d'objets pouvant exister.

Avec le constructeur ci-dessus en place, voici comment créer un périphérique appelé 'iMagic':

$ device = new Device (nouvelle batterie (), 'iMagic'); // iMagic connected echo $ device-> name; // iMagic

Comme vous pouvez le constater, les arguments transmis à la classe sont en fait transmis à la méthode constructeur. Vous pouvez aussi dire que la méthode connect a été appelée et que le $ name la propriété a été peuplée.

Disons que nous oublions de donner un nom. Voici ce qui se passe:

$ device = new Device (nouvelle batterie ()); // Résultat: Avertissement PHP: Argument manquant 2 pour Device :: __ construct ()

__destruct ()

Comme son nom l'indique, le __destruct () La méthode est appelée lorsque l'objet est détruit. Il n'accepte aucun argument et est couramment utilisé pour effectuer des opérations de nettoyage telles que la fermeture d'une connexion à une base de données. Dans notre cas, nous l'utilisons pour nous déconnecter du réseau..

class Device //… fonction publique __destruct () // déconnecter du réseau $ this-> disconnect (); echo $ this-> name. ' a été détruit' . PHP_EOL;  //…

Avec le destructeur ci-dessus en place, voici ce qui se passe lorsqu'un objet Device est détruit:

$ device = new Device (nouvelle batterie (), 'iMagic'); // iMagic connected unset ($ device); // iMagic déconnecté // iMagic a été détruit

Ici, nous avons détruit l'objet en utilisant non défini (). Avant sa destruction, le destructeur appelle le déconnecter () méthode et imprime un message, que vous pouvez voir dans les commentaires.


Surcharge de propriété

Remarque: La version de "surcharge" de PHP n'est pas tout à fait la même que la plupart des autres langues, bien que les mêmes résultats puissent être atteints.

Ce prochain ensemble de méthodes magiques concerne la gestion de l'accès aux propriétés, en définissant ce qui se passe lorsque vous essayez d'accéder à une propriété qui n'existe pas (ou qui n'est pas accessible). Ils peuvent être utilisés pour créer des pseudo propriétés. Cela s'appelle surcharge en PHP.

__obtenir()

le __obtenir() La méthode est appelée lorsque le code tente d'accéder à une propriété non accessible. Il accepte un argument, qui est le nom de la propriété. Il devrait renvoyer une valeur, qui sera traitée comme la valeur de la propriété. Se souvenir du $ data propriété dans notre classe d'appareils? Nous allons stocker ces "pseudo propriétés" en tant qu'éléments du tableau de données et permettre aux utilisateurs de notre classe d'y accéder via __obtenir(). Voici à quoi ça ressemble:

class Device //… fonction publique __get ($ name) // vérifie si la clé nommée existe dans notre tableau if (array_key_exists ($ name, $ this-> data)) // puis renvoie la valeur du tableau retourné $ this-> data [$ name];  return null;  //…

Un usage populaire du __obtenir() méthode consiste à étendre le contrôle d’accès en créant des propriétés "en lecture seule". Prenez notre classe Battery, par exemple, qui a une propriété privée. Nous pouvons permettre au privé $ charge propriété à lire depuis le code extérieur, mais non modifiée. Le code ressemblerait à ceci:

classe Battery private $ charge = 0; fonction publique __get ($ name) if (isset ($ this -> $ name)) return $ this -> $ name;  return null;  //…

Dans cet exemple, notez l'utilisation de variables variables pour accéder dynamiquement à une propriété. En supposant que la valeur 'utilisateur' pour $ name, $ this -> $ name Se traduit par $ this-> utilisateur.

__ensemble()

le __ensemble() La méthode est appelée lorsque le code tente de modifier la valeur d'une propriété non accessible. Il accepte deux arguments, qui sont le nom de la propriété et la valeur. Voici à quoi cela ressemble pour le tableau "pseudo variables" de notre classe Device:

class Device //… public function __set ($ name, $ value) // utilise le nom de la propriété comme clé de tableau $ this-> data [$ name] = $ value;  //…

__isset ()

le __isset () la méthode est appelée quand les appels de code isset () sur une propriété qui n'est pas accessible. Il accepte un argument, qui est le nom de la propriété. Il devrait renvoyer une valeur booléenne représentant l'existence d'une valeur. Encore une fois en utilisant notre tableau de variables, voici à quoi cela ressemble:

class Device //… fonction publique __isset ($ name) // vous pouvez également utiliser isset () ici return array_key_exists ($ name, $ this-> data);  //…

__unset ()

le __unset () méthode est appelée lorsque le code tente de non défini () une propriété qui n'est pas accessible. Il accepte un argument, qui est le nom de la propriété. Voici à quoi ressemble notre:

class Device //… fonction publique __unset ($ name) // transfère le unset () à notre élément de tableau unset ($ this-> data [$ name]);  //…

Surcharge de propriété en action

Voici toutes les méthodes magiques relatives aux propriétés que nous avons déclarées:

class Device //… public $ data = array (); // stocke divers données dans un tableau //… fonction publique __get ($ name) // vérifie si la clé nommée existe dans notre tableau if (array_key_exists ($ name, $ this-> data)) // renvoie ensuite la valeur du tableau retourne $ this-> data [$ name];  return null;  public function __set ($ name, $ value) // utilise le nom de la propriété comme clé du tableau $ this-> data [$ name] = $ value;  public function __isset ($ name) // vous pouvez également utiliser isset () ici return array_key_exists ($ name, $ this-> data);  public function __unset ($ name) // transmet le unset () à notre élément de tableau unset ($ this-> data [$ name]);  //…

Avec les méthodes magiques ci-dessus, voici ce qui se passe lorsque nous essayons d'accéder à une propriété appelée name. Rappelez-vous qu'il n'y a pas vraiment de $ name propriété déclarée, bien que vous ne sachiez jamais cela sans voir le code de classe interne.

$ device-> user = 'Steve'; echo $ device-> user; // Steve

Nous avons défini et récupéré avec succès la valeur d'une propriété inexistante. Bien où est-il stocké alors?

print_r ($ device-> data); / * Array ([utilisateur] => Steve) * /

Comme vous pouvez le voir, le $ data la propriété contient maintenant un élément 'name' avec notre valeur.

var_dump (isset ($ device-> user)); // bool (true)

Ci-dessus est le résultat de l'appel isset () sur la fausse propriété.

unset ($ device-> user); var_dump (isset ($ device-> user)); // bool (false)

Ci-dessus est le résultat de la désactivation de la fausse propriété. Juste pour être sûr, voici notre tableau de données vide:

print_r ($ device-> data); / * Tableau () * /

Représenter des objets sous forme de texte

Parfois, vous voudrez peut-être convertir un objet en une représentation sous forme de chaîne. Si vous essayez simplement d'imprimer un objet, nous obtiendrons une erreur, telle que celle ci-dessous:

$ device = new Device (nouvelle batterie (), 'iMagic'); echo $ device; // Résultat: erreur fatale PHP capturable: l'objet de la classe Device n'a pas pu être converti en chaîne.

__toString ()

le __toString () La méthode est appelée lorsque le code tente de traiter un objet comme une chaîne. Il n'accepte aucun argument et devrait renvoyer une chaîne. Cela nous permet de définir comment l’objet sera représenté. Dans notre exemple, nous allons créer un résumé simple:

class Device … fonction publique __toString () // sommes-nous connectés? $ connected = (isset ($ this-> connection))? 'connecté': 'déconnecté'; // combien de données avons-nous? $ count = count ($ this-> data); // tout mettre ensemble renvoie $ this-> name. ' est ' . $ connecté. ' avec ' . $ compte. 'articles en mémoire'. PHP_EOL; …

Avec la méthode ci-dessus définie, voici ce qui se passe lorsque nous essayons d'imprimer un objet Device:

$ device = new Device (nouvelle batterie (), 'iMagic'); echo $ device; // iMagic est connecté avec 0 éléments en mémoire

L'objet Device est maintenant représenté par un court résumé contenant le nom, le statut et le nombre d'éléments stockés..

__set_state () (PHP 5.1)

La statique __set_state () La méthode (disponible à partir de PHP version 5.1) est appelée lorsque var_export () la fonction est appelée sur notre objet. le var_export () La fonction est utilisée pour convertir une variable en code PHP. Cette méthode accepte un tableau associatif contenant les valeurs de propriété de l'objet. Par souci de simplicité, utilisez-le bien dans notre classe de batteries.

class Battery //… fonction statique publique __set_state (array $ array) $ obj = new self (); $ obj-> setCharge ($ array ['charge']); return $ obj;  //…

Notre méthode crée simplement une instance de sa classe parente et la définit pour imputer la valeur du tableau passé. Avec la méthode ci-dessus définie, voici ce qui se passe lorsque nous utilisons var_export () sur un objet Device:

$ device = new Device (nouvelle batterie (), 'iMagic'); var_export ($ device-> battery); / * Battery :: __ set_state (array ('charge' => 0,)) * / eval ('$ battery ='. Var_export ($ device-> battery, true). ';'); var_dump ($ batterie); / * objet (Batterie) # 3 (1) ["charge: privé"] => int (0) * /

Le premier commentaire montre ce qui se passe réellement, à savoir que var_export () appelle simplement Batterie :: __ set_state (). Le deuxième commentaire nous montre recréer avec succès la batterie.


Clonage d'objets

Les objets, par défaut, sont transmis par référence. Donc, assigner d'autres variables à un objet ne copiera pas réellement l'objet, il créera simplement une nouvelle référence au même objet. Pour vraiment copier un objet, nous devons utiliser le cloner mot-clé.

Cette stratégie de «référence par référence» s'applique également aux objets contenus dans des objets. Même si nous clonons un objet, les objets enfants qu'il contient ne seront pas clonés. Nous nous retrouverions donc avec deux objets partageant le même objet enfant. Voici un exemple qui illustre cela:

$ device = new Device (nouvelle batterie (), 'iMagic'); $ device2 = clone $ device; $ appareil-> batterie-> setCharge (65); echo $ device2-> batterie-> charge; // 65

Ici, nous avons cloné un objet Device. Rappelez-vous que tous les objets Device contiennent un objet Battery. Pour démontrer que les deux clones de l'appareil partagent la même batterie, la modification apportée à la batterie de $ device est reflétée dans la batterie de $ device2..

__cloner()

le __cloner() méthode peut être utilisée pour résoudre ce problème. Il est appelé sur la copie d'un objet cloné après le clonage. C'est ici que vous pouvez cloner n'importe quel objet enfant.

class Device … fonction publique __clone () // copier notre objet Battery $ this-> battery = clone $ this-> battery; …

Avec cette méthode déclarée, nous pouvons maintenant être sûrs que les appareils clonés ont chacun leur propre batterie..

$ device = new Device (nouvelle batterie (), 'iMagic'); $ device2 = clone $ device; $ appareil-> batterie-> setCharge (65); echo $ device2-> batterie-> charge; // 0

Les modifications apportées à la batterie d'un périphérique n'affectent pas l'autre.


Sérialisation des objets

La sérialisation est le processus qui convertit toutes les données en un format de chaîne. Ceci peut être utilisé pour stocker des objets entiers dans un fichier ou une base de données. Lorsque vous désérialisez les données stockées, l'objet d'origine est exactement comme avant. Un problème avec la sérialisation, cependant, est que tout ne peut pas être sérialisé, comme les connexions à la base de données. Heureusement, il existe des méthodes magiques qui nous permettent de gérer ce problème.

__dormir()

le __dormir() la méthode est appelée lorsque le sérialiser () la fonction est appelée sur l'objet. Il n'accepte aucun argument et devrait renvoyer un tableau de toutes les propriétés à sérialiser. Vous pouvez également effectuer les tâches en attente ou le nettoyage éventuellement nécessaires dans cette méthode..

Pointe: Éviter de faire quelque chose de destructeur dans __dormir() puisque cela affectera l'objet en direct, et vous ne pourrez pas toujours en finir.

Dans notre exemple Device, la propriété de connexion représente une ressource externe qui ne peut pas être sérialisée. Donc notre __dormir() La méthode retourne simplement un tableau de toutes les propriétés sauf $ connexion.

class Device public $ name; // le nom de l'appareil public $ battery; // contient un objet Battery public $ data = array (); // stocke divers données dans un tableau public $ connection; // contient des ressources de connexion //… public function __sleep () // liste les propriétés à sauvegarder, return array ('name', 'battery', 'data');  //…

Notre __dormir() renvoie simplement une liste des noms des propriétés qui doivent être préservées.

__Réveillez-vous()

le __Réveillez-vous() la méthode est appelée lorsque le désérialiser () La fonction est appelée sur l'objet stocké. Il n'accepte aucun argument et n'a rien à renvoyer. Utilisez-le pour rétablir toute connexion à une base de données ou ressource perdue lors de la sérialisation..

Dans notre exemple d’appareil, nous devons simplement rétablir notre connexion en appelant notre relier() méthode.

class Device //… fonction publique __wakeup () // se reconnecter au réseau $ this-> connect ();  //…

Méthode Surcharge

Ces deux dernières méthodes concernent les méthodes. C'est le même concept que les méthodes de surcharge de propriété (__obtenir(), __ensemble(), etc), mais appliqué aux méthodes.

__appel()

le __appel() est appelé lorsque le code tente d'appeler des méthodes inaccessibles ou inexistantes. Il accepte deux arguments: le nom de la méthode appelée et un tableau d'arguments. Vous pouvez utiliser cette information pour appeler la même méthode dans un objet enfant, par exemple.

Dans les exemples, notez l’utilisation de la call_user_func_array () une fonction. Cette fonction nous permet d’appeler dynamiquement une fonction nommée (ou une méthode) avec les arguments stockés dans un tableau. Le premier argument identifie la fonction à appeler. Dans le cas des méthodes de dénomination, le premier argument est un tableau contenant un nom de classe ou une instance d'objet et le nom de la propriété. Le second argument est toujours un tableau indexé d’arguments à transmettre..

Dans notre exemple, nous allons passer l'appel de méthode à notre $ connexion propriété (que nous supposons est un objet). Nous allons retourner le résultat de ce droit au code de l'appelant.

class Device //… fonction publique __call ($ name, $ arguments) // assurez-vous que notre objet enfant a cette méthode if (method_exists ($ this-> connection, $ name)) // transfère l'appel à notre enfant retour d'objet call_user_func_array (array ($ this-> connection, $ name), $ arguments);  return null;  //…

La méthode ci-dessus serait appelée si nous essayons d'appeler le iDontExist () méthode:

$ device = new Device (nouvelle batterie (), 'iMagic'); $ device-> iDontExist (); // __call () le transmet à $ device-> connection-> iDontExist ()

__callStatic () (PHP 5.3)

le __callStatic () (disponible à partir de PHP version 5.3) est identique à __appel() sauf qu'il est appelé lorsque le code tente d'appeler des méthodes inaccessibles ou inexistantes dans un contexte statique.

La seule différence dans notre exemple est que nous déclarons la méthode statique et nous référons un nom de classe au lieu d'un objet.

class Device //… fonction statique publique __callStatic ($ name, $ arguments) // vérifie que notre classe a cette méthode if (method_exists ('Connection', $ name))) // transmet l'appel statique à notre retour de classe call_user_func_array (array ('Connexion', $ name), $ arguments);  return null;  //…

La méthode ci-dessus serait appelée si nous essayons d'appeler la statique iDontExist () méthode:

Device :: iDontExist (); // __callStatic () le transmet à Connection :: iDontExist ()

Utiliser des objets comme fonctions

Parfois, vous voudrez peut-être utiliser un objet en tant que fonction. Pouvoir utiliser un objet en tant que fonction vous permet de passer des fonctions sous forme d'arguments comme vous pouvez le faire dans d'autres langues..

__invoke () (PHP 5.3)

le __invoquer() (disponible à partir de PHP version 5.3) est appelé lorsque le code tente d'utiliser l'objet en tant que fonction. Tous les arguments définis dans cette méthode seront utilisés comme arguments de la fonction. Dans notre exemple, nous allons simplement imprimer l'argument qu'il reçoit.

class Device //… fonction publique __invoke ($ data) echo $ data;  //…

Avec ce qui est défini ci-dessus, voici ce qui se produit lorsque nous utilisons un périphérique en tant que fonction:

$ device = new Device (nouvelle batterie (), 'iMagic'); $ device ('test'); // équiv à $ device -> __ invoke ('test') // Sorties: test

Bonus: __autoload ()

Ce n'est pas une méthode magique, mais c'est quand même très utile. le __autoload () La fonction est automatiquement appelée lorsqu'une classe qui n'existe pas est référencée. Il est destiné à vous donner une dernière chance de charger le fichier contenant la déclaration de classe avant l’échec de votre script. C'est utile car vous ne voulez pas toujours charger toutes les classes au cas où vous en auriez besoin.

La fonction accepte un argument: le nom de la classe référencée. Supposons que chaque classe soit dans un fichier nommé 'classname.class.php' dans le répertoire 'inc'. Voici à quoi ressemblerait votre autoload:

function __autoload ($ class_name) $ class_name = strtolower ($ class_name); include_once './inc/'. $ nom_classe. '.class.php'; 

Conclusion

Les méthodes magiques sont extrêmement utiles et fournissent des outils puissants pour développer des infrastructures d’application flexibles. Ils rapprochent les objets PHP de ceux des autres langages orientés objet en vous permettant de reproduire certaines de leurs fonctionnalités les plus utiles. Vous pouvez lire les pages de manuel de PHP sur les méthodes magiques ici. J'espère que ce tutoriel a été utile et a clairement expliqué les concepts. Si vous avez des questions, n'hésitez pas à demander dans les commentaires. Merci d'avoir lu.