Décodage de la classe de proxy dans OpenCart

Le plus souvent, nous prenons les choses pour acquises. Si quelque chose fonctionne comme prévu, nous ne nous soucions pas de son fonctionnement interne pour comprendre le mécanisme sous-jacent. Ou pour le dire autrement, nous ne creusons pas dans quelque chose jusqu'à ce que nous ayons une sorte de problème!

De même, je me posais toujours des questions sur quelques concepts utilisés dans OpenCart dans le cadre sous-jacent, dont l'un était la classe Proxy. Il m'a fallu un certain temps pour le comprendre et j'ai pensé que c'était un très bon sujet à partager avec vous, car il est toujours amusant de savoir quelque chose de nouveau..

Qu'est-ce qu'une classe proxy??

Bien que vous trouviez en ligne divers documents définissant le terme mandataire, la définition fournie par Wikipedia est frappante et facile à comprendre..

Un proxy, dans sa forme la plus générale, est une classe fonctionnant comme interface avec quelque chose d'autre.

Ainsi, le mandataire délègue le contrôle à l'objet qu'il a l'intention d'utiliser et agit donc au nom de la classe réelle qui est instanciée. En fait, le modèle de conception de proxy est un modèle très populaire utilisé par les frameworks populaires en fonction des besoins. Considérant le fait qu'une discussion de la méthode proxy en elle-même est un sujet si vaste et hors du propos de cet article, je vais résumer rapidement son utilisation la plupart du temps:

  • Agir comme un wrapper pour fournir des fonctionnalités supplémentaires.
  • Retarder l'instanciation d'objets coûteux, également appelée chargement différé.

Dans le contexte d'OpenCart, on pourrait dire que le modèle de proxy est utilisé pour ajouter des fonctionnalités à la classe de proxy de base. Cela dit, la classe proxy de base elle-même ne fournit rien, sauf quelques méthodes magiques! Comme nous le verrons dans la section suivante, la classe proxy s'enrichit au moment de l'exécution en y attachant des propriétés..

Avant de passer à la section suivante, examinons rapidement la classe proxy. Il réside dans système / moteur / proxy.php.

$ key;  fonction publique __set ($ key, $ value) $ this -> $ key = $ value;  fonction publique __call ($ key, $ args) $ arg_data = array (); $ args = func_get_args (); foreach ($ args en tant que $ arg) if ($ arg instanceof Ref) $ arg_data [] = & $ arg-> getRef ();  else $ arg_data [] = & $ arg;  if (isset ($ this -> $ key)) return call_user_func_array ($ this -> $ key, $ arg_data);  else $ trace = debug_backtrace (); sortie('Remarquer: Propriété non définie: Proxy :: '. Clé $. ' dans '. $ trace [1] ['fichier']. ' en ligne '. $ trace [1] ['line']. ''); 

Comme vous pouvez le constater, trois méthodes magiques sont implémentées: __obtenir(), __ensemble(), et __appel(). Parmi eux, les __appel() l'implémentation de la méthode est importante, et nous y reviendrons bientôt.

Fonctionnement de la classe de proxy avec le modèle

Dans cette section, je vais expliquer comment exactement un appel comme $ this-> model_catalog_category-> getCategory ($ category_id) fonctionne hors de la boîte.

En fait, l'histoire commence par la déclaration suivante.

$ this-> load-> model ('catalogue / catégorie');

Lors de l’amorçage, la structure OpenCart stocke tous les objets génériques dans le Enregistrement objet afin qu'ils puissent être récupérés à volonté. À la suite de cela, le $ this-> charge appel retourne le Chargeur objet du registre.

le Chargeur classe fournit différentes méthodes pour charger différents composants, mais ce qui nous intéresse ici est la modèle méthode. Tirons rapidement l'extrait de la modèle méthode de système / moteur / loader.php.

modèle de fonction publique ($ route) // Désinfecte l'appel $ route = preg_replace ('/ [^ a-zA-Z0-9 _ \ /] /', ", (chaîne) $ route); // déclenche les événements précédents $ this-> registry-> get ('event') -> trigger ('model /'. $ route. '/ before', array (& $ route)); if (! $ this-> registry-> a ( 'modèle_'. str_replace (tableau ('/', '-', '.'), tableau ('_', ","), $ route)) $ file = DIR_APPLICATION. 'modèle /'. $ route . '.php'; $ class = 'Modèle'. preg_replace ('/ [^ a-zA-Z0-9] /', ", $ route); if (is_file ($ fichier)) include_once ($ fichier); $ proxy = nouveau proxy (); foreach (get_class_methods ($ class) en tant que $ méthode) $ proxy -> $ méthode = $ this-> callback ($ this-> registre, $ route. '/'. $ méthode);  $ this-> registry-> set ('model_'. str_replace (array ('/', '-', '.'), array ('_', ","), (string) $ route), $ Procuration);  else throw new \ Exception ('Erreur: Impossible de charger le modèle'. $ route. '!');  // Déclenche les événements de publication $ this-> registry-> get ('event') -> trigger ('model /'. $ Route. '/ After', array (& $ route)); 

