alejoluc/lazypdo

仅在有需要时连接的PDO替代品

0.2.6 2018-06-25 11:46 UTC

This package is auto-updated.

Last update: 2024-09-10 06:09:12 UTC


README

内容

  1. 安装
  2. 在连接错误时
  3. 用法
  4. 在依赖注入容器中

本包提供PHP原生PDO类的替代品。

LazyPDO类在意义上是“懒”的,与原生PDO不同,它不会在实例化时尝试连接到数据库服务器。相反,它将存储所有连接细节并等待实际需要连接时,例如执行查询。

如果您已实施有效的缓存机制,并且数据库可能不需要响应用户请求的所有操作,这将很有用。

LazyPDO 扩展 PDO,因此LazyPDO的任何实例都是PDO的实例。这意味着您可以在需要PDO实例的地方传递LazyPDO实例。

<?php
// autoloading, etc.
use alejoluc\LazyPDO\LazyPDO;

function expectsPDO(PDO $dependency) {
    //...
}

$lazypdo = new LazyPDO('mysql:host=localhost;dbname=db;charset=utf8', 'root', 'root');
expectsPDO($lazypdo); // Valid

安装

选项A) 从命令行

composer require alejoluc/lazypdo:*

选项B) 在composer.json的"require"部分添加依赖项,然后根据需要运行composer installcomposer update

{
  "require": {
    "alejoluc/lazypdo": "*"
  }
}

在连接错误时

在深入了解使用细节之前,您需要考虑的是连接错误。在PHP原生PDO中,连接错误将在尝试使用错误的连接字符串实例化类时立即引发,或者当数据库服务器拒绝凭据时。在这种情况下,在PDO的实例化周围使用简单的try/catch结构就足够了。然而,由于这个类是“懒”的,它将连接延迟到实际需要时,这意味着连接错误可以在您的代码的任何地方引发(即,在第一次调用需要建立连接的方法时)。为了避免需要在每个数据库调用内部包装try/catch块(只要您不使用PDO::ERRMODE_EXCEPTION),您可以使用onConnectionError()方法指定一个回调,以处理连接时可能引发的PDOException。可以传递任何callableonConnectionError()方法,而不仅仅是函数,因此$lazypdo->onConnectionError([$myErrorHandler, 'handle']);也是有效的。以下是一个使用函数的示例

<?php
// autoload, etc....
use alejoluc\LazyPDO\LazyPDO;

$pdo = new LazyPDO('mysql:host=localhost;dbname=db;charset=utf8', 'not_a_valid_user', 'pass');
$pdo->onConnectionError(function($ex) use ($app){
    $error = $ex->getMessage();
    $app->logError('PDO reported an error: ' . $error);
    $app->getDevelopers->angryEmail($error);
    $app->redirect('/database-maintenance');
    $app->shutdown();
});


$stmt = $pdo->prepare('SELECT ...'); // This will attempt a connection, the connection will fail, and the previously defined callback will handle the raised PDOException.

然而,许多应用程序都有顶级try/catch,或者它们有自己的错误处理。因此,默认情况下,如果您没有指定回调,LazyPDO将只是引发异常,直到它(希望)被捕获并正确处理。

用法

LazyPDO可以像使用PDO一样使用。如果您了解PDO,您就了解LazyPDO。就这么简单。以下是几个示例来更新您的思维。请注意,您可以从PDO或LazyPDO访问常量,并且可以混合它们,但为什么这样做呢?只使用PDO::*常量,以使其清晰表明它们是互操作的。

使用PDO::ERRMODE为ERRMODE_SILENT(默认PDO行为)

<?php
// require composer autoloading here

use alejoluc\LazyPDO\LazyPDO;

$pdo = new LazyPDO('mysql:host=localhost;dbname=information_schema;charset=utf8', 'root', 'root', [
    LazyPDO::ATTR_DEFAULT_FETCH_MODE => PDO::FETCH_ASSOC,
    PDO::ATTR_EMULATE_PREPARES       => false,
]);

// This will fail, there is no CHARACTER_SET table. To test the success scenario, change it to CHARACTER_SETS
$stmt = $pdo->prepare('SELECT * FROM CHARACTER_SET WHERE DEFAULT_COLLATE_NAME = ?');

