Refactor fat controllers into lean controllers by extracting business logic to services, handlers, and dedicated classes
/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.
// BAD: Fat controller with too much logic
#[Route('/orders', methods: ['POST'])]
public function create(Request $request): Response
{
$data = json_decode($request->getContent(), true);
// Validation logic in controller
if (empty($data['items'])) {
return new JsonResponse(['error' => 'Items required'], 400);
}
// Business logic in controller
$order = new Order();
$order->setCustomer($this->getUser());
$order->setStatus('pending');
$total = 0;
foreach ($data['items'] as $itemData) {
$product = $this->em->find(Product::class, $itemData['productId']);
if (!$product) {
return new JsonResponse(['error' => 'Product not found'], 400);
}
if ($product->getStock() < $itemData['quantity']) {
return new JsonResponse(['error' => 'Insufficient stock'], 400);
}
$item = new OrderItem();
$item->setProduct($product);
$item->setQuantity($itemData['quantity']);
$item->setPrice($product->getPrice());
$order->addItem($item);
$total += $product->getPrice() * $itemData['quantity'];
$product->setStock($product->getStock() - $itemData['quantity']);
}
$order->setTotal($total);
// Coupon logic
if (!empty($data['coupon'])) {
$coupon = $this->em->getRepository(Coupon::class)
->findOneBy(['code' => $data['coupon']]);
if ($coupon && $coupon->isValid()) {
$discount = $total * ($coupon->getDiscount() / 100);
$order->setDiscount($discount);
$order->setTotal($total - $discount);
}
}
$this->em->persist($order);
$this->em->flush();
// Send email
$email = (new Email())
->to($this->getUser()->getEmail())
->subject('Order Confirmation')
->text('Your order has been placed.');
$this->mailer->send($email);
return new JsonResponse(['id' => $order->getId()], 201);
}
<?php
// src/Service/OrderService.php
namespace App\Service;
use App\Dto\CreateOrderRequest;
use App\Entity\Order;
use App\Entity\User;
class OrderService
{
public function __construct(
private ProductService $products,
private CouponService $coupons,
private EntityManagerInterface $em,
private OrderNotificationService $notifications,
) {}
public function createOrder(User $user, CreateOrderRequest $request): Order
{
// Validate and reserve products
$items = $this->products->reserveItems($request->items);
// Create order
$order = Order::create($user, $items);
// Apply coupon if provided
if ($request->couponCode) {
$discount = $this->coupons->apply($request->couponCode, $order);
$order->applyDiscount($discount);
}
$this->em->persist($order);
$this->em->flush();
// Async notification
$this->notifications->orderCreated($order);
return $order;
}
}
<?php
// src/Dto/CreateOrderRequest.php
namespace App\Dto;
use Symfony\Component\Validator\Constraints as Assert;
final readonly class CreateOrderRequest
{
public function __construct(
#[Assert\NotBlank]
#[Assert\Count(min: 1)]
#[Assert\Valid]
public array $items,
public ?string $couponCode = null,
) {}
}
<?php
// src/Controller/Api/OrderController.php
namespace App\Controller\Api;
use App\Dto\CreateOrderRequest;
use App\Service\OrderService;
use Symfony\Bundle\FrameworkBundle\Controller\AbstractController;
use Symfony\Component\HttpFoundation\JsonResponse;
use Symfony\Component\HttpKernel\Attribute\MapRequestPayload;
use Symfony\Component\Routing\Attribute\Route;
#[Route('/api/orders')]
class OrderController extends AbstractController
{
public function __construct(
private OrderService $orderService,
) {}
#[Route('', methods: ['POST'])]
public function create(
#[MapRequestPayload] CreateOrderRequest $request
): JsonResponse {
$order = $this->orderService->createOrder(
$this->getUser(),
$request
);
return new JsonResponse(['id' => $order->getId()], 201);
}
}
#[Route('/posts/{id}', methods: ['PUT'])]
public function update(
Post $post,
#[MapRequestPayload] UpdatePostRequest $request
): JsonResponse {
$this->denyAccessUnlessGranted('EDIT', $post);
$post = $this->postService->update($post, $request);
return new JsonResponse(PostOutput::fromEntity($post));
}
use Symfony\Component\Security\Http\Attribute\IsGranted;
#[Route('/admin/users')]
#[IsGranted('ROLE_ADMIN')]
class AdminUserController extends AbstractController
{
#[Route('', methods: ['GET'])]
public function list(): Response
{
// Already authorized by class attribute
}
}
#[Route('/contact', methods: ['POST'])]
public function contact(
#[MapRequestPayload] ContactRequest $request
): JsonResponse {
// $request is already validated
$this->contactService->send($request);
return new JsonResponse(['status' => 'sent']);
}
// Symfony automatically converts {id} to Post entity
#[Route('/posts/{id}', methods: ['GET'])]
public function show(Post $post): Response
{
// 404 handled automatically if not found
return $this->render('post/show.html.twig', ['post' => $post]);
}
// DTO handles validation rules
final readonly class CreateUserRequest
{
#[Assert\NotBlank]
#[Assert\Email]
public string $email;
#[Assert\NotBlank]
#[Assert\Length(min: 8)]
public string $password;
}
// Service handles business rules
class UserService
{
public function register(CreateUserRequest $request): User
{
$this->ensureEmailUnique($request->email);
$user = User::register($request->email, $request->password);
$this->em->persist($user);
$this->em->flush();
return $user;
}
}
// Voter handles access control
class PostVoter extends Voter
{
protected function voteOnAttribute(string $attribute, mixed $subject, TokenInterface $token): bool
{
return match ($attribute) {
'EDIT' => $subject->getAuthor() === $token->getUser(),
default => false,
};
}
}
// Async via Messenger
class OrderService
{
public function create(CreateOrderRequest $request): Order
{
// ... create order
$this->bus->dispatch(new SendOrderConfirmation($order->getId()));
return $order;
}
}
class OrderControllerTest extends WebTestCase
{
public function testCreateOrder(): void
{
$user = UserFactory::createOne();
ProductFactory::createMany(3);
$this->client->loginUser($user->object());
$this->client->request('POST', '/api/orders', [], [], [
'CONTENT_TYPE' => 'application/json',
], json_encode([
'items' => [
['productId' => 1, 'quantity' => 2],
],
]));
$this->assertResponseStatusCodeSame(201);
}
}
new Entity() in controllerApplies 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.