Skill api-resource
Membuat atau memodifikasi resource API Platform menggunakan DTO, Object Mapper, serta state provider atau processor.
- Bintang GitHub
- 29
- File
- 1
- Commit terakhir
- 22 Okt 2026
- Lisensi
- —
- Tool yang diizinkan
- —
Apa fungsi skill api-resource?
api-resource adalah agent skill. Membuat atau memodifikasi resource API Platform menggunakan DTO, Object Mapper, serta state provider atau processor.
Ringkasan dibuat dari SKILL.md skill ini.
Tentang skill api-resource
Developer yang membangun web API dapat menggunakan skill ini untuk merancang, membuat, atau memodifikasi resource API Platform. Skill ini membantu dalam mendefinisikan bentuk data publik melalui deklarasi resource, memastikan dokumentasi Hydra, OpenAPI, dan GraphQL tetap akurat. Skill ini mendukung berbagai pola arsitektur, mulai dari setup sederhana entity-as-resource untuk prototyping cepat hingga strategi DTO yang terdekopel untuk sistem yang kompleks.
Ketika agent perlu mengekspos sebuah entity melalui HTTP atau mengubah bentuk model data yang sudah ada, agent dapat mengimplementasikan DTO dengan Object Mapper. Proses ini menggunakan stateOptions dan atribut Map untuk menghubungkan representasi publik ke Doctrine entity atau Laravel Eloquent model yang mendasarinya. Skill ini juga menangani kebutuhan tingkat lanjut seperti mengonfigurasi nested sub-resources dengan uriVariables, mengimplementasikan custom state provider dan processor untuk logika non-CRUD, serta mengelola angka presisi tinggi menggunakan BcMath. Skill ini menyediakan struktur yang diperlukan untuk menangani input DTO per operasi, sehingga memungkinkan payload yang berbeda untuk membaca dan menulis data.
Apa yang bisa dilakukan agent dengannya
- 1Mengekspos entity sebagai API resourceAgent dapat dengan cepat mengubah database entity menjadi endpoint API yang fungsional untuk operasi CRUD dasar.
- 2Memisahkan bentuk API dari skema databaseDeveloper dapat menggunakan DTO dan Object Mapper untuk memastikan kontrak API publik tidak berubah saat struktur database di bawahnya berubah.
- 3Mengimplementasikan transformasi data yang kompleksAnda dapat membuat transformer kustom untuk menangani logika mapping yang rumit antara model data dan resource API Anda.
- 4Mengonfigurasi hierarki resource bersarangAgent dapat mengatur struktur URL yang kompleks untuk resource yang merupakan bagian dari parent entity, seperti pesan di dalam sebuah mailbox.
Ditulis dari SKILL.md milik skill tersebut. Setiap use case diverifikasi dengan kutipan darinya.
Cara install skill api-resource
Setiap perintah menyalin folder skill ke direktori skills client. Restart client atau mulai sesi baru untuk memuatnya.Install skill api-resource di Plugin
/plugin marketplace add api-platform/skillset /plugin install api-platform@api-platform-skillset
Install skill api-resource di Claude Code
~/.claude/skills/api-resource/SKILL.md
$ npx degit api-platform/skillset/skills/api-resource ~/.claude/skills/api-resourceInstall skill api-resource di Claude Code (project)
.claude/skills/api-resource/SKILL.md
$ npx degit api-platform/skillset/skills/api-resource .claude/skills/api-resourceInstall skill api-resource di Codex
~/.agents/skills/api-resource/SKILL.md
$ npx degit api-platform/skillset/skills/api-resource ~/.agents/skills/api-resourceInstall skill api-resource di Gemini CLI
~/.gemini/skills/api-resource/SKILL.md
$ npx degit api-platform/skillset/skills/api-resource ~/.gemini/skills/api-resourceInstall skill api-resource di 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
Lihat di 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
Berkas dalam skill ini
1Pertanyaan seputar skill api-resource
Bagaimana cara install skill api-resource di Claude Code?
Jalankan npx degit api-platform/skillset/skills/api-resource ~/.claude/skills/api-resource. Claude Code memuat skill dari ~/.claude/skills (semua proyek) atau .claude/skills (satu proyek) dan menggunakan skill saat deskripsinya cocok dengan tugas.
Client mana saja yang bisa menggunakan skill api-resource?
Client apa pun yang membaca format Agent Skills: Claude Code, Codex, Gemini CLI, dan claude.ai (unggah sebagai ZIP). Script dalam skill hanya berjalan di client yang mengizinkan eksekusi kode.
Kapan saya harus menggunakan DTO alih-alih entity sebagai resource?
Gunakan DTO jika bentuk API publik perlu dipisahkan dari skema database Anda, terutama jika field perlu diubah namanya atau ditransformasi.
Bagaimana cara mengaktifkan Object Mapper di API Platform?
Object Mapper akan aktif saat Anda menyediakan stateOptions yang menyebutkan class entity atau document serta atribut #[Map] pada DTO.
Bagaimana cara menangani angka dengan presisi tinggi seperti mata uang?
Tipekan property sebagai \BcMath\Number untuk memastikan data diserialisasi sebagai string dan menghindari float drift.