如何正确设置 PDO 连接

有时我看到有关连接到数据库的问题。
大多数答案都不是我的方式,或者我可能只是得不到正确的答案。不管怎样,我从来没有想过,因为我做事的方式对我很有效。

但是这里有一个疯狂的想法; 也许我这样做是完全错误的,如果是这样的话; 我真的很想知道如何正确地连接到 MySQL 数据库使用 PHP 和 PDO,并使其易于访问。

我是这么做的:

首先,这是我的文件结构 (脱光):

public_html/


* index.php


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

Index.php
在最顶端,我有 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');

我知道有一个更好的,或者更正确的方法来包含类,但是我不记得是什么了。还没来得及调查但我觉得是 autoload的问题。诸如此类。

Configure.php
在这里,我基本上只是覆盖了一些 Php.ini属性,并为站点做了一些其他的全局配置

Connect.php
我已经把连接到一个类,这样其他类可以 延伸这一个..。

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

在这里,我相信自从我最近开始学习 OOP,并使用 PDO 而不是 mysql 以来,还有很大的改进空间。
因此,我只是遵循了一些初学者教程,并尝试了不同的东西...

Sessions.php
除了处理常规会话之外,我还将一些类初始化为下面这样的会话:

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

这样的话,这个课程可以在任何地方使用。这可能不是一个好的实践(?) ..。
无论如何,这是这种方法允许我在任何地方做的:

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

在我的 sqlQuery-同学们(extends我的 connect_pdo-同学们)中,我有一个名为 getAreaName的公共函数,它处理对我的数据库的请求。
我觉得挺不错的。

非常有效
所以我基本上就是这么做的。
此外,每当我需要从数据库中提取不在类中的内容时,我都会做类似的事情:

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

因为我将连接放入 Connect _ pdo. php中的一个变量中,所以我只需要引用它就可以了。有用。我得到了预期的结果。

But regardless of that; I would really appreciate if you guys could tell me if I'm way off here. What I should do instead, areas I could or should change for improvement etc...

我渴望学习..。

94400 次浏览

I would suggest not using $_SESSION to access your DB connection globally.

You can do one of a few things (in order of worst to best practices):

  • Access $dbh using global $dbh inside of your functions and classes
  • Use a singleton registry, and access that globally, like so:

    $registry = MyRegistry::getInstance();
    $dbh = $registry->getDbh();
    
  • Inject the database handler into the classes that need it, like so:

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

I would highly recommend the last one. It is known as dependency injection (DI), inversion of control (IoC), or simply the Hollywood principle (Don't call us, we'll call you).

However, it is a little more advanced and requires more "wiring" without a framework. So, if dependency injection is too complicated for you, use a singleton registry instead of a bunch of global variables.

The goal

As I see it, your aim in this case is twofold:

  • create and maintain a single/reusable connection per database
  • make sure that the connection has been set up properly

Solution

I would recommend to use both anonymous function and factory pattern for dealing with PDO connection. The use of it would looks like this :

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

Then in a different file or lower in the same file:

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

The factory itself should look something like this:

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


}

This way would let you have a centralized structure, which makes sure that connection is created only when required. It also would make the process of unit-testing and maintenance much easier.

The provider in this case would be found somewhere at the bootstrap stage. This approach would also give a clear location where to define the configuration, that you use for connecting to the DB.

Keep in mind that this is an extremely simplified example. You also might benefit from watching two following videos:

Also, I would strongly recommend reading a proper tutorial about use of PDO (there are a log of bad tutorial online).

I recently came to a similar answer/question on my own. This is what I did, in case anyone is interested:

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

To call it you only need to modify this line:

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

And the type-hinting if you are using it to (\Library\PDO $DB).

It's really similar to both the accepted answer and yours; however it has a notably advantage. Consider this code:

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


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

While it might look like normal PDO (it changes by that \Library\ only), it actually doesn't initialize the object until you call the first method, whichever it is. That makes it more optimized, since the PDO object creation is slightly expensive. It's a transparent class, or what it's called a Ghost, a form of Lazy Loading. You can treat the $DB as a normal PDO instance, passing it around, doing the same operations, etc.

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

There are a few basic flaws in your setup:

  1. The configure.php file shouldn't be in the web server's document root — if the server is ever missconfigured you might expose credentials to the public. You might think it won't happen to you, but it's a risk you just don't need to be taking. Classes also shouldn't be there... this isn't as important but still anything that doesn't need to be public shouldn't be public.
  2. Unless you're working with an especially large project, you shouldn't have an "initialization" directory. Loading one large file is approximately 10x faster than loading ten small files with the same contents. This tends to really add up as a project grows and can really slow down PHP sites.
  3. Try not to load things unless you actually need to. For example don't connect with PDO unless you actually need to. Don't session_start() you actually read/write to session. Don't include a class definition file unless you create an instance of the class. There are limits to how many connections you can have. And APIs like session establish "locks" that can pause code execution for other people using the same resource.
  4. As far as I can tell, you're not using Composer. You should be using it - it'll make life so much easier both for your own code and for third party dependencies.

Here's my proposed directory structure, which is similar to what I use for medium sized projects:

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

The init.php file might look something like:

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.php might look like:

require '../init.php';


$pdo = $pdoConnect();


// go from there

other.php might be similar but maybe it doesn't connect to the database, therefore doesn't execute $pdoConnect.

As much as possible, you should write the bulk of your code into the classes directory. Keep index.php, other.php, etc as short and sweet as possible.