Cascade operations automatically propagate persist and delete operations from parent entities to their related children, reducing boilerplate code and ensuring related entities stay synchronized.
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();
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
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
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();
Use cascade persist when:
Use cascade remove when:
Don't use cascade when:
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.
ObjectQuel cascade operations happen in your application code, not at the database level. This means:
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.
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.
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();
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();