93

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

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

这是我的做法:

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

public_html/

* index.php  

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

index.php
在最顶部,我有require('initialize/load.initialize.php');.

加载.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 以来,还有很大的改进空间。
所以我刚刚学习了几个初学者教程并尝试了不同的东西......

session.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)

在 my sqlQuery- class(即extendsmy 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内的变量中,因此我只是引用了它,我很高兴。有用。我得到了我的预期结果......

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

我渴望学习...

4

5 回答 5

106

目标

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

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

解决方案

我建议同时使用匿名函数和工厂模式来处理 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的正确教程(网上有一篇糟糕教程的日志)。

于 2012-07-06T21:09:30.870 回答
24

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

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

  • $dbh使用global $dbh你的函数和类的内部访问
  • 使用单例注册表,并在全局范围内访问它,如下所示:

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

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

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

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

于 2012-07-06T20:48:49.760 回答
7

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

<?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,一种延迟加载的形式。您可以将 $DB 视为普通的 PDO 实例,将其传递,执行相同的操作等。

于 2014-01-12T00:21:17.233 回答
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();
}
于 2014-09-29T11:51:45.720 回答
0

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

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

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

于 2021-05-25T06:22:51.980 回答