Hoe een PDO-verbinding correct in te stellen

Van tijd tot tijd zie ik vragen over het verbinden met de database.
De meeste antwoorden zijn niet zoals ik het doe, of ik krijg de antwoorden misschien niet goed. In ieder geval; Ik heb er nooit over nagedacht omdat de manier waarop ik het doe voor mij werkt.

Maar hier is een gekke gedachte; Misschien doe ik dit helemaal verkeerd, en als dat het geval is; Ik zou heel graag willen weten hoe ik op de juiste manier verbinding kan maken met een MySQL-database met behulp van PHP en PDO en deze gemakkelijk toegankelijk kan maken.

Dit is hoe ik het doe:

Ten eerste, hier is mijn bestandsstructuur (uitgekleed):

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

index.php
Helemaal bovenaan heb ik 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');

Ik weet dat er een betere of correctere manier is om klassen op te nemen, maar ik kan me niet herinneren wat het was. Ik heb nog geen tijd gehad om ernaar te kijken, maar ik denk dat het iets was met autoload. zoiets…

configure.php
Hier overschrijf ik eigenlijk gewoon enkele php.ini-eigenschappen en doe ik een andere globale configuratie voor de site

connect.php
Ik heb de verbinding met een klas gemaakt zodat andere klassen deze kunnen verlengen

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

Hier geloof ik dat er ruimte is voor enorme verbetering sinds ik onlangs ben begonnen met het leren van OOP en het gebruik van PDO in plaats van mysql.
Dus ik heb net een paar tutorials voor beginners gevolgd en verschillende dingen uitgeprobeerd…

sessions.php
Naast het afhandelen van reguliere sessies, initialiseer ik ook sommige lessen in een sessie zoals deze:

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

Op deze manier is deze les overal beschikbaar. Dit is misschien geen goede gewoonte(?)…
Hoe dan ook, dit is wat ik met deze aanpak overal kan doen:

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

In mijn sqlQueryklasse, die extendsmijn connect_pdoklasse, ik heb een openbare functie genaamd getAreaNamedie het verzoek naar mijn database afhandelt.
Best netjes denk ik.

Werkt als een tierelier
Dus dat is eigenlijk hoe ik het doe.
Ook, wanneer ik iets uit mijn DB moet ophalen van niet binnen een klas, doe ik gewoon iets soortgelijks als dit:

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

Sinds ik de verbinding in een variabele in connect_pdo.phpheb gezet, hoef ik er alleen maar naar te verwijzen en ik ben klaar om te gaan. Het werkt. Ik krijg mijn verwachte resultaten…

Maar ongeacht dat; Ik zou het erg op prijs stellen als jullie me kunnen vertellen of ik er ver naast zit. Wat ik in plaats daarvan zou moeten doen, gebieden die ik zou kunnen of moeten veranderen voor verbetering enz…

Ik ben leergierig…


Antwoord 1, autoriteit 100%

Het doel

Zoals ik het zie, is je doel in dit geval tweeledig:

  • een enkele/herbruikbare verbinding per database maken en onderhouden
  • zorg ervoor dat de verbinding correct is ingesteld

Oplossing

Ik zou aanraden om zowel de anonieme functie als het fabriekspatroon te gebruiken voor het omgaan met PDO-verbindingen. Het gebruik ervan ziet er als volgt uit:

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

Vervolgens in een ander bestand of lager in hetzelfde bestand:

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

De fabriek zelf zou er ongeveer zo uit moeten zien:

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

Op deze manier zou je een gecentraliseerde structuur hebben, die ervoor zorgt dat er alleen verbinding wordt gemaakt als dat nodig is. Het zou ook het proces van unit-testing en onderhoud veel gemakkelijker maken.

De provider zou in dit geval ergens in de bootstrap-fase te vinden zijn. Deze benadering zou ook een duidelijke locatie geven waar de configuratie moet worden gedefinieerd, die u gebruikt om verbinding te maken met de database.

Houd er rekening mee dat dit een uiterst vereenvoudigd voorbeeldis. U kunt ook profiteren van het bekijken van twee volgende video’s:

Ik zou ook sterk aanbevelen om een goede tutorial te lezen over het gebruik van PDO (er is een logboek met slechte tutorials online).


Antwoord 2, autoriteit 23%

Ik raad aan om $_SESSIONniet te gebruiken om wereldwijd toegang te krijgen tot uw DB-verbinding.

U kunt een van de volgende dingen doen (in volgorde van slechtste naar bestepraktijken):

  • Toegang tot $dbhmet global $dbhbinnen uw functies en klassen
  • Gebruik een singleton-register en open dat wereldwijd, zoals:

    $registry = MyRegistry::getInstance();
    $dbh = $registry->getDbh();
    
  • Injecteer de database-handler in de klassen die het nodig hebben, zoals:

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

Ik zou de laatste ten zeerste aanbevelen. Het staat bekend als afhankelijkheidsinjectie (DI), inversion of control (IoC), of gewoon het Hollywood-principe (bel ons niet, we bellen u).

