Skill api-resource
Crea o modifica recursos de API Platform utilizando DTOs, Object Mappers y state providers o processors.
- Estrellas en GitHub
- 29
- Archivos
- 1
- Último commit
- 22 oct 2026
- Licencia
- —
- Herramientas permitidas
- —
¿Qué hace la skill api-resource?
api-resource es una agent skill. Crea o modifica recursos de API Platform utilizando DTOs, Object Mappers y state providers o processors.
Resumen generado a partir del SKILL.md de la skill.
Sobre la skill api-resource
Los desarrolladores que construyen APIs web pueden usar este SKILL para diseñar, crear o modificar recursos de API Platform. Ayuda a definir la estructura pública de los datos mediante declaraciones de recursos, asegurando que la documentación de Hydra, OpenAPI y GraphQL se mantenga precisa. El SKILL admite varios patrones arquitectónicos, desde configuraciones simples de entidad como recurso para prototipado rápido hasta estrategias de DTO desacoplados para sistemas complejos.
Cuando un agente necesita exponer una entidad a través de HTTP o remodelar un modelo de datos existente, puede implementar DTOs con el Object Mapper. Este proceso utiliza stateOptions y el atributo Map para conectar las representaciones públicas con las entidades de Doctrine o los modelos de Laravel Eloquent subyacentes. El SKILL también gestiona requisitos avanzados como la configuración de subrecursos anidados con uriVariables, la implementación de state providers y processors personalizados para lógica que no sea CRUD, y la gestión de números de alta precisión usando BcMath. Proporciona la estructura necesaria para manejar DTOs de entrada por operación, permitiendo diferentes payloads para la lectura y escritura de datos.
Lo que un agente puede hacer con ella
- 1Exponer una entidad como un recurso de APIUn agente puede convertir rápidamente una entidad de base de datos en un endpoint de API funcional para operaciones CRUD básicas.
- 2Desacoplar la estructura de la API del esquema de la base de datosLos desarrolladores pueden usar DTOs y un Object Mapper para asegurar que el contrato de la API pública no cambie cuando cambie la estructura de la base de datos subyacente.
- 3Implementar transformaciones de datos complejasPuedes crear transformadores personalizados para manejar lógica de mapeo intrincada entre tus modelos de datos y tus recursos de API.
- 4Configurar jerarquías de recursos anidadosUn agente puede configurar estructuras de URL complejas para recursos que pertenecen a una entidad padre, como mensajes dentro de un buzón de correo.
Escrito a partir del SKILL.md de la skill. Cada caso de uso se verifica con una cita del mismo.
Cómo instalar la skill api-resource
Cada comando copia la carpeta de la skill al directorio de skills del cliente. Reinicia el cliente o inicia una nueva sesión para cargarla.Instalar la skill api-resource en Plugin
/plugin marketplace add api-platform/skillset /plugin install api-platform@api-platform-skillset
Instalar la skill api-resource en Claude Code
~/.claude/skills/api-resource/SKILL.md
$ npx degit api-platform/skillset/skills/api-resource ~/.claude/skills/api-resourceInstalar la skill api-resource en Claude Code (project)
.claude/skills/api-resource/SKILL.md
$ npx degit api-platform/skillset/skills/api-resource .claude/skills/api-resourceInstalar la skill api-resource en Codex
~/.agents/skills/api-resource/SKILL.md
$ npx degit api-platform/skillset/skills/api-resource ~/.agents/skills/api-resourceInstalar la skill api-resource en Gemini CLI
~/.gemini/skills/api-resource/SKILL.md
$ npx degit api-platform/skillset/skills/api-resource ~/.gemini/skills/api-resourceInstalar la skill api-resource en Claude.ai
npx degit api-platform/skillset/skills/api-resource api-resource && zip -r api-resource.zip api-resource # claude.ai → Customize → Skills → + → Create skill → Upload a skill → api-resource.zip
SKILL.md
Ver en GitHubCreating API Platform Resources
Design-First Principle
An API Platform resource is built from three concerns (see https://api-platform.com/docs/core/design/):
- Resource declaration — a plain PHP object marked
#[ApiResource]describing the public shape. Single source of truth for Hydra, OpenAPI and GraphQL. - Data retrieval — a state provider hydrates that object (Get, GetCollection).
- Data persistence — a state processor writes it (Post, Put, Patch, Delete).
Design the public shape first; the resource class doesn't have to be a Doctrine entity. How you wire it to persistence is a separate decision:
| Approach | When | Hookup |
|---|---|---|
| Entity as Resource | Prototyping, plain CRUD, internal model == public shape | #[ApiResource] on the entity; built-in Doctrine provider/processor (zero wiring) |
| DTO with Object Mapper | Decoupled public shape over a Doctrine entity/document — whether fields match 1:1 or need renames/transforms | stateOptions: new Options(entityClass:/documentClass:) and #[Map] on the DTO |
| Input DTOs per Operation | Write model differs from read model (stricter create/update payloads) | per-operation input: + processor |
| Custom Provider/Processor | Non-CRUD, external data, complex domain logic, CQRS | hand-written ProviderInterface/ProcessorInterface (see state-provider/state-processor) |
Rule of thumb: entity-as-resource is convenient but couples your public contract to your schema — fine for prototypes, probably not for large or non-CRUD systems. Decouple with a DTO as soon as the two shapes diverge.
Entity as Resource (Simplest)
Mark the entity itself — built-in Doctrine providers/processors handle everything. No provider, processor, or mapping to write:
use ApiPlatform\Metadata\ApiResource;
use Doctrine\ORM\Mapping as ORM;
#[ORM\Entity]
#[ApiResource]
class Book
{
#[ORM\Id, ORM\GeneratedValue, ORM\Column]
public ?int $id = null;
#[ORM\Column]
public string $title = '';
}
Use this for prototypes and straight CRUD. Migrate to a DTO (strategies below) once the API shape must differ from the schema.
DTO with Object Mapper (Recommended)
One mechanism, not two: API Platform's ObjectMapperProvider /
ObjectMapperProcessor activate only when both are present — stateOptions
naming the entityClass: (or documentClass:) and a #[Map] attribute on the
DTO (and on the input entity for writes). On read it runs
map($entity, $resourceClass); on write map($inputDto, $entityClass) then persists
via the Doctrine processor. There is no Object-Mapper mode without stateOptions.
Fields that line up by name map automatically; add #[Map(source: …)] to rename or
transform: to convert. The same config covers both the trivial 1:1 case and
arbitrary renames/transforms:
<?php
namespace App\ApiResource;
use ApiPlatform\Doctrine\Odm\State\Options;
use ApiPlatform\Metadata\ApiResource;
use ApiPlatform\Metadata\Get;
use ApiPlatform\Metadata\GetCollection;
use App\Document\Order as DocumentOrder;
use App\Transformer\CustomerTransformer;
use Symfony\Component\ObjectMapper\Attribute\Map;
#[ApiResource(
operations: [
new Get(stateOptions: new Options(documentClass: DocumentOrder::class)),
new GetCollection(stateOptions: new Options(documentClass: DocumentOrder::class)),
]
)]
#[Map(target: DocumentOrder::class)]
final class Order
{
public string $id;
// Simple 1:1 field mapping (automatic when names match)
public string $status;
// Map from a different source field
#[Map(source: 'customerName')]
public string $buyer;
// Transform with a custom callable
#[Map(source: 'rawData', transform: new CustomerTransformer())]
public CustomerDto $customer;
}
Custom Transformers
Implement TransformCallableInterface for complex mappings:
<?php
namespace App\Transformer;
use Symfony\Component\DependencyInjection\Attribute\Exclude;
use Symfony\Component\ObjectMapper\TransformCallableInterface;
#[Exclude]
final class CustomerTransformer implements TransformCallableInterface
{
public function __construct(private readonly string $field = 'name') {}
public function __invoke(mixed $value, object $source, ?object $target): mixed
{
// $value = source field value, $source = full source object
$dto = new CustomerDto();
$dto->name = $value[$this->field] ?? '';
return $dto;
}
}
Use #[Exclude] so Symfony's container doesn't try to autowire transformer constructor args.
Input DTOs per Operation
When the write model differs from the read model, give individual operations their
own input: DTO and processor (the resource class stays the read model):
#[ApiResource(
operations: [
new Post(input: CreateOrderInput::class, processor: OrderCreateProcessor::class),
new Patch(input: UpdateOrderInput::class, processor: OrderUpdateProcessor::class),
]
)]
class Order { /* read model */ }
Nested Sub-Resources with uriVariables
For resources nested under parents (e.g., /accounts/{accountId}/mailboxes/{mailboxId}/messages):
#[ApiResource(
operations: [
new Get(
uriTemplate: '/accounts/{accountId}/mailboxes/{mailboxId}/messages/{id}',
uriVariables: ['accountId', 'mailboxId', 'id'],
stateOptions: new Options(
handleLinks: MessageLinkHandler::class,
documentClass: Message::class,
)
),
new GetCollection(
uriTemplate: '/accounts/{accountId}/mailboxes/{mailboxId}/messages',
uriVariables: ['accountId', 'mailboxId'],
stateOptions: new Options(
handleLinks: MessageLinkHandler::class,
documentClass: Message::class,
),
itemUriTemplate: '/accounts/{accountId}/mailboxes/{mailboxId}/messages/{id}',
),
]
)]
The handleLinks class validates parent ownership and applies security filters. See the securing-collections skill for implementation details.
Hidden IRI fields for URI generation:
#[ApiProperty(readable: false, writable: false)]
#[Map(source: 'account', transform: new DocumentIdTransformer())]
public string $accountId;
Custom Named Operations
Add non-CRUD actions on the same resource:
new Put(
uriTemplate: '/orders/{id}/cancel',
input: CancelOrderInput::class,
processor: OrderCancelProcessor::class,
name: '_api_order_cancel',
),
new Get(
write: true, // triggers processor on GET
uriTemplate: '/orders/{id}/download',
processor: OrderDownloadProcessor::class,
name: '_api_order_download',
),
Use write: true on Get operations that need a processor (e.g., file downloads):
a Get's write phase is off by default, so the processor never fires until you flip
the flag. See operations → Read & write phases for the read/write lifecycle.
Backed Enums
As a property
A backed enum property serializes to its ->value automatically:
#[ApiResource]
class Person
{
public GenderType $genderType; // backed enum → {"genderType": "female"}
}
As a resource
Expose a backed enum as a read-only resource so clients can discover allowed values:
use ApiPlatform\Metadata\ApiResource;
#[ApiResource]
enum AvailabilityStatus: string
{
case InStock = 'InStock';
case OutOfStock = 'OutOfStock';
}
GET /availability_statuses lists all cases; GET /availability_statuses/InStock
returns one.
High-Precision Numbers
For monetary/scientific values that must avoid float drift, type a property as
\BcMath\Number (PHP 8.4 native, requires ext-bcmath). It serializes as a string
to preserve precision:
class Invoice
{
public ?\BcMath\Number $total; // → "300.55"
}
Laravel (Eloquent)
The design-first split, DTOs, Object Mapper, per-operation input:, nested
uriVariables/handleLinks, custom operations and backed-enum resources all work on
Laravel. Deltas:
- Model as resource:
#[ApiResource]on a class extendingIlluminate\Database\Eloquent\Model; the Eloquent provider/processor handle CRUD with zero wiring. - Eloquent
Options(ApiPlatform\Laravel\Eloquent\State\Options) usesmodelClass:(notentityClass:/documentClass:); it carriesmodelClass+handleLinksonly — norepositoryMethod. - Object Mapper is supported: pair
stateOptions: new Options(modelClass: ProductModel::class)with#[Map(source: ProductModel::class)]on the DTO; per-property#[Map]andTransformCallableInterfaceare identical. - Properties: Eloquent models have no typed properties, so declare
#[ApiProperty]at the class level withproperty:(#[ApiProperty(property: 'title', identifier: true)]). DTO/ApiResourceclasses use property-level attributes.\BcMath\Numberand backed enums behave the same.
Checklist
When creating a new resource:
- Create the DTO class in
src/ApiResource/ - Add
#[ApiResource]with operations - Define provider for read operations (or use
stateOptions) - Define processor for write operations
- Add validation constraints to input DTOs
- For nested resources: configure
uriVariablesandhandleLinks - Add
#[Map]attributes for entity/document mapping - Add
#[ApiProperty]for OpenAPI documentation
Archivos de esta skill
1Preguntas sobre la skill api-resource
¿Cómo instalo la skill api-resource en Claude Code?
Ejecuta npx degit api-platform/skillset/skills/api-resource ~/.claude/skills/api-resource. Claude Code carga las skills desde ~/.claude/skills (todos los proyectos) o .claude/skills (un proyecto) y usa una skill cuando su descripción coincide con la tarea.
¿Qué clientes pueden usar la skill api-resource?
Cualquier cliente que lea el formato Agent Skills: Claude Code, Codex, Gemini CLI y claude.ai (subir como ZIP). Los scripts de la skill se ejecutan solo donde el cliente permite la ejecución de código.
¿Cuándo debería usar un DTO en lugar de una entidad como recurso?
Usa un DTO cuando necesites desacoplar la estructura de la API pública de tu esquema de base de datos, especialmente si los campos requieren ser renombrados o transformados.
¿Cómo activo el Object Mapper en API Platform?
El Object Mapper se activa cuando proporcionas tanto stateOptions nombrando la clase de la entidad o documento como un atributo #[Map] en el DTO.
¿Cómo puedo manejar números de alta precisión como divisas?
Define la propiedad como \BcMath\Number para asegurar que se serialice como un string y evitar errores de precisión de punto flotante.