sylius-labs/association-hydrator
Doctrine ORM association hydrator for Sylius apps, inspired by Ocramius’ hydration optimization technique. Helps efficiently load and hydrate entity associations to reduce queries and improve performance in read-heavy workflows.
Installation:
composer require sylius-labs/association-hydrator
Add to your config/packages/doctrine.yaml:
doctrine:
orm:
dbal:
types:
association_hydrator: SyliusLabs\AssociationHydrator\DBAL\Types\AssociationHydratorType
First Use Case: Apply the hydrator to a one-to-many or many-to-many association in your entity:
use SyliusLabs\AssociationHydrator\Annotation as Hydrator;
#[Hydrator\HydrateAssociations]
#[ORM\OneToMany(targetEntity: Product::class, mappedBy: "category")]
private Collection $products;
$entityManager->getConnection()->getDatabasePlatform()->registerDoctrineTypeMapping('association_hydrator', 'json');
$entityManager->getConnection()->getDatabasePlatform()->registerDoctrineTypeMapping('association_hydrator', 'array');
Lazy vs. Eager Hydration:
@HydrateAssociations) for automatic hydration on find()/findAll().$hydrator = new AssociationHydrator($entityManager);
$hydrator->hydrate($entity, ['products']);
Partial Hydration:
// Hydrate only specific associations
$hydrator->hydrate($entity, ['products', 'tags']);
Custom Serialization: Override default JSON/array serialization via a custom type:
#[Hydrator\HydrateAssociations(type: "custom_type")]
#[ORM\ManyToMany(targetEntity: Tag::class)]
private Collection $tags;
Register the type in doctrine.yaml:
types:
custom_type: App\Doctrine\DBAL\Types\CustomHydratorType
QueryBuilder Integration:
$qb->select('e', 'HYDRATE(e.products) AS products_hydrated');
HydrateAssociations with DataTransformer to avoid N+1 queries in form submissions.@ApiResource to hydrate associations in serializers:
#[ApiResource(collectionOperations: ["get"], itemOperations: ["get"])]
class CategoryEntity { ... }
SoftDeleteable).Circular References:
Category ↔ Product) may cause infinite loops.maxDepth:
$hydrator->hydrate($entity, ['products'], 1); // Depth limit
Database Schema Mismatch:
JSON in PostgreSQL, JSON in MySQL 5.7+).InvalidArgumentException if the column type is unsupported.Caching Conflicts:
entityManager->clear() cautiously after bulk hydration.Transaction Boundaries:
Enable SQL Logging:
# config/packages/dev/doctrine.yaml
doctrine:
dbal:
logging: true
profiling: true
Look for HYDRATE() in generated SQL.
Check Hydration Status:
if (!$entity->products->isInitialized()) {
$hydrator->hydrate($entity, ['products']);
}
Custom Hydrator:
Implement HydratorInterface for non-standard logic:
class CustomHydrator implements HydratorInterface {
public function hydrate(EntityManagerInterface $em, object $entity, string $association, array $options): void {
// Custom logic (e.g., API-specific filtering)
}
}
Event Subscribers:
Listen to preHydrate/postHydrate events:
$eventDispatcher->addListener(
AssociationHydratorEvents::PRE_HYDRATE,
fn(PreHydrateEvent $event) => $event->setOptions(['filter' => 'active'])
);
Batch Hydration: For large datasets, use batch processing:
$hydrator->hydrateBatch($entities, ['products'], 100);
products_json) to avoid repeated hydration.$cache = new RedisCache();
$hydrator->setCache($cache);
How can I help you explore Laravel packages today?