Créer un framework PHP5 - Partie 2

Avec la structure de base de notre cadre en place, il est temps d’y ajouter des fonctionnalités. Dans ce didacticiel, nous allons créer un gestionnaire de modèles et un gestionnaire de base de données, nous rapprochant ainsi d’un puissant framework adapté à la plupart des projets. Si vous ne l'avez pas déjà fait, assurez-vous d'abord de lire la première partie de cette série.!




MVC: Tweak la structure

Dans la première partie de ce tutoriel, nous avons créé un dossier appelé contrôleurs pour stocker la logique métier de nos applications. Comme l'a souligné Daok dans un commentaire, ce n'est pas le meilleur endroit pour toute la logique métier, et qu'un modèle devrait être utilisé pour stocker cette logique. Auparavant, j’avais toujours utilisé la base de données elle-même comme modèle dans la plupart de mes applications. Cependant, en séparant un peu plus ce dernier, nous allons rendre notre cadre encore plus puissant et plus facile à étendre..

Donc qu'est-ce MVC? MVC est un modèle de conception (comme les modèles Singleton et Registry que nous avons examinés à la partie 1) et il correspond à Model View Controller. Ce modèle a pour but de séparer la logique métier, les actions de l'interface utilisateur et l'interface utilisateur de un autre. Bien que nous n'allions pas utiliser nos modèles et nos contrôleurs pour l'instant, mettons à jour la structure de dossiers de notre framework afin d'inclure le dossier "modèles". Le modèle contiendra la logique métier principale et le contrôleur traitera l’interaction de l’utilisateur (par exemple, la soumission de données, telle qu’un commentaire). NB: Notre fonction __autoload n'a pas besoin d'être modifiée.

Gestionnaire de base de données

La plupart des sites Web et des applications Web qui utilisent PHP utilisent également un moteur de base de données, tel que MySQL. Si nous conservons toutes nos fonctions liées à la base de données au même endroit, nous pouvons (en théorie) changer facilement le moteur de base de données que nous utilisons. Nous pouvons également faciliter certaines opérations, telles que l'insertion d'enregistrements, la mise à jour des enregistrements ou la suppression d'enregistrements de la base de données. Cela peut également faciliter la tâche lorsque vous utilisez plusieurs connexions à une base de données..

Alors… que devrait notre gestionnaire de base de données do:

  • Gérer les connexions à la base de données
  • Essayez de fournir un certain niveau d'abstraction à partir de la base de données
  • Cache les requêtes afin que nous puissions les utiliser plus tard
  • Faciliter les opérations de base de données

