Live Coding : PHP Orienté Objet - Base de données

Par Nouvelle-Techno.fr le 25 mai 2020 - Catégories : PHP Tutoriel Live-Coding

Lire l'article sur le site d'origine

Nombreux sont les cas dans lesquels la connexion à une base de données est nécessaire.

Dans le cadre du développement orienté objet, plusieurs approches existent pour gérer cette connexion, nous allons en traiter une parmi tant d'autres.

La classe Db

Nous allons commencer par créer une classe "Db" dans un dossier "Db" et sur le namespace "App\Db".

Cette classe utilisant le Design Pattern Singleton, qui n'est instancié qu'une seule fois, permettra d'étendre la classe PDO et d'initier la connexion à la base de données.

Voici le code proposé pour cette classe

<?php

namespace App\Db;

// On "importe" PDO
use PDO;
use PDOException;

class Db extends PDO
{
    // Instance unique de la classe
    private static $instance;

    // Informations de connexion
    private const DBHOST = 'localhost';
    private const DBUSER = 'root';
    private const DBPASS = '';
    private const DBNAME = 'demo_poo';

    private function __construct()
    {
        // DSN de connexion
        $_dsn = 'mysql:dbname='. self::DBNAME . ';host=' . self::DBHOST;

        // On appelle le constructeur de la classe PDO
        try{
            parent::__construct($_dsn, self::DBUSER, self::DBPASS);

            $this->setAttribute(PDO::MYSQL_ATTR_INIT_COMMAND, 'SET NAMES utf8');
            $this->setAttribute(PDO::ATTR_DEFAULT_FETCH_MODE, PDO::FETCH_ASSOC);
            $this->setAttribute(PDO::ATTR_ERRMODE, PDO::ERRMODE_EXCEPTION);
        }catch(PDOException $e){
            die($e->getMessage());
        }
    }


    public static function getInstance():self
    {
        if(self::$instance === null){
            self::$instance = new self();
        }
        return self::$instance;
    }
}

Une fois cette classe créée, nous pourrons l'utiliser pour mettre en place nos modèles de gestion de la base.

Le modèle

Dans le modèle, nous allons écrire les différentes méthodes utilisables par défaut pour les manipulations de la base de données.

Nous allons donc mettre en place un "CRUD" (Create, Read, Update, Delete)

Tout d'abord, nous créons un dossier "Models" dans lequel on crée un fichier "Model.php".

Ce fichier contiendra le code de base suivant

<?php
namespace App\Models;

use App\Db\Db;

class Model extends Db
{
    // Table de la base de données
    protected $table;

    // Instance de connexion
    private $db;

}

Nous allons tout d'abord créer une méthode qui executera la requête ou la préparera selon les cas

/**
 * Méthode qui exécutera les requêtes
 * @param string $sql Requête SQL à exécuter
 * @param array $attributes Attributs à ajouter à la requête 
 * @return PDOStatement|false 
 */
public function requete(string $sql, array $attributs = null)
{
    // On récupère l'instance de Db
    $this->db = Db::getInstance();

    // On vérifie si on a des attributs
    if($attributs !== null){
        // Requête préparée
        $query = $this->db->prepare($sql);
        $query->execute($attributs);
        return $query;
    }else{
        // Requête simple
        return $this->db->query($sql);
    }
}

Nous allons ensuite ajouter des méthodes permettant de créer notre "CRUD". Les exemples ci-dessous ne sont pas exhaustifs, il est possible d'en ajouter.

Les méthodes "find" (Read)

Je commence par la partie "read" du CRUD, partie la plus utilisée.

Nous allons ajouter trois méthodes "find" permettant de sélectionner :

Le findAll

Pour sélectionner tous les enregistrements d'une table, méthode assez courte et simple, ne prenant aucun paramètre, nous allons procéder comme suit

/**
 * Sélection de tous les enregistrements d'une table
 * @return array Tableau des enregistrements trouvés
 */
public function findAll()
{
    $query = $this->requete('SELECT * FROM '.$this->table);
    return $query->fetchAll();
}

Cette méthode est terminée, nous verrons comment l'utiliser plus tard dans cet article.

Le findBy

