logo

Types de blocs supplémentaires (EBT) – Nouvelle expérience de Layout Builder❗

Types de blocs supplémentaires (EBT) – types de blocs stylisés et personnalisables : diaporamas, onglets, cartes, accordéons et bien d’autres. Paramètres intégrés pour l’arrière-plan, la boîte DOM, les plugins JavaScript. Découvrez dès aujourd’hui le futur de la création de mises en page.

Démo des modules EBT Télécharger les modules EBT

❗Types de paragraphes supplémentaires (EPT) – Nouvelle expérience Paragraphes

Types de paragraphes supplémentaires (EPT) – ensemble de modules basé sur les paragraphes analogiques.

Démo des modules EPT Télécharger les modules EPT

GLightbox is a pure javascript lightbox (Colorbox alternative without jQuery)❗

It can display images, iframes, inline content and videos with optional autoplay for YouTube, Vimeo and even self-hosted videos.

Demo GLightbox Download GLightbox

Défilement

1.5. Connexion des classes pour travailler avec la base de données et les templates

05/07/2025, by Ivan

Nous avons créé la structure de notre framework, il est maintenant temps de penser au stockage des données : actualités, produits. Un objet pour travailler avec la base de données doit pouvoir :

  • Gérer la connexion à la base de données
  • Fournir une petite abstraction de la base de données
  • Mettre en cache les requêtes
  • Simplifier les opérations communes sur la base de données

Pour cela, nous allons créer l'objet Registry/objects/db.class.php :

<?php

/**
 * Gestion de la base de données
 * Fournit une abstraction légère de la base de données
 */
class database {

  /**
   * Permet plusieurs connexions à la base de données
   * rarement utilisé, mais parfois utile
   */
  private $connections = array();

  /**
   * Connexion active
   * setActiveConnection($id) permet de changer la connexion active
   */
  private $activeConnection = 0;

  /**
   * Requêtes exécutées et mises en cache
   */
  private $queryCache = array();

  /**
   * Données extraites et mises en cache
   */
  private $dataCache = array();

  /**
   * Dernière requête exécutée
   */
  private $last;

  /**
   * Constructeur
   */
  public function __construct()
  {

  }

  /**
   * Création d'une nouvelle connexion
   * @param string hostname de la base
   * @param string nom d'utilisateur
   * @param string mot de passe
   * @param string nom de la base utilisée
   * @return int identifiant de la nouvelle connexion
   */
  public function newConnection( $host, $user, $password, $database )
  {
    $this->connections[] = new mysqli( $host, $user, $password, $database );
    $connection_id = count( $this->connections )-1;
    if( mysqli_connect_errno() )
    {
      trigger_error('Erreur de connexion : '.$this->connections[$connection_id]->error, E_USER_ERROR);
    }

    return $connection_id;
  }

  /**
   * Fermer la connexion active
   */
  public function closeConnection()
  {
    $this->connections[$this->activeConnection]->close();
  }

  /**
   * Changer la connexion active
   * @param int nouvel id de connexion
   */
  public function setActiveConnection( int $new )
  {
    $this->activeConnection = $new;
  }

  /**
   * Mettre en cache une requête
   * @param string requête SQL
   * @return int identifiant de la requête en cache
   */
  public function cacheQuery( $queryStr )
  {
    if( !$result = $this->connections[$this->activeConnection]->query( $queryStr ) )
    {
      trigger_error('Erreur d’exécution et mise en cache de la requête : '.$this->connections[$this->activeConnection]->error, E_USER_ERROR);
      return -1;
    }
    else
    {
      $this->queryCache[] = $result;
      return count($this->queryCache)-1;
    }
  }

  /**
   * Obtenir le nombre de lignes dans le cache
   * @param int id cache requête
   * @return int nombre de lignes
   */
  public function numRowsFromCache( $cache_id )
  {
    return $this->queryCache[$cache_id]->num_rows;
  }

  /**
   * Obtenir les résultats d'une requête mise en cache
   * @param int id cache requête
   * @return array ligne de résultat
   */
  public function resultsFromCache( $cache_id )
  {
    return $this->queryCache[$cache_id]->fetch_array(MYSQLI_ASSOC);
  }

  /**
   * Mettre en cache des données
   * @param array données
   * @return int identifiant dans le cache données
   */
  public function cacheData( $data )
  {
    $this->dataCache[] = $data;
    return count( $this->dataCache )-1;
  }

