sad_spirit / pg_wrapper
复杂 PostgreSQL 类型的转换器以及 PHP pgsql 扩展的面向对象包装器
Requires (Dev)
- ext-pgsql: *
- phpstan/phpstan: ^1.3
- phpunit/phpunit: ^8.0|^9.0
- psr/cache: ^1.0
- vimeo/psalm: ^4.16
Suggests
- ext-pgsql: Used for communicating with PostgreSQL
- psr/cache-implementation: Used for DB metadata caching
This package is auto-updated.
Last update: 2024-09-17 23:04:04 UTC
README
注意:master 分支正在升级以支持 PHP 8.2+
分支 2.x 包含与 PHP 7.2+ 兼容的稳定版本
此包包含两个部分和目的
- 将 PostgreSQL 数据类型 转换为其 PHP 等效类型以及反过来转换,
- 以及 PHP 的原生 pgsql 扩展 的面向对象包装器。
虽然转换部分可以单独使用,例如与 PDO 一起使用,但像透明转换查询结果这样的功能只能在包装器中使用。
安装
使用 composer 需求此包
composer require sad_spirit/pg_wrapper
pg_wrapper 需要 PHP 8.2 或更高版本。要使用访问数据库的类,必须启用原生 pgsql 扩展(该扩展不是强制要求)。
最低支持的 PostgreSQL 版本是 9.3
强烈建议在生产环境中使用 PSR-6 兼容 的元数据缓存,以防止在每个页面请求时从数据库中查找可能的元数据。
为什么需要进行类型转换?
PostgreSQL 支持一组大型(且可扩展)的复杂数据库类型:数组、范围、几何和日期/时间类型、组合(行)类型、JSON...
create table test ( strings text[], coords point, occupied daterange, age interval, document json ); insert into test values ( array['Mary had', 'a little lamb'], point(55.75, 37.61), daterange('2014-01-13', '2014-09-19'), age('2014-09-19', '2014-01-13'), '{"title":"pg_wrapper","text":"pg_wrapper is cool"}' );
不幸的是,与 PostgreSQL 通信的 PHP 扩展(pgsql 和 PDO_pgsql)都无法将这些复杂类型映射到其 PHP 等效类型。它们返回字符串表示形式
var_dump(pg_fetch_assoc(pg_query($conn, 'select * from test')));
产生
array(5) {
'strings' =>
string(28) "{"Mary had","a little lamb"}"
'coords' =>
string(13) "(55.75,37.61)"
'occupied' =>
string(23) "[2014-01-13,2014-09-19)"
'age' =>
string(13) "8 mons 6 days"
'document' =>
string(50) "{"title":"pg_wrapper","text":"pg_wrapper is cool"}"
}
这就是这个库发挥作用的地方
$result = $connection->execute('select * from test'); var_dump($result[0]);
产生
array(5) {
'strings' =>
array(2) {
[0] =>
string(8) "Mary had"
[1] =>
string(13) "a little lamb"
}
'coords' =>
class sad_spirit\pg_wrapper\types\Point#18 (1) {
private $_coordinates =>
array(2) {
'x' =>
double(55.75)
'y' =>
double(37.61)
}
}
'occupied' =>
class sad_spirit\pg_wrapper\types\DateTimeRange#19 (1) {
...
}
'age' =>
class sad_spirit\pg_wrapper\types\DateInterval#22 (16) {
...
}
'document' =>
array(2) {
'title' =>
string(10) "pg_wrapper"
'text' =>
string(18) "pg_wrapper is cool"
}
}
为什么还需要另一个面向对象的包装器,当我们已经有 PDO、Doctrine DBAL 等工具时?
抽象层的目的是针对最低通用语言,因此它有意隐藏了一些我们可以使用原生扩展以及 / 或添加另一层复杂性的低级 API。
- PDO 不暴露
pg_query_params()
,因此即使你只执行一次查询,你也必须prepare()
/execute()
每个查询。 - Postgres 只支持原生的
$1
位置参数,而 PDO 有位置?
和命名:foo
参数。PDO 实际上重写了查询以将后者转换为前者,这在 PHP 7.4 之前防止了使用 PDO 与包含?
的 Postgres 操作符,并且在使用美元引号字符串时仍然可能导致问题。 - PDO 不暴露
pg_field_type_oid()
和其PDOStatement::getColumnMeta()
返回类型名称,而不包含模式名称 并且 可能每次都会运行元数据查询来获取它。
另一个例子:数据库抽象的一个非常常见问题是使用 IN
子句向查询提供参数列表
SELECT * FROM stuff WHERE id IN (?)
其中 ?
实际上代表一个可变数量的参数。
一方面,如果您不需要抽象,Postgres 本身就有原生数组类型,这可以通过以下查询轻松实现
-- in case of using PDO just replace $1 with a PDO-compatible placeholder SELECT * FROM stuff WHERE id = ANY($1::INTEGER[])
传递数组字面量作为其参数值
use sad_spirit\pg_wrapper\converters\DefaultTypeConverterFactory; $arrayLiteral = (new DefaultTypeConverterFactory()) ->getConverterForTypeSpecification('INTEGER[]') ->output([1, 2, 3]);
另一方面,Doctrine DBAL 有自己的参数列表解决方案,这再次依赖于重写 SQL,并且不与 prepare()
/ execute()
一起使用。它也具有 "支持" 数组类型,但这只是(反)序列化 PHP 数组,而不是将它们从/到原生数据库表示形式转换,这显然与上述查询不兼容。
文档
位于 wiki
类型转换
与 PostgreSQL 一起工作