La méthode findBy est plus complexe, étant donné qu'elle requiert de démonter un tableau d'arguments et de créer une chaîne de caractères ainsi qu'un tableau de valeurs correspondantes.

Ainsi, si nous souhaitons chercher les enregistrements qui ont un statut bien précis, comme un champ "actif" à 1, la requête SQL correspondante sera

SELECT * FROM annonces WHERE actif = 1;

Mais nous recevrons l'information sous forme de tableau PHP

['actif' => 1]

Nous allons donc devoir démonter ce tableau pour le transformer. Nous utiliserons une boucle et créerons deux autres tableaux

/**
 * Sélection de plusieurs enregistrements suivant un tableau de critères
 * @param array $criteres Tableau de critères
 * @return array Tableau des enregistrements trouvés
 */
public function findBy(array $criteres)
{
    $champs = [];
    $valeurs = [];

    // On boucle pour "éclater le tableau"
    foreach($criteres as $champ => $valeur){
        $champs[] = "$champ = ?";
        $valeurs[]= $valeur;
    }

    // On transforme le tableau en chaîne de caractères séparée par des AND
    $liste_champs = implode(' AND ', $champs);

    // On exécute la requête
    return $this->requete("SELECT * FROM {$this->table} WHERE $liste_champs", $valeurs)->fetchAll();
}

Le find

Enfin, la méthode "find" qui permettra de récupérer 1 enregistrement en fonction de son id

/**
 * Sélection d'un enregistrement suivant son id
 * @param int $id id de l'enregistrement
 * @return array Tableau contenant l'enregistrement trouvé
 */
public function find(int $id)
{
    // On exécute la requête
    return $this->requete("SELECT * FROM {$this->table} WHERE id = $id")->fetch();
}

L'insertion de données (Create)

Nous aurons régulièrement à insérer des données.

Nous enverrons les données à enregistrer sous forme de tableau associatif PHP

Ce tableau sera séparé en deux parties, l'une contenant les champs, l'autre les valeurs, comme pour la méthode "findBy"

/**
 * Insertion d'un enregistrement suivant un tableau de données
 * @param Model $model Objet à créer
 * @return bool
 */
public function create(Model $model)
{
    $champs = [];
    $inter = [];
    $valeurs = [];

    // On boucle pour éclater le tableau
    foreach($model as $champ => $valeur){
        // INSERT INTO annonces (titre, description, actif) VALUES (?, ?, ?)
        if($valeur !== null && $champ != 'db' && $champ != 'table'){
            $champs[] = $champ;
            $inter[] = "?";
            $valeurs[] = $valeur;
        }
    }

    // On transforme le tableau "champs" en une chaine de caractères
    $liste_champs = implode(', ', $champs);
    $liste_inter = implode(', ', $inter);

    // On exécute la requête
    return $this->requete('INSERT INTO '.$this->table.' ('. $liste_champs.')VALUES('.$liste_inter.')', $valeurs);
}

La mise à jour de données (Update)

La mise à jour utilisera la même approche que la création à la différence de l'ajout de l'id de l'enregistrement à modifier.

/**
 * Mise à jour d'un enregistrement suivant un tableau de données
 * @param int $id id de l'enregistrement à modifier
 * @param Model $model Objet à modifier
 * @return bool
 */
public function update(int $id, Model $model)
{
    $champs = [];
    $valeurs = [];

    // On boucle pour éclater le tableau
    foreach($model as $champ => $valeur){
        // UPDATE annonces SET titre = ?, description = ?, actif = ? WHERE id= ?
        if($valeur !== null && $champ != 'db' && $champ != 'table'){
            $champs[] = "$champ = ?";
            $valeurs[] = $valeur;
        }
    }
    $valeurs[] = $id;

    // On transforme le tableau "champs" en une chaine de caractères
    $liste_champs = implode(', ', $champs);

    // On exécute la requête
    return $this->requete('UPDATE '.$this->table.' SET '. $liste_champs.' WHERE id = ?', $valeurs);
}

La suppression de données (Delete)

Pour supprimer des données, rien de plus "simple", nous allons uniquement passer l'id de l'enregistrement à supprimer.

/**
 * Suppression d'un enregistrement
 * @param int $id id de l'enregistrement à supprimer
 * @return bool 
 */
