Page 1 sur 1

PDO singleton

Posté : 24 avr. 2017, 11:24
par kevin254kl
Bonjour,

Je suis un tuto https://www.primfx.com/forum/programmat ... letter-530 pour créer une newletter.

Je veux utiliser une classe pdo de connexion mais j'ai quelque problème

Code : Tout sélectionner

<!DOCTYPE html> <html> <head> <title>New Letter</title> </head> <body> <?php session_start(); include_once('SPDO.class.php'); $bdd = SPDO::getInstance(); $info = $bdd->prepare('SELECT * FROM newsletter WHERE id = ?'); $info->execute(array($_SERVER['REMOTE_ADDR'])); $newsletterexist = $info->rowCount(); ?> <?php if($newsletterexist == 0) { ?> <form method="POST"> <label>Adresse e-mail</label><br /> <input type="email" name="newsletter" /><br /><br /> <input type="submit" name="newsletterform" value="Envoyer"/> </form> <?php } else { ?> <label>Adresse e-mail</label><br /> <input type="email" name="newsletter" disabled/><br /><br /> <?php while($news = $reqnewsletter->fetch()) { ?> <a href="?deinscription=<?= $news['id'] ?>">Me déinscrire de la Newsletter</a> <?php } ?> <?php } ?> </body> </html>
la classe

Code : Tout sélectionner

<?php class SPDO { /** * Instance of SPDO * * @var PDO * @access private */ private $PDOInstance = null; /** * Instance of SPDO * * @var SPDO * @access private * @static */ private static $instance = null; /** * Constante: user name of database * * @var string */ const DEFAULT_SQL_USER = 'root'; /** * Constante: host database * * @var string */ const DEFAULT_SQL_HOST = 'localhost'; /** * Constante: password host * * @var string */ const DEFAULT_SQL_PASS = ''; /** * Constante: databasename * * @var string */ const DEFAULT_SQL_DTB = 'newletter'; /** * Constructeur * * @param void * @return void * @see PDO::__construct() * @access private */ private function __construct() { $this->PDOInstance = new PDO('mysql:dbname='.self::DEFAULT_SQL_DTB.';host='.self::DEFAULT_SQL_HOST,self::DEFAULT_SQL_USER ,self::DEFAULT_SQL_PASS); } /** * make and return object spo instance * * @access public * @static * @param void * @return SPDO $instance */ public static function getInstance() { if(is_null(self::$instance)) { self::$instance = new SPDO(); } return self::$instance; } } ?>
J'ai l'erreur

Code : Tout sélectionner

atal error: Call to undefined method SPDO::prepare() in C:\wamp64\www\newletter\newletter.view.php on line 11
Il faut hériter de la classe PDO?

Je vous remercie pour votre aide bonne journée.

Re: PDO singleton

Posté : 24 avr. 2017, 12:27
par moogli
salut,

A priori ta classe ne semble pas faite pour cela, je ne sais pas trop a quoi elle peux servir dans l'état
- Pas possible de fournir d'autre identifiant que ceux dans la classe, du coup pour mettre en prod (ou simplement changer d'environnement) faut modifier le code => pas bien
- impossible de récupérer l'instance de PDO donc ben loose :)
Ta classe ressemble à un commentaire de la doc : http://php.net/manual/fr/ref.pdo-mysql.php#120267 (toujours avec le problème de modifier le code pour changer d'environnement.

deux solutions :
- Soit tu hérites de PDO pour gérer l'instanciation et pourquoi ajouter des fonctionnalités (à voir)
- Soit tu crées une factory et la tu va pouvoir gérer une (ou plusieurs au choix) instance de PDO.

je pense que c'est le second pattern qu'il te faut, le premier pourquoi pas je ne trouve pas ça terrible (généralement l'héritage pour cela je trouve ça moyen).

une factory c'est relativement simple à faire.
je t'ai fait une exemple rapide (multi connexion) : https://gist.github.com/tipounet/32986b ... 779141a6ee

pense au try catch ;)
avec cela tu auras un objet PDO à chaque fois, c'est toujours le même pour chaque base.

@+

Re: PDO singleton

Posté : 24 avr. 2017, 15:29
par kevin254kl
Merci beaucoup pour vos explications toujours très précise.

