Handle database transactions with Doctrine UnitOfWork; implement optimistic locking, flush strategies, and transaction boundaries
/plugin marketplace add MakFly/superpowers-symfony/plugin install makfly-superpowers-symfony@MakFly/superpowers-symfonyThis skill inherits all available tools. When active, it can use any tool Claude has access to.
By default, Doctrine wraps each flush() in a transaction:
$user = new User();
$user->setEmail('test@example.com');
$em->persist($user);
$em->flush(); // Auto-commits in transaction
For multiple operations that must succeed or fail together:
<?php
// src/Service/OrderService.php
class OrderService
{
public function __construct(
private EntityManagerInterface $em,
) {}
public function createOrderWithPayment(User $user, array $items): Order
{
$this->em->beginTransaction();
try {
// Create order
$order = new Order();
$order->setCustomer($user);
$order->setStatus(OrderStatus::PENDING);
foreach ($items as $item) {
$orderItem = new OrderItem();
$orderItem->setProduct($item['product']);
$orderItem->setQuantity($item['quantity']);
$order->addItem($orderItem);
}
$this->em->persist($order);
// Create payment
$payment = new Payment();
$payment->setOrder($order);
$payment->setAmount($order->getTotal());
$this->em->persist($payment);
$this->em->flush();
$this->em->commit();
return $order;
} catch (\Exception $e) {
$this->em->rollback();
throw $e;
}
}
}
Cleaner approach:
public function createOrder(User $user, array $items): Order
{
return $this->em->wrapInTransaction(function () use ($user, $items) {
$order = new Order();
$order->setCustomer($user);
foreach ($items as $item) {
$order->addItem(new OrderItem($item));
}
$this->em->persist($order);
return $order;
});
}
// Good: Single flush for all changes
$user = new User();
$user->setEmail('test@example.com');
$em->persist($user);
$profile = new Profile();
$profile->setUser($user);
$em->persist($profile);
$em->flush(); // One transaction, one commit
// Bad: Multiple flushes = multiple transactions
$user = new User();
$em->persist($user);
$em->flush(); // Transaction 1
$profile = new Profile();
$profile->setUser($user);
$em->persist($profile);
$em->flush(); // Transaction 2 - not atomic!
// Service layer flushes
class UserService
{
public function register(string $email): User
{
$user = new User();
$user->setEmail($email);
$this->em->persist($user);
$this->em->flush(); // Service controls transaction boundary
return $user;
}
}
// Controller doesn't flush
class UserController
{
#[Route('/register', methods: ['POST'])]
public function register(Request $request, UserService $service): Response
{
$user = $service->register($request->get('email'));
return new Response('Created', 201);
}
}
Prevent concurrent modification conflicts:
<?php
// src/Entity/Article.php
#[ORM\Entity]
class Article
{
#[ORM\Version]
#[ORM\Column(type: 'integer')]
private int $version = 1;
public function getVersion(): int
{
return $this->version;
}
}
Usage:
use Doctrine\ORM\OptimisticLockException;
public function updateArticle(int $id, string $content, int $expectedVersion): void
{
$article = $this->em->find(Article::class, $id);
// Lock with expected version
$this->em->lock($article, LockMode::OPTIMISTIC, $expectedVersion);
$article->setContent($content);
try {
$this->em->flush();
} catch (OptimisticLockException $e) {
// Version mismatch - someone else modified it
throw new ConflictException('Article was modified by another user');
}
}
Lock rows in database:
use Doctrine\DBAL\LockMode;
public function processPayment(int $orderId): void
{
$this->em->beginTransaction();
try {
// Lock the row for update
$order = $this->em->find(
Order::class,
$orderId,
LockMode::PESSIMISTIC_WRITE
);
if ($order->getStatus() !== OrderStatus::PENDING) {
throw new \Exception('Order already processed');
}
$order->setStatus(OrderStatus::PROCESSING);
$this->em->flush();
$this->em->commit();
} catch (\Exception $e) {
$this->em->rollback();
throw $e;
}
}
Lock modes:
PESSIMISTIC_READ: Shared lock (SELECT ... FOR SHARE)PESSIMISTIC_WRITE: Exclusive lock (SELECT ... FOR UPDATE)use Doctrine\DBAL\Exception\ConnectionLost;
try {
$this->em->flush();
} catch (ConnectionLost $e) {
// Reconnect and retry
$this->em->getConnection()->connect();
$this->em->flush();
}
use Doctrine\DBAL\Exception\UniqueConstraintViolationException;
try {
$user = new User();
$user->setEmail($email);
$this->em->persist($user);
$this->em->flush();
} catch (UniqueConstraintViolationException $e) {
throw new DuplicateEmailException('Email already exists');
}
After a rollback, the EntityManager may be in an inconsistent state:
try {
$this->em->flush();
} catch (\Exception $e) {
$this->em->rollback();
// Clear the EntityManager
$this->em->clear();
// Re-fetch entities if needed
$user = $this->em->find(User::class, $userId);
}
// Clear all managed entities
$this->em->clear();
// Clear specific entity type
$this->em->clear(User::class);
// Good pattern
class OrderService
{
public function createOrder(CreateOrderDTO $dto): Order
{
return $this->em->wrapInTransaction(function () use ($dto) {
$order = new Order();
// ... build order
$this->em->persist($order);
return $order;
});
}
}
Applies Anthropic's official brand colors and typography to any sort of artifact that may benefit from having Anthropic's look-and-feel. Use it when brand colors or style guidelines, visual formatting, or company design standards apply.
Creating algorithmic art using p5.js with seeded randomness and interactive parameter exploration. Use this when users request creating art using code, generative art, algorithmic art, flow fields, or particle systems. Create original algorithmic art rather than copying existing artists' work to avoid copyright violations.
Create beautiful visual art in .png and .pdf documents using design philosophy. You should use this skill when the user asks to create a poster, piece of art, design, or other static piece. Create original visual designs, never copying existing artists' work to avoid copyright violations.