Compte tenu de l'exemple susmentionné, la valeur de la $ route l'argument est catalogue / catégorie. Tout d'abord, la valeur de la $ route variable est assainie, et ensuite elle déclenche la avant événement pour permettre à d’autres auditeurs de module de modifier la valeur du paramètre. $ route variable.

Ensuite, il vérifie l'existence de l'objet de modèle demandé dans le registre. Si le registre contient l'objet demandé, aucun traitement supplémentaire n'est nécessaire..

Si l'objet n'est pas trouvé, il s'ensuit une procédure intéressante pour le charger. C'est l'extrait de code que nous recherchons dans le contexte de cet article..

Pour commencer, il prépare le chemin du fichier du modèle demandé et le charge s'il existe..

… $ File = DIR_APPLICATION. 'modèle/' . $ route. '.php'; $ class = 'Model'. preg_replace ('/ [^ a-zA-Z0-9] /', ", $ route); if (is_file ($ fichier)) include_once ($ fichier);…… 

Suite à cela, il instancie la Procuration objet.

$ proxy = nouveau proxy ();

Maintenant, faites attention à la prochaine pour loop-it fait beaucoup plus qu'il n'y paraît.

foreach (get_class_methods ($ class) en tant que $ méthode) $ proxy -> $ méthode = $ this-> callback ($ this-> registre, $ route. '/'. $ méthode); 

Dans notre cas, la valeur de $ classe devrait être ModèleCatalogCatégorie. le get_class_methods ($ class) snippet charge toutes les méthodes du ModèleCatalogCatégorie classe et boucle à travers elle. Que fait-il dans la boucle? Regardons de près.

Dans la boucle, il appelle le rappeler méthode de même classe. Il est intéressant de noter que la méthode de rappel renvoie la fonction appelable qui est assignée à la $ proxy objet avec la clé comme nom de méthode. Bien entendu, l’objet proxy n’a pas de telles propriétés; ça va être créé à la volée en utilisant le __ensemble() méthode magique!

Ensuite, le $ proxy L'objet est ajouté au Registre afin qu'il puisse être récupéré plus tard si nécessaire. Examinez de près l’élément clé de la ensemble méthode. Dans notre cas, il devrait être model_catalog_category.

$ this-> registry-> set ('modèle_'. str_replace (tableau ('/', '-', '.'), tableau ('_', ","), (chaîne) $ route), $ proxy )

À la fin, ça va appeler le après événement pour permettre à d’autres auditeurs de module de modifier la valeur du paramètre. $ route variable.

C'est une partie de l'histoire.

Passons en revue ce qui se passe exactement lorsque vous utilisez les éléments suivants dans votre contrôleur.

$ this-> model_catalog_category-> getCategory ($ category_id);

le $ this-> model_catalog_category extrait tente de trouver une correspondance pour la model_catalog_category clé dans le registre. Si vous vous demandez comment, il suffit de regarder dans la Manette définition de classe dans le système / moteur / contrôleur.php file-it fournit le __obtenir() méthode magique qui le fait.

Comme nous venons de le dire, cela devrait retourner le $ proxy objet qui est affecté à cette clé particulière. Ensuite, il essaie d'appeler le getCategory méthode sur cet objet. Mais la classe Proxy n'implémente pas une telle méthode, alors comment cela va-t-il fonctionner??

le __appel() méthode magique à la rescousse! Chaque fois que vous appelez une méthode qui n'existe pas dans la classe, le contrôle est transféré vers le __appel() méthode magique.

Explorons cela en détail pour comprendre ce qui se passe. Ouvrez le fichier de classe Proxy et faites attention à cette méthode.

le clé $ contient le nom de la fonction appelée-getCategory. D'autre part, $ args contient les arguments passés à la méthode, et il devrait s'agir d'un tableau d'un élément contenant l'id de catégorie qui est passé.

Ensuite, il y a un tableau $ arg_data qui stocke des références d'arguments. Franchement, je ne suis pas sûr si le code $ arg instanceof Ref fait aucun sens ou pas là. Si quelqu'un sait pourquoi c'est là, je serais heureux d'apprendre.

En outre, il essaie de vérifier l’existence de la clé $ propriété dans le $ proxy objet, et il en résulte quelque chose comme ça.

if (isset ($ this-> getCategory)) 

Rappelons que plus tôt nous avions assigné toutes les méthodes de ModèleCatalogCatégorie classe en tant que propriétés du $ proxy objet utilisant un pour boucle. Pour votre commodité, je vais coller à nouveau ce code.

… Foreach (get_class_methods ($ class) as $ method) $ proxy -> $ method = $ this-> callback ($ this-> registry, $ route. '/'. $ Method); … 

Donc, il devrait être là, et il devrait également nous rendre la fonction callable! Et enfin, il appelle cette fonction callable en utilisant le call_user_func_array fonction en passant la fonction elle-même appelable et les arguments de la méthode.

Passons maintenant à la définition de la fonction appelable elle-même. Je vais prendre l'extrait de la rappeler méthode définie dans système / moteur / loader.php.

… Function ($ args) use ($ registry, & $ route) statique $ model = array (); $ output = null; // Déclenche les événements précédents $ result = $ registry-> get ('event') -> trigger ('model /'. $ Route. '/ Before', array (& $ route, & $ args, & $ output) ) if ($ result) return $ result;  // Stocke l'objet de modèle if (! Isset ($ model [$ route])) $ file = DIR_APPLICATION. 'modèle/' . substr ($ route, 0, strrpos ($ route, '/')). '.php'; $ class = 'Model'. preg_replace ('/ [^ a-zA-Z0-9] /', ", substr ($ route, 0, strrpos ($ route, '/'))); if (is_file ($ fichier)) include_once ($ $ model [$ route] = new $ class ($ registry); else lancer new \ Exception ('Erreur: Impossible de charger le modèle'. substr ($ route, 0, strrpos ($ route, '/' )). '!'); $ method = substr ($ route, strrpos ($ route, '/') + 1); $ callable = array ($ modèle [$ route], $ méthode); if (is_callable ($ callable)) $ output = call_user_func_array ($ callable, $ args); new new. Exception ('Erreur: impossible d'appeler le modèle /'. $ route. '!)) events $ result = $ registry-> get ('event') -> trigger ('model /'. $ route. '/ after', tableau (& $ route, & $ args, & $ sortie)); if ($ result) return $ result; return $ output;;… 

Comme il s’agit d’une fonction anonyme, il a conservé les valeurs sous forme de $ registre et $ route les variables qui ont été passées précédemment à la méthode de rappel. Dans ce cas, la valeur de la $ route variable devrait être catalogue / catégorie / getCatégorie.

En dehors de cela, si nous regardons l'extrait important de cette fonction, il instancie la ModèleCatalogCatégorie objet et le stocke dans un statique modèle $ tableau.

… // Stocke l'objet de modèle si (! Isset ($ model [$ route])) $ file = DIR_APPLICATION. 'modèle/' . substr ($ route, 0, strrpos ($ route, '/')). '.php'; $ class = 'Model'. preg_replace ('/ [^ a-zA-Z0-9] /', ", substr ($ route, 0, strrpos ($ route, '/'))); if (is_file ($ fichier)) include_once ($ $ model [$ route] = new $ class ($ registry); else lancer new \ Exception ('Erreur: Impossible de charger le modèle'. substr ($ route, 0, strrpos ($ route, '/' )). '!');… 

Et voici un extrait qui saisit le nom de la méthode à appeler à l'aide de la touche $ route variable.

$ method = substr ($ route, strrpos ($ route, '/') + 1);

Nous avons donc une référence d'objet et un nom de méthode qui nous permet de l'appeler en utilisant le call_user_func_array une fonction. L'extrait suivant fait exactement cela!

… If (is_callable ($ callable)) $ output = call_user_func_array ($ callable, $ args);  else throw new \ Exception ('Erreur: impossible d'appeler model /'. $ route. '!'); … 

A la fin de la méthode, le résultat obtenu est renvoyé via le $ sortie variable. Et oui, c'est une autre partie de l'histoire!

J'ai délibérément ignoré le pré et poster code d'événements qui vous permet de remplacer les méthodes des classes OpenCart principales. En utilisant cela, vous pouvez remplacer n'importe quelle méthode de classe principale et la modifier en fonction de vos besoins. Mais gardons cela pour un autre jour, car ce sera trop pour pouvoir être inséré dans un seul article!

Voilà donc comment cela fonctionne tout à fait. J'espère que vous serez plus confiant à propos de ces appels sténographiques OpenCart et de leur fonctionnement interne.

Conclusion

Ce dont nous venons de parler est l’un des concepts intéressants et ambigus d’OpenCart: l’utilisation de la méthode Proxy dans un cadre prenant en charge les conventions de raccourci pour appeler des méthodes de modèle. J'espère que l'article était suffisamment intéressant et a enrichi vos connaissances du cadre OpenCart.

J'aimerais connaître vos commentaires à ce sujet, et si vous pensez que je devrais aborder de tels sujets dans mes prochains articles, n'hésitez pas à laisser un mot à ce sujet.!