alejoluc / lazypdo
仅在有需要时连接的PDO替代品
Requires
- php: >=5.6.0
This package is auto-updated.
Last update: 2024-09-10 06:09:12 UTC
README
内容
本包提供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 install
或composer update
{ "require": { "alejoluc/lazypdo": "*" } }
在连接错误时
在深入了解使用细节之前,您需要考虑的是连接错误。在PHP原生PDO中,连接错误将在尝试使用错误的连接字符串实例化类时立即引发,或者当数据库服务器拒绝凭据时。在这种情况下,在PDO的实例化周围使用简单的try/catch结构就足够了。然而,由于这个类是“懒”的,它将连接延迟到实际需要时,这意味着连接错误可以在您的代码的任何地方引发(即,在第一次调用需要建立连接的方法时)。为了避免需要在每个数据库调用内部包装try/catch块(只要您不使用PDO::ERRMODE_EXCEPTION
),您可以使用onConnectionError()
方法指定一个回调,以处理连接时可能引发的PDOException
。可以传递任何callable
到onConnectionError()
方法,而不仅仅是函数,因此$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