如何正确设置 PDO 连接

How to properly set up a PDO connection

提问人:ThomasK 提问时间:7/7/2012 最后编辑:manianThomasK 更新时间:5/25/2021 访问量:96190

问:

我不时看到有关连接到数据库的问题。
大多数答案都不是我的方式,或者我可能只是没有得到正确的答案。无论如何;我从来没有想过,因为我做这件事的方式对我有用。

但这里有一个疯狂的想法;也许我做错了,如果是这样的话;我真的很想知道如何使用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');

我知道有一种更好或更正确的方法来包含类,但不记得它是什么。还没有时间研究它,但我认为它是自动加载的东西。类似的东西......

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

连接.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。
所以我只是遵循了几个初学者教程并尝试了不同的东西......

会话.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)

在我的 -class 中,我有一个叫做公共函数的公共函数,它处理对我的数据库的请求。
我觉得很整洁。
sqlQueryextendsconnect_pdogetAreaName

就像一个魅力,
所以基本上这就是我的做法。
此外,每当我需要从不在一个类中从我的数据库中获取一些东西时,我都会做类似的事情:

$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内部的变量中,因此我只需引用它就可以开始了。它有效。我得到了我预期的结果......

但无论如何;如果你们能告诉我我是否离这里很远,我将不胜感激。我应该做什么,我可以或应该改变以改进的领域等......

我渴望学习......

php mysql sql pdo

评论

9赞 Lusitanian 7/7/2012
您应该使用自动加载器,而不是一次将每个文件都包含在应用程序中。
4赞 Madara's Ghost 7/7/2012
这个问题可能是代码审查中最好的

答:

23赞 Ian Unruh 7/7/2012 #1

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

您可以执行以下几项操作之一(按最差到最佳实践的顺序):

  • 在函数和类内部使用$dbhglobal $dbh
  • 使用单例注册表,并全局访问该注册表,如下所示:

    $registry = MyRegistry::getInstance();
    $dbh = $registry->getDbh();
    
  • 将数据库处理程序注入到需要它的类中,如下所示:

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

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

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

评论

0赞 ThomasK 7/7/2012
因此,当我将 -class 设置为会话时,我会全局访问我的数据库连接,因为它扩展了吗?sqlQueryconnect_pdo
107赞 tereško 7/7/2012 #2

目标

在我看来,你在这种情况下有两个目标:

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

溶液

我建议同时使用匿名函数和工厂模式来处理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 );
    }

}

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

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

请记住,这是一个非常简化的示例。观看以下两个视频也可能对您有所帮助:

另外,我强烈建议您阅读有关使用PDO的适当教程(网上有不良教程的日志)。

评论

3赞 tereško 3/30/2013
因为即使是 PHP5.3 也接近 EOL。大多数具有过时PHP版本的网站实际上只是Wordpress的廉价托管。据我估计,5.3 之前的环境对专业发展的影响(他们会从这样的片段中受益)可以忽略不计。
5赞 PeeHaa 12/11/2013
@thelolcat我同意你的看法。这或多或少相同的答案。也就是说,如果您没有看到它完全不同的事实。
1赞 tereško 12/11/2013
@thelolcat,那么你应该了解什么是依赖注入。而不是继续让自己难堪。有趣的是,上面帖子中的第二个视频(标题为:“不要寻找东西”)实际上解释了 DI 是什么以及如何使用它......但是,对于这种微不足道的事情,你当然太先进了。
2赞 Strawberry 5/23/2015
这是一个古老的答案,但是一个很好的答案 - 最后是指向 Mysql pdo 的一个很好的链接
1赞 tereško 6/1/2016
@teecee您应该首先学习如何使用 PDO。我会推荐这个教程:wiki.hashphp.org/PDO_Tutorial_for_MySQL_Developers,因为它是专门为想要从 PDO 迁移到的人制作的。然后,您可以回过头来查看此解决方案,该解决方案针对的是那些已经使用 PDO,但需要一种方法在多个类之间共享数据库连接的人。mysql_*
7赞 Francisco Presencia 1/12/2014 #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(它仅因它而改变),但它实际上不会初始化对象,直到您调用第一个方法,无论它是什么。这使得它更加优化,因为 PDO 对象的创建成本略高。这是一个透明的类,或者它所谓的 Ghost,一种延迟加载的形式。您可以将$DB视为普通的 PDO 实例,传递它,执行相同的操作等。\Library\

评论

0赞 Yang 8/20/2014
这称为“装饰器模式”
0赞 hi-code 9/29/2014 #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();
}
2赞 Abhi Beckert 5/25/2021 #5

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

  1. 该文件不应位于 Web 服务器的文档根目录中 — 如果服务器配置错误,您可能会向公众公开凭据。你可能认为这不会发生在你身上,但这是一个你不需要承担的风险。课程也不应该在那里......这并不那么重要,但任何不需要公开的东西都不应该公开。configure.php
  2. 除非你正在处理一个特别大的项目,否则你不应该有一个“初始化”目录。加载一个大文件大约比加载 10 个内容相同的小文件快 10 倍。随着项目的发展,这往往会真正增加,并且会真正减慢PHP站点的速度。
  3. 除非你真的需要,否则尽量不要加载东西。例如,除非您确实需要,否则不要与 PDO 连接。你不是真的读/写会话吗?除非创建类的实例,否则不要包含类定义文件。您可以拥有的连接数是有限制的。会话等 API 会建立“锁”,为使用相同资源的其他人暂停代码执行。session_start()
  4. 据我所知,您没有使用 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。

您应该尽可能地将大部分代码写入 classes 目录。保持 、 等尽可能简短和甜美。index.phpother.php