spryker / api-platform
ApiPlatform module
Requires
- php: >=8.3
- api-platform/api-pack: ^1.4.0
- api-platform/doctrine-common: ^4.3.0
- api-platform/doctrine-orm: ^4.3.0
- api-platform/documentation: ^4.3.0
- api-platform/hal: ^4.3.0
- api-platform/http-cache: ^4.3.0
- api-platform/hydra: ^4.3.0
- api-platform/json-api: ^4.3.0
- api-platform/json-schema: ^4.3.0
- api-platform/jsonld: ^4.3.0
- api-platform/metadata: ^4.3.0
- api-platform/openapi: ^4.3.0
- api-platform/serializer: ^4.3.0
- api-platform/state: ^4.3.0
- api-platform/symfony: ^4.3.0
- api-platform/validator: ^4.3.0
- friendsofcxml/cxml-php: ^2.0.0
- spryker/doctrine-inflector: ^1.0.0 || ^2.0.0
- spryker/entity-tag: ^1.0.0
- spryker/glossary-storage: ^1.0.0
- spryker/kernel: ^3.48.0
- spryker/locale: ^4.0.0
- spryker/log: ^3.0.0
- spryker/oauth: ^2.0.0
- spryker/store: ^1.0.0
- spryker/transfer: ^3.27.0
- symfony/config: ^6.0.0 || ^7.0.0
- symfony/console: ^6.0.0 || ^7.0.0
- symfony/expression-language: ^6.0.0 || ^7.0.0
- symfony/finder: ^6.0.0 || ^7.0.0
- symfony/http-kernel: ^6.0.0 || ^7.0.0
- symfony/security-bundle: ^6.0.0 || ^7.0.0
- symfony/yaml: ^6.0.0 || ^7.0.0
Requires (Dev)
Conflicts
- spryker/serializer: <1.1.1
This package is auto-updated.
Last update: 2026-08-20 15:45:06 UTC
README
Installation
composer require spryker/api-platform
Canonical nested objects (*.object.yml)
A project can define the canonical inner shape of a nested object once and have it flow into every resource that tags the matching objectName. With no object files present the generator output is byte-for-byte identical to the default behavior — it is a pure project opt-in.
File location
Canonical object files live in a dedicated, reserved subdirectory literally named objects/ — distinct from resource definition files. The directory name is always objects, never named after a resource or module.
To contrast the two kinds of files clearly:
resources/api/storefront/
├── checkout.resource.yml # a resource definition
├── checkout.validation.yml # its validation
└── objects/ # reserved dir — canonical objects only
├── address.object.yml
└── address.object.validation.yml
Only *.object.yml and *.object.validation.yml files belong in objects/. Resource files (*.resource.yml) are placed directly in the per-apiType directory, never inside objects/.
Naming The <dashed-name>.<kind>.yml pattern is shared by both file types — only the kind word differs. address.object.yml is the canonical-object analog of checkout.resource.yml; address.object.validation.yml is the analog of checkout.validation.yml. The object vs resource distinction marks the artifact kind, not a different naming scheme.
Full path patterns:
resources/api/<apiType>/objects/<dashed-name>.object.yml
resources/api/<apiType>/objects/<dashed-name>.object.validation.yml # optional validation
Example paths:
src/Pyz/resources/api/storefront/objects/address.object.ymlsrc/Pyz/resources/api/storefront/objects/address.object.validation.yml
Central directory
A project may also keep canonical object files in one central, configured location instead of (or in addition to) the per-module objects/ directories. Both locations are scanned and supported simultaneously.
Enable it via the Symfony bundle config node spryker_api_platform.canonical_object_search_directories, keyed by API type. Relative paths are resolved against the project root; %kernel.project_dir% is also supported:
# config/packages/spryker_api_platform.yaml spryker_api_platform: canonical_object_search_directories: storefront: - '%kernel.project_dir%/config/api/objects/storefront'
config/api/objects/<apiType>/<dashed-name>.object.yml
config/api/objects/<apiType>/<dashed-name>.object.validation.yml # optional validation
The same *.object.yml / *.object.validation.yml filename rules apply. The core default is an empty list, so without this configuration behavior is identical to scanning module locations only. Files in a central directory are always treated as the project layer (their path carries no /Pyz/ segment for path-based detection), so they participate in the standard project > feature > core precedence.
Defining the same objectName more than once within the same layer (for example one module file and one central-directory file, both project) is a fail-loud error: generation aborts with an ApiSchemaGenerationException naming both source files. The same name across different layers is fine — that is the normal override.
File format
# address.object.yml object: name: Address # CamelCase; matches objectName: Address in resource YAMLs properties: salutation: { type: string, description: 'Address salutation.' } firstName: { type: string, description: 'First name.' } zipCode: { type: string, description: 'ZIP / postal code.' }
| Key | Type | Required | Notes |
|---|---|---|---|
object.name |
string | yes | CamelCase; must match the objectName: join tag in resource YAMLs |
object.properties |
map | yes | Field definitions — same syntax as resource properties |
object.extends |
string | no | CamelCase name of another canonical object; its resolved fields are inherited first |
object.omit |
string[] | no | Field names to drop from the extends base before applying own properties |
Composition (extends / omit)
# address-snapshot.object.yml object: name: AddressSnapshot extends: Address # inherits all Address fields omit: [id, idCompanyBusinessUnitAddress] # drops write-only fields properties: country: { type: string, description: 'Country name.' } # adds read-only field
Resolution order: base extends fields → omit removals → own properties (own wins). Cycles throw ApiSchemaGenerationException.
objectName join tag
Every resource property that declares objectName: Address is the join key for this feature:
# checkout.resource.yml (core — ships dormant tags) properties: billingAddress: type: object objectName: Address # dormant when no address.object.yml exists; activates when the file is present readable: false writable: true properties: zipCode: { type: string }
When a canonical file for Address exists:
- The property's inner
propertiesare replaced with the canonical shape. - The mount attributes (
readable,writable,required,nullable) stay on the reference site — they are not owned by the canonical. - One shared
Generated\Api\<ApiType>\Addressclass is emitted; no per-resource companion class is generated for that property.
When no canonical file exists, the inline properties block is used exactly as today (no change).
Validation
Field-level validation is authored in the parallel *.object.validation.yml using the same format as resource validation files. On a canonicalized property the reference site's own Collection constraint is superseded by an Assert\Valid cascade to the canonical class, which carries the field-level constraints.
Layer precedence
Layer detection uses the same path rules as resource files: /Pyz/ → project, /SprykerFeature/ → feature, else core. Merge precedence: project > feature > core — so a project can add one field to a feature-layer canonical without redefining the whole object.
Core ships no *.object.yml files. The feature is available for project, feature, and core layers; today only projects use it.
Generated output
One shared class per canonical object is emitted to Generated\Api\<ApiType>\<ObjectName> (e.g. Generated\Api\Storefront\Address). All resource classes that reference the canonical use this single shared class.
Schema and API class discovery
Schema files and Glue API classes are discovered by ApiDirectoryLocator at conventional, fixed-depth locations inside the configured source_directories — the filesystem is not scanned recursively.
Discovered layouts for resource schemas (resources/api/{apiType}):
{sourceDirectory}/resources/api/{apiType} # source directory is a module root
{sourceDirectory}/{Module}/resources/api/{apiType} # conventional module layout
{sourceDirectory}/{Org}/{Module}/resources/api/{apiType} # organization nesting (e.g. vendor)
{sourceDirectory}/Glue/{Module}/resources/api/{apiType} # project-level layout (src/Pyz)
Discovered layouts for API classes (Glue/{Module}/Api/{ApiType}):
{sourceDirectory}/Glue/{Module}/Api/{ApiType} # project-level layout (src/Pyz)
{sourceDirectory}/src/{Org}/Glue/{Module}/Api/{ApiType} # module source root
{sourceDirectory}/{Module}/src/{Org}/Glue/{Module}/Api/{ApiType} # module checkout / vendor package
The following invariants are enforced by the locator and pinned by unit tests (ApiDirectoryLocatorTest, SchemaFileDiscoveryTest, SprykerApiPlatformBundleTest) — keep them in mind when changing discovery:
- Case-sensitive matching on every filesystem. Literal path segments (
resources,api, the API type,Glue,Api) must match with the exact requested casing. A miscased directory that would be found on a case-insensitive development machine but not on case-sensitive CI/production hosts is rejected everywhere. - Symlinked path segments are not followed. Directories reached through a symlink below a source directory are not discovered (matching Symfony Finder's default used previously).
- Fixed depth. Directories nested deeper than the layouts above are not discovered. Point
source_directoriesone level deeper instead of relying on recursive lookup. - Memoized per instance. All compiler passes share one discovery instance per container build; repeated lookups with identical input return the first result. Code that creates schema directories mid-process must not expect a re-lookup on the same instance to see them.
Serializer decorators
CXmlEncoder and CXmlNormalizer decorate serializer-aware services (serializer.encoder.xml, api_platform.serializer.normalizer.item) and therefore take their place in the serializer chain. Two contracts apply to these (and any future) serializer decorators; both were the source of production bugs and are pinned by CXmlSerializerAwarenessTest and CXmlNormalizerDelegationTest:
- The decorator must implement
SerializerAwareInterfaceand forwardsetSerializer()to the decorated service — the serializer only injects itself into the objects registered in the chain, so a decorator that keeps it to itself leaves the decorated service without a serializer (everyapplication/xmlrequest then fails with HTTP 500). - The decorator must support everything the decorated service supports (
supportsNormalization(),supportsDenormalization(),getSupportedTypes()delegate to the decorated service). Narrowing support to one format removes the decorated normalizer from the chain for all other formats —text/csvresponses then bypass API Platform property metadata and exposereadable: falseproperties.
Documentation
The authoritative documentation lives in spryker-docs. Start here:
- API Platform overview — concepts, architecture, and the resource generation workflow.
- Resource schemas —
*.resource.ymlreference. - Validation schemas —
*.validation.ymlreference. - Relationships — declaring includes between resources.
- CodeBucket support — region-specific resource variants.
- Testing — writing tests for API Platform resources.
- IDE integration — PHPStorm and VSCode setup for YAML autocomplete.
- Integration guide — installing and configuring API Platform in a project.
- Migration from Glue REST — moving legacy endpoints to API Platform.
- Troubleshooting — common issues and solutions.