  /**
   * Obtenir des données du cache
   * @param int id cache données
   * @return array données
   */
  public function dataFromCache( $cache_id )
  {
    return $this->dataCache[$cache_id];
  }

  /**
   * Supprimer des enregistrements dans une table
   * @param string table
   * @param string condition de suppression
   * @param int nombre de lignes à supprimer
   */
  public function deleteRecords( $table, $condition, $limit )
  {
    $limit = ( $limit == '' ) ? '' : ' LIMIT ' . $limit;
    $delete = "DELETE FROM {$table} WHERE {$condition} {$limit}";
    $this->executeQuery( $delete );
  }

  /**
   * Mettre à jour des enregistrements dans une table
   * @param string table
   * @param array changements champ => valeur
   * @param string condition
   * @return bool
   */
  public function updateRecords( $table, $changes, $condition )
  {
    $update = "UPDATE " . $table . " SET ";
    foreach( $changes as $field => $value )
    {
      $update .= "`" . $field . "`='{$value}',";
    }

    // supprimer la dernière virgule
    $update = substr($update, 0, -1);
    if( $condition != '' )
    {
      $update .= " WHERE " . $condition;
    }

    $this->executeQuery( $update );

    return true;
  }

  /**
   * Insérer des enregistrements dans une table
   * @param string table
   * @param array données champ => valeur
   * @return bool
   */
  public function insertRecords( $table, $data )
  {
    $fields  = "";
    $values = "";

    foreach ($data as $f => $v)
    {
      $fields  .= "`$f`,";
      $values .= ( is_numeric( $v ) && ( intval( $v ) == $v ) ) ? $v."," : "'$v',";
    }

    $fields = substr($fields, 0, -1);
    $values = substr($values, 0, -1);

    $insert = "INSERT INTO $table ({$fields}) VALUES({$values})";
    $this->executeQuery( $insert );
    return true;
  }

  /**
   * Exécuter une requête SQL
   * @param string requête
   */
  public function executeQuery( $queryStr )
  {
    if( !$result = $this->connections[$this->activeConnection]->query( $queryStr ) )
    {
      trigger_error('Erreur lors de l’exécution de la requête : '.$this->connections[$this->activeConnection]->error, E_USER_ERROR);
    }
    else
    {
      $this->last = $result;
    }
  }

  /**
   * Obtenir les lignes du dernier résultat (hors cache)
   * @return array
   */
  public function getRows()
  {
    return $this->last->fetch_array(MYSQLI_ASSOC);
  }

  /**
   * Obtenir le nombre de lignes affectées du dernier résultat
   * @return int
   */
  public function affectedRows()
  {
    return $this->connections[$this->activeConnection]->affected_rows;
  }

  /**
   * Assainir les données pour sécurité
   * @param string données à nettoyer
   * @return string données nettoyées
   */
  public function sanitizeData( $data )
  {
    return $this->connections[$this->activeConnection]->real_escape_string( $data );
  }

  /**
   * Destructeur, ferme toutes les connexions
   */
  public function __destruct()
  {
    foreach( $this->connections as $connection )
    {
      $connection->close();
    }
  }
}
?>

Avant de connecter la base de données, regardons ce que fait notre classe. Nous pourrons effectuer des opérations simples d’insertion, mise à jour, suppression via les méthodes :

// Insertion
$registry->getObject('db')->insertRecords( 'products', array('name'=>'Tasse' ) );
// Mise à jour
$registry->getObject('db')->updateRecords( 'products', array('name'=>'Tasse rouge' ), 'ID=2' );
// Suppression
$registry->getObject('db')->deleteRecords( 'products', "name='Tasse rouge'", 5 );

Notre classe supporte aussi la mise en cache.

Créons maintenant un autre objet pour gérer les templates dans Registry/objects/template.class.php :

<?php

if ( ! defined( 'FW' ) )
{
  echo 'Ce fichier ne peut être appelé que depuis index.php et non directement';
  exit();
}

/**
 * Classe de gestion des templates
 */
class template {

  private $page;

  public function __construct()
  {
    include( APP_PATH . '/Registry/objects/page.class.php');
    $this->page = new Page();
  }

  public function addTemplateBit( $tag, $bit )
  {
    if( strpos( $bit, 'Views/' ) === false )
    {
      $bit = 'Views/Templates/' . $bit;
    }
    $this->page->addTemplateBit( $tag, $bit );
  }

  private function replaceBits()
  {
    $bits = $this->page->getBits();
    foreach( $bits as $tag => $template )
    {
      $templateContent = file_get_contents( $template );
      $newContent = str_replace( '{' . $tag . '}', $templateContent, $this->page->getContent() );
      $this->page->setContent( $newContent );
    }
  }

  private function replaceTags()
  {
    $tags = $this->page->getTags();
    foreach( $tags as $tag => $data )
    {
      if( is_array( $data ) )
      {
        if( $data[0] == 'SQL' )
        {
          $this->replaceDBTags( $tag, $data[1] );
        }
        elseif( $data[0] == 'DATA' )
        {
          $this->replaceDataTags( $tag, $data[1] );
        }
      }
      else
      {
        $newContent = str_replace( '{' . $tag . '}', $data, $this->page->getContent() );
        $this->page->setContent( $newContent );
      }
    }
  }

  private function replaceDBTags( $tag, $cacheId )
  {
    $block = '';
    $blockOld = $this->page->getBlock( $tag );

    while ($tags = Registry::getObject('db')->resultsFromCache( $cacheId ) )
    {
      $blockNew = $blockOld;
      foreach ($tags as $ntag => $data)
      {
        $blockNew = str_replace("{" . $ntag . "}", $data, $blockNew);
      }
      $block .= $blockNew;
    }
    $pageContent = $this->page->getContent();
    $newContent = str_replace( '' . $blockOld . '', $block, $pageContent );
    $this->page->setContent( $newContent );
  }

  private function replaceDataTags( $tag, $cacheId )
  {
    $block = $this->page->getBlock( $tag );
    $blockOld = $block;
    while ($tags = Registry::getObject('db')->dataFromCache( $cacheId ) )
    {
      foreach ($tags as $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 );
  }

  public function getPage()
  {
    return $this->page;
  }

  public function buildFromTemplates()
  {
    $bits = func_get_args();
    $content = "";
    foreach( $bits as $bit )
    {
      if( strpos( $bit, 'skins/' ) === false )
      {
        $bit = 'Views/Templates/' . $bit;
      }
      if( file_exists( $bit ) == true )
      {
        $content .= file_get_contents( $bit );
      }
    }
    $this->page->setContent( $content );
  }

  public function dataToTags( $data, $prefix )
  {
    foreach( $data as $key => $content )
    {
      $this->page->addTag( $key.$prefix, $content);
    }
  }

  public function parseTitle()
  {
    $newContent = str_replace('', '<title>'. $this->page->getTitle(), $this->page->getContent() );
    $this->page->setContent( $newContent );
  }

  public function parseOutput()
  {
    $this->replaceBits();
    $this->replaceTags();
    $this->parseTitle();
  }

}
?>
</pre>

<p>Nous avons aussi défini que l’objet Page est appelé dans le templateur, donc nous devons définir ce dernier dans <code>Registry/objects/page.class.php</code> :</p>

<pre class="1;">
<?php

/**
 * Notre classe Page
 * Permet d’ajouter des fonctionnalités utiles
 * Par exemple : pages protégées par mot de passe, ajout de js/css, etc.
 */
class page {

  private $css = array();
  private $js = array();
  private $bodyTag = '';
  private $bodyTagInsert = '';

  private $authorised = true;
  private $password = '';

  private $title = '';
  private $tags = array();
  private $postParseTags = array();
  private $bits = array();
  private $content = "";

  function __construct() { }

  public function getTitle()
  {
    return $this->title;
  }

  public function setPassword( $password )
  {
    $this->password = $password;
  }

  public function setTitle( $title )
  {
    $this->title = $title;
  }

  public function setContent( $content )
  {
    $this->content = $content;
  }

  public function addTag( $key, $data )
  {
    $this->tags[$key] = $data;
  }

  public function getTags()
  {
    return $this->tags;
  }

  public function addPPTag( $key, $data )
  {
    $this->postParseTags[$key] = $data;
  }

  public function getPPTags()
  {
    return $this->postParseTags;
  }

  public function addTemplateBit( $tag, $bit )
  {
    $this->bits[ $tag ] = $bit;
  }

  public function getBits()
  {
    return $this->bits;
  }

  public function 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;
  }

  public function getContent()
  {
    return $this->content;
  }

}
?>
</pre>

<p>Maintenant que nous avons créé les classes pour la base de données et les templates, connectons-les.</p>

<p>Créons la méthode <code>storeCoreObjects()</code> dans <code>Registry/registry.class.php</code> :</p>

<pre class="1;">
public function storeCoreObjects()
{
  $this->storeObject('database', 'db' );
  $this->storeObject('template', 'template' );
}
</pre>

<p>Dans cette méthode, nous indiquerons les classes à connecter.</p>

<p>Créons aussi une table <code>users</code> avec trois champs : id, name, email. Je joindrai un fichier SQL sur Git pour l'exemple.</p>

<p>Créons un template <code>Views/Templates/main.tpl.php</code> pour afficher la page principale :</p>

<pre class="1;">
<html>
<head>
    <title> Powered by PCA Framework</title>
</head>
<body>
<h1>Nos membres</h1>
<p>Voici la liste de nos membres :</p>
<ul>
<!-- START members -->
<li>{name} {email}</li>
<!-- END members -->
</ul>
</body>
</html>
</pre>

<p>Nous avons défini une balise members et des tokens {name}, {email}. Nous verrons plus en détail le fonctionnement du templateur dans un autre article.</p>

<p>Enfin, modifions <code>index.php</code> pour connecter la base de données et le template :</p>

<pre class="1;">
<?php
/**
 * Framework
 * Point d'entrée de notre framework
 */

// démarrer la session
session_start();

error_reporting(E_ALL);

// constantes
define( "APP_PATH", dirname( __FILE__ ) ."/" );
define( "FW", true );

/**
 * Fonction d'autoload magique
 * Charge les controllers à la demande
 * @param string nom de la classe
 */
function __autoload( $class_name )
{
    require_once('Controllers/' . $class_name . '/' . $class_name . '.php' );
}

// charger le registry
require_once('Registry/registry.class.php');
$registry = Registry::singleton();

// stocker les objets principaux
$registry->storeCoreObjects();

// accès à la base de données
$registry->getObject('db')->newConnection('localhost', 'root', '', 'framework');

// charger le template principal
$registry->getObject('template')->buildFromTemplates('main.tpl.php');

// requête à la table users
$cache = $registry->getObject('db')->cacheQuery('SELECT * FROM users');

// associer la balise users aux résultats SQL
$registry->getObject('template')->getPage()->addTag('users', array('SQL', $cache) );

// définir le titre de la page
$registry->getObject('template')->getPage()->setTitle('Nos utilisateurs');

// parser et afficher la page
$registry->getObject('template')->parseOutput();
print $registry->getObject('template')->getPage()->getContent();

// afficher le nom du framework pour vérification
print $registry->getFrameworkName();

exit();

?>
</pre>

<p>Si tout fonctionne et que la base contient des utilisateurs, vous devriez voir un résultat similaire :</p>

<p><img alt="Nos utilisateurs" src="/sites/default/files/inline-images/7511265.png" /></p>

<p>En cas d'erreur, vous pouvez consulter le code sur GitHub. Voici quelques corrections que j'ai dû faire :</p>

<p><strong>Renommer la classe database en db dans <code>Registry/objects/db.class.php</code> :</strong></p>

<pre class="1;">
-class database {
+class db {
</pre>

<p><strong>Définir les méthodes statiques dans <code>Registry/registry.class.php</code> :</strong></p>

<pre class="1;">
-    public function storeObject( $object, $key )
+    public static function storeObject( $object, $key )
-        require_once('objects/' . $object . '.class.php');
+        require_once('Registry/objects/' . $object . '.class.php');
-        self::$objects[ $key ] = new $object( self::$instance );
+        self::$objects[ $key ] = new $object( self::$instance );

-    public function storeCoreObjects()
+    public function storeCoreObjects()
-      $this->storeObject('database', 'db' );
+      $this->storeObject('db', 'db' );
       $this->storeObject('template', 'template' );
-    }
-
-    public function getObject( $key )
+    }

-    public function getObject( $key )
+    public static function getObject( $key )
     {
         if( is_object ( self::$objects[ $key ] ) )
         {
</pre>

<p>Il a également fallu créer un contrôleur db (Controllers/db/db.php) et corriger une erreur dans le templateur <code>Registry/objects/template.class.php</code> :</p>

<pre class="1;">
-  public function parseTitle()
-  {
-    $newContent = str_replace('<title>', '<title>'. $this->$page->getTitle(), $this->page->getContent() );
-    $this->page->setContent( $newContent );
-  }
+  public function parseTitle()
+  {
+    $newContent = str_replace('<title>', '<title>'. $this->page->getTitle(), $this->page->getContent() );
+    $this->page->setContent( $newContent );
+  }
</pre>