如何正确设置 PDO 连接

Posted

技术标签:

【中文标题】如何正确设置 PDO 连接【英文标题】:How to properly set up a PDO connection 【发布时间】:2012-07-07 08:02:24 【问题描述】:

我有时会看到有关连接数据库的问题。 大多数答案不是我这样做的方式,或者我可能无法正确得到答案。反正;我从来没有考虑过,因为我这样做的方式对我有用。

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

我是这样做的:

首先,这是我的文件结构(精简)

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-properties 并为站点做一些其他的全局配置

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-class,其中extends 我的connect_pdo-class,我有一个名为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 内的变量中,因此我只是引用了它,我可以开始了。有用。我得到了预期的结果...

但不管怎样;如果你们能告诉我我是否离开这里,我将不胜感激。我应该做什么,我可以或应该改变的地方等等......

我很想学习...

【问题讨论】:

您应该使用自动加载器,而不是一次在您的应用程序中包含每个文件 这个问题可能最好在Code Review 【参考方案1】:

目标

在我看来,您在这种情况下的目标是双重的:

为每个数据库创建和维护单个/可重复使用的连接 确保已正确设置连接

解决方案

我建议同时使用匿名函数和工厂模式来处理 PDO 连接。它的使用看起来像这样:

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

然后在不同的文件或更低的文件中:

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

工厂本身应该是这样的:

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


这种方式可以让您拥有一个集中式结构,确保仅在需要时创建连接。它还可以使单元测试和维护过程变得更加容易。

在这种情况下,提供程序可以在引导阶段的某个地方找到。这种方法还将提供一个明确的位置来定义配置,用于连接到数据库。

请记住,这是一个极其简化的示例。您还可以从观看以下两个视频中受益:

Global State and Singletons Don't Look For Things!

另外,我强烈建议阅读a proper tutorial 了解 PDO 的使用(网上有不良教程的日志)。

【讨论】:

因为即使是 PHP5.3 也接近 EOL。大多数具有过时 PHP 版本的网站实际上只是廉价的 Wordpress 托管。在我看来,5.3 之前的环境对职业发展的影响(他们会从像这样的 sn-ps 中受益)可以忽略不计。 @thelolcat 我同意你的看法。它或多或少相同的答案。也就是说,如果您没有看到它完全不同的事实。 @thelolcat ,那么你应该了解dependency injection 是什么。而不是继续让自己难堪。有趣的是,上面帖子中的第二个视频(标题为:“Don't Look For Things”)实际上解释了 DI 是什么以及如何使用它......但当然你太先进了这么琐碎的事情。 这是一个旧答案,但很好 - 最后是 Mysql pdo 的一个很好的链接 @teecee 你应该首先学习如何使用 PDO。我会推荐本教程:wiki.hashphp.org/PDO_Tutorial_for_MySQL_Developers,因为它专为想要从 mysql_* 迁移到 PDO 的人而设计。然后你可以回来看看这个解决方案,它是针对那些已经使用 PDO,但需要一种方法来在多个类之间共享 DB 连接的。【参考方案2】:

我建议不要使用$_SESSION 来全局访问您的数据库连接。

您可以执行以下操作之一(按照从最坏到最佳的做法):

在您的函数和类中使用global $dbh 访问$dbh

使用单例注册表,并在全局范围内访问它,如下所示:

$registry = MyRegistry::getInstance();
$dbh = $registry->getDbh();

将数据库处理程序注入需要它的类中,如下所示:

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

我强烈推荐最后一个。它被称为依赖注入 (DI)、控制反转 (IoC),或者简称为好莱坞原则(不要打电话给我们,我们会打电话给你)。

但是,它更高级一些,并且在没有框架的情况下需要更多的“布线”。因此,如果依赖注入对您来说太复杂,请使用单例注册表而不是一堆全局变量。

【讨论】:

所以当我将sqlQuery-class 设置为会话时,我可以全局访问我的数据库连接,因为它扩展了connect_pdo【参考方案3】:

我最近自己提出了一个类似的答案/问题。这就是我所做的,以防万一有人感兴趣:

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

调用它只需要修改这一行:

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

如果您将其用于 (\Library\PDO $DB),还有类型提示。

这与接受的答案和您的答案非常相似;但是它有一个显着的优势。考虑这段代码:

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

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

虽然它可能看起来像普通的 PDO(它仅由 \Library\ 更改),但它实际上不会初始化对象,直到您调用第一个方法,无论它是什么。这使得它更加优化,因为 PDO 对象的创建稍微昂贵。它是一个透明类,或者称为Ghost,Lazy Loading 的一种形式。你可以把 $DB 当作一个普通的 PDO 实例,传递它,做同样的操作,等等。

【讨论】:

这叫做“装饰者模式”【参考方案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();

【讨论】:

【参考方案5】:

您的设置存在一些基本缺陷:

    configure.php 文件不应位于 Web 服务器的文档根目录中 - 如果服务器配置错误,您可能会向公众公开凭据。你可能认为这不会发生在你身上,但这是你不需要承担的风险。课程也不应该在那里......这并不重要,但任何不需要公开的东西都不应该公开。 除非您正在处理一个特别大的项目,否则您不应该有一个“初始化”目录。加载一个大文件比加载相同内容的十个小文件快大约 10 倍。随着项目的发展,这往往会真正加起来,并且确实会减慢 PHP 网站的速度。 除非确实需要,否则尽量不要加载内容。例如,除非您确实需要,否则不要连接 PDO。不要session_start() 你实际上读/写会话。除非您创建类的实例,否则不要包含类定义文件。您可以拥有的连接数是有限制的。像 session 这样的 API 会建立“锁”,可以为使用相同资源的其他人暂停代码执行。 据我所知,您没有使用 Composer。您应该使用它 - 它会让您自己的代码和第三方依赖项的生活变得更加轻松。

这是我建议的目录结构,类似于我用于中型项目的目录结构:

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

init.php 文件可能类似于:

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 可能看起来像:

require '../init.php';

$pdo = $pdoConnect();

// go from there

other.php 可能类似,但它可能不连接到数据库,因此不执行 $pdoConnect。

您应该尽可能将大部分代码写入类目录。让index.phpother.php 等内容尽可能简短而温馨。

【讨论】:

以上是关于如何正确设置 PDO 连接的主要内容,如果未能解决你的问题,请参考以下文章

PDO 关闭连接

将数据库和PDO选项数组传递到我的连接PHP文件中

Drupal 8 连接获得 PDO 权限被拒绝

如何在Laravel中修复PDO sqlsrv连接超时?

如何正确格式化 PDO 结果? - 以字符串形式返回的数字结果?

如何正确使用 PDO 对象进行参数化 SELECT 查询