inkant / engi
SQL模板引擎
Requires (Dev)
- phpstan/phpstan: ^1.10
- phpunit/phpunit: ^11.1
This package is auto-updated.
Last update: 2024-10-02 19:41:54 UTC
README
类似于 sprintf,但占位符可自定义、可扩展,其行为灵活且以SQL为导向。
安装
composer require inkant/engi
TL;DR
请勿在生产环境中使用 DummyEscaper
请使用 PgEscaper,PdoEscaper 或您自己的自定义转义实现,而不是 DummyEscaper
。
DummyEscaper
仅用于示例。
i?
- 尝试将转换为 int SQL 类型位置参数
i?name1
- 尝试转换为名为 "name1" 的 int SQL 命名参数
?
- 尝试将传递的位置参数类型解析为合理的 SQL 类型
?name1
- 尝试将传递的命名参数 "name1" 类型解析为合理的 SQL 类型
$query = new \Inkant\Engi\Query( 'SELECT i?, ?, ?name1, i?name1', [ '100', 100, 'name1' => '454' ]); $result = "SELECT 100, 100, '454', 454"; $compiler = new Compiler(new DummyEscaper()); $compiler->compile($query) === $result ?: throw new \Exception();
更多 和 大多数 具有子查询和参数类型的描述性测试用例
请查看 Query 中的 resolvers 设置示例 ::resolvers(),它可以扩展以自定义占位符或/和自定义解析器
架构
解析器 负责从字符串模板中提供 占位符 作为 抽象语法树 的部分。
抽象语法树 是 composite
模式的实现,其中叶是 SQL 上下文中的 令牌(必须具有字符串表示形式),而 组合(例如 占位符)是更复杂的 SQL 结构。
每个 组合 都必须通过调用 compile(mixed ...$dependencies)
方法转换为 令牌。
每个 令牌 都必须通过调用 compile(mixed ...$dependencies)
方法转换为字符串。
令牌 必须由 组装器 调用方法 assemble
组装,提供 令牌 和 ...$dependencies
。
...$dependencies
是在 compile
期间所需的任何依赖项(例如数据库版本/名称,转义字符串机制)。
占位符
占位符只是带有可选名称字符串 [a-zA-Z0-9_]+
的子字符串
?
- 如果没有提供名称,则使用下一个位置参数
?email
- 使用数组参数项,键为 email
i?id
- 使用数组参数项,键为 id
转换为 int
占位符使同时使用命名和位置参数成为可能。
此外,它还允许在不同上下文中重用同一个值。
i?id
- 将字符串值 '100' 转换为 SQL integer
值 100
s?id
- (值类型明显,可以用 "?id" 替换) 将字符串值 '100' 转换为 SQL text
值 '100'
?id
- 将值 '100' 解析为 SQL text
值 '100',基于其值类型为字符串
$query = new \Inkant\Engi\Query( 'SELECT i?id, s?id, ?id', [ 'id' => '100' ]); $result = "SELECT 100, '100', '100'"; $compiler = new Compiler(new DummyEscaper()); $compiler->compile($query) === $result ?: throw new \Exception();
为了确定由什么来具体替换占位符,使用 解析器。
转义占位符
有时占位符可能会干扰 SQL 语法。例如,PostgreSQL 有 jsonb 操作符 "?",如果使用 "?" 作为占位符,则可以通过将其重复两次来转义。
因此,占位符 "???" 将解析为 "?",不会影响 PostgreSQL 语法。
可以通过 EscapeResolver 实现。
转义值
不同的数据库有不同的转义方式(例如,避免 SQL 注入)值标记(例如,字符串)。
PgEscaper 和 PdoEscaper 实现了转义字符串并避免 SQL 注入所必需的接口。
转义字符串
为了实现数据库无关性,例如,StringToken 在其 ...$dependencies
中需要实现 EscapeStringInterface,在 compile(...$dependencies)
过程中
转义二进制
BinaryToken 在其 ...$dependencies
中需要实现 EscapeBinaryInterface,在 compile(...$dependencies)
过程中
转义标识符
有时需要转义表名、列名等。例如,IdentifierToken 在其 ...$dependencies
中需要实现 EscapeIdentifierInterface,在 compile(...$dependencies)
过程中
PgEscaper 使用 PostgreSQL 驱动程序特定的函数 pg_escape_identifier
实现 EscapeIdentifierInterface。
PdoEscaper 也实现了 EscapeIdentifierInterface,但由于 PDO 不提供任何转义标识符函数,因此 PdoEscaper::escapeIdentifier
返回标识符 ASIS。
解析器
解析器负责将传递的值解析为 SQL 值/结构中的占位符。
解析器很简单,这里是一个 StringResolver 的例子
class StringResolver extends ResolverAbstract { public function resolve(mixed $value): ?StringToken { return is_string($value) ? new StringToken($value) : null; } }
KeyValueResolver
在 UPDATE SET
中非常有用,可以将关联数组解析为 key=value 列表 ['column1' => 1, 'column2' => 2] => 'column1=1,column2=2'
ListResolver
在 IN
中非常有用。如果 array_is_list
,则列表解析为逗号分隔的 SQL 列表。
[1, 2.38] => '1,2.38'
AstResolver
使嵌套查询成为可能。
如果值是 AstInterface(例如,Query),则它将被嵌入并编译在 compile
中
IdentifierResolver
用于表名和列名的占位符,不会作为字符串值进行引号处理。