Jak prawidłowo skonfigurować połączenie PDO

92

Od czasu do czasu pojawiają się pytania dotyczące łączenia się z bazą danych.
Większość odpowiedzi nie jest tak, jak to robię, lub po prostu mogę nie uzyskać poprawnych odpowiedzi. Tak czy inaczej; Nigdy o tym nie myślałem, ponieważ sposób, w jaki to robię, działa na mnie.

Ale oto szalona myśl; Może robię to wszystko źle, a jeśli tak jest; Naprawdę chciałbym wiedzieć, jak poprawnie połączyć się z bazą danych MySQL za pomocą PHP i PDO i uczynić ją łatwo dostępną.

Oto jak to robię:

Po pierwsze, oto moja struktura plików (rozebrana) :

public_html/

* index.php  

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

index.php
Na samej górze mam 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');

Wiem, że istnieje lepszy lub bardziej poprawny sposób włączania zajęć, ale nie pamiętam, co to było. Nie miałem jeszcze czasu, żeby się temu przyjrzeć, ale myślę, że to było coś z tym autoload. coś w tym stylu...

configure.php
Tutaj po prostu nadpisuję niektóre właściwości php.ini i wykonuję inną globalną konfigurację witryny

connect.php
Umieściłem połączenie w klasie, aby inne klasy mogły ją rozszerzyć ...

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

Wierzę, że jest miejsce na ogromne ulepszenia, odkąd niedawno zacząłem uczyć się OOP i używać PDO zamiast mysql.
Właśnie skorzystałem z kilku samouczków dla początkujących i wypróbowałem różne rzeczy ...

session.php
Oprócz obsługi zwykłych sesji, inicjalizuję również niektóre klasy w sesję taką jak ta:

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

W ten sposób ta klasa jest dostępna w każdym miejscu. To może nie być dobra praktyka (?) ... W
każdym razie to podejście pozwala mi robić z każdego miejsca:

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

Wewnątrz mojej sqlQuery- klasy , która jest extendsmoją connect_pdo- klasą , mam funkcję publiczną o nazwie, getAreaNamektóra obsługuje żądanie do mojej bazy danych.
Myślę, że całkiem nieźle.

Działa jak urok.
Tak to właśnie robię.
Poza tym za każdym razem, gdy potrzebuję pobrać coś z mojej bazy danych spoza klasy, po prostu robię coś podobnego do tego:

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

Ponieważ umieściłem połączenie w zmiennej wewnątrz connect_pdo.php , po prostu nawiązałem do tego i jestem gotowy . To działa. Otrzymuję oczekiwane rezultaty ...

Ale niezależnie od tego; Byłbym wdzięczny, gdybyście mogli mi powiedzieć, że jestem daleko. Co powinienem zrobić zamiast tego, obszary, które mógłbym lub powinienem zmienić w celu poprawy itp.

Chętnie się uczę ...

ThomasK
źródło
9
Powinieneś użyć autoloadera zamiast dołączać każdy plik naraz do aplikacji.
Lusitanian,
4
To pytanie jest prawdopodobnie najlepsze w Code Review
Madara's Ghost

Odpowiedzi:

105

Cel

Jak widzę, twój cel w tym przypadku jest dwojaki:

  • tworzenie i utrzymywanie pojedynczego połączenia wielokrotnego użytku na bazę danych
  • upewnij się, że połączenie zostało poprawnie skonfigurowane

Rozwiązanie

Zalecałbym użycie zarówno funkcji anonimowej, jak i wzorca fabrycznego do obsługi połączenia PDO. Jego użycie wyglądałoby następująco:

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

Następnie w innym pliku lub niżej w tym samym pliku:

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

Sama fabryka powinna wyglądać mniej więcej tak:

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

}

W ten sposób uzyskasz scentralizowaną strukturę, która zapewnia, że ​​połączenie jest tworzone tylko wtedy, gdy jest to wymagane. Ułatwiłoby to również znacznie proces testowania jednostkowego i konserwacji.

Dostawca w tym przypadku zostałby znaleziony gdzieś na etapie ładowania początkowego. Takie podejście dałoby również jasne miejsce, w którym należy zdefiniować konfigurację używaną do łączenia się z bazą danych.

Należy pamiętać, że jest to niezwykle uproszczony przykład . Możesz także skorzystać z obejrzenia dwóch następujących filmów:

