[RFC]: Replace caller-supplied Literal slots in Sql\Ddl with backed enums and typed arguments
维护者通常 1 天内回复
@simon-mundy 已经在做这个了。
开始于 2026年9月4日。
评估
这个 Issue 还没有评估数据。
描述
Proposed Version
0.6.0 for the enums, typed arguments and string-compat setters; the string-getter retype waits for the next major.
Basic Information
Every place under src/Sql/Ddl/ where a caller-supplied string becomes an Argument\Literal or is concatenated into a spec string gets a backed enum (closed value sets) or a typed int (numeric slots), with a one-minor string-compat path. This is the core counterpart of phpdb-mysql#79.
Background
The DDL layer renders three kinds of argument: Argument\Identifier (quoted as an identifier), Argument\Value (quoted or bound) and Argument\Literal (inserted verbatim). Going through every getExpressionData() under src/Sql/Ddl/ (and processTableOptions() for table options), these are the places where a Literal, or a bare string concatenated into the spec, comes from the caller rather than a class constant:
| Class | Slot | Origin | Rendered probe |
|---|---|---|---|
Index |
column prefix length | untyped array $lengths, concatenated into the spec |
INDEX `i`(`email`(20) INJECT)) |
Index |
USING type |
setType(string) -> Literal |
USING HASH; DROP |
ForeignKey |
ON DELETE / ON UPDATE rule |
string setters -> Literal |
ON DELETE CASCADE; DROP |
Integer |
display width | options['length'] (untyped via the constructor array, bool|string via setOption()), concatenated |
INTEGER NOT NULL (11) (also a positional bug, #178) |
Check |
expression | untyped constructor with a string|ExpressionInterface docblock -> Literal |
an expression slot by design; an Expression is accepted at construction and fails at render (#177) |
CreateTable / AlterTable processTableOptions() |
table option key | setOption(string $name, ...), strtoupper() only |
ENGINE = INNODB; DROP TABLE Y; -- = 'x' |
Example: setOption('row_format', 'DYNAMIC'), 'algorithm' and 'lock' with a plain string render a quoted value MySQL rejects with 1064. Only a Literal works, and nothing tells the caller that.
Safe by construction and not proposed to change: Column::$type (protected, set by the subclass), AbstractLengthColumn / AbstractPrecisionColumn lengths (built from ?int), AbstractTimestampColumn's fixed ON UPDATE CURRENT_TIMESTAMP, all identifier slots, Column defaults given as an Argument\Literal, and table-option values given as a Sql\Literal. Those last two are raw by definition.
Considerations
- Docs bug. Only
Argument\Literalitself says aLiteraldefault is raw.Column::setDefault()and the DDL docs don't, anddocs/book/sql-ddl/columns.md:340-346,alter-drop.md,examples.mdandintro.mdall showsetDefault('CURRENT_TIMESTAMP')producing an unquoted default. It actually rendersDEFAULT 'CURRENT_TIMESTAMP', which MySQL rejects (1067). Worth its own issue; listed here because it is the other side of the same invariant. - Getters.
getOnDeleteRule(): string,getOnUpdateRule(): stringandIndex::getType(): ?stringare asserted as strings inForeignKeyTest.php:106-132andIndexTest.php:67. Retyping them in a minor would be a BC break, so they stay until the next major. SET DEFAULTis valid grammar that InnoDB doesn't honour (on 8.0.46 the DDL is accepted and RESTRICT is enforced at DML time). Keep it in the enum, document the caveat.- Index prefix length: MySQL rejects 0 with 1391, so
positive-intis exactly the server's bound. - Integer display width is itself deprecated since 8.0.17; keeping it as a typed
intis for the other platforms, and the MySQL decorator will drop it. - Overlaps: #169 (typing the option arrays), phpdb-mysql#79 (the adapter side), the statements umbrella, #179 (
RowFormat/Algorithm/Lockand key validation live there).
Proposal(s)
Sql\Ddl\Constraint\ReferentialAction: string { NoAction = 'NO ACTION'; Restrict = 'RESTRICT'; Cascade = 'CASCADE'; SetNull = 'SET NULL'; SetDefault = 'SET DEFAULT' }forForeignKey::setOnDeleteRule()/setOnUpdateRule()and the constructor.Sql\Ddl\Index\IndexType: string { BTree = 'BTREE'; Hash = 'HASH' }forIndex::setType().Index::$lengthstypedlist<positive-int>(validated in the constructor) and rendered asLiteral((string) $int)in the values array, not in the spec.Integerlength asint(see #178).- Table option keys validated against a pattern or an enum of known options; string values keep going through
quoteTrustedValue(), keyword values throughRowFormat/Algorithm/Lockenums (#179).
Compatibility:
- Setters keep accepting
stringfor one minor, resolved withEnum::tryFrom(strtoupper(trim($value)))and rejected withInvalidArgumentExceptionwhen unknown. Passing a string is deprecated in the docblock and removed at the next major. - Getters keep returning
stringfor the same period. Add enum-returning siblings now (getOnDeleteAction(): ReferentialAction,getOnUpdateAction(): ReferentialAction,Index::getIndexType(): ?IndexType) and retype the string getters at the next major.
Test plan:
- No
getExpressionData()undersrc/Sql/Ddl/constructs anArgument\Literalfrom a caller string exceptCheck(documented) andColumndefaults (documented insetDefault()and the docs, with theCURRENT_TIMESTAMPexamples corrected). - No
getExpressionData()orprocessTableOptions()concatenates a caller value into the spec string. - Each enum has a unit test for accepted values, the string-compat path and rejection.
- The probe strings above render quoted/escaped or throw; none reaches SQL verbatim.
- Existing string-getter tests pass unchanged.
Appendix/Additional Info
- #91 (backed enums for metadata constants), #169 (untyped option arrays), phpdb-mysql#79 (the same fix on the adapter side).
- From an internal DDL audit (not published). The full slot table there includes the adapter side; the core rows are reproduced above.
- 主要语言
- PHP
- 星标
- 18
- 派生
- 8
- 平均合并
- 6 天 11 小时
- 30 天内合并 PR
- 14
环境准备
- 提供 Dockerfile 或 Docker Compose 文件
- 没有 Pull Request 模板
- 没有贡献指南
从这里开始
- 先读完整个 Issue,再读项目的贡献指南。
- 在 Issue 下留言说明你要接手 —— 这能避免两个人做同样的事。
- Fork 仓库,在一个分支上完成修改。
- 提交 Pull Request,并在描述里引用这个 Issue 编号。
php-db/phpdb 的其他 Issue
-
bug
难度 2/5 1-3 小时 新手友好度 82/100
维护者通常 1 天内回复
-
RFC
难度 3/5 1-2 天 新手友好度 66/100
维护者通常 1 天内回复
-
RFC
难度 5/5 一周以上 新手友好度 38/100
维护者通常 1 天内回复
-
RFC
难度 3/5 1-2 天 新手友好度 68/100
维护者通常 1 天内回复
-
RFC
难度 4/5 3-5 天 新手友好度 45/100
维护者通常 1 天内回复
相似的 Issue
-
难度 2/5 1-3 小时 新手友好度 82/100
WordPress/two-factor#1022 · 1 条评论 ·
维护者通常 1 天内回复
-
Messenger
难度 2/5 1-3 小时 新手友好度 62/100
symfony/symfony-docs#23237 ·
维护者通常 3 天内回复
-
难度 2/5 1-3 小时 新手友好度 62/100
glpi-project/glpi#25883 ·
维护者通常 1 天内回复
-
sync-en
难度 2/5 1-3 小时 新手友好度 70/100
维护者通常 1 天内回复
-
sync-en
难度 2/5 1-3 小时 新手友好度 72/100
维护者通常 4 天内回复