J'ai une classe Connexion et avec l'autoloading de composer j'inclus mes classes

Code : Tout sélectionner

<?php require_once '../vendor/autoload.php'; class Connexion { public function __construct() { $o = new Database\PDOOptions(); $o->setDbname('newletter'); $o->setDriver('mysql'); $o->setHost('localhost'); $o->setPort(3306); $o->setOptions([]); $o->setUser('root'); $o->setPasswd(''); return Database\PDOFactory::get($o); } }
Cela évite de mettre mes paramètres dans une vue.

Le problème c'est qu'il ne reconnait pas la fonction prepare de pdo

Code : Tout sélectionner

<!DOCTYPE html> <html> <head> <title>New Letter</title> </head> <body> <?php session_start(); include_once('Connexion.php'); $bdd = new Connexion(); $bdd->prepare('SELECT * FROM newsletter WHERE id = ?'); $bdd->execute(array($_SERVER['REMOTE_ADDR'])); $newsletterexist = $bdd->rowCount(); ?> <?php if($newsletterexist == 0) { ?> <form method="POST"> <label>Adresse e-mail</label><br /> <input type="email" name="newsletter" /><br /><br /> <input type="submit" name="newsletterform" value="Envoyer"/> </form> <?php } else { ?> <label>Adresse e-mail</label><br /> <input type="email" name="newsletter" disabled/><br /><br /> <?php while($news = $reqnewsletter->fetch()) { ?> <a href="?deinscription=<?= $news['id'] ?>">Me déinscrire de la Newsletter</a> <?php } ?> <?php } ?> </body> </html>
J'ai l'erreur

Code : Tout sélectionner

Fatal error: Call to undefined method Connexion::prepare() in C:\wamp64\www\newletter\src\newletter.view.php on line 11
l'objet connexion est bien instancié donc je ne comprend pas d'ou peut venir le problème?

Merci encore pour votre aide.

Re: PDO singleton

Posté : 25 avr. 2017, 10:49
par moogli
ah ben oui un un new sur un classe retourne un objet de la classe dont tu demandes une instance. Un constructeur retour rien tu ne peux pas faire ainsi.

à la limite une méthode statique à la place du constructeur mais bon c'est un peu inutile.
tu n'es pas obligé d'utiliser ma classe tel quel, tu peux très bien mettre la récupération des informations de connexion dans une méthode de la factory qui va retourner l'objet 'option'.

et ne met pas les info en dur dans le code, met ça dans un fichier de configuration (texte genre ini, yaml, xml ou ce que tu veux).
tu peux faire quelque chose comme ça
<?php
class PDOFactory
{
    private static $instances = [];

    public static function get(PDOOptions $options): PDO
    {
        $hash = $options->hash();
        if (!isset(self::$instances[$hash])) {
            $dsn = $options->getDriver() . ':host=' . $options->getHost() . ';';
            if (!empty($options->getPort())) {
                $dsn .= 'port=' . $options->getPort() . ';';
            }
            if (!empty($options->getDbname())) {
                $dsn .= 'dbname=' . $options->getDbname() . ';';
            }
            self::$instances[$hash] = new \PDO($dsn, $options->getUser(), $options->getPasswd(), $options->getOptions());
        }
        return self::$instances[$hash];
    }

    public static function create(){
      return self::get(self::getOptions());
    }
    private static function getOptions(){
      $o = new PDOOptions();
      $o->setDbname('test');
      $o->setDriver('mysql');
      $o->setHost('localhost');
      $o->setPort(3306);
      $o->setOptions([]);
      $o->setUser('test');
      $o->setPasswd('test');
      return $o;
    }
}
la méthode getOptions() devrait utiliser un fichier de configuration (faut p'tet voir pour une constant indiquant l'emplacement de base de tes fichiers pour éviter les soucis d'accès et de chemin relatif ou pas ;) )

tu parles de ne pas mettre les informations de connexion dans la vue, mais tu appels la DAO directement c'est moyen aussi ;)
Regarde le motif de conception : MVC pour comprendre la subtilité ;)


@+

Re: PDO singleton

Posté : 25 avr. 2017, 12:12
par kevin254kl
Super merci cela fonctionne.