Również gorąco polecam przeczytanie odpowiedniego samouczka na temat korzystania z PDO (w Internecie jest dziennik złych tutoriali).

tereško
źródło
3
Ponieważ nawet PHP5.3 zbliża się do EOL. Większość witryn z przestarzałymi wersjami PHP to w rzeczywistości tani hosting dla Wordpress. Oceniam, że wpływ środowisk wcześniejszych niż 5.3 na rozwój zawodowy (które trochę skorzystałyby na takich fragmentach) jest znikomy.
tereško
5
@thelolcat Zgadzam się z tobą. To jest mniej więcej taka sama odpowiedź. To znaczy, jeśli nie widzisz faktu, że jest zupełnie inaczej.
PeeHaa,
1
@thelolcat, powinieneś dowiedzieć się, czym jest wstrzykiwanie zależności . Zamiast dalej się zawstydzać. Co zabawne, drugi film w powyższym poście (zatytułowany: „Nie szukaj rzeczy” ) właściwie wyjaśniał, czym jest DI i jak go używać ... ale oczywiście jesteś zbyt zaawansowany na takie trywialne rzeczy.
tereško,
2
To stara odpowiedź, ale dobra - i świetny link do Mysql pdo na końcu
Strawberry
1
@teecee powinieneś zacząć od nauczenia się, jak używać PDO. Polecam ten poradnik: wiki.hashphp.org/PDO_Tutorial_for_MySQL_Developers , ponieważ jest stworzony właśnie dla osób, które chcą przejść z mysql_*do PDO. Następnie możesz wrócić i spojrzeć na to rozwiązanie, które jest skierowane do tych, którzy już używają PDO, ale potrzebują sposobu na współdzielenie połączenia DB między wieloma klasami.
tereško
24

Sugerowałbym, aby nie używać $_SESSIONglobalnego dostępu do połączenia DB.

Możesz zrobić jedną z kilku rzeczy (w kolejności od najgorszych do najlepszych ):

  • Dostęp $dbhza pomocą global $dbhfunkcji i klas
  • Użyj pojedynczego rejestru i uzyskaj do niego dostęp globalnie, na przykład:

    $registry = MyRegistry::getInstance();
    $dbh = $registry->getDbh();
    
  • Wprowadź procedurę obsługi bazy danych do klas, które jej potrzebują, na przykład:

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

Gorąco polecam ten ostatni. Jest znany jako iniekcja zależności (DI), inwersja kontroli (IoC) lub po prostu zasada Hollywood (nie dzwoń do nas, zadzwonimy do ciebie).

Jest jednak trochę bardziej zaawansowany i wymaga więcej „okablowania” bez szkieletu. Tak więc, jeśli wstrzykiwanie zależności jest dla ciebie zbyt skomplikowane, użyj pojedynczego rejestru zamiast wielu zmiennych globalnych.

Ian Unruh
źródło
Więc uzyskuję dostęp do mojego połączenia db globalnie, kiedy sqlQueryustawię moją -class na sesję, ponieważ się rozszerza connect_pdo?
ThomasK,
7

Niedawno sam doszedłem do podobnej odpowiedzi / pytania. Oto co zrobiłem, na wypadek gdyby ktoś był zainteresowany:

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

Aby to nazwać, wystarczy zmodyfikować tę linię:

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

I podpowiedzi typu, jeśli używasz go do (\ Library \ PDO $ DB).

Jest bardzo podobna zarówno do zaakceptowanej odpowiedzi, jak i do Twojej; jednak ma to znaczącą zaletę. Rozważ ten kod:

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

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

Chociaż może wyglądać jak normalne PDO (zmienia się \Library\tylko przez to ), w rzeczywistości nie inicjalizuje obiektu, dopóki nie wywołasz pierwszej metody, cokolwiek to jest. To sprawia, że ​​jest bardziej zoptymalizowany, ponieważ tworzenie obiektów PDO jest nieco kosztowne. To przezroczysta klasa lub coś, co nazywa się Ghost , forma Lazy Loading . Możesz traktować $ DB jako normalną instancję PDO, przekazując ją sobie, wykonując te same operacje itp.

Francisco Presencia
źródło
Nazywa się to „wzorem dekoratora”
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();
}
kod hi-code
źródło