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

Temps de lecture : 33 minutes environ.

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 :




  • Tous les enregistrements d'une table (findAll)

  • Une sélection d'enregistrements en fonction de critères (findBy)

  • Un seul enregistrement par son id (find)



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




  • Le 1er contiendra les noms des champs

  • Le 2ème les valeurs cherchées




/**
* 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 :




  • Le nom de la table concernée dans la base de données

  • La liste des champs sous forme de propriétés "protected" pour permettre l'hydratation

  • Les accesseurs de ces propriétés



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

6 - Live Coding : PHP Orienté Objet - Base de données
Article publié le - Modifié le

Catégories : PHP Tutoriel Live-Coding poo

Mots-clés : Tutoriel Base de données Models MySQL PHP Live-Coding POO Orienté Objet pdo objet bdd

Partager : Partager sur Facebook Partager sur Twitter Partager sur LinkedIn