Cascading
Cascade operations automatically propagate persist and delete operations from parent entities to their related children, reducing boilerplate code and ensuring related entities stay synchronized.
What are Cascade Operations?
Cascade operations allow you to persist or delete related entities automatically when you persist or delete a parent entity:
class OrderEntity {
/**
* @Orm\OneToMany(targetEntity="OrderItemEntity", mappedBy="orderId")
* @Orm\Cascade(operations={"persist", "remove"})
*/
public EntityCollection $items;
}
// Without cascade, you'd need to persist each item individually
$order = new OrderEntity();
$item1 = new OrderItemEntity();
$item2 = new OrderItemEntity();
$entityManager->persist($order);
$entityManager->persist($item1); // Without cascade
$entityManager->persist($item2); // Without cascade
$entityManager->flush();
// With cascade, persisting the order persists all items
$order = new OrderEntity();
$order->items->add($item1);
$order->items->add($item2);
$entityManager->persist($order); // Items are automatically persisted
$entityManager->flush();
Cascade Persist
Automatically persist related entities when the parent is persisted:
class OrderEntity {
/**
* @Orm\OneToMany(targetEntity="OrderItemEntity", mappedBy="orderId")
* @Orm\Cascade(operations={"persist"})
*/
public EntityCollection $items;
public function __construct() {
$this->items = new EntityCollection();
}
}
// Usage
$order = new OrderEntity();
$order->setCustomerId(123);
$item1 = new OrderItemEntity();
$item1->setProductId(1);
$item1->setQuantity(2);
$item2 = new OrderItemEntity();
$item2->setProductId(2);
$item2->setQuantity(1);
$order->items->add($item1);
$order->items->add($item2);
// Only need to persist the order
$entityManager->persist($order);
$entityManager->flush(); // Both items are saved automatically
Cascade Remove
Automatically delete related entities when the parent is deleted:
class OrderEntity {
/**
* @Orm\OneToMany(targetEntity="OrderItemEntity", mappedBy="orderId")
* @Orm\Cascade(operations={"remove"})
*/
public EntityCollection $items;
}
// Usage
$order = $entityManager->find(OrderEntity::class, 123);
// Deleting the order automatically deletes all items
$entityManager->remove($order);
$entityManager->flush(); // All order items are deleted too
Combining Cascade Operations
You can cascade both persist and remove operations:
class OrderEntity {
/**
* @Orm\OneToMany(targetEntity="OrderItemEntity", mappedBy="orderId")
* @Orm\Cascade(operations={"persist", "remove"})
*/
public EntityCollection $items;
}
// Both operations cascade
$order = new OrderEntity();
$order->items->add(new OrderItemEntity());
$entityManager->persist($order); // Item is persisted
$entityManager->flush();
$entityManager->remove($order); // Item is deleted
$entityManager->flush();
When to Use Cascade
Use cascade persist when:
- Child entities only exist as part of the parent (composition)
- Creating the parent always creates children (e.g., Order → OrderItems)
- Children have no meaning without the parent
Use cascade remove when:
- Deleting the parent should delete all children
- Children cannot exist without the parent
- You want to maintain referential integrity at the application level
Don't use cascade when:
- Related entities are independent (e.g., Product → Category)
- Related entities are shared across multiple parents
- You need fine-grained control over persistence
Cascade with ManyToOne
Cascade can also be used on ManyToOne relationships:
class OrderItemEntity {
/**
* @Orm\ManyToOne(targetEntity="OrderEntity")
* @Orm\Cascade(operations={"persist"})
*/
private OrderEntity $order;
}
// Creating an item automatically creates the order
$item = new OrderItemEntity();
$item->setOrder(new OrderEntity());
$entityManager->persist($item); // Order is automatically persisted
$entityManager->flush();
This is less common but useful when the child entity controls the parent's lifecycle.
Important Considerations
Cascade is Application-Level
ObjectQuel cascade operations happen in your application code, not at the database level. This means:
- ObjectQuel loads related entities and explicitly deletes them
- Lifecycle events (@PreDelete, @PostDelete) fire for cascaded deletes
- You have full control and visibility over what's being deleted
- Database foreign key constraints are not required (but can be added)
Performance Impact
Cascade operations can trigger multiple database queries:
// Deleting an order with 100 items triggers:
// 1. SELECT to load the order
// 2. SELECT to load all items
// 3. DELETE for each item (100 queries)
// 4. DELETE for the order
$order = $entityManager->find(OrderEntity::class, 123);
$entityManager->remove($order);
$entityManager->flush();
For large collections, consider batch operations or database-level cascading.
Bidirectional Relationships
Be careful with bidirectional cascade operations to avoid infinite loops:
// DANGEROUS - Cascading both directions
class OrderEntity {
/**
* @Orm\OneToMany(targetEntity="OrderItemEntity", mappedBy="orderId")
* @Orm\Cascade(operations={"persist", "remove"})
*/
public EntityCollection $items;
}
class OrderItemEntity {
/**
* @Orm\ManyToOne(targetEntity="OrderEntity")
* @Orm\Cascade(operations={"remove"}) // Can create circular cascade
*/
private OrderEntity $order;
}
Typically, only cascade from parent to children, not both directions.
Examples
Blog Post with Comments
class PostEntity {
/**
* @Orm\OneToMany(targetEntity="CommentEntity", mappedBy="postId")
* @Orm\Cascade(operations={"remove"})
*/
public EntityCollection $comments;
}
// Deleting a post deletes all comments
$post = $entityManager->find(PostEntity::class, 1);
$entityManager->remove($post);
$entityManager->flush();
Shopping Cart with Items
class CartEntity {
/**
* @Orm\OneToMany(targetEntity="CartItemEntity", mappedBy="cartId")
* @Orm\Cascade(operations={"persist", "remove"})
*/
public EntityCollection $items;
}
// Adding items and saving cart persists everything
$cart = new CartEntity();
$cart->items->add(new CartItemEntity());
$cart->items->add(new CartItemEntity());
$entityManager->persist($cart);
$entityManager->flush();
Best Practices
- Use cascade for composition relationships (parent owns children)
- Avoid cascade for association relationships (independent entities)
- Be cautious with cascade remove - it permanently deletes data
- Document cascade behavior clearly in entity classes
- Test cascade operations thoroughly, especially removes
- Consider performance impact for large collections
- Use only on the owning side or inverse side, not both