name: php-domain-types
description: Introduce, refactor, and normalize domain type aliases in PHP projects, especially codebases checked by Psalm and/or PHPStan. Use when Codex needs to create or reorganize a Types.php catalog, replace repeated array shapes with named aliases, add @psalm-import-type or @phpstan-import-type, decide whether a structure is a domain concept or only an implementation detail, or repair static-analysis issues caused by alias refactors.
PHP Domain Types
Overview
Introduce shared type aliases only for concepts that carry stable meaning across the project. Centralize them, import them where reused, and keep static-analysis compatibility intact.
Workflow
- Inspect the project before naming anything.
- Search for
@psalm-type,@phpstan-type,@psalm-import-type,@phpstan-import-type, repeatedarray{...}, repeatedarray<string, mixed>, and repeatedlist<...>. - Check
composer.json, CI config, and analyzer config to see whether Psalm, PHPStan, or both are active.
- Promote only real domain concepts into aliases.
- Promote serialized payloads, transport envelopes, link lists, profile payloads, stable config groups, keyed maps by IDs or types, and reused collection shapes.
- Keep local helper arrays, pass-by-reference accumulators, temporary analyzer workarounds, and concrete containers such as
SplStackout of the catalog unless they are shared public concepts.
- Design the catalog as a bounded type dictionary.
- Create one
Types.phpper namespace boundary or bounded context, not one repo-wide junk drawer. - Make the catalog non-instantiable.
- Add
@psalm-suppress UnusedClassif the class exists only to host aliases. - Group aliases by concern: scalar atoms, collections, serialized shapes, analyzer interop.
- Name aliases in domain language, not PHP plumbing language.
- Prefer names such as
UserId,SchemaUrl,RouteParams,ValidationErrorsByField,EventsByRequestId,PublicEventData. - Use suffixes consistently:
*List,*Map,*Set,*ById,*ByType,*Data. - Use
*Arrayonly when the concrete serialized array shape itself is important. - Read naming-patterns.md when names or boundaries are unclear.
- Integrate aliases incrementally.
- Add aliases first.
- Replace only repeated or semantically important docblocks.
- Import shared aliases with
@psalm-import-typeand/or@phpstan-import-type. - Keep native PHP signatures honest; do not widen runtime types to match docblocks.
- If a local helper becomes harder to understand after aliasing, leave it as a raw array annotation.
- Keep analyzer compatibility explicit.
- Use tool-specific tags when the project runs both analyzers:
@psalm-type,@psalm-import-type,@psalm-return,@psalm-param,@phpstan-type,@phpstan-import-type,@phpstan-return,@phpstan-param. - Do not assume a tool-specific alias will be understood through a plain
@returnor@param. - If optional keys become explicit and tests or analyzers start failing, update guards and fixtures instead of weakening the alias.
- Validate after each meaningful batch.
- Run the analyzers and tests the project already uses.
- Treat parser errors, invalid imports, and optional-key warnings as feedback on the alias boundary.
- Prefer shrinking alias scope over adding suppressions when the catalog becomes fragile.
Heuristics
- Introduce an alias when it clarifies a shared concept better than the raw shape.
- Avoid introducing an alias only because a shape is long.
- Split runtime structures from serialized structures when their meanings differ.
- Prefer one good alias imported in many places over many near-duplicate aliases.
- Rename weak aliases such as
Data,Info,Items, orMapto something with a real subject.
Quick Checks
- Does the alias express business, transport, persistence, or API meaning?
- Is the shape reused across files or likely to be imported?
- Does the alias make review easier than the raw shape?
- Is the alias naming a stable thing, not a temporary implementation trick?
- Will both active analyzers accept the way the alias is declared and imported?