Adapter 对象是 Zend\Db 最重要的子组件。它负责让 Zend\Db 的代码适应目标 PHP 扩展和数据库。以这种方式,它为 PHP 扩展创建了一个抽象层,被称为 Zend\Db 适配器的“Driver(驱动)”部分。它也为厂商指定的 SQL/RDBMS 实现平台创建了一个轻量级的抽象层,被称为适配器的“Platform(平台)”部分。
创建适配器——快速入门(Creating an Adapter – Quickstart)
实例化 Zend\Db\Adapter\Adapter 类就可以简单地创建一个适配器。最常见但不是最明显的使用情况,是像适配器传入一个配置项数组。
1 |
$adapter = new Zend\Db\Adapter\Adapter($configArray); |
此驱动数组是扩展层所需参数的抽象。下面表格包含了配置数组中可用的“键-值”对。
键 | 要求? | 值 |
---|---|---|
driver | 要求 | Mysqli, Sqlsrv, Pdo_Sqlite, Pdo_Mysql, Pdo=OtherPdoDriver |
database | 通常要求 | 数据库(架构)名称 |
username | 通常要求 | 链接的用户名 |
password | 通常要求 | 链接的密码 |
hostname | 一般不要求 | 链接的IP地址或主机名 |
port | 一般不要求 | the port to connect to (if applicable) |
charset | 一般不要求 | the character set to use |
注意
其他名称也可以正常工作。实际上,如果 PHP 手册使用特定的命名,我们的 Diver 都会支持此命名。例如,dbname 大多数情况下可以取替“database”。另一个示例,在 Sqlsrv 的情况下,UID 会替换点 username。选择哪种格式取决于你,上面的表格代表了官方的抽象名称。
例如,使用 ext/mysqli 的 MYSQL 链接:
1 2 3 4 5 6 |
$adapter = new Zend\Db\Adapter\Adapter(array( 'driver' => 'Mysqli', 'database' => 'zend_db_example', 'username' => 'developer', 'password' => 'developer-password' )); |
另一个示例,使用 PDO 的 Sqlite 链接:
1 2 3 4 |
$adapter = new Zend\Db\Adapter\Adapter(array( 'driver' => 'Pdo_Sqlite', 'database' => 'path/to/sqlite.db' )); |
重要的是要知道,通过使用这种创建适配器的风格,Adapter 将试图创建任何没有明确提供的依赖。Driver 对象将由构造方法中的配置数组创建。Platform 对象会根据已实例化 Driver 类的类型而创建。最后,一个默认的 ResultSet 对象将创建并应用。这些对象都将被注入,要做到这一点,请看下一节内容。
官方支持的驱动的列表:
- Mysqli :ext/mysql 驱动
- Pgsql:ext/pgsql 驱动
- Sqlsrv:ext/sqlsrv 驱动(微软的)
- Pdo_Mysql: MySQL PDO 扩展
- Pdo_Sqlite:SQLite PDO 扩展
- Pdo_Pgsql:PostgreSQL PDO 扩展
使用依赖注入创建适配器(Creating an Adapter Using Dependency Injection)
创建适配器更具表象力、更明确方式是预先注入所有的依赖。Zend\Db\Adapter\Adapter 使用构造方法注入,所有需要的依赖都是通过构造方法注入的,具有以下签名(伪代码):
1 2 3 4 5 6 |
use Zend\Db\Adapter\Platform\PlatformInterface; use Zend\Db\ResultSet\ResultSet; class Zend\Db\Adapter\Adapter { public function __construct($driver, PlatformInterface $platform = null, ResultSet $queryResultSetPrototype = null) } |
可以注入的内容:
- $driver – 一个由连接参数组成的数组(见上文),或者 Zend\Db\Adapter\Driver\DriverInterface 的实例。
- $platform – (可选)Zend\Db\PlatformInterface 的实例,默认会根据 driver 实现创建。
- $queryResultSetPrototype – (可选)Zend\Db\ResultSet\Resultset 的实例,要了解这个对象的角色,阅读下面通过适配器查询一节。
通过 Zend\Db\Adapter\Adapt::query()查询预处理(Query Preparation Through Zend\Db\Adapter\Adapter::query())
默认情况下,query() 代表你使用“预处理”作为执行 SQL 语句的方式。这通常意味这你将提供一个以占位符代替值的 SQL 语句,然后分别提供这些占位符的值。使用Zend\Db\Adapter\Adapter 示例:
1 |
$adapter->query('SELECT * FROM `artist` WHERE `id` = ?', array(5)); |
上面实例将通过以下步骤:
- 创建一个新的 Statement 对象
- 如果有必要,准备一个进入 ParameterContainer 的数组
- 将 ParameterContainer 注入到 Statement 对象中
- 执行 Statement 对象,生成一个 Result 对象
- 检查 Result 对象,检查已提供的 sql 是一条“query”还是一个生成语句的结果集
- 如果为生成查询的结果集,克隆 ResultSet 原型,将 Result 作为数据源注入,返回
- 否则,返回Result
创建一个新的 Statement 对象 如果有必要,预备一个传入 ParameterContainer 中的数组 将 ParameterContainer 注入进 Statement 对象中 执行 Statement 对象,产生一个 Result 对象
通过 Zend\Db\Adapter\Adapter::query()查询执行(Query Execution Through Zend\Db\Adapter\Adapter::query())
某些情况下,你需要直接执行一条语句。执行 sql 语句而不是执行预处理的目的可能是引文你尝试执行一条 DDL 语句(数据定义语言)。
1 |
$adapter->query('ALTER TABLE ADD INDEX(`foo_index`) ON (`foo_column`)', Adapter::QUERY_MODE_EXECUTE); |
主要的区别是,你必须提供 Adapter::Query_MODE_EXECUTE作为第二个参数。
创建语句(Creating Statements)
虽然 query() 对通过 Adapter 的一次性快速的数据库查询很有用,通常创建语句并直接与其交互是明智的,那样你就对“预处理-然后-执行”工作流程有了更大的控制权。要做到这点,Adapter 给了你一段叫做 createStatement()的程序,允许你创建一个 Driver 特定的 Statement,因此你可以管理你自己的“预处理-然后-执行”的工作流程。
1 2 3 |
// with optional parameters to bind up-front $statement = $adapter->createStatement($sql, $optionalParameters); $result = $statement->execute(); |
使用驱动对象(Using the Driver Object)
Zend\Adapter\Adapter 主要在 Driver 对象中通过各种 ext/mysqli、ext/sqlsrv、PDO、PHP级驱动完成实现连接级抽象的工作,从而让使用 ZendDb 的所有接口变成了可能。为了做到这点,每个驱动都得由三个对象组成:
- 连接:Zend\Db\Adapter\Driver\ConnectionInterface
- 语句:Zend\Db\Adapter\Driver\StatementInterface
- 结果:Zend\Db\Adapter\Driver\ResultInterface
当有新的实例被请求时,每个内置的驱动都会将“原型设计”作为创建对象的方式。工作流程如下:
- 使用连接参数集合创建适配器
- 适配器选择适当的驱动进行实例化,例如 Zend\Db\Adapter\Driver\Mysqli
- 驱动类被实例化
- 如果没有连接,statement 或 result 对象被注入,默认的被实例化
当请求特定的工作流程时,此驱动就可以被调用了。下面是 Driver API 大致的样子:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 |
namespace Zend\Db\Adapter\Driver; interface DriverInterface { const PARAMETERIZATION_POSITIONAL = 'positional'; const PARAMETERIZATION_NAMED = 'named'; const NAME_FORMAT_CAMELCASE = 'camelCase'; const NAME_FORMAT_NATURAL = 'natural'; public function getDatabasePlatformName($nameFormat = self::NAME_FORMAT_CAMELCASE); public function checkEnvironment(); public function getConnection(); public function createStatement($sqlOrResource = null); public function createResult($resource); public function getPrepareType(); public function formatParameterName($name, $type = null); public function getLastGeneratedValue(); } |
从此 DriverInterface 接口出发,你可以
- 确定此驱动支持的平台的名称(用于选择正确的 platform 对象)
- 检验环境是否支持此驱动
- 返回 Connection 对象
- 创建 Statement 对象,可选,由 SQL 语句植入(这通常是一个原型的 statement 对象的克隆)
- 创建 Result 对象 ,可选,由语句资源植入(这通常是一个原型的 result 对象的克隆)
- 格式化参数名,注意区分扩展间参数命名方式的不同。
- 检索最后产生的值(例如自动递增值)
Statement 对象大致是这样的:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 |
namespace Zend\Db\Adapter\Driver; interface StatementInterface extends StatementContainerInterface { public function getResource(); public function prepare($sql = null); public function isPrepared(); public function execute($parameters = null); /** Inherited from StatementContainerInterface */ public function setSql($sql); public function getSql(); public function setParameterContainer(ParameterContainer $parameterContainer); public function getParameterContainer(); } |
Result 对象大致是这样的:
1 2 3 4 5 6 7 8 9 10 11 |
namespace Zend\Db\Adapter\Driver; interface ResultInterface extends \Countable, \Iterator { public function buffer(); public function isQueryResult(); public function getAffectedRows(); public function getGeneratedValue(); public function getResource(); public function getFieldCount(); } |
使用 Platform 对象(Using The Platform Object)
Platform 对象提供了一个 API ,以针对特定平台特定的 SQL 实现的方式协助执行查询。细微之处如怎样引用标识符或值,或者标识符分隔符字符是什么都是由这个对象处理的。要做到这些,platform 对象的接口大致是这样的:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 |
namespace Zend\Db\Adapter\Platform; interface PlatformInterface { public function getName(); public function getQuoteIdentifierSymbol(); public function quoteIdentifier($identifier); public function quoteIdentifierChain($identiferChain) public function getQuoteValueSymbol(); public function quoteValue($value); public function quoteValueList($valueList); public function getIdentifierSeparator(); public function quoteIdentifierInFragment($identifier, array $additionalSafeWords = array()); } |
虽然可以实例化自己的 Platform 对象,但一般来讲,从已配置的适配器中获取适当的 Platform 实例更容易些(默认情况下,Plateform 对象将匹配底层的驱动实现):
1 2 3 |
$platform = $adapter->getPlatform(); // 或者 $platform = $adapter->platform; // 魔术属性访问 |
下面是使用 Platform 的示例:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 |
/** @var $adapter Zend\Db\Adapter\Adapter */ /** @var $platform Zend\Db\Adapter\Platform\Sql92 */ $platform = $adapter->getPlatform(); // "first_name" echo $platform->quoteIdentifier('first_name'); // " echo $platform->getQuoteIdentifierSymbol(); // "schema"."mytable" echo $platform->quoteIdentifierChain(array('schema','mytable'))); // ' echo $platform->getQuoteValueSymbol(); // 'myvalue' echo $platform->quoteValue('myvalue'); // 'value', 'Foo O\\'Bar' echo $platform->quoteValueList(array('value',"Foo O'Bar"))); // . echo $platform->getIdentifierSeparator(); // "foo" as "bar" echo $platform->quoteIdentifierInFragment('foo as bar'); // additionally, with some safe words: // ("foo"."bar" = "boo"."baz") echo $platform->quoteIdentifierInFragment('(foo.bar = boo.baz)', array('(', ')', '=')); |
使用 Parameter 容器(Using The Parameter Container)
ParameterContainer 对象是一个存放各种参数的容器,这些对象需要传到 Statement 对象中以填充 SQL 语句的所有参数化的部分。此对象实现了 ArrayAccess 接口。下面是 ParameterContainer API:
namespace Zend\Db\Adapter; class ParameterContainer implements \Iterator, \ArrayAccess, \Countable { public function __construct(array $data = array()) /** methods to interact with values */ public function offsetExists($name) public function offsetGet($name) public function offsetSetReference($name, $from) public function offsetSet($name, $value, $errata = null) public function offsetUnset($name) /** set values from array (will reset first) */ public function setFromArray(Array $data) /** methods to interact with value errata */ public function offsetSetErrata($name, $errata) public function offsetGetErrata($name) public function offsetHasErrata($name) public function offsetUnsetErrata($name) /** errata only iterator */ public function getErrataIterator() /** get array with named keys */ public function getNamedArray() /** get array with int keys, ordered by position */ public function getPositionalArray() /** iterator: */ public function count() public function current() public function next() public function key() public function valid() public function rewind() /** merge existing array of parameters with existing parameters */ public function merge($parameters) }
除了处理参数的名称和值,容器也会协助追踪参数类型,将PHP类型转为 SQL 可处理的类型。例如,这可能很重要:
$container->offsetSet('limit', 5);
作为整型绑定。要做到这一点,传入 ParameterContainer::TYPE_INTEGER 常量作为第三个参数:
$container->offsetSet('limit', 5, $container::TYPE_INTEGER);
这将确保如果底层驱动支持参数类型约束,此转译过的信息也将一并传入到实际的 PHP 数据库驱动中。
示例(Examples)
创建一个 Driver 和 轻巧的查询,预处理并迭代结果
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 |
$adapter = new Zend\Db\Adapter\Adapter($driverConfig); $qi = function($name) use ($adapter) { return $adapter->platform->quoteIdentifier($name); }; $fp = function($name) use ($adapter) { return $adapter->driver->formatParameterName($name); }; $sql = 'UPDATE ' . $qi('artist') . ' SET ' . $qi('name') . ' = ' . $fp('name') . ' WHERE ' . $qi('id') . ' = ' . $fp('id'); /** @var $statement Zend\Db\Adapter\Driver\StatementInterface */ $statement = $adapter->query($sql); $parameters = array( 'name' => 'Updated Artist', 'id' => 1 ); $statement->execute($parameters); // DATA INSERTED, NOW CHECK /* @var $statement Zend\Db\Adapter\DriverStatementInterface */ $statement = $adapter->query('SELECT * FROM ' . $qi('artist') . ' WHERE id = ' . $fp('id')); /* @var $results Zend\Db\ResultSet\ResultSet */ $results = $statement->execute(array('id' => 1)); $row = $results->current(); $name = $row['name']; |