if ($stmt === false) { // With this error mode, you must manually check errors
    $error = $pdo->errorInfo()[2];
    echo "Database error: $error";
    die();
}

$stmt->bindValue(1, 'utf8_general_ci');
$stmt->execute();

var_dump($stmt->fetchAll());

使用ERR_MODE为ERRMODE_EXCEPTION,推荐的或之前一周或两周的行为

<?php
// require composer autoloading here

use alejoluc\LazyPDO\LazyPDO;

$pdo = new LazyPDO('mysql:host=localhost;dbname=information_schema;charset=utf8', 'root', 'root', [
    PDO::ATTR_ERRMODE                => LazyPDO::ERRMODE_EXCEPTION,
    LazyPDO::ATTR_DEFAULT_FETCH_MODE => PDO::FETCH_ASSOC,
    PDO::ATTR_EMULATE_PREPARES       => false,
]);

try {
    // This will fail, there is no CHARACTER_SET table. To test the success scenario, change it to CHARACTER_SETS
    $stmt = $pdo->prepare('SELECT * FROM CHARACTER_SET WHERE DEFAULT_COLLATE_NAME = ?');
    $stmt->bindValue(1, 'utf8_general_ci');
    $stmt->execute();

    var_dump($stmt->fetchAll());
} catch (PDOException $e) {
    echo "Database error: " . $e->getMessage();
    die();
}

再次强调,如果您有应用程序级别的try/catch或捕获所有错误和异常的自定义错误处理器,这可能是您应该做的,那么try/catch结构是不必要的。

为什么不用依赖注入容器呢?

您可以,也可以将LazyPDO放入其中。但如果你想使用原生的PDO,您也可以通过使用DIC(依赖注入容器)来获得延迟加载的行为,只要您从不直接传递PDO键,而是传递容器(在这种情况下,它不是DIC,而是一个服务定位器,有些人认为这是一种反模式,应该避免,但并非所有都是)。然而,如果您直接传递PDO键,那么您将不会获得与LazyPDO相同的行为,因为当您访问DIC中的一个成员时,DIC最有可能实例化该键中包含的内容。以Pimple为例,顺便说一下,我认为它非常棒。

<?php
// autoload, instantiate a pimple container into $c, store an hypothetical caching server connection into it, etc.
$c['db'] = function(){
    return new PDO('....');
};

function getUserData($userId, $db, $cacheConnection) {
    if ($cacheConnection->inCache('user:' . $userId)) {
        return $cacheConnection->getCached('user:' . $userId);
    } else {
        $stmt = $db->prepare('SELECT ...');
        // and so on and so on
    }
}

$data = getUserData('admin', $c['db'], $c['cache']); // You are accessing the 'db' key inside the Container, and a connection will try to be established because of that, although you can see in the getUserData() definition that no connection may be needed at all.

为了确保原生PDO仅在必要时才被实例化,函数和调用应该重构为如下所示

<?php
function getUserData($userId, $c) {
    if ($c['cache']->inCache('user:' . $userId)) {
        return $c['cache']->getCached('user:' . $userId);
    } else {
        $stmt = $c['db']->prepare('SELECT ...'); // PDO instantiation happens here, so it will not happen if the data is cached
        // and so on and so on
    }
}

getUserData('admin', $c);

然而,如果在DIC中使用LazyPDO而不是PDO,在前面两个代码示例中,数据库连接只有在缓存的数据找不到时才会建立。让我们再次看看第一个示例,但这次用LazyPDO代替

<?php
// autoload, instantiate a pimple container into $c, store an hypothetical caching server connection into it, etc.
use alejoluc\LazyPDO\LazyPDO;
$c['db'] = function(){
    return new LazyPDO('....');
};

function getUserData($userId, $db, $cacheConnection) {
    if ($cacheConnection->inCache('user:' . $userId)) {
        return $cacheConnection->getCached('user:' . $userId);
    } else {
        $stmt = $db->prepare('SELECT ...'); // The connection will try to be established here, not on the getUserData() function call
    }
}

$data = getUserData('admin', $c['db'], $c['cache']); // No connection to the database will try to be established here because $c['db'] will return an instance of LazyPDO