Het is echter iets geavanceerder en vereist meer “bedrading” zonder kader. Dus als afhankelijkheidsinjectie te ingewikkeld voor je is, gebruik dan een singleton-register in plaats van een heleboel globale variabelen.


Antwoord 3, autoriteit 7%

Ik kwam onlangs zelf tot een soortgelijk antwoord/vraag. Dit is wat ik deed, voor het geval iemand geïnteresseerd is:

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

Om het aan te roepen hoeft u alleen deze regel aan te passen:

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

En de type-hint als je het gebruikt om (\Bibliotheek\PDO $DB).

Het lijkt erg op zowel het geaccepteerde antwoord als het jouwe; het heeft echter een opmerkelijk voordeel. Overweeg deze code:

$DB = new \Library\PDO( /* args */ );
$STH = $DB->prepare("SELECT * FROM users WHERE user = ?");
$STH->execute(array(25));
$User = $STH->fetch();

Hoewel het er misschien uitziet als een normale PDO (het verandert alleen door die \Library\), initialiseert het het object pas als u de eerste methode aanroept, welke dat ook is. Dat maakt het meer geoptimaliseerd, omdat het maken van een PDO-object enigszins duur is. Het is een transparante klasse, of wat het een Ghostwordt genoemd, een vorm van Lazy Loading. U kunt de $DB behandelen als een normale PDO-instantie, deze doorgeven, dezelfde bewerkingen uitvoeren, enz.


Antwoord 4

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

Antwoord 5

Er zijn een paar basisfouten in uw configuratie:

  1. Het bestand configure.phpmag niet in de documenthoofdmap van de webserver staan ​​- als de server ooit verkeerd is geconfigureerd, kunt u inloggegevens openbaar maken. Je denkt misschien dat het jou niet zal overkomen, maar het is een risico dat je gewoon niet hoeft te nemen. Lessen zouden er ook niet moeten zijn… dit is niet zo belangrijk, maar toch zou alles wat niet openbaar hoeft te zijn niet openbaar moeten zijn.
  2. Tenzij je met een bijzonder groot project werkt, zou je geen map “initialisatie” moeten hebben. Het laden van één groot bestand is ongeveer 10x sneller dan het laden van tien kleine bestanden met dezelfde inhoud. Dit kan behoorlijk oplopen naarmate een project groeit en kan PHP-sites echt vertragen.
  3. Probeer dingen niet te laden, tenzij het echt nodig is. Maak bijvoorbeeld geen verbinding met PDO, tenzij dat echt nodig is. Niet session_start()u daadwerkelijk leest/schrijft naar een sessie. Voeg geen klassedefinitiebestand toe, tenzij u een instantie van de klasse maakt. Er zijn grenzen aan het aantal verbindingen dat u kunt hebben. En API’s, zoals sessies, stellen “vergrendelingen” in die de uitvoering van code kunnen pauzeren voor andere mensen die dezelfde bron gebruiken.
  4. Voor zover ik weet, gebruik je Composer niet. Je zou het moeten gebruiken – het zal het leven zoveel gemakkelijker maken, zowel voor je eigen code als voor afhankelijkheden van derden.

Hier is mijn voorgestelde directorystructuur, die vergelijkbaar is met wat ik gebruik voor middelgrote projecten:

init.php                Replaces public_html/initialize. Your PDO connection details
                        are held here.
classes/                Replaces public_html/classes
vendor/autoload.php     Your class autoload script generated using the
                        industry standard Composer command line tool
composer.json           The file where you describe how autoload.php
                        operates among other things. For example if you
                        don't use namespaces (maybe you should) it might be:
                        {"autoload": {"psr-4": { "": "classes/" }}}
public_html/index.php   Your landing page
public_html/other.php   Some other page
public_html/css/foobar.css ...and so on for all static resources

Het bestand init.phpkan er ongeveer zo uitzien:

date_default_timezone_set('Etc/UTC');
require 'vendor/autoload.php';
$pdoConnect = function() {
  static $pdo = false;
  if (!$pdo) {
    $pdo = new PDO('mysql:dbname=db;host=localhost', 'user', 'password');
    $pdo->setAttribute(PDO::ATTR_ERRMODE, PDO::ERRMODE_EXCEPTION);
    $pdo->setAttribute(PDO::ATTR_DEFAULT_FETCH_MODE, PDO::FETCH_OBJ);
  }
  return $pdo;
};
// similar anonymous functions for session_start(), etc.

index.phpkan er als volgt uitzien:

require '../init.php';
$pdo = $pdoConnect();
// go from there

other.phpkan vergelijkbaar zijn, maar misschien maakt het geen verbinding met de database en voert het daarom $pdoConnect niet uit.

Je moet zoveel mogelijk het grootste deel van je code in de klassendirectory schrijven. Houd index.php, other.php, enz. zo kort en krachtig mogelijk.

Other episodes