Regardons le code de notre gestionnaire de base de données, ensuite nous en discuterons.

 connexions [] = new mysqli ($ hôte, $ utilisateur, $ mot de passe, $ base de données); $ connection_id = count ($ this-> connections) -1; if (mysqli_connect_errno ()) trigger_error ('Erreur de connexion à l'hôte.'. $ this-> connexions [$ ID_connexion] -> erreur, E_USER_ERROR);  return $ id_connexion;  / ** * Ferme la connexion active * @retour de retour * / fonction publique closeConnection () $ this-> connexions [$ this-> activeConnection] -> close ();  / ** * Modifier la connexion à la base de données activement utilisée pour la prochaine opération * @ @param int la nouvelle ID de connexion * @return void * / public function setActiveConnection (int $ new) $ this-> activeConnection = $ new;  / ** * Stocke une requête dans le cache de la requête pour traitement ultérieur * * @param Chaîne de la chaîne de requête * @return le pointé sur la requête dans la fonction de cache * / public cacheQuery ($ queryStr) if (! $ Result = $ this-> connexions [$ this-> activeConnection] -> requête ($ queryStr)) trigger_error ('Erreur lors de l'exécution de la requête en cache:'. $ this-> connexions [$ this-> activeConnection] -> erreur, E_USER_ERROR); return -1;  else $ this-> queryCache [] = $ result; nombre de retour ($ this-> queryCache) -1;  / ** * Récupère le nombre de lignes du cache * @param int du pointeur de cache de requête * @return int le nombre de lignes * / public function numRowsFromCache ($ cache_id) return $ this-> queryCache [$ cache_id] -> num_rows;  / ** * Obtient les lignes d'une requête en cache * @param int le pointeur de cache de la requête * @return array la ligne * / public function resultsFromCache ($ cache_id) return $ this-> queryCache [$ cache_id] -> fetch_array ( MYSQLI_ASSOC);  / ** * Stocker des données dans un cache pour plus tard * @param array les données * @return in the pointées vers le tableau dans le cache de données * / public function cacheData ($ data) $ this-> dataCache [] = $ data; nombre de retour ($ this-> dataCache) -1;  / ** * Récupérer les données du cache de données * @ @param int cache de données pointé * @return tableau la fonction data * / public dataFromCache ($ id_ cache) return $ this-> dataCache [$ id_ cache];  / ** * Supprimer des enregistrements de la base de données * @param Chaîne de la table pour supprimer des lignes de * @param Chaîne de la condition pour laquelle les lignes doivent être supprimées * @param int le nombre de lignes à supprimer * @ @return void * / fonction publique deleteRecords ($ table, $ condition, $ limite) $ limite = ($ limite == ")?": 'LIMIT'. $ limite; $ delete = "DELETE FROM $ table WHERE $ condition $ limite"; $ this-> executeQuery ($ delete);  / ** * Mettre à jour les enregistrements de la base de données * @param Chaîne de la table * Tableau @param de modifications = = valeur * @param Chaîne la condition * @return bool * / public function updateRecords ($ table, $ changements, $ condition ) $ update = "UPDATE". $ table. " ENSEMBLE "; foreach ($ change en tant que $ field => $ value) $ update. = "'". $ field. "'=' $ value ',";  // supprime notre fin, $ update = substr ($ update, 0, -1); if ($ condition! = ") $ update. =" WHERE ". $ condition; $ this-> executeQuery ($ update); renvoyer true; / ** * Insérer des enregistrements dans la base de données * @param Chaîne la base de données table * @param tableau de données à insérer champ => valeur * @return bool * / public function insertRecords ($ table, $ data) // configurer des variables pour les champs et les valeurs $ fields = ""; $ values ​​= ""; // les peupler pour chaque ($ data comme $ f => $ v) $ fields. = "'$ f',"; $ valeurs. = (is_numeric ($ v) && (intval ($ v) == $ v ))? $ v. ",": "'$ v',"; // supprime notre fin, $ fields = substr ($ champs, 0, -1); // supprime notre fin, $ values ​​= substr ( $ values, 0, -1); $ insert = "INSERT INTO table ($ fields) VALEURS ($ valeurs)"; $ this-> executeQuery ($ insert); return true; / ** * Exécuter une chaîne de requête * @param Chaîne de la requête * @return void * / fonction publique executeQuery ($ queryStr) if (! $ Result = $ this-> connexions [$ this-> activeConnection] -> requête ($ queryStr)) trigger_error ('Erreur lors de l'exécution de la requête:'. $ this-> connections [$ t his-> activeConnection] -> error, E_USER_ERROR);  else $ this-> last = $ result;  / ** * Récupère les lignes de la dernière requête exécutée, à l'exclusion des requêtes en cache * @return array * / public function getRows () return $ this-> last-> fetch_array (MYSQLI_ASSOC);  / ** * Obtient le nombre de lignes affectées à partir de la requête précédente * @return int le nombre de lignes affectées * / public function restrictedRows () return $ this -> $ this-> connections [$ this-> activeConnection] - > affects_rows;  / ** * Désinfection des données * @param Chaîne des données à assainir * @return Chaîne des données assainies * / fonction publique sanitizeData ($ data) return $ this-> connexions [$ this-> activeConnection] -> real_escape_string ( $ data);  / ** * Déconstruit l'objet * ferme toutes les connexions à la base de données * / public function __deconstruct () foreach ($ this-> connections as $ connection) $ connection-> close (); ?>

Avant de discuter plus en détail de cette question, il convient de souligner que ce gestionnaire de base de données est très basique. Nous pourrions fournir une abstraction complète en n'exécutant pas les requêtes directement, mais en construisant des requêtes basées sur les paramètres d'une fonction de requête, puis en l'exécutant..

