kematjaya / crud-maker-api-bundle
Symfony MakerBundle command that generates API Platform CRUD (DTO, service, state processor/extension) plus a frontend spec sidecar for @kematjaya/crud-ui-generator, for an existing Doctrine entity
Package info
github.com/kematjaya0/crud-maker-api-bundle
Type:symfony-bundle
pkg:composer/kematjaya/crud-maker-api-bundle
Requires
- php: >=8.2
- doctrine/doctrine-bundle: ^3.2
- doctrine/orm: ^3.5
- kematjaya/crud-maker-core: ^8.0
- nikic/php-parser: ^5.0
- symfony/config: ^7.0|^8.0
- symfony/console: ^7.0|^8.0
- symfony/dependency-injection: ^7.0|^8.0
- symfony/http-foundation: ^7.0|^8.0
- symfony/http-kernel: ^7.0|^8.0
- symfony/maker-bundle: ^1.60
- symfony/routing: ^7.0|^8.0
- symfony/translation: ^7.0|^8.0
- symfony/validator: ^7.0|^8.0
- symfony/yaml: ^7.0|^8.0
Requires (Dev)
None
Suggests
None
Provides
None
Conflicts
None
Replaces
None
README
Command Symfony MakerBundle yang men-generate sisi tulis (write-side) dari sebuah resource
API Platform (Input DTO, Service, WriteProcessor, endpoint export
CSV) untuk entity Doctrine yang sudah ada, plus sidecar crud-specs/{Entity}.json yang dibaca
@kematjaya/crud-ui-generator
untuk men-generate frontend Next.js yang sepadan (halaman list/create/edit, tabel dengan
search/pagination/bulk-delete/export CSV, form, BFF proxy routes).
Jalur kode terpisah dari
kematjaya/crud-maker-twig-bundle —
pakai bundle itu kalau Anda merender view Twig sisi server, bukan aplikasi API Platform +
Next.js.
1. Instalasi
composer require kematjaya/crud-maker-api-bundle
Daftarkan bundle-nya di config/bundles.php:
Kematjaya\CrudMakerBundle\Api\CrudMakerApiBundle::class => ['all' => true],
Bundle ini tidak membutuhkan symfony/twig-bundle, symfony/form, atau
symfony/security-csrf — hanya yang benar-benar dibutuhkan untuk generate CRUD API Platform.
Anda tetap perlu api-platform/symfony di project secara terpisah (maker akan mengecek dan
memberi peringatan kalau tidak ada, tapi tidak memaksanya sebagai dependency composer wajib).
2. Siapkan entity-nya
Entity-nya harus sudah ada (make:entity atau ditulis manual) beserta repository class-nya.
Kedua bentuk berikut didukung — maker mendeteksi bentuk mana yang dipakai entity Anda dan
men-generate kode yang sesuai:
- constructor +
update():new Entity($field1, $field2)saat create, lalu$entity->update($field1, $field2)saat edit (urutan parameter sama dengan urutan mapping field Doctrine di entity). - setter (bentuk umum hasil
make:entitypolos):new Entity()lalu$entity->setField1(...)->setField2(...), tidak perlu constructor/update().
Kalau tidak ada pola yang cocok (misalnya constructor dengan argumen wajib tapi tanpa update()
yang sepadan, atau ada field yang tidak punya setter), command akan gagal dengan pesan error
yang jelas alih-alih diam-diam men-generate kode yang rusak.
3. Jalankan maker-nya
php bin/console make:kmj-api-crud
Command ini akan menanyakan:
| Pertanyaan | Maksud |
|---|---|
entity-class |
Entity yang mau dibuatkan CRUD-nya (ada autocomplete) |
owner-property |
Properti relasi yang membatasi baris data ke user saat ini (mis. owner) — isi - kalau tidak ada |
with-tests |
Generate test PHPUnit untuk Service-nya? |
searchable-fields |
Nama field, dipisah koma, yang bisa dicari dari frontend (mis. title) — isi - kalau tidak ada |
write-entity-attributes |
Tambahkan #[ApiResource]/#[ApiFilter] ke file entity secara otomatis? (default: ya) — lihat di bawah. Tetap ditanya (tidak seperti dua baris berikutnya) karena ini MENGEDIT file entity yang sudah ada, bukan cuma menulis file baru. |
relation-display-fields |
Label dropdown untuk relasi #[ORM\ManyToOne] yang entity terkaitnya BELUM pernah di-generate lewat command ini (lihat "Relasi ManyToOne" di bawah) — format properti:field, pisah koma kalau lebih dari satu (mis. category:name), isi - kalau tidak ada relasi semacam itu |
Dua hal berikut TIDAK ditanya — otomatis di-default, tetap bisa di-override lewat argumen posisional non-interaktif (lihat contoh di bawah) kalau perlu:
| Argumen | Default otomatis |
|---|---|
permission-prefix |
pluralize(nama entity), huruf kecil (mis. Category → categories). Catatan: ini hanya label bebas untuk permission key dan route frontend — bukan URL API sebenarnya. URL resource API Platform yang sesungguhnya selalu berupa pluralize(tableize($shortName)) dan ditulis apa adanya ke field apiResourcePath di spec JSON, terlepas dari nilai permission-prefix ini — jadi biasanya sama, tapi kalau di-override manual bisa beda (lihat "Relasi ManyToOne" soal kenapa perbedaan ini penting untuk field relasi). |
with-access-control |
true — gerbangi create/edit/delete/export lewat isGranted() dari kematjaya/access-control-bundle |
Non-interaktif:
php bin/console make:kmj-api-crud Note owner notes true true title true --no-interaction
Relasi ManyToOne
Setiap properti #[ORM\ManyToOne] pada entity — SELAIN yang dipilih sebagai owner-property untuk
run ini (itu ditangani terpisah lewat CurrentUser{Entity}Extension, bukan lewat mekanisme ini) —
otomatis terdeteksi dan diperlakukan sebagai field relasi: masuk ke Input DTO dengan tipe entity
terkait (plus #[ApiProperty(schema: ['type' => 'string', 'format' => 'iri-reference'])] supaya
openapi-typescript di frontend generate tipe string bukan schema Category penuh), dikecualikan
dari trim() di Service, dan ditulis ke crud-specs/{Entity}.json sebagai
"type": "relation" — field JSON inilah yang membuat @kematjaya/crud-ui-generator merender
field-nya sebagai react-select AsyncSelect pencarian server-side alih-alih <select> native
(lihat js/README.md).
Field relasi butuh tahu properti mana di entity terkait yang jadi label dropdown-nya
(displayField/searchParam), dan URL BFF frontend entity terkait (relatedFrontendPath) — dua
hal yang tidak bisa ditebak dari metadata Doctrine saja:
- Kalau entity terkait sudah pernah di-generate lewat
make:kmj-api-crud(sudah punyacrud-specs/{RelatedEntity}.jsonsendiri): kedua nilai itu otomatis diambil dari situ — fieldsearchablepertama di sana jadidisplayField/searchParam, danpermissionPrefix-nya jadirelatedFrontendPath. Tidak perlu isi apa-apa. - Kalau belum (mis. relasi ke entity yang bukan resource CRUD sendiri): isi argumen
relation-display-fields(lihat tabel di atas). Kalau tidak diisi, command GAGAL dengan pesan error yang jelas — generator ini sengaja tidak menebak sembarang properti (mis. asal pilihname) sebagai fallback diam-diam.
relatedApiResourcePath (URI ApiPlatform asli entity terkait, ditanam sebagai prefix IRI yang
disubmit) SELALU dihitung deterministik (pluralize(tableize($relatedEntity))), sama seperti
apiResourcePath di level entity itu sendiri — tidak bergantung pada permissionPrefix entity
terkait, jadi tidak perlu sidecar atau argumen tambahan untuk nilai ini. Ini sengaja dipisah dari
relatedFrontendPath — menggabungkan keduanya jadi satu nilai adalah persis bug yang pernah
ditambal untuk apiResourcePath/permissionPrefix di level entity (lihat catatan di
js/README.md dan js/src/spec.ts).
Hanya ManyToOne yang didukung — OneToOne/OneToMany/ManyToMany dilewati begitu saja (tidak
dianggap error, cuma tidak diproses sebagai field).
4. Apa saja yang ditulis
src/Dto/{Entity}Input.php,src/Service/{Entity}Service(Interface).php,src/State/{Entity}WriteProcessor.phpsrc/Controller/{Entity}ExportDataController.php— endpoint data export CSV (/api/{prefix}/export-data, dibatasi rate limiter, mengembalikan JSON — frontend yang mengubahnya jadi file CSV yang bisa diunduh)src/State/CurrentUser{Entity}Extension.php(hanya kalauowner-propertydiisi) — membatasiGetCollection/Getke baris milik user saat initests/Unit/Service/{Entity}ServiceTest.php(hanya kalauwith-testsdipilih)crud-specs/{Entity}.json— input untuk generator frontend, termasukapiResourcePath(URI ApiPlatform sebenarnya untuk resource ini, mis./api/categories) yang wajib dibaca apa adanya oleh@kematjaya/crud-ui-generator, bukan diturunkan/ditebak daripermissionPrefix. Properti#[ORM\ManyToOne](selainowner-property) masuk ke sini sebagai"type": "relation"— lihat "Relasi ManyToOne" di bawah.- File entity itu sendiri, kalau
write-entity-attributesdikonfirmasi:#[ApiResource(operations: [...])]dan (kalau ada field searchable)#[ApiFilter(SearchFilter::class, ...)], ditambahkan lewat manipulasi AST (printer format-preserving darinikic/php-parser— teknik yang sama dipakaimake:entitysecara internal) sehingga bagian lain file tidak tersentuh. Kalau entity sudah punya salah satu atribut ini, atauwrite-entity-attributesditolak, blok kodenya akan dicetak saja untuk Anda tempel manual. config/packages/framework.yaml: entriframework.rate_limiter.{limiter}untuk endpoint export, disisipkan lewat penyisipan teks bertarget (komentar/format lain di file tidak tersentuh). Idempotent — limiter dengan nama yang sudah ada dibiarkan apa adanya.config/permissions/default.yaml(hanya kalauwith-access-control): item permission untuk resource ini (gated: true+actions: { create, edit, delete, bulk_delete, export_selected, export_all }), ditambahkan ke (atau dibuat di) bagianMaster. Kalau item dengankeytersebut sudah ada, hanya bagian yang belum ada yang dilengkapi (gated: truedan/atau action key yang hilang) — field lain, termasuk label action custom, tidak tersentuh. Item baru mendapathref/iconplaceholder yang perlu Anda tinjau ulang.
Kalau salah satu file config di atas tidak ditemukan, atau strukturnya tidak dikenali, writer akan mencetak bloknya saja untuk Anda tempel manual, bukan menggagalkan seluruh command — tetap cek next-steps yang dicetak di kedua kondisi.
5. Langkah manual yang tersisa (dicetak setelah generate)
- Setelah permission key ada, jalankan
bin/console kematjaya:access-control:sync(kalauwith-access-control). - Tinjau ulang Service kalau urutan field entity mungkin tidak cocok dengan Input DTO.
6. Generate frontend-nya (@kematjaya/crud-ui-generator)
Sidecar crud-specs/{Entity}.json dari langkah 4 dikonsumsi oleh
@kematjaya/crud-ui-generator,
generator frontend CRUD untuk Next.js yang dipublikasikan ke npm — sumbernya ada di
js/ di monorepo
kematjaya/crud-maker-bundle (paket npm terpisah, bukan composer/PHP — bundle ini hanya
menghasilkan spec JSON yang dibacanya). Generator ini dijalankan saat development (seperti
Plop/Hygen), bukan library komponen runtime.
Prasyarat
Generator ini menyasar project yang sudah mengikuti konvensi Next.js di ekosistem ini — menjalankannya pada project yang belum punya komponen-komponen berikut akan menghasilkan file yang tidak bisa di-compile sampai Anda menambahkannya:
@kematjaya/bootstrap-ui-kituntukListPageCard/TextField/Button/dll.@kematjaya/access-control-uiuntukusePermissions()src/lib/http.ts,src/lib/bff.ts(helper proxy BFF —authedBackend,validateOrigin,parseJson,jsonProblem)src/lib/permissions.tsyang meng-exportrequirePermission()src/types/api.ts+src/types/api.generated.ts(tipe dari OpenAPI viaopenapi-typescript)
Instalasi & cara pakai
Tidak perlu instalasi terpisah untuk pemakaian sekali pakai — npx akan mengambilnya otomatis:
cd ../frontend # atau lokasi project Next.js-nya
# 1. generate ulang tipe OpenAPI — HARUS dijalankan SETELAH #[ApiResource]/#[ApiFilter]
# dari langkah 5 sudah terpasang di entity backend, supaya path/schema resource baru ikut masuk
npm run api:types
# 2. generate frontend CRUD-nya (halaman list/create/edit, tabel, form, BFF proxy routes)
npx @kematjaya/crud-ui-generator ../backend/crud-specs/{Entity}.json --src src
# 3. format file hasil generate (tidak otomatis lewat Prettier)
npm run format
Kalau lebih suka dipasang sebagai dev dependency permanen daripada npx setiap kali:
npm install --save-dev @kematjaya/crud-ui-generator
npx crud-ui-generate ../backend/crud-specs/{Entity}.json --src src
Apa saja yang di-generate
Per entity (dilewati kalau file sudah ada — aman dijalankan ulang):
app/dashboard/{entities}/page.tsx,new/page.tsx,[id]/edit/page.tsxcomponents/{entities}/{Entity}Table.tsx,{Entity}Form.tsx,use{Entities}Export.tslib/{entities}-query.ts,lib/{entities}-csv.tsapp/api/{entities}/route.ts,[id]/route.ts,export/route.ts(BFF proxy)
Primitive UI bersama, tidak spesifik satu entity (ditulis sekali, dipakai ulang semua entity):
components/crud/DeleteConfirmModal.tsx, BulkActionsBar.tsx, PaginationBar.tsx,
SearchPanel.tsx, ExportAllButton.tsx.
Ditambahkan ke (multi-entity, idempotent — tiap entity dapat satu blok yang dijaga marker):
lib/api-shapes.ts, lib/schemas.ts (satu Zod schema per entity), types/api.ts.
Catatan penting
- Tipe id diambil dari
idTypedi spec (uuid/int/string, dibaca dari kolom id sebenarnya di entity) — spec lama tanpa field ini default keuuid. - Search di halaman list hanya menyambungkan field
searchablepertama walaupun beberapa field ditandai searchable di spec (endpoint export sendiri meng-OR-kan semuanya). - Field
textareadilewati dari tabel/export CSV (teks panjang) — selain itu (text/number/boolean) menjadi kolom. - Getter diasumsikan ada pada entity/tipe hasil generate, dalam bentuk konvensional
get{Field}(). - Panggilan BFF ke backend selalu memakai
apiResourcePathdari spec, bukanpermissionPrefix.permissionPrefixcuma menamai route/folder frontend dan permission key — nilainya bebas dan bisa berbeda dari URI plural asli ApiPlatform (contoh nyata: entityCategoryyang di-generate denganpermissionPrefix: "category"tetap disajikan backend di/api/categories, bukan/api/category). Kalau ini tidak diikuti dengan benar, GET/POST/dst. dari frontend akan 404 walau kodenya terlihat benar — cekapiResourcePathdicrud-specs/{Entity}.jsonkalau menemukan gejala ini.
Lihat js/README.md di
monorepo (atau halaman npm) untuk
referensi lengkap dan paling baru — bagian ini bisa saja tertinggal dari versi npm yang lebih
baru.
Meng-override template generator
# config/packages/crud_generator.yaml crud_maker_api: templates: path: '%kernel.project_dir%/generator'
Template custom dicari lebih dulu, baru jatuh kembali ke skeleton bawaan bundle — Anda hanya perlu meng-override yang memang ingin diubah.