Comment configurer correctement une connexion PDO

92

De temps en temps, je vois des questions concernant la connexion à la base de données.
La plupart des réponses ne sont pas comme je le fais, ou je pourrais simplement ne pas obtenir les réponses correctement. En tous cas; Je n'y ai jamais pensé parce que ma façon de faire fonctionne pour moi.

Mais voici une pensée folle; Peut-être que je fais tout cela mal, et si c'est le cas; J'aimerais vraiment savoir comment me connecter correctement à une base de données MySQL en utilisant PHP et PDO et la rendre facilement accessible.

Voici comment je fais:

Tout d'abord, voici ma structure de fichiers (dépouillée) :

public_html/

* index.php  

* initialize/  
  -- load.initialize.php  
  -- configure.php  
  -- sessions.php   

index.php
Tout en haut, j'ai require('initialize/load.initialize.php');.

load.initialize.php

#   site configurations
    require('configure.php');
#   connect to database
    require('root/somewhere/connect.php');  //  this file is placed outside of public_html for better security.
#   include classes
    foreach (glob('assets/classes/*.class.php') as $class_filename){
        include($class_filename);
    }
#   include functions
    foreach (glob('assets/functions/*.func.php') as $func_filename){
        include($func_filename);
    }
#   handle sessions
    require('sessions.php');

Je sais qu'il existe une meilleure façon, ou plus correcte, d'inclure des cours, mais je ne me souviens pas de ce que c'était. Je n'ai pas encore eu le temps de l'examiner, mais je pense que c'était quelque chose avec autoload. quelque chose comme ca...

configure.php
Ici, je remplace simplement certaines propriétés de php.ini et fais une autre configuration globale pour le site

connect.php
J'ai mis la connexion sur une classe pour que d'autres classes puissent étendre celle-ci ...

class connect_pdo
{
    protected $dbh;

    public function __construct()
    {
        try {
            $db_host = '  ';  //  hostname
            $db_name = '  ';  //  databasename
            $db_user = '  ';  //  username
            $user_pw = '  ';  //  password

            $con = new PDO('mysql:host='.$db_host.'; dbname='.$db_name, $db_user, $user_pw);  
            $con->setAttribute( PDO::ATTR_ERRMODE, PDO::ERRMODE_EXCEPTION );
            $con->exec("SET CHARACTER SET utf8");  //  return all sql requests as UTF-8  
        }
        catch (PDOException $err) {  
            echo "harmless error message if the connection fails";
            $err->getMessage() . "<br/>";
            file_put_contents('PDOErrors.txt',$err, FILE_APPEND);  // write some details to an error-log outside public_html  
            die();  //  terminate connection
        }
    }

    public function dbh()
    {
        return $this->dbh;
    }
}
#   put database handler into a var for easier access
    $con = new connect_pdo();
    $con = $con->dbh();
//

Ici, je pense qu'il y a place pour une amélioration massive depuis que j'ai récemment commencé à apprendre la POO et à utiliser PDO au lieu de mysql.
Je viens donc de suivre quelques tutoriels pour débutants et d'essayer différentes choses ...

sessions.php
En plus de gérer les sessions régulières, j'initialise également certaines classes dans une session comme celle-ci:

if (!isset($_SESSION['sqlQuery'])){
    session_start();
    $_SESSION['sqlQuery'] = new sqlQuery();
}

De cette façon, cette classe est disponible partout. Ce n'est peut-être pas une bonne pratique (?) ...
Bref, c'est ce que cette approche me permet de faire de partout:

echo $_SESSION['sqlQuery']->getAreaName('county',9);  // outputs: Aust-Agder (the county name with that id in the database)

À l'intérieur de ma sqlQuery- classe , que extendsma connect_pdo- classe , j'ai une fonction publique appelée getAreaNamequi gère la demande à ma base de données.
Assez chouette je pense.

Fonctionne comme un charme
Alors c'est comme ça que je le fais.
De plus, chaque fois que j'ai besoin de récupérer quelque chose de ma base de données hors d'une classe, je fais simplement quelque chose de similaire à ceci:

$id = 123;

$sql = 'SELECT whatever FROM MyTable WHERE id = :id';
$qry = $con->prepare($sql);
$qry -> bindParam(':id', $id, PDO::PARAM_INT);
$qry -> execute();
$get = $qry->fetch(PDO::FETCH_ASSOC);

Depuis que j'ai mis la connexion dans une variable à l'intérieur de connect_pdo.php , je viens d'y faire référence et je suis prêt à partir. Ça marche. J'obtiens mes résultats attendus ...

Mais indépendamment de cela; J'apprécierais vraiment que vous me disiez si je suis loin d'ici. Ce que je devrais faire à la place, les domaines que je pourrais ou devrais changer pour amélioration, etc.

J'ai hâte d'apprendre ...

ThomasK
la source
9
Vous devez utiliser un chargeur automatique au lieu d'inclure chaque fichier dans votre application à la fois.
lusitanien
4
Cette question est probablement la meilleure sur Code Review
Madara's Ghost

Réponses:

105

Le but

Selon moi, votre objectif dans ce cas est double:

  • créer et maintenir une connexion unique / réutilisable par base de données
  • assurez-vous que la connexion a été correctement configurée

Solution

Je recommanderais d'utiliser à la fois la fonction anonyme et le modèle d'usine pour gérer la connexion PDO. Son utilisation ressemblerait à ceci:

$provider = function()
{
    $instance = new PDO('mysql:......;charset=utf8', 'username', 'password');
    $instance->setAttribute(PDO::ATTR_ERRMODE, PDO::ERRMODE_EXCEPTION);
    $instance->setAttribute(PDO::ATTR_EMULATE_PREPARES, false);
    return $instance;
};

$factory = new StructureFactory( $provider );

Puis dans un fichier différent ou inférieur dans le même fichier:

$something = $factory->create('Something');
$foobar = $factory->create('Foobar');

L'usine elle-même devrait ressembler à ceci:

class StructureFactory
{
    protected $provider = null;
    protected $connection = null;

    public function __construct( callable $provider )
    {
        $this->provider = $provider;
    }

    public function create( $name)
    {
        if ( $this->connection === null )
        {
            $this->connection = call_user_func( $this->provider );
        }
        return new $name( $this->connection );
    }

}

De cette façon, vous auriez une structure centralisée, qui garantit que la connexion n'est créée que lorsque cela est nécessaire. Cela faciliterait également le processus de test unitaire et de maintenance.

Le fournisseur dans ce cas se trouverait quelque part au stade du bootstrap. Cette approche donnerait également un emplacement clair où définir la configuration que vous utilisez pour vous connecter à la base de données.

Gardez à l'esprit qu'il s'agit d'un exemple extrêmement simplifié . Vous pourriez également bénéficier de regarder deux vidéos suivantes:

Aussi, je recommanderais fortement de lire un tutoriel approprié sur l'utilisation de PDO (il existe un journal de mauvais tutoriel en ligne).

tereško
la source
3
Puisque même PHP5.3 se rapproche de EOL. La majorité des sites avec des versions PHP obsolètes ne sont en fait qu'un hébergement bon marché pour Wordpress. L'impact des environnements pré-5.3 sur le développement professionnel (ils pourraient en quelque sorte bénéficier d'extraits comme celui-ci) est négligeable, selon mon estimation.
tereško
5
@thelolcat Je suis d'accord avec vous. Il est plus ou moins la même réponse. C'est si vous ne voyez pas le fait que c'est complètement différent.
PeeHaa
1
@thelolcat, alors vous devriez apprendre ce qu'est l' injection de dépendances . Au lieu de continuer à vous embarrasser. Curieusement, la deuxième vidéo du post ci-dessus (intitulée: "Don't Look For Things" ) expliquait en fait ce qu'est la DI et comment l'utiliser ... mais bien sûr, vous êtes trop avancé pour des choses aussi triviales.
tereško
2
C'est une vieille réponse, mais une bonne - et un excellent lien vers Mysql pdo à la fin
Fraise
1
@teecee vous devez commencer par apprendre à utiliser PDO. Je recommanderais ce tutoriel: wiki.hashphp.org/PDO_Tutorial_for_MySQL_Developers , car il est fait précisément pour les personnes qui souhaitent migrer mysql_*vers PDO. Ensuite, vous pouvez revenir et regarder ces solutions, qui s'adressent à ceux qui utilisent déjà PDO, mais qui ont besoin d'un moyen de partager la connexion DB entre plusieurs classes.
tereško
24

Je suggérerais de ne pas utiliser $_SESSIONpour accéder à votre connexion DB globalement.

Vous pouvez faire l'une des choses suivantes (de la pire aux meilleures pratiques):

  • Accès en $dbhutilisant l' global $dbhintérieur de vos fonctions et classes
  • Utilisez un registre singleton et accédez-y globalement, comme ceci:

    $registry = MyRegistry::getInstance();
    $dbh = $registry->getDbh();
  • Injectez le gestionnaire de base de données dans les classes qui en ont besoin, comme ceci:

    class MyClass {
        public function __construct($dbh) { /* ... */ }
    }

Je recommande vivement le dernier. C'est ce qu'on appelle l'injection de dépendances (DI), l'inversion de contrôle (IoC) ou simplement le principe hollywoodien (ne nous appelez pas, nous vous appellerons).

Cependant, il est un peu plus avancé et nécessite plus de "câblage" sans cadre. Donc, si l'injection de dépendances est trop compliquée pour vous, utilisez un registre singleton au lieu d'un tas de variables globales.

Ian Unruh
la source
Donc j'accède globalement à ma connexion db quand je mets ma sqlQuery-class en session puisqu'elle s'étend connect_pdo?
ThomasK
7

Je suis récemment venu à une réponse / question similaire par moi-même. Voici ce que j'ai fait, au cas où quelqu'un serait intéressé:

<?php
namespace Library;

// Wrapper for \PDO. It only creates the rather expensive instance when needed.
// Use it exactly as you'd use the normal PDO object, except for the creation.
// In that case simply do "new \Library\PDO($args);" with the normal args
class PDO
  {
  // The actual instance of PDO
  private $db;

  public function __construct() {
    $this->args = func_get_args();
    }

  public function __call($method, $args)
    {
    if (empty($this->db))
      {
      $Ref = new \ReflectionClass('\PDO');
      $this->db = $Ref->newInstanceArgs($this->args);
      }

    return call_user_func_array(array($this->db, $method), $args);
    }
  }

Pour l'appeler, il vous suffit de modifier cette ligne:

$DB = new \Library\PDO(/* normal arguments */);

Et l'indication de type si vous l'utilisez pour (\ Library \ PDO $ DB).

C'est vraiment similaire à la fois à la réponse acceptée et à la vôtre; cependant il a un avantage notable. Considérez ce code:

$DB = new \Library\PDO( /* args */ );

$STH = $DB->prepare("SELECT * FROM users WHERE user = ?");
$STH->execute(array(25));
$User = $STH->fetch();

Bien qu'il puisse ressembler à un PDO normal (il ne change que par cela \Library\), il n'initialise en fait pas l'objet tant que vous n'appelez pas la première méthode, quelle qu'elle soit. Cela le rend plus optimisé, car la création d'objet PDO est légèrement coûteuse. C'est une classe transparente, ou ce qu'on appelle un Ghost , une forme de Lazy Loading . Vous pouvez traiter le $ DB comme une instance PDO normale, le faire circuler, effectuer les mêmes opérations, etc.

Francisco Presencia
la source
C'est ce qu'on appelle "motif décorateur"
Yang
0
$dsn = 'mysql:host=your_host_name;dbname=your_db_name_here'; // define host name and database name
    $username = 'you'; // define the username
    $pwd='your_password'; // password
    try {
        $db = new PDO($dsn, $username, $pwd);
    }
    catch (PDOException $e) {
        $error_message = $e->getMessage();
        echo "this is displayed because an error was found";
        exit();
}
salut-code
la source