Nos méthodes d'enregistrement, de suppression, d'insertion et de mise à jour facilitent l'exécution de tâches courantes (comme nous l'avons mentionné ci-dessus, nous pourrions l'étendre pour en faire beaucoup plus), en fournissant uniquement des informations telles que le nom de la table, un tableau de champs et des valeurs correspondantes, valeurs limites et conditions. Les requêtes peuvent également être "mises en cache" afin que nous puissions les utiliser ultérieurement. Je trouve cette fonctionnalité (ainsi que la possibilité de "mettre en cache" des tableaux de données) est très pratique lorsqu'elle est combinée à un gestionnaire de modèles, car nous pouvons facilement parcourir des lignes de données et les intégrer facilement à nos modèles, comme vous le ferez voir quand on regarde le gestionnaire de modèles.

 // insert record $ registry-> getObject ('db') -> insertRecords ('testTable', array ('name' => 'Michael')); // met à jour un enregistrement $ registry-> getObject ('db') -> updateRecords ('testTable', array ('name' => 'MichaelP'), 'ID = 2'); // supprime un enregistrement (jusqu'à 5 dans ce cas) $ registry-> getObject ('db') -> deleteRecords ('testTable', "name = 'MichaelP'", 5);

Nous pouvons également travailler relativement facilement avec plusieurs connexions de base de données, du moment que nous basculons entre les connexions appropriées lorsque cela est nécessaire (bien que cela ne fonctionne pas lors de la mise en cache des requêtes et de leur extraction via notre gestionnaire de modèles sans autre travail), par exemple, extrait de code ci-dessous nous permettrait de supprimer des enregistrements de deux bases de données.

 // notre deuxième connexion à la base de données (supposons que nous ayons déjà une connexion à notre base de données principale) $ newConnection = $ registry-> getObject ('db') -> newConnection ('localhost', 'root', 'password', 'secondDB '); // supprime la connexion à la base de données principale $ registry-> getObject ('db') -> deleteRecords ('testTable', "name = 'MichaelP'", 5); // change notre connexion active à la base de données, pour permettre aux futures requêtes d'être sur la deuxième connexion $ registry-> getObject ('db') -> setActiveConnection ($ newConnection); // supprime la connexion à la base de données secondaire $ registry-> getObject ('db') -> deleteRecords ('testTable', "name = 'MichaelP'", 5); // rétablit la connexion active pour que les futures requêtes soient sur la connexion à la base de données principale $ registry-> getObject ('db') -> setActiveConnection (0);

Comment pourrions-nous vouloir étendre cette classe?

  • Abstraction complète
  • Utiliser l'héritage, créer une interface et en faire hériter les classes de base de données, chacune pour différents moteurs de base de données
  • Stocker l'ID de connexion avec la requête lors de la mise en cache des requêtes
  • Améliorer la désinfection des données, en fonction du type de données à désinfecter

Gestionnaire de modèles

Le gestionnaire de modèles gérera toutes les sorties, il doit pouvoir travailler avec différents fichiers de modèles, remplacer les espaces réservés (je les appelle des balises) par des données et parcourir différentes parties du modèle avec plusieurs lignes de données de la base de données..

Pour simplifier les choses, nous allons utiliser une classe de page pour contenir le contenu lié à la page. Cela nous facilite également l’extension et l’ajout de fonctionnalités plus tard. Le gestionnaire de modèles va gérer cet objet.

 page = nouvelle page ();  / ** * Ajouter un bit de modèle sur notre page * @param String $ tag la balise où nous insérons le modèle, par exemple. hello * @param Chaîne $ bit le bit de modèle (chemin du fichier ou simplement le nom du fichier) * @return void * / fonction publique addTemplateBit ($ tag, $ bit) if (strpos ($ bit, 'skins /' ) === faux) $ bit = 'skins /'. PCARegistry :: getSetting ('skin'). '/ templates /'. $ bit;  $ this-> page-> addTemplateBit ($ tag, $ bit);  / ** * Mettre les bits de modèle dans le contenu de notre page * Met à jour le contenu de la page * @return void * / private function replaceBits () $ bits = $ this-> page-> getBits (); foreach ($ bits en tant que $ tag => $ template) $ templateContent = fichier_get_contents ($ bit); $ newContent = str_replace (''. $ tag. '', $ templateContent, $ this-> page-> getContent ()); $ this-> page-> setContent ($ newContent);  / ** * Remplace les balises de notre page par le contenu * @return void * / private function replaceTags () // récupère les balises $ tags = $ this-> page-> getTags (); // les passe tous pour chaque ($ tags en tant que $ tag => $ data) if (is_array ($ data)) if ($ data [0] == 'SQL') // c'est une requête en cache… remplace les balises de base de données $ this-> replaceDBTags ($ tag, $ data [1]);  elseif ($ data [0] == 'DATA') // il s'agit de données en cache… remplace les balises de données $ this-> replaceDataTags ($ tag, $ data [1]);  else // remplace le contenu $ newContent = str_replace (''. balise $. '', $ data, $ this-> page-> getContent ()); // met à jour le contenu des pages $ this-> page-> setContent ($ newContent);  / ** * Remplacez le contenu de la page par les données de la base de données DB * @param String $ la balise définissant la zone de contenu * @param int $ cacheId l'ID de la requête dans le cache de la requête * @ @return void * / private function replaceDBTags ($ tag, $ cacheId) $ block = "; $ blockOld = $ this-> page-> getBlock ($ tag); // pour chaque enregistrement relatif à la requête… while ($ tags = PCARegistry :: getObject ( 'db') -> resultsFromCache ($ cacheId)) $ blockNew = $ blockOld; // crée un nouveau bloc de contenu avec les résultats remplacés dans foreach ($ tags comme $ ntag => $ data) $ blockNew = str_replace ("". $ ntag. "", $ data, $ blockNew); $ block. = $ blockNew; $ pageContent = $ this-> page-> getContent (); // supprime le séparateur dans le modèle , nettoyeur HTML $ newContent = str_replace (''. $ blockOld. '', $ block, $ pageContent); // met à jour le contenu de la page $ this-> page-> setContent ($ newContent);  / ** * Remplacer le contenu de la page par les données du cache * @param String $ tag le tag définissant la zone de contenu * @param int $ cacheId l'ID de données dans le cache de données * @ @ turnurn void * / private function replaceDataTags ($ tag, $ cacheId) $ block = $ this-> page-> getBlock ($ tag); $ blockOld = $ block; while ($ tags = PCARegistry :: getObject ('db') -> dataFromCache ($ cacheId)) foreach ($ tags en tant que $ tag => $ data) $ blockNew = $ blockOld; $ blockNew = str_replace ("". $ tag. "", $ data, $ blockNew);  $ block. = $ blockNew;  $ pageContent = $ this-> page-> getContent (); $ newContent = str_replace ($ blockOld, $ block, $ pageContent); $ this-> page-> setContent ($ newContent);  / ** * Récupère l'objet de page * @return Object * / fonction publique getPage () return $ this-> page;  / ** * Définit le contenu de la page en fonction d'un certain nombre de modèles * passe les emplacements de fichier de modèle comme arguments individuels * @return void * / public function buildFromTemplates () $ bits = func_get_args (); $ content = ""; foreach ($ bits en tant que $ bit) if (strpos ($ bit, 'skins /') === false) $ bit = 'skins /'. PCARegistry :: getSetting ('skin'). '/ templates /'. $ bit;  if (file_exists ($ bit) == true) $ content. = fichier_get_contents ($ bit);  $ this-> page-> setContent ($ content);  / ** * Convertit un tableau de données (une ligne de base de données?) En balises * @param array les données * @param string un préfixe ajouté au nom du champ pour créer le nom de la balise * @return void * / public fonction dataToTags ($ data, $ prefix) foreach ($ data as $ key => $ content) $ this-> page-> addTag ($ key. $ prefix, $ content);  fonction publique parseTitle () $ newContent = str_replace ('','<title>'. $ page-> getTitle (), $ this-> page-> getContent ()); $ this-> page-> setContent ($ newContent);  / ** * Analyser l'objet de page dans une sortie * @return void * / public function parseOutput () $ this-> replaceBits (); $ this-> replaceTags (); $ this-> parseTitle (); ?></pre> <p> <em>Alors, que fait exactement cette classe?? </em></p> <p> <strong>Crée notre objet de page et le base à partir de fichiers modèles</strong>, l'objet de page contient le contenu et les informations nécessaires à la création du code HTML de la page. Nous construisons ensuiteFromTemplate ('templatefile.tpl.php', 'templatefile2.tpl.php') pour obtenir le contenu initial de notre page. Cette méthode prend un nombre quelconque de fichiers de modèle comme arguments et les assemble dans l'ordre, utile pour modèles d'en-tête, de contenu et de pied de page.</p> <p> <strong>Gère le contenu associé à la page</strong> en aidant l'objet page à conserver un enregistrement des données à remplacer dans la page, ainsi que des bits de modèle supplémentaires devant être incorporés dans la page (addTemplateBit ('userbar', 'usertoolsbar.tpl.php')).</p> <p> <strong>Ajoute des données et du contenu à la page</strong> en effectuant diverses opérations de remplacement sur le contenu de la page, notamment en récupérant les résultats d'une requête en cache et en les ajoutant à la page.</p> <p>Le fichier de modèle doit indiquer en lui-même où une requête en cache doit être extraite et les données de la requête remplacées. Lorsque le gestionnaire de modèles rencontre une balise à remplacer qui est une requête, il obtient le bloc de la page où il doit effectuer une itération en appelant getBlock ('block') sur l'objet de page. Ce bloc de contenu est ensuite copié pour chaque enregistrement de la requête et ses balises sont remplacées par les résultats de la requête. Nous verrons comment cela se présente dans le modèle ultérieurement dans ce didacticiel..</p> <h3>Gestionnaire de modèles: page</h3> <p>L'objet de page est géré par le gestionnaire de modèles et contenait tous les détails liés à la page. Cela laisse le gestionnaire de modèles libre de gestion, tout en facilitant l’extension ultérieure des fonctionnalités de celui-ci..</p> <pre> <?php /** * This is our page object * It is a seperate object to allow some interesting extra functionality to be added * Some ideas: passwording pages, adding page specific css/js files, etc */ class page  // room to grow later? private $css = array(); private $js = array(); private $bodyTag ="; private $bodyTagInsert ="; // future functionality? private $authorised = true; private $password ="; // page elements private $title ="; private $tags = array(); private $postParseTags = array(); private $bits = array(); private $content = ""; /** * Constructor… */ function __construct()   public function getTitle()  return $this->Titre;  fonction publique setPassword ($ password) $ this-> password = $ password;  fonction publique setTitle ($ title) $ this-> title = $ title;  public function setContent ($ content) $ this-> content = $ content;  fonction publique addTag ($ key, $ data) $ this-> tags [$ key] = $ data;  fonction publique getTags () return $ this-> tags;  fonction publique addPPTag ($ key, $ data) $ this-> postParseTags [$ key] = $ data;  / ** * Récupère les balises à analyser après le premier lot * @return array * / public function getPPTags () return $ this-> postParseTags;  / ** * Ajoute un bit de modèle à la page sans ajouter le contenu pour le moment * * @param Chaîne de la balise où le modèle est ajouté * @param Chaîne du nom de fichier du modèle * @return void * / fonction public addTemplateBit ($ tag, $ bit) $ this-> bits [$ tag] = $ bit;  / ** * Obtenez les bits de modèle à saisir dans la page * @return array le tableau de balises de modèle et les noms de fichier de modèle * / public function getBits () return $ this-> bits;  / ** * Obtient une partie du contenu de la page * @param Chaîne la balise encapsulant le bloc ( <!-- START tag --> bloc <!-- END tag --> ) * * @return Chaîne le bloc de contenu * / fonction publique getBlock ($ tag) preg_match ('#<!-- START '. $tag . ' -->(. +?)<!-- END '. $tag . ' -->#si ', $ this-> content, $ tor); $ tor = str_replace ('<!-- START '. $tag . ' -->', "", $ tor [0]); $ tor = str_replace ('<!-- END ' . $tag . ' -->', "", $ tor); return $ tor;  fonction publique getContent () return $ this-> content; ?></pre> <p>Comment cette classe peut-elle être étendue et améliorée?</p> <ul> <li>PostParseTags: Vous pouvez souhaiter remplacer les balises <em>après</em> la majeure partie de la page a été analysée, peut-être que le contenu de la base de données contient des balises qui doivent être analysées.</li> <li>Pages avec mot de passe: attribuez un mot de passe à une page, vérifiez si l'utilisateur a le mot de passe dans un cookie ou une session pour lui permettre de voir la page..</li> <li>Pages restreintes (bien que nous ayons d’abord besoin de nos composants d’authentification!)</li> <li>Modifier le </li> <li>Ajouter dynamiquement des références aux fichiers javascript et css en fonction de la page ou de l'application.</li> </ul> <h3>Charger des objets de base</h3> <p>Maintenant que nous avons des objets que notre registre va stocker pour nous, nous devons dire au registre quels sont ces objets. J'ai créé une méthode dans l'objet PCARegistry appelée loadCoreObjects qui (comme il est dit) charge les objets centraux. Cela signifie que vous pouvez simplement appeler cela depuis notre fichier index.php pour charger le registre avec ces objets..</p> <pre> fonction publique storeCoreObjects () $ this-> storeObject ('database', 'db'); $ this-> storeObject ('template', 'template'); </pre> <p>Cette méthode peut être modifiée ultérieurement pour incorporer les autres objets principaux que le registre doit charger. Bien sûr, il peut y avoir des objets que nous voulons que notre registre gère, mais uniquement en fonction de l'application pour laquelle l'infrastructure est utilisée. Ces objets seraient chargés en dehors de cette méthode.</p> <h3>Quelques données</h3> <p>Afin de pouvoir démontrer les nouvelles fonctionnalités ajoutées à notre framework, nous avons besoin d'une base de données pour utiliser le gestionnaire de base de données, ainsi que certaines des fonctions de gestion de modèles (où nous remplaçons un bloc de contenu par les lignes de la base de données)..</p> <p>Le site de démonstration que nous allons créer avec notre framework d’ici la fin de cette série de tutoriels est un site Web avec un répertoire des membres. Nous allons donc créer un tableau de base des bases de données pour les profils des membres, contenant un ID, un nom et une adresse électronique..</p> <img src="//accentsconagua.com/img/images_27_8/create-a-php5-framework-part-2_2.png"> <p>Évidemment, nous avons besoin de quelques lignes de données dans cette table!</p> <h3>Un modèle rapide</h3> <p>Pour que quoi que ce soit soit affiché, nous avons besoin d’un modèle de base, dans lequel nous listerons les données de notre table de membres..</p> <pre> <html> <head> <title> Propulsé par PCA Framework   

Nos membres

Vous trouverez ci-dessous une liste de nos membres:

  • nom email

Les commentaires HTML des membres START et des membres END indiquent le bloc des membres (obtenu via la méthode getBlock () de la page), le gestionnaire de modèles effectuant une itération dans les enregistrements de la base de données et les affichant..

Cadre utilisé

Maintenant, nous devons rassembler tout cela, avec notre fichier index.php:

 // require notre registre require_once ('PCARegistry / pcaregistry.class.php'); $ registry = PCARegistry :: singleton (); // stocke ces objets de base $ registry-> storeCoreObjects (); // crée une connexion à la base de données $ registry-> getObject ('db') -> newConnection ('localhost', 'root', ", 'pcaframework'); // définit le paramètre de skin par défaut (nous les stockons dans la base de données plus tard…) $ registry-> storeSetting ('default', 'skin'); // remplit notre objet page à partir d'un fichier modèle $ registry-> getObject ('template') -> buildFromTemplates ('main.tpl.php') ; // cache une requête de nos membres table $ cache = $ registry-> getObject ('db') -> cacheQuery ('SELECT * FROM members'); // assigne cette balise à la balise des membres $ registry-> getObject (' template ') -> getPage () -> addTag (' members ', array (' SQL ', $ cache)); // définit le titre de la page $ registry-> getObject (' template ') -> getPage () -> setTitle ('Nos membres'); // tout analyser, et le recracher $ registry-> getObject ('template') -> parseOutput (); print $ registry-> getObject ('template') -> getPage () -> getContent ();

Si nous voyons maintenant cette page dans notre navigateur Web, les résultats de la requête sont affichés sur la page:

À venir dans la partie 3…

Dans la troisième partie, nous ferons un léger détour par le côté développement de notre framework, et examinerons comment concevoir en tenant compte de notre framework, et comment découper des modèles HTML de manière à ce qu'ils conviennent à notre framework. Lorsque nous commencerons à construire notre première application avec notre framework, nous examinerons plus en détail le fonctionnement de ces classes. Enfin, merci pour vos commentaires la dernière fois!

  • Abonnez-vous au flux RSS NETTUTS pour plus de commentaires et d'articles sur le développement Web au quotidien.