Configure API Platform resources with operations, pagination, and output DTOs for clean, versioned REST/GraphQL APIs
/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.
composer require api-platform/core
<?php
// src/Entity/Product.php
namespace App\Entity;
use ApiPlatform\Metadata\ApiResource;
use ApiPlatform\Metadata\Get;
use ApiPlatform\Metadata\GetCollection;
use ApiPlatform\Metadata\Post;
use ApiPlatform\Metadata\Put;
use ApiPlatform\Metadata\Patch;
use ApiPlatform\Metadata\Delete;
use Doctrine\ORM\Mapping as ORM;
use Symfony\Component\Validator\Constraints as Assert;
#[ORM\Entity]
#[ApiResource(
operations: [
new GetCollection(),
new Post(),
new Get(),
new Put(),
new Patch(),
new Delete(),
],
paginationItemsPerPage: 20,
)]
class Product
{
#[ORM\Id]
#[ORM\GeneratedValue]
#[ORM\Column]
private ?int $id = null;
#[ORM\Column(length: 255)]
#[Assert\NotBlank]
#[Assert\Length(min: 3, max: 255)]
private string $name;
#[ORM\Column(type: 'text', nullable: true)]
private ?string $description = null;
#[ORM\Column]
#[Assert\Positive]
private int $price; // In cents
#[ORM\Column]
private \DateTimeImmutable $createdAt;
public function __construct()
{
$this->createdAt = new \DateTimeImmutable();
}
// Getters and setters...
}
#[ApiResource(
operations: [
new GetCollection(
uriTemplate: '/products',
paginationEnabled: true,
paginationItemsPerPage: 30,
),
new Post(
uriTemplate: '/products',
security: "is_granted('ROLE_ADMIN')",
validationContext: ['groups' => ['create']],
),
new Get(
uriTemplate: '/products/{id}',
),
new Put(
uriTemplate: '/products/{id}',
security: "is_granted('ROLE_ADMIN')",
),
new Patch(
uriTemplate: '/products/{id}',
security: "is_granted('ROLE_ADMIN')",
),
new Delete(
uriTemplate: '/products/{id}',
security: "is_granted('ROLE_ADMIN')",
),
],
)]
class Product { /* ... */ }
use ApiPlatform\Metadata\Post;
use App\Controller\PublishProductController;
#[ApiResource(
operations: [
// Standard CRUD...
new Post(
uriTemplate: '/products/{id}/publish',
controller: PublishProductController::class,
name: 'publish_product',
openapiContext: [
'summary' => 'Publish a product',
'description' => 'Makes the product visible to customers',
],
),
],
)]
class Product { /* ... */ }
<?php
// src/Controller/PublishProductController.php
namespace App\Controller;
use App\Entity\Product;
use Doctrine\ORM\EntityManagerInterface;
use Symfony\Bundle\FrameworkBundle\Controller\AbstractController;
use Symfony\Component\HttpKernel\Attribute\AsController;
#[AsController]
class PublishProductController extends AbstractController
{
public function __construct(
private EntityManagerInterface $em,
) {}
public function __invoke(Product $product): Product
{
$product->setPublished(true);
$product->setPublishedAt(new \DateTimeImmutable());
$this->em->flush();
return $product;
}
}
#[ApiResource(
paginationEnabled: true,
paginationItemsPerPage: 20,
paginationMaximumItemsPerPage: 100,
paginationClientItemsPerPage: true, // Allow client to set itemsPerPage
)]
class Product { /* ... */ }
GET /api/products?page=2
GET /api/products?itemsPerPage=50
Response includes pagination metadata:
{
"@context": "/api/contexts/Product",
"@id": "/api/products",
"@type": "hydra:Collection",
"hydra:totalItems": 150,
"hydra:member": [...],
"hydra:view": {
"@id": "/api/products?page=2",
"hydra:first": "/api/products?page=1",
"hydra:last": "/api/products?page=8",
"hydra:previous": "/api/products?page=1",
"hydra:next": "/api/products?page=3"
}
}
#[ApiResource(
paginationEnabled: true,
paginationPartial: true,
paginationViaCursor: [
['field' => 'createdAt', 'direction' => 'DESC'],
['field' => 'id', 'direction' => 'DESC'],
],
)]
class Product { /* ... */ }
Separate your API representation from your entities:
<?php
// src/Dto/ProductOutput.php
namespace App\Dto;
final class ProductOutput
{
public function __construct(
public readonly int $id,
public readonly string $name,
public readonly ?string $description,
public readonly string $formattedPrice, // "€19.99"
public readonly string $createdAt,
) {}
}
<?php
// src/State/ProductOutputProvider.php
namespace App\State;
use ApiPlatform\Metadata\Operation;
use ApiPlatform\State\ProviderInterface;
use App\Dto\ProductOutput;
use App\Entity\Product;
class ProductOutputProvider implements ProviderInterface
{
public function __construct(
private ProviderInterface $decorated,
) {}
public function provide(Operation $operation, array $uriVariables = [], array $context = []): object|array|null
{
$product = $this->decorated->provide($operation, $uriVariables, $context);
if ($product instanceof Product) {
return $this->transform($product);
}
// Collection
return array_map(fn($p) => $this->transform($p), $product);
}
private function transform(Product $product): ProductOutput
{
return new ProductOutput(
id: $product->getId(),
name: $product->getName(),
description: $product->getDescription(),
formattedPrice: sprintf('€%.2f', $product->getPrice() / 100),
createdAt: $product->getCreatedAt()->format('c'),
);
}
}
#[ApiResource(
operations: [
new Get(
output: ProductOutput::class,
provider: ProductOutputProvider::class,
),
new GetCollection(
output: ProductOutput::class,
provider: ProductOutputProvider::class,
),
],
)]
class Product { /* ... */ }
<?php
// src/Dto/CreateProductInput.php
namespace App\Dto;
use Symfony\Component\Validator\Constraints as Assert;
final class CreateProductInput
{
#[Assert\NotBlank]
#[Assert\Length(min: 3, max: 255)]
public string $name;
public ?string $description = null;
#[Assert\Positive]
public int $priceInCents;
}
<?php
// src/State/CreateProductProcessor.php
namespace App\State;
use ApiPlatform\Metadata\Operation;
use ApiPlatform\State\ProcessorInterface;
use App\Dto\CreateProductInput;
use App\Entity\Product;
use Doctrine\ORM\EntityManagerInterface;
class CreateProductProcessor implements ProcessorInterface
{
public function __construct(
private EntityManagerInterface $em,
) {}
public function process(mixed $data, Operation $operation, array $uriVariables = [], array $context = []): Product
{
/** @var CreateProductInput $data */
$product = new Product();
$product->setName($data->name);
$product->setDescription($data->description);
$product->setPrice($data->priceInCents);
$this->em->persist($product);
$this->em->flush();
return $product;
}
}
#[ApiResource(
operations: [
new Post(
input: CreateProductInput::class,
processor: CreateProductProcessor::class,
),
],
)]
class Product { /* ... */ }
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.