CodeBucket Support in API Platform
Edit on GitHubThis document explains how to create and use CodeBucket-specific API resources in API Platform.
Overview
CodeBucket support enables API Platform to serve different resource variants based on the runtime APPLICATION_CODE_BUCKET environment constant. This allows you to maintain Code Bucket-specific API resources without requiring separate container compilations for each Code Bucket.
Use cases
Use CodeBucket resources when you need:
- Code Bucket-specific properties: EU-specific GDPR fields, tax rates, compliance data
- Code Bucket-specific validation: Different validation rules per Code Bucket or country
- Localized business logic: Country-specific processing requirements
Key benefits
- Single container: Compile once, serve hundreds of CodeBuckets
- Runtime resolution: Automatic selection of correct resource variant
- Graceful fallback: Base resources used when CodeBucket variant doesn’t exist
- No duplication: Share common logic while extending for specific needs
How CodeBucket resolution works
Architecture overview
API Platform uses a decorator pattern to intercept resource resolution and select the appropriate CodeBucket variant at runtime:
>Request → Symfony Routing → API Platform
↓
CodeBucket Decorators
↓
┌──────────────────┴──────────────────┐
│ │
Resource Class Resolver Resource Name Collection Factory
(Runtime resolution) (Compile-time filtering)
│ │
└──────────────────┬──────────────────┘
↓
Selected Resource Variant
(Base or CodeBucket-specific)
Runtime resolution flow
>1. Request: GET glue-backend.eu.spryker.local/stores
APPLICATION_CODE_BUCKET = 'EU'
2. CodeBucketResourceNameCollectionFactory
→ Filters resource collection at compile time
→ Includes only EU variants or base resources (no conflicts)
3. CodeBucketResourceClassResolver
→ Reads APPLICATION_CODE_BUCKET = 'EU'
→ Base class: StoresBackendResource
→ Builds variant name: StoresEUBackendResource
→ Checks: class_exists('Generated\Api\Backend\StoresEUBackendResource')
→ Validates: StoresEUBackendResource::CODE_BUCKET === 'EU'
→ Returns: StoresEUBackendResource
4. API Platform executes with EU-specific resource
→ Properties: base + EU-specific fields
→ Validations: base + EU-specific rules
CodeBucket detection mechanism
The system identifies CodeBucket resources using three checks:
- Class existence:
class_exists($codeBucketClassName) - Constant check:
defined("$className::CODE_BUCKET") - Value match:
constant("$className::CODE_BUCKET") === $currentCodeBucket
Generated CODE_BUCKET constant
CodeBucket resources automatically get a CODE_BUCKET constant during generation:
// StoresEUBackendResource.php (EU variant)
final class StoresEUBackendResource
{
public const string CODE_BUCKET = 'EU';
// Properties...
}
// StoresBackendResource.php (base - no constant)
final class StoresBackendResource
{
// Properties... (no CODE_BUCKET constant)
}
Resource naming conventions
File naming pattern
Resource schemas follow the pattern: {resource-name}.resource.yml
Validation schemas follow the pattern: {resource-name}.validation.yml
CodeBuckets are specified inside the schema files, not in the filename.
>src/Pyz/Glue/Store/resources/api/backend/
├── stores.resource.yml # Resource schema (CodeBucket variants defined inside)
└── stores.validation.yml # Validation schema (CodeBucket variants defined inside)
Generated class naming pattern
The generator creates classes following: {ResourceName}{CodeBucket}{ApiType}Resource
| Schema File | CodeBucket (defined in file) | Generated Class |
|---|---|---|
stores.resource.yml |
None (base) | StoresBackendResource |
stores.resource.yml |
EU | StoresEUBackendResource |
stores.resource.yml |
AT | StoresATBackendResource |
stores.resource.yml (Storefront) |
EU | StoresEUStorefrontResource |
URL consistency
All CodeBucket variants share the same URL path, with the Code Bucket defined in the domain:
- EU:
glue-backend.eu.spryker.local/stores→StoresEUBackendResource - AT:
glue-backend.at.spryker.local/stores→StoresATBackendResource - DE:
glue-backend.de.spryker.local/stores→StoresBackendResource(orStoresDEBackendResourceif variant exists)
The URL path is identical (/stores), but the Code Bucket in the domain determines which resource variant is used. Only the properties, validations, and business logic differ between variants.
Creating CodeBucket resources
Basic example
Step 1: Create base resource
src/Pyz/Glue/Store/resources/api/backend/stores.resource.yml
resource:
name: Stores
shortName: Store
description: "Store resource"
provider: "Pyz\\Glue\\Store\\Api\\Backend\\Provider\\StoreBackendProvider"
processor: "Pyz\\Glue\\Store\\Api\\Backend\\Processor\\StoreBackendProcessor"
operations:
- type: Get
- type: GetCollection
properties:
idStore:
type: integer
writable: false
identifier: true
name:
type: string
description: "Store name"
timezone:
type: string
description: "Store timezone"
Step 2: Define CodeBucket variant
Within the same stores.resource.yml file, define the EU-specific variant by specifying the CodeBucket:
# EU-specific store resource (defined in same file)
resource:
name: Stores
shortName: Store
description: "EU-specific store resource"
codeBucket: EU
# Same provider and processor as base
provider: "Pyz\\Glue\\Store\\Api\\Backend\\Provider\\StoreBackendProvider"
processor: "Pyz\\Glue\\Store\\Api\\Backend\\Processor\\StoreBackendProcessor"
operations:
- type: Get
- type: GetCollection
properties:
idStore:
type: integer
writable: false
identifier: true
name:
type: string
description: "Store name"
timezone:
type: string
description: "Store timezone"
# EU-specific properties
taxRate:
type: number
description: "EU tax rate"
openapiContext:
example: 19.0
gdprContactEmail:
type: string
description: "GDPR contact email"
openapiContext:
example: "[email protected]"
vatRegistrationNumber:
type: string
description: "VAT registration number"
Step 3: Create validation schemas
Within src/Pyz/Glue/Store/resources/api/backend/stores.validation.yml, define CodeBucket-specific validation:
post:
taxRate:
- NotBlank
- Range:
min: 0
max: 100
gdprContactEmail:
- NotBlank
- Email
vatRegistrationNumber:
- NotBlank
- Regex:
pattern: '/^[A-Z]{2}[0-9]{8,12}$/'
message: "Invalid VAT format"
Step 4: Generate resources
docker/sdk cli GLUE_APPLICATION=GLUE_BACKEND glue api:generate backend
This generates:
src/Generated/Api/Backend/StoresBackendResource.php(base)src/Generated/Api/Backend/StoresEUBackendResource.php(withCODE_BUCKET = 'EU')
Step 5: Implement Provider with CodeBucket awareness
src/Pyz/Glue/Store/Api/Backend/Provider/StoreBackendProvider.php
<?php
namespace Pyz\Glue\Store\Api\Backend\Provider;
use ApiPlatform\Metadata\Operation;
use ApiPlatform\State\ProviderInterface;
use Pyz\Glue\Store\Business\StoreFacadeInterface;
class StoreBackendProvider implements ProviderInterface
{
public function __construct(
protected StoreFacadeInterface $storeFacade,
) {
}
public function provide(Operation $operation, array $uriVariables = [], array $context = []): object|array|null
{
if (isset($uriVariables['idStore'])) {
return $this->getStore((int) $uriVariables['idStore'], $operation);
}
return $this->getStores($operation);
}
protected function getStore(int $idStore, Operation $operation): ?object
{
$storeTransfer = $this->storeFacade->findStoreById($idStore);
if ($storeTransfer === null) {
return null;
}
$resourceClass = $operation->getClass();
$resource = new $resourceClass();
$resource->fromArray($storeTransfer->toArray());
// Add EU-specific data if this is an EU resource
if (defined("$resourceClass::CODE_BUCKET") && $resourceClass::CODE_BUCKET === 'EU') {
$resource->taxRate = $storeTransfer->getTaxRate();
$resource->gdprContactEmail = $storeTransfer->getGdprContactEmail();
$resource->vatRegistrationNumber = $storeTransfer->getVatRegistrationNumber();
}
return $resource;
}
protected function getStores(Operation $operation): array
{
$stores = $this->storeFacade->getAllStores();
$resources = [];
foreach ($stores as $storeTransfer) {
$resourceClass = $operation->getClass();
$resource = new $resourceClass();
$resource->fromArray($storeTransfer->toArray());
if (defined("$resourceClass::CODE_BUCKET") && $resourceClass::CODE_BUCKET === 'EU') {
$resource->taxRate = $storeTransfer->getTaxRate();
$resource->gdprContactEmail = $storeTransfer->getGdprContactEmail();
$resource->vatRegistrationNumber = $storeTransfer->getVatRegistrationNumber();
}
$resources[] = $resource;
}
return $resources;
}
}
Schema layering with CodeBuckets
CodeBucket resources support multi-layer schema merging just like base resources.
Example: Multi-layer CodeBucket resource
Core layer (vendor):
# vendor/spryker/store/resources/api/backend/stores.resource.yml
resource:
name: Stores
codeBucket: EU
properties:
taxRate:
type: number
Project layer:
# src/Pyz/Glue/Store/resources/api/backend/stores.resource.yml
resource:
name: Stores
codeBucket: EU
properties:
taxRate:
required: true # Override core
companyVatId:
type: string # Project-specific
Merged result (StoresEUBackendResource):
resource:
name: Stores
properties:
taxRate:
type: number
required: true # From project
companyVatId:
type: string # From project
Fallback behavior
Graceful degradation
When APPLICATION_CODE_BUCKET is set but no matching variant exists, the system falls back to the base resource:
>Scenario: APPLICATION_CODE_BUCKET = 'AT'
Available resources:
- StoresBackendResource (base)
- StoresEUBackendResource (EU variant)
Result: StoresBackendResource is used (graceful fallback)
No errors, system continues to work
No CodeBucket set
When APPLICATION_CODE_BUCKET is not set:
>Available resources:
- StoresBackendResource (base)
- StoresEUBackendResource (EU variant)
- StoresATBackendResource (AT variant)
Result: StoresBackendResource is used
All CodeBucket variants are excluded from resource collection
Resource collection filtering
CodeBucketResourceNameCollectionFactory
This decorator filters the resource collection at compile time to prevent conflicts between base and CodeBucket variants.
When APPLICATION_CODE_BUCKET is not set:
- Include all base resources (no CODE_BUCKET constant)
- Exclude all CodeBucket variants
When APPLICATION_CODE_BUCKET is set (example: ‘EU’):
- Group resources by base class name
- For each group:
- If EU variant exists → include ONLY the EU variant
- If no EU variant exists → include base resource (fallback)
- Exclude all non-matching CodeBucket variants
Example filtering:
>Available classes:
- StoresBackendResource (base)
- StoresEUBackendResource (EU)
- StoresATBackendResource (AT)
- CustomersBackendResource (base, no variants)
With APPLICATION_CODE_BUCKET = 'EU':
✓ StoresEUBackendResource (EU variant exists)
✗ StoresBackendResource (excluded, EU variant preferred)
✗ StoresATBackendResource (excluded, wrong CodeBucket)
✓ CustomersBackendResource (no variant, base used)
With APPLICATION_CODE_BUCKET = 'DE':
✓ StoresBackendResource (no DE variant, fallback to base)
✗ StoresEUBackendResource (excluded, wrong CodeBucket)
✗ StoresATBackendResource (excluded, wrong CodeBucket)
✓ CustomersBackendResource (no variant, base used)
Benefits of filtering
- OpenAPI Generation: Only the correct variant appears in OpenAPI documentation
- Route Registration: Only the correct variant routes are registered
- No Conflicts: Base and CodeBucket variants never coexist in routing
- Performance: Filtering happens once and is cached
Container compilation
Single container for all CodeBuckets
API Platform compiles a single container that contains ALL resources (base and all CodeBucket variants). Runtime resolution selects the correct variant per request.
At compile time:
- Discover all resource classes in
src/Generated/Api/{ApiType}/ - Build metadata for ALL resources (base + all CodeBucket variants)
- Register routes for ALL resources (all map to same URLs)
- Cache everything
At runtime:
- CodeBucket decorators intercept resolution
- Select correct resource based on
APPLICATION_CODE_BUCKET - Only selected resource is used for that request
- Different CodeBuckets served from same container
Performance characteristics
- One-time cost: Container compilation happens once
- No overhead: Runtime resolution uses local cache per request
- Scalability: Supports hundreds of CodeBuckets without performance impact
Debugging CodeBucket resources
Verify CODE_BUCKET constant generation
After regenerating resources, verify the constant was added:
# Check EU resource has constant
grep "CODE_BUCKET" src/Generated/Api/Backend/StoresEUBackendResource.php
# Expected: public const string CODE_BUCKET = 'EU';
# Verify base resource has no constant
grep "CODE_BUCKET" src/Generated/Api/Backend/StoresBackendResource.php
# Expected: no match (empty output)
Debug resource resolution
Use the debug command to inspect which resources are available:
# List all resources
docker/sdk cli GLUE_APPLICATION=GLUE_BACKEND glue api:debug --list
# Show specific resource
docker/sdk cli GLUE_APPLICATION=GLUE_BACKEND glue api:debug stores --api-type=backend
# Show merged schema
docker/sdk cli GLUE_APPLICATION=GLUE_BACKEND glue api:debug stores --api-type=backend --show-merged
# Show all contributing source files
docker/sdk cli GLUE_APPLICATION=GLUE_BACKEND glue api:debug stores --api-type=backend --show-sources
Verify runtime resolution
Test different CodeBuckets by accessing different domains:
# Test EU CodeBucket
curl -X GET http://glue-backend.eu.spryker.local/stores
# Should include: taxRate, gdprContactEmail, vatRegistrationNumber
# Test AT CodeBucket (if no AT variant exists)
curl -X GET http://glue-backend.at.spryker.local/stores
# Should return base resource (fallback)
# Test DE CodeBucket
curl -X GET http://glue-backend.de.spryker.local/stores
# Should return base resource or DE-specific variant if available
Best practices
1. Keep base resources complete
The base resource should contain all common properties that work across all CodeBuckets:
# ✅ Good - Base is complete
# stores.resource.yml (base variant)
resource:
name: Stores
properties:
idStore:
type: integer
name:
type: string
timezone:
type: string
# stores.resource.yml (EU variant - defined in same file)
resource:
name: Stores
codeBucket: EU
properties:
idStore:
type: integer
name:
type: string
timezone:
type: string
taxRate: # EU-specific addition
type: number
2. Use CodeBuckets for true regional differences
Only create CodeBucket variants when there are genuine regional requirements:
# ✅ Good use cases:
- EU GDPR compliance fields
- Code Bucket-specific tax calculations
- Country-specific validation rules
- Code Bucket-specific business logic
# ❌ Bad use cases:q
- Language translations (use locales instead)
- Minor field variations (use base resource)
- Temporary feature flags (use feature toggles)
3. Share Provider and Processor logic
Use the same Provider and Processor classes for base and CodeBucket variants when possible:
# Both use same implementation
resource:
provider: "Pyz\\Glue\\Store\\Api\\Backend\\Provider\\StoreBackendProvider"
processor: "Pyz\\Glue\\Store\\Api\\Backend\\Processor\\StoreBackendProcessor"
Detect the resource type inside the Provider using the CODE_BUCKET constant:
$resourceClass = $operation->getClass();
if (defined("$resourceClass::CODE_BUCKET")) {
$codeBucket = $resourceClass::CODE_BUCKET;
// Apply CodeBucket-specific logic
}
4. Document CodeBucket-specific fields
Clearly document which fields are CodeBucket-specific in your schema descriptions:
gdprContactEmail:
type: string
description: "GDPR contact email (EU-specific requirement for GDPR compliance)"
5. Test fallback behavior
Always test that your application works correctly when:
- CodeBucket is not set (uses base resource)
- CodeBucket is set but variant doesn’t exist (falls back to base)
- CodeBucket variant exists (uses variant)
Testing
CodeBucket resources can be tested using the standard API Platform testing infrastructure. The test environment automatically generates resources before tests execute.
For detailed testing guidance, see API Platform Testing.
Troubleshooting
Resource not found for CodeBucket
If you get a “Resource not found” error for a specific CodeBucket:
- Verify the schema file naming:
{resource-name}.resource.ymlfor resources,{resource-name}.validation.ymlfor validations - Verify the CodeBucket is defined inside the schema file
- Regenerate resources:
docker/sdk cli GLUE_APPLICATION=GLUE_BACKEND glue api:generate backend - Verify the CODE_BUCKET constant exists in the generated class
- Check that
APPLICATION_CODE_BUCKETmatches the CodeBucket defined in your schema file
Wrong resource variant used
If the wrong variant is being used:
- Verify
APPLICATION_CODE_BUCKETis set correctly in your environment - Clear caches:
docker/sdk cli rm -rf data/cache/* - Check that the resource class name follows the pattern:
{Name}{CodeBucket}{ApiType}Resource - Verify the CODE_BUCKET constant value matches
APPLICATION_CODE_BUCKET
For more troubleshooting guidance, see API Platform Troubleshooting.
Next steps
- Resource Schemas - Deep dive into schema syntax
- Validation Schemas - Define validation rules
- API Platform Enablement - Creating resources
- API Platform Testing - Testing your resources
- Troubleshooting - Common issues
Thank you!
For submitting the form