public function delete(int $id){
    return $this->requete("DELETE FROM {$this->table} WHERE id = ?", [$id]);
}

L'hydratation

Nous allons enfin écrire une méthode permettant l'hydratation de notre objet, c'est à dire la définition de ses propriétés à partir d'un tableau ou d'un formulaire, par exemple.

/**
 * Hydratation des données
 * @param array $donnees Tableau associatif des données
 * @return self Retourne l'objet hydraté
 */
public function hydrate(array $donnees)
{
    foreach ($donnees as $key => $value){
        // On récupère le nom du setter correspondant à l'attribut.
        $method = 'set'.ucfirst($key);
        
        // Si le setter correspondant existe.
        if (method_exists($this, $method)){
            // On appelle le setter.
            $this->$method($value);
        }
    }
    return $this;
}

Utiliser notre modèle

Notre modèle principal étant terminé, nous allons l'utiliser pour effectuer des actions sur notre base de données.

Nous allons donc créer un modèle pour chaque table de notre base de données qui contiendra les informations qui lui sont spécifiques.

Nous indiquerons donc :

Notre classe, pour une table "annonces" contiendra donc le code suivant

<?php
namespace App\Models;

/**
 * Modèle pour la table "annonces"
 */
class AnnoncesModel extends Model
{
    protected $id;

    protected $titre;

    protected $description;

    protected $created_at;

    protected $actif;

    public function __construct()
    {
        $this->table = 'annonces';
    }

    /**
     * Obtenir la valeur de id
     */ 
    public function getId():int
    {
        return $this->id;
    }

    /**
     * Définir la valeur de id
     *
     * @return  self
     */ 
    public function setId(int $id):self
    {
        $this->id = $id;

        return $this;
    }

    /**
     * Obtenir la valeur de titre
     */ 
    public function getTitre():string
    {
        return $this->titre;
    }

    /**
     * Définir la valeur de titre
     *
     * @return  self
     */ 
    public function setTitre(string $titre):self
    {
        $this->titre = $titre;

        return $this;
    }

    /**
     * Obtenir la valeur de description
     */ 
    public function getDescription():string
    {
        return $this->description;
    }

    /**
     * Définir la valeur de description
     *
     * @return  self
     */ 
    public function setDescription(string $description):self
    {
        $this->description = $description;

        return $this;
    }

    /**
     * Obtenir la valeur de created_at
     */ 
    public function getCreatedAt()
    {
        return $this->created_at;
    }

    /**
     * Définir la valeur de created_at
     *
     * @return  self
     */ 
    public function setCreatedAt($created_at):self
    {
        $this->created_at = $created_at;

        return $this;
    }

    /**
     * Obtenir la valeur de actif
     */ 
    public function getActif():int
    {
        return $this->actif;
    }

    /**
     * Définir la valeur de actif
     *
     * @return  self
     */ 
    public function setActif(int $actif):self
    {
        $this->actif = $actif;

        return $this;
    }
}

Utiliser notre modèle

Nous pourrons enfin utiliser notre modèle pour accéder à notre base de données.

Nous devrons commencer par instancier notre modèle "AnnoncesModel" pour ensuite pouvoir l'utiliser

$annoncesModel = new AnnoncesModel();

Si nous souhaitons récupérer toutes les annonces de la base de données, nous écrirons

$annonces = $annoncesModel->findAll();

Pour obtenir uniquement les annonces actives

$annonces = $annoncesModel->findBy(['actif' => 1]);

Si nous souhaitons créer une nouvelle annonce, par ajout des différentes propriétés

$annonce = $annoncesModel
    ->setTitre('Titre de l\'annonce')
    ->setDescription('Description de l\'annonce');
$annoncesModel->create($annonce);

Si nous souhaitons hydrater notre objet à partir d'un tableau

$donnees = [
    'titre' => 'Mon autre titre',
    'description' => 'Ma nouvelle description',
];
$annonce = $annoncesModel->hydrate($donnees);

Et ainsi de suite...

Obtenir de l'aide

Pour obtenir de l'aide, vous pouvez accéder au serveur Discord pour une entraide par chat

#Tutoriel #Base de données #Models #PHP #Live-Coding #Orienté Objet