[RFC]: Replace caller-supplied Literal slots in Sql\Ddl with backed enums and typed arguments
Mantenedores costumam responder em até 1 dia
@simon-mundy já está trabalhando nisso.
Desde 4/9/2026.
Avaliação
Esta issue ainda não foi avaliada.
Descrição
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.
- Linguagem predominante
- PHP
- Estrelas
- 18
- Forks
- 8
- Merge médio
- 6d 11h
- PRs com merge (30d)
- 14
Preparar o ambiente
- Inclui um Dockerfile ou arquivo Docker Compose
- Sem modelo de pull request
- Sem guia de contribuição
Primeiros passos
- Leia a issue inteira e depois o guia de contribuição do projeto.
- Comente na issue dizendo que vai assumir — evita que duas pessoas façam o mesmo trabalho.
- Faça um fork do repositório e trabalhe em uma branch.
- Abra um pull request que referencie o número da issue.
Mais de php-db/phpdb
-
bug
Dificuldade 2/5 1-3 horas Facilidade para iniciantes 82/100
Mantenedores costumam responder em até 1 dia
-
RFC
Dificuldade 3/5 1-2 dias Facilidade para iniciantes 66/100
Mantenedores costumam responder em até 1 dia
-
RFC
Dificuldade 5/5 Mais de uma semana Facilidade para iniciantes 38/100
Mantenedores costumam responder em até 1 dia
-
RFC
Dificuldade 3/5 1-2 dias Facilidade para iniciantes 68/100
Mantenedores costumam responder em até 1 dia
-
RFC
Dificuldade 4/5 3-5 dias Facilidade para iniciantes 45/100
Mantenedores costumam responder em até 1 dia
Todas as issues de php-db/phpdb
Issues semelhantes
-
Bug Enhancement Performance
Dificuldade 2/5 1-3 horas Facilidade para iniciantes 78/100
Mantenedores costumam responder em até 1 dia
-
Feature Status: Needs Triage
Dificuldade 2/5 1-3 horas Facilidade para iniciantes 73/100
Mantenedores costumam responder em até 1 dia
-
frontend low-priority
Dificuldade 2/5 1-3 horas Facilidade para iniciantes 77/100
mplodowski/dynamicpdf-plugin#336 ·
Mantenedores costumam responder em até 1 dia
-
Dificuldade 2/5 1-3 horas Facilidade para iniciantes 68/100
AdvancedCustomFields/acf#1044 ·
-
Add ZammadTalvez já em andamento @Arslan-TR assumiu hoje. Abertarequest
Dificuldade 2/5 1-3 horas Facilidade para iniciantes 66/100
endoflife-date/endoflife.date#11298 · 1 comentário ·
Mantenedores costumam responder em até 1 dia