Skip to content

Make the Doctrine mapping describe the tables the module actually installs - #249

Draft
boo-code wants to merge 1 commit into
PrestaShop:devfrom
boo-code:fix/doctrine-annotations-match-shipped-schema
Draft

Make the Doctrine mapping describe the tables the module actually installs#249
boo-code wants to merge 1 commit into
PrestaShop:devfrom
boo-code:fix/doctrine-annotations-match-shipped-schema

Conversation

@boo-code

@boo-code boo-code commented Sep 6, 2026

Copy link
Copy Markdown
Questions Answers
Description? The three entities declare column types, nullability and indexes that differ from sql/install/*.sql, so doctrine:schema:update emits statements that fail or destroy data on a shop where the module is installed. error_message and message are TEXT in the install SQL and VARCHAR(255) NOT NULL in the mapping, so the emitted CHANGE fails with ERROR 1406 Data too long for column on any consent text over 255 characters. id_guest and client_name are nullable in the install SQL and NOT NULL in the mapping. The two psgdpr_log indexes created by the install SQL are absent from the mapping, so the schema tool treats them as unknown and emits DROP INDEX for both - including the module's own covering index idx_id_customer.
Type? bug fix
BC breaks? no
Deprecations? no
Fixed ticket? Related to PrestaShop/PrestaShop#35521
How to test? On a 9.x shop with this module installed, run php bin/console doctrine:schema:update --dump-sql and look at the ps_psgdpr_* lines. Before this change the output contains three VARCHAR(255) NOT NULL narrowings and two DROP INDEX ... ON ps_psgdpr_log; after it, none of them.

Measured

PrestaShop 9.2.0, psgdpr at dev (2.0.3), the three tables created from this module's own
sql/install/*.sql. Only the mapping changed between the two runs.

Before:

ALTER TABLE ps_psgdpr_consent CHANGE active active TINYINT(1) NOT NULL, CHANGE error error TINYINT(1) NOT NULL, CHANGE error_message error_message VARCHAR(255) NOT NULL;
ALTER TABLE ps_psgdpr_consent_lang MODIFY id_gdpr_consent INT NOT NULL;
DROP INDEX `primary` ON ps_psgdpr_consent_lang;
ALTER TABLE ps_psgdpr_consent_lang CHANGE id_gdpr_consent id_gdpr_consent INT NOT NULL, CHANGE message message VARCHAR(255) NOT NULL;
ALTER TABLE ps_psgdpr_consent_lang ADD CONSTRAINT FK_9C2246C9BEB78BEF FOREIGN KEY (id_gdpr_consent) REFERENCES ps_psgdpr_consent (id_gdpr_consent);
ALTER TABLE ps_psgdpr_consent_lang ADD CONSTRAINT FK_9C2246C9BA299860 FOREIGN KEY (id_lang) REFERENCES ps_lang (id_lang) ON DELETE CASCADE;
CREATE INDEX IDX_9C2246C9BEB78BEF ON ps_psgdpr_consent_lang (id_gdpr_consent);
CREATE INDEX IDX_9C2246C9BA299860 ON ps_psgdpr_consent_lang (id_lang);
ALTER TABLE ps_psgdpr_consent_lang ADD PRIMARY KEY (id_gdpr_consent, id_lang);
DROP INDEX id_customer ON ps_psgdpr_log;
DROP INDEX idx_id_customer ON ps_psgdpr_log;
ALTER TABLE ps_psgdpr_log CHANGE id_guest id_guest INT NOT NULL, CHANGE client_name client_name VARCHAR(255) NOT NULL;

After (this branch alone, #230 not applied):

ALTER TABLE ps_psgdpr_consent CHANGE active active TINYINT(1) NOT NULL, CHANGE error error TINYINT(1) NOT NULL;
ALTER TABLE ps_psgdpr_consent_lang MODIFY id_gdpr_consent INT NOT NULL;
DROP INDEX `primary` ON ps_psgdpr_consent_lang;
ALTER TABLE ps_psgdpr_consent_lang CHANGE id_gdpr_consent id_gdpr_consent INT NOT NULL;
ALTER TABLE ps_psgdpr_consent_lang ADD CONSTRAINT FK_9C2246C9BEB78BEF FOREIGN KEY (id_gdpr_consent) REFERENCES ps_psgdpr_consent (id_gdpr_consent);
ALTER TABLE ps_psgdpr_consent_lang ADD CONSTRAINT FK_9C2246C9BA299860 FOREIGN KEY (id_lang) REFERENCES ps_lang (id_lang) ON DELETE CASCADE;
CREATE INDEX IDX_9C2246C9BEB78BEF ON ps_psgdpr_consent_lang (id_gdpr_consent);
CREATE INDEX IDX_9C2246C9BA299860 ON ps_psgdpr_consent_lang (id_lang);
ALTER TABLE ps_psgdpr_consent_lang ADD PRIMARY KEY (id_gdpr_consent, id_lang);

After (this branch stacked on #230):

ALTER TABLE ps_psgdpr_consent CHANGE active active TINYINT(1) NOT NULL, CHANGE error error TINYINT(1) NOT NULL;
ALTER TABLE ps_psgdpr_consent_lang CHANGE id_gdpr_consent id_gdpr_consent INT NOT NULL;
ALTER TABLE ps_psgdpr_consent_lang ADD CONSTRAINT FK_9C2246C9BEB78BEF FOREIGN KEY (id_gdpr_consent) REFERENCES ps_psgdpr_consent (id_gdpr_consent);
ALTER TABLE ps_psgdpr_consent_lang ADD CONSTRAINT FK_9C2246C9BA299860 FOREIGN KEY (id_lang) REFERENCES ps_lang (id_lang) ON DELETE CASCADE;
CREATE INDEX IDX_9C2246C9BEB78BEF ON ps_psgdpr_consent_lang (id_gdpr_consent);
CREATE INDEX IDX_9C2246C9BA299860 ON ps_psgdpr_consent_lang (id_lang);

Nothing destructive is left. The remaining statements are additive foreign keys and indexes the install
SQL never created, plus two safe items left deliberately out of scope (below).

The two failures are real, not theoretical, measured directly against MySQL 8:

ALTER TABLE zz_pk_probe ADD PRIMARY KEY (id_gdpr_consent, id_lang);
  -> ERROR 1062 (23000): Duplicate entry '1-1' for key 'zz_pk_probe.PRIMARY'   (2 shops)
ALTER TABLE zz_msg_probe CHANGE message message VARCHAR(255) NOT NULL;
  -> ERROR 1406 (22001): Data too long for column 'message' at row 1           (400-char message)

Deliberately out of scope

active and error are INT(10) in the install SQL and boolean in the mapping, so the schema tool
emits TINYINT(1). The mapping is right - they are flags, and the getters return bool - so the fix
belongs in the install SQL plus an upgrade script, not in the annotation. The statement is safe on the
values these columns hold, so it is left alone here.

MODIFY id_gdpr_consent INT NOT NULL on ps_psgdpr_consent_lang removes an AUTO_INCREMENT the install
SQL puts on a column that is a foreign key into ps_psgdpr_consent. Doctrine is right to drop it; same
reasoning, same conclusion.

Notes

Two details that are easy to get wrong here:

  • Doctrine's text type maps to LONGTEXT, not TEXT. Without length=65535 the emitted statement
    becomes CHANGE message message LONGTEXT DEFAULT NULL - safe, but still a diff. The length is what
    makes it disappear.
  • An @ORM\Table() that omits indexes is not neutral. schema:update treats the mapping as the desired
    state, so any index it does not know about is emitted as DROP INDEX, and --force would remove the
    module's own covering index.

The module has no PHPUnit harness (CI is php-linter, php-cs-fixer and PHPStan only), so the verification
above stands in for a unit test. doctrine:schema:validate reports [OK] The mapping files are correct.,
and php-cs-fixer --dry-run on src/Entity is clean.

…talls

The three entities declare column types and indexes that differ from
sql/install/*.sql, so doctrine:schema:update emits statements that fail or
destroy data on a shop where the module is installed:

  ALTER TABLE ps_psgdpr_consent      CHANGE error_message error_message VARCHAR(255) NOT NULL;
  ALTER TABLE ps_psgdpr_consent_lang CHANGE message message VARCHAR(255) NOT NULL;
  ALTER TABLE ps_psgdpr_log          CHANGE id_guest id_guest INT NOT NULL, CHANGE client_name client_name VARCHAR(255) NOT NULL;
  DROP INDEX id_customer     ON ps_psgdpr_log;
  DROP INDEX idx_id_customer ON ps_psgdpr_log;

error_message and message are TEXT in the install SQL, so narrowing them to
VARCHAR(255) fails with 'Data too long for column' on any consent text longer
than 255 characters. id_guest and client_name are nullable in the install SQL.
The two psgdpr_log indexes are created by the install SQL but absent from the
mapping, so the schema tool treats them as unknown and drops them.

Align the mapping with the shipped tables. Doctrine's text type maps to
LONGTEXT unless a length is given, so length=65535 is needed to land on TEXT.
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant