Skip to content

fix(database): support union-typed entity columns via explicit Column type#145

Open
TuVanDev wants to merge 1 commit into
developfrom
fix/media-attachable-id-union-type
Open

fix(database): support union-typed entity columns via explicit Column type#145
TuVanDev wants to merge 1 commit into
developfrom
fix/media-attachable-id-union-type

Conversation

@TuVanDev

Copy link
Copy Markdown
Collaborator

Summary

EntityMetadataFactory threw Property '...' must have a type declaration for any entity property with a PHP union type (ReflectionUnionType), with no escape hatch. This broke db:migrate/db:diff outright for any project with marko/media installed, since Marko\Media\Entity\MediaAttachment::$attachableId is declared int|string (a polymorphic foreign key — an attachment can belong to entity types whose primary keys are int or string).

Fix

EntityMetadataFactory now accepts a ReflectionUnionType property when it carries an explicit #[Column(type: ...)] attribute (already a supported mechanism for named-type columns, just not consulted before the union-type guard threw). MediaAttachment::$attachableId is now declared #[Column(type: 'varchar', length: 255)], preserving its int|string PHP type while giving the factory an unambiguous DB column type. Union properties without an explicit #[Column(type:...)] still throw, now with a clearer EntityException::unionTypeRequiresColumnType() message instead of the previous misleading "must have a type declaration".

The ReflectionNamedType code path is unchanged — this is a strictly additive branch.

Notes for reviewers

  • Hydrated attachableId always comes back as a PHP string regardless of the original id's type (varchar columns hydrate to string, and int|string accepts that with no TypeError) — code comparing it against a real int PK should use loose comparison or cast explicitly. No existing usage in packages/media does a strict comparison.
  • This escape hatch only casts safely end-to-end for unions that include string in their type list (a varchar column round-trips fine). A hypothetical future union like int|bool would still need casting support at hydration time — out of scope here since no such entity currently exists.
  • packages/media ships no migration files — schema is derived entirely from entity metadata via SchemaRegistry, so this fix directly unblocks db:diff/db:migrate for any consumer.

Test plan

  • ./vendor/bin/pest packages/database/tests — 883 passed
  • ./vendor/bin/pest packages/media/tests — 38 passed
  • Two new regression tests confirmed to fail on develop (pre-fix) with the original misleading error, and pass on this branch
  • ./vendor/bin/phpcs --standard=phpcs.xml clean on all changed files
  • ./vendor/bin/phpstan — zero new errors vs develop baseline

… type

EntityMetadataFactory rejected any entity property whose reflection type is
not a single named type, throwing "must have a type declaration" for union
types such as `int|string`. Because db:migrate/db:diff parse metadata for
every discovered entity, marko/media's MediaAttachment (a polymorphic foreign
key declared `int|string $attachableId`) broke both commands for any project
with marko/media installed, regardless of whether the entity was used.

Handle ReflectionUnionType: a union has no single reflection type to infer a
column type from, so require an explicit #[Column(type: ...)] and use it as
the database type. Declare the media_attachments.attachable_id column as
varchar, which holds both int and string primary keys while preserving the
polymorphic union on the PHP side.
@github-actions github-actions Bot added the bug Something isn't working label Jul 20, 2026
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

bug Something isn't working

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant