Cómo configurar correctamente una conexión PDO

92

De vez en cuando veo preguntas sobre la conexión a la base de datos.
La mayoría de las respuestas no son como las hago yo, o es posible que no las obtenga correctamente. De todas formas; Nunca lo había pensado porque la forma en que lo hago me funciona.

Pero he aquí un pensamiento loco; Quizás estoy haciendo todo mal, y si ese es el caso; Realmente me gustaría saber cómo conectarme correctamente a una base de datos MySQL usando PHP y PDO y hacerla fácilmente accesible.

Así es como lo estoy haciendo:

En primer lugar, aquí está mi estructura de archivos (simplificada) :

public_html/

* index.php  

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

index.php
En la parte superior, tengo 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');

Sé que hay una forma mejor, o más correcta, de incluir clases, pero no recuerdo cuál era. Aún no he tenido tiempo de investigarlo, pero creo que fue algo con autoload. algo como eso...

configure.php
Aquí básicamente anulo algunas propiedades de php.ini y hago alguna otra configuración global para el sitio

connect.php
He puesto la conexión en una clase para que otras clases puedan extender esta ...

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();
//

Aquí creo que hay margen para una mejora masiva desde que comencé a aprender OOP y a usar PDO en lugar de mysql.
Así que acabo de seguir un par de tutoriales para principiantes y probé cosas diferentes ...

session.php
Además de manejar sesiones regulares, también inicializo algunas clases en una sesión como esta:

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

De esta manera esta clase está disponible en todo el lugar. Esto podría no ser una buena práctica (?) ...
De todos modos, esto es lo que este enfoque me permite hacer desde todas partes:

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

Dentro de my sqlQuery- class , que extendsmy connect_pdo- class , tengo una función pública llamada getAreaNameque maneja la solicitud a mi base de datos.
Bastante ordenado, creo.

Funciona de maravilla.
Así es básicamente como lo estoy haciendo.
Además, siempre que necesito buscar algo de mi base de datos que no está dentro de una clase, simplemente hago algo similar a esto:

$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);

Desde que puse la conexión en una variable dentro de connect_pdo.php , solo me he referido a ella y estoy listo para comenzar. Funciona. Obtengo mis resultados esperados ...

Pero independientemente de eso; Realmente agradecería que me dijeran si me voy de aquí. Lo que debería hacer en su lugar, las áreas que podría o debería cambiar para mejorar, etc.

Estoy ansioso por aprender ...

ThomasK
fuente
9
Debería utilizar un autocargador en lugar de incluir todos los archivos de su aplicación a la vez.
Lusitano
4
Esta pregunta es probablemente la mejor en Code Review
Madara's Ghost

Respuestas:

105

La meta

Como yo lo veo, su objetivo en este caso es doble:

  • crear y mantener una conexión única / reutilizable por base de datos
  • asegúrese de que la conexión se haya configurado correctamente

Solución

Recomendaría usar tanto la función anónima como el patrón de fábrica para tratar con la conexión PDO. Su uso se vería así:

$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 );

Luego, en un archivo diferente o inferior en el mismo archivo:

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

La fábrica en sí debería verse así:

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 esta manera, le permitiría tener una estructura centralizada, lo que garantiza que la conexión se cree solo cuando sea necesario. También facilitaría mucho el proceso de prueba unitaria y mantenimiento.

En este caso, el proveedor se encontraría en algún lugar de la etapa de arranque. Este enfoque también proporcionaría una ubicación clara donde definir la configuración que utiliza para conectarse a la base de datos.

Tenga en cuenta que este es un ejemplo extremadamente simplificado . También puede beneficiarse de ver los dos videos siguientes:

Además, recomendaría encarecidamente leer un tutorial adecuado sobre el uso de PDO (hay un registro de tutoriales incorrectos en línea).

tereško
fuente
3
Dado que incluso PHP5.3 se está acercando a EOL. La mayoría de los sitios con versiones de PHP desactualizadas son en realidad solo alojamiento barato para Wordpress. En mi opinión, el impacto de los entornos anteriores a 5.3 en el desarrollo profesional (que se beneficiarían de fragmentos como este) es insignificante.
tereško
5
@thelolcat Estoy de acuerdo contigo. Que es más o menos la misma respuesta. Eso es si no ves el hecho de que es completamente diferente.
PeeHaa
1
@thelolcat, entonces deberías aprender qué es la inyección de dependencia . En lugar de seguir avergonzándote. Curiosamente, el segundo video en la publicación anterior (titulado: "No busques cosas" ) en realidad explica qué es DI y cómo usarlo ... pero, por supuesto, eres demasiado avanzado para cosas tan triviales.
tereško
2
Esta es una respuesta antigua, pero buena, y un gran enlace a Mysql pdo al final
Strawberry
1
@teecee, primero debe comenzar aprendiendo a usar PDO. Recomendaría este tutorial: wiki.hashphp.org/PDO_Tutorial_for_MySQL_Developers , porque está hecho precisamente para personas que desean migrar de mysql_*a PDO. Luego, puede volver y ver estas soluciones, que están dirigidas a aquellos que ya usan PDO, pero necesitan una forma de compartir la conexión de base de datos entre varias clases.
tereško
24

Sugeriría no usar $_SESSIONpara acceder a su conexión de base de datos globalmente.

Puede hacer una de las siguientes cosas (en orden de las peores a las mejores prácticas):

  • Acceda $dbhusando global $dbhdentro de sus funciones y clases
  • Use un registro singleton y acceda a eso globalmente, así:

    $registry = MyRegistry::getInstance();
    $dbh = $registry->getDbh();
  • Inyecte el controlador de la base de datos en las clases que lo necesiten, así:

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

Recomiendo encarecidamente el último. Se lo conoce como inyección de dependencia (DI), inversión de control (IoC) o simplemente el principio de Hollywood (No nos llame, nosotros lo llamaremos).

Sin embargo, es un poco más avanzado y requiere más "cableado" sin marco. Entonces, si la inyección de dependencia es demasiado complicada para usted, use un registro singleton en lugar de un montón de variables globales.

Ian Unruh
fuente
¿Entonces accedo a mi conexión db globalmente cuando configuro mi sqlQueryclase en sesión ya que se extiende connect_pdo?
ThomasK
7

Recientemente llegué a una respuesta / pregunta similar por mi cuenta. Esto es lo que hice, por si a alguien le interesa:

<?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);
    }
  }

Para llamarlo solo necesitas modificar esta línea:

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

Y la sugerencia de tipo si la está usando para (\ Library \ PDO $ DB).

Es realmente similar tanto a la respuesta aceptada como a la suya; sin embargo, tiene una ventaja notable. Considere este código:

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

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

Si bien puede parecer un PDO normal (solo cambia por eso \Library\), en realidad no inicializa el objeto hasta que llama al primer método, cualquiera que sea. Eso lo hace más optimizado, ya que la creación de objetos PDO es un poco cara. Es una clase transparente, o lo que se llama Fantasma , una forma de Carga diferida . Puede tratar el $ DB como una instancia de PDO normal, pasarlo, realizar las mismas operaciones, etc.

Francisco Presencia
fuente
Esto se llama "Patrón de decorador"
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();
}
hi-code
fuente