6
CRUD Generator dan Content Manager
Badaso memisahkan CRUD menjadi dua lapis. Lapis konfigurasi berupa baris di tabel data_types dan data_rows, sedangkan lapis artefak deploy berupa berkas seeder dan migration di aplikasi host. BadasoCRUDController memegang lapis konfigurasi dan menerima FileGenerator lewat constructor, sementara ContentManager, ContentGenerator, dan FileGenerator memegang lapis berkas. Nama tabel kedua model dibentuk saat konstruksi dari config('badaso.database.prefix'). src/Controllers/BadasoCRUDController.php:30-401 src/Models/DataType.php:16-212 src/Models/DataRow.php:18-253
DataType::dataRows adalah relasi hasMany yang diurutkan naik lewat kolom order, jadi urutan field pada form mengikuti nilai order yang ditulis controller. src/Models/DataType.php:43-464
Dua pintu masuk CRUD
Panel admin memakai dua kelompok endpoint. Kelompok crud mengelola konfigurasi CRUD yang sudah ada, sedangkan kelompok table membaca skema tabel nyata dan bisa membuat konfigurasi awal. src/Routes/api.php:136-1535
| Method | URI | Handler | Middleware |
|---|---|---|---|
| GET | /crud | BadasoCRUDController@browse | browse_crud_data |
| GET | /crud/read | BadasoCRUDController@read | read_crud_data |
| PUT | /crud/edit | BadasoCRUDController@edit | edit_crud_data |
| POST | /crud/add | BadasoCRUDController@add | add_crud_data |
| DELETE | /crud/delete | BadasoCRUDController@delete | delete_crud_data |
| GET | /crud/read-by-slug | BadasoCRUDController@readBySlug | read_crud_data |
| GET | /crud/maintenance | BadasoCRUDController@setMaintenanceState | maintenance_crud_data |
| GET | /table | BadasoTableController@browse | badaso.middleware.authenticate |
| GET | /table/read | BadasoTableController@read | badaso.middleware.authenticate |
| GET | /table/data | BadasoTableController@getDataByTable | badaso.middleware.authenticate |
| GET | /table/generate-crud | BadasoTableController@generateCRUD | badaso.middleware.authenticate |
| GET | /table/data-type | BadasoTableController@getDataType | badaso.middleware.authenticate |
| GET | /table/relation-data-by-slug | BadasoTableController@getRelationDataBySlug | badaso.middleware.authenticate |
BadasoCRUDController::browse menyaring Badaso::getProtectedTables() dari SchemaManager::listTables(), lalu memasangkan setiap tabel dengan DataType hasil where('name', $key)->first() sebagai crud_data. src/Controllers/BadasoCRUDController.php:42-726
BadasoCRUDController::read justru bergerak dari arah sebaliknya. Ia mengambil DataRow yang sudah dimiliki DataType, mengumpulkan kolom lewat pluck('field'), lalu menambahkan field yang belum terdaftar dengan tipe hasil DataTypeToComponent::convert, browse dan read aktif, sedangkan add, edit, dan delete dimatikan. src/Controllers/BadasoCRUDController.php:74-1167 readBySlug melakukan hal yang sama dengan kunci slug. src/Controllers/BadasoCRUDController.php:118-1378
BadasoTableController::generateCRUD adalah jalur cepat: satu DataType baru dibuat dari SchemaManager::describeTable, lalu satu DataRow per kolom dengan browse, read, edit, add, delete semuanya true dan order berurutan mulai 1. src/Controllers/BadasoTableController.php:72-1259 Daftar tabel mentah untuk pilihan ini datang dari BadasoTableController::browse. src/Controllers/BadasoTableController.php:22-4110
Menyusun DataType dan DataRow
add memvalidasi bahwa name wajib ada tabelnya di database, unik di DataType, dan tidak termasuk Badaso::getProtectedTables(). Kolom pada rows.*.field juga diverifikasi: kolom harus ada di tabel, dan kolom notnull tanpa nilai default ditolak bila add tidak dicentang. src/Controllers/BadasoCRUDController.php:286-30311 edit memakai aturan yang sama tanpa batasan tabel terproteksi. src/Controllers/BadasoCRUDController.php:139-15512
Field yang disimpan add mencakup display_name_singular, display_name_plural (default Str::plural), icon, model_name, policy_name, controller, order_column, generate_permissions, server_side, notification yang di-json_encode, dan is_soft_delete dari create_soft_delete. src/Controllers/BadasoCRUDController.php:342-36113 edit menimpa field yang sama pada DataType yang ditemukan lewat id. src/Controllers/BadasoCRUDController.php:196-21514
Setiap baris form menjadi satu DataRow dengan order = $index + 1. Kolom relation hanya di-json_encode bila type bernilai relation dan jumlah kunci relasi berada di antara 4 sampai 5. src/Controllers/BadasoCRUDController.php:363-40115 Pada edit, seluruh DataRow lama dihapus lebih dulu lewat DataRow::where('data_type_id', ...)->delete(), lalu ditulis ulang dari payload. src/Controllers/BadasoCRUDController.php:217-26116
sequenceDiagram
participant Admin
participant CRUD as BadasoCRUDController
participant DB as data_types + data_rows
participant Ev as CRUDDataAdded
participant Orch as BadasoDeploymentOrchestrator
participant FG as FileGenerator
participant Seed as CRUD seeder files
Admin->>CRUD: POST /crud/add
CRUD->>CRUD: validate name, rows, create_soft_delete
CRUD->>DB: simpan DataType
CRUD->>DB: simpan DataRow per field
CRUD->>Ev: event new CRUDDataAdded
Ev->>Orch: CRUDDataChanged dengan change_type Added
Orch->>Orch: cek config badaso-watch-tables
Orch->>FG: CRUDDataAddedHandler
FG->>Seed: generateDataTypeSeedFile dan generateDataRowSeedFile
FG->>Seed: updateOrchestraSeeder
%% fig: Alur add CRUD sampai penulisan seeder
%% desc: Controller menyimpan konfigurasi CRUD, memicu event, lalu FileGenerator menulis seeder dan menyisipkan kelasnya ke orchestrator seeder.Data relasi dibaca kembali oleh accessor getRelationAttribute, yang melakukan json_decode pada kolom relation. src/Models/DataRow.php:27-3817
Permission, menu, dan jejak aktivitas
Bila generate_permissions bernilai benar, edit memanggil Permission::generateFor($data_type->name, true); bila salah, Permission::removeFrom yang dipanggil. add hanya memanggil generateFor. Sesudah itu addEditMenuItem menyiapkan item menu, event perubahan data dipicu, dokumentasi API digenerate, dan transaksi di-commit; kegagalan apa pun memicu DB::rollBack. src/Controllers/BadasoCRUDController.php:263-28418 src/Controllers/BadasoCRUDController.php:403-43119
addEditMenuItem memakai config('badaso.default_menu') sebagai kunci menu, membuat Menu bila belum ada, dan menyusun url menjadi / + kunci menu + / + slug. Permission item menu diisi browse_ + name bila generate_permissions aktif. deleteMenuItem menghapus item menu berdasarkan url yang sama. src/Controllers/BadasoCRUDController.php:472-51920
delete menjalankan deleteAPIDocs, Permission::removeFrom, deleteMenuItem, lalu $data_type->delete() dan event(new CRUDDataDeleted(...)) di dalam satu transaksi. src/Controllers/BadasoCRUDController.php:433-47021 generateAPIDocs menulis berkas dari ApiDocs::getStub lewat LaravelFileSystem::put, sedangkan deleteAPIDocs menghapusnya bila ada. src/Controllers/BadasoCRUDController.php:521-54222
Event dan orchestrator
CRUDDataAdded menyimpan data_type dan data, lalu di dalam constructor memicu CRUDDataChanged dengan penanda 'Added'. src/Events/CRUDDataAdded.php:16-2323 CRUDDataChanged membawa tiga properti: data_type, data, dan change_type. src/Events/CRUDDataChanged.php:18-2524
OrchestratorEventServiceProvider memetakan CRUDDataChanged ke listener BadasoCRUDDataChanged. src/Providers/OrchestratorEventServiceProvider.php:12-1625 Listener itu hanya meneruskan event ke BadasoDeploymentOrchestrator::handle. src/Listeners/BadasoCRUDDataChanged.php:21-2426
BadasoDeploymentOrchestrator memakai tiga konstanta change_type, yaitu Added, Updated, dan Deleted, yang masing-masing dipetakan ke handler pada konstanta HANDLERS. src/BadasoDeploymentOrchestrator.php:15-2927 Sebelum handler dipanggil, handle lebih dulu memeriksa config('badaso-watch-tables'): bila nama tabel tidak ada di daftar pantau, method langsung return tanpa melakukan apa pun. Handler diselesaikan lewat $this->app->make(...), dan kegagalan dibungkus menjadi OrchestratorHandlerNotFoundException. src/BadasoDeploymentOrchestrator.php:47-6528
graph LR
E[CRUDDataChanged] --> L[BadasoCRUDDataChanged]
L --> O[BadasoDeploymentOrchestrator::handle]
O -->|nama tabel tidak dipantau| X[return tanpa handler]
O -->|Added| H1[CRUDDataAddedHandler]
O -->|Updated| H2[CRUDDataUpdatedHandler]
O -->|Deleted| H3[CRUDDataDeletedHandler]
H1 --> F1[generateDataTypeSeedFile dan generateDataRowSeedFile]
H2 --> F2[deleteAndGenerate]
H3 --> F3[deleteSeedFiles dan generateSeedFileForDeletedData]
style O fill:#f59e0b,color:#000
style L fill:#8b5cf6,color:#fff
style H1 fill:#2d7d46,color:#fff
style H2 fill:#2d7d46,color:#fff
style H3 fill:#dc2626,color:#fff
style X fill:#dc2626,color:#fff
%% fig: Dispatch event ke handler orchestrator
%% desc: Satu listener meneruskan perubahan ke orchestrator, yang memilih handler berdasarkan change_type dan melewati tabel yang tidak dipantau.CRUDDataAddedHandler menghasilkan dua berkas: seeder DataType lalu seeder DataRow. src/OrchestratorHandlers/CRUDDataAddedHandler.php:28-3729 CRUDDataUpdatedHandler cukup memanggil deleteAndGenerate. src/OrchestratorHandlers/CRUDDataUpdatedHandler.php:26-3130 CRUDDataDeletedHandler memanggil $data_type->destroy($data_type->id), menghapus permission, menghapus seeder lama, lalu membuat seeder penghapus agar data bisa di-rollback. src/OrchestratorHandlers/CRUDDataDeletedHandler.php:29-4531
Seeder dari stub
Nama kelas seeder dibentuk dari slug yang dipecah pada tanda -, setiap potongan di-ucfirst, lalu ditambah suffix. src/ContentManager/FileSystem.php:166-17632 Ketiga suffix didefinisikan sebagai konstanta: CRUDDataTypeAdded, CRUDDataRowAdded, dan CRUDDataDeleted. src/ContentManager/FileGenerator.php:15-2133 Berkas seeder ditulis ke base_path().'/database/seeders/Badaso/CRUD', dan direktori itu dibuat dengan mkdir bila belum ada. src/ContentManager/FileSystem.php:93-10034
| Konstanta | Nilai | Stub sumber |
|---|---|---|
TYPE_SEEDER_SUFFIX | CRUDDataTypeAdded | data_seed.stub |
ROW_SEEDER_SUFFIX | CRUDDataRowAdded | row_seed.stub |
DELETED_SEEDER_SUFFIX | CRUDDataDeleted | delete_seed.stub |
generateSeedFile | suffix dari pemanggil | seed.stub |
generateBDOMigrationFile | prefix create/alter | migration.stub |
generateDataTypeSeedFile mengambil data_seed.stub, mengganti {{class}}, memanggil populateContentToStubFile dengan suffix TYPE_SEEDER_SUFFIX, menjalankan addDataTypeId, menulis berkas, lalu memperbarui orchestrator seeder. src/ContentManager/FileGenerator.php:52-8435 generateDataRowSeedFile mengulang pola yang sama dengan row_seed.stub dan suffix ROW_SEEDER_SUFFIX. src/ContentManager/FileGenerator.php:91-12136 generateSeedFileForDeletedData memakai delete_seed.stub dan suffix DELETED_SEEDER_SUFFIX. src/ContentManager/FileGenerator.php:182-20837
addDataTypeId mengganti string '#dataTypeId' menjadi '$data_type->id' agar seeder memakai id hasil insert, bukan angka tetap. src/ContentManager/FileGenerator.php:215-22238 deleteAndGenerate menjalankan deleteSeedFiles, lalu membuat ulang seeder tipe dan seeder baris. src/ContentManager/FileGenerator.php:128-13539 deleteSeedFiles menghapus dua berkas seeder milik slug tersebut. src/ContentManager/FileGenerator.php:160-17540
updateOrchestraSeeder membuka berkas BadasoDeploymentOrchestratorSeeder, meneruskan isinya ke ContentManager::updateDeploymentOrchestraSeederContent, kemudian menulis ulang berkas. src/ContentManager/FileGenerator.php:142-15541 Method itu hanya membungkus ContentGenerator::generateOrchestraSeederContent. src/ContentManager/ContentManager.php:60-7842
generateOrchestraSeederContent menyisipkan baris $this->seed(<Class>::class); di antara penanda #orchestraseeder_start dan #orchestraseeder_end. Bila penanda tidak ditemukan, ia memakai regex pada run() untuk menyisipkan baris yang sama sebelum kurung tutup. Untuk kelas ber-suffix CRUDDataDeleted, baris seed milik kelas tipe dan baris lama dilepas lebih dulu. src/ContentManager/ContentGenerator.php:204-24143 generateManualSeederContent melakukan hal serupa dengan penanda #manualgenerateseeder_start dan #manualgenerateseeder_end. src/ContentManager/ContentGenerator.php:250-28744
generateSeedFile dipakai untuk seeder manual: ia menolak tabel yang tidak ada lewat Schema::hasTable, membaca seluruh baris dengan $this->database_manager->table($table_name)->get(), memakai seed.stub, dan menambahkan kelas ke BadasoManualGenerateSeeder dengan memanggil generateManualSeederContent. src/ContentManager/FileGenerator.php:227-26045
Placeholder stub yang diisi ContentManager
populateContentToStubFile memilih pengisi berdasarkan suffix, sehingga satu stub hanya menerima placeholder yang relevan. src/ContentManager/ContentManager.php:177-19446
| Placeholder | Pengisi | Nilai yang disisipkan |
|---|---|---|
{{delete_statements}} | getDeleteStatement | hapus DataType berdasarkan name |
{{permission_insert_statements}} | getPermissionStatement | Permission::generateFor('<name>') |
{{permission_delete_statements}} | getPermissionStatement($data_type, 'delete') | Permission::removeFrom('<name>') |
{{menu_insert_statements}} | getMenuInsertStatements | firstOrFail menu default, lalu isi atau buat MenuItem |
{{menu_delete_statements}} | generateMenuDeleteStatements | hapus MenuItem pada url yang dibentuk |
{{datatype_slug_statement}} | getDataTypeSlugStatement | $data_type = ...where('name', '<name>')->first() |
{{insert_statements}} | populateInsertStatements | \DB::table('<tabel>')->insert(<array>) |
Statement hapus DataType memakai where('name', '%s') dua kali, sekali untuk first() dan sekali untuk delete(). src/ContentManager/ContentGenerator.php:14-2047 Statement menu membentuk url dari config('badaso.default_menu') dan slug, memakai highestOrderMenuItem untuk order, serta mengisi permission browse_ + name hanya bila generate_permissions benar. src/ContentManager/ContentGenerator.php:23-5648 src/ContentManager/ContentGenerator.php:179-19549 Statement permission dipilih berdasarkan argumen $type: tanpa $type memakai generateFor, dengan $type memakai removeFrom. src/ContentManager/ContentGenerator.php:71-7750
populateDataTypeSeederContent mengisi delete, permission, dan menu, lalu membuang kunci translations dan data_rows dari $data_type->toArray() sebelum menulis insert. src/ContentManager/ContentManager.php:97-11551 populateDataRowSeederContent memakai $data_type->dataRows, mengambil nama tabel dari $rows->last()->getTable(), dan mengisi {{datatype_slug_statement}} lebih dulu. src/ContentManager/ContentManager.php:122-14052 populateCRUDDataDeletedSeederContent mengisi delete, permission delete bila generate_permissions benar, dan menu delete. src/ContentManager/ContentManager.php:147-17053
populateInsertStatements membentuk satu baris \DB::table('%s')->insert(%s); dan memakai ContentGenerator::formatContent untuk merapikan hasil var_export. src/ContentManager/ContentManager.php:229-24354 formatContent membuang indeks numerik, mengganti indentasi menjadi empat spasi, dan melacak tanda kutip agar string tidak ikut diindentasi. src/ContentManager/ContentGenerator.php:86-12755
repackContentData menyiapkan baris sebelum ditulis: kolom id dibuang, data_type_id dan foreign_key diganti menjadi #dataTypeId, sedangkan details dan relation di-json_encode bila masih berupa array atau object. src/ContentManager/ContentManager.php:25-5356
Migration dari stub
generateBDOMigrationFile mengambil migration.stub, menyusun {{schema_up}} dari MigrationParser::getMigrationSchemaUp dan {{schema_down}} dari getMigrationSchemaDown, menambahkan schema relasi bila $relations tidak kosong, lalu menulis berkas lewat addContentToMigrationFile. src/ContentManager/FileGenerator.php:290-32057
Stub migration mendefinisikan kelas {{class}} extends Migration dengan method up() dan down(). Blok up() membungkus {{schema_up}} dalam try/catch (PDOException $ex) yang memanggil $this->down() lalu melempar ulang exception. src/stubs/migration.stub:7-3458
Nama berkas migration dibentuk Carbon::now()->format('Y_m_d_his') diikuti prefix, nama tabel, _table_, dan empat huruf terakhir nama kelas. src/ContentManager/FileSystem.php:65-7059
⚠️
addTablePolymorphismbisa menjalankanArtisan::call('migrate', ['--path' => 'database/migrations/badaso/', '--force' => true])langsung di dalam requestaddatauedit. Untuk relasibelongs_to_many, tabel pivot dibuat lewatgenerateBDOMigrationFilehanya bilaSchema::hasTable($table)bernilai salah, lalumigratedijalankan saat itu juga. src/Controllers/BadasoCRUDController.php:544-63460
Pemetaan tipe kolom ke komponen
DataTypeToComponent::convert membaca tabel $type_list dan mengembalikan komponen form yang sesuai; tipe yang tidak dikenal dikembalikan apa adanya. Pemetaan yang tersedia mencakup CHAR/VARCHAR/JSON ke text, TEXT ke textarea, ENUM ke select, SET ke select_multiple, BOOL/BOOLEAN ke switch, INT/BIGINT/DECIMAL ke number, serta DATETIME/TIMESTAMP/TIME ke datetime. src/Helpers/DataTypeToComponent.php:8-4661 src/Helpers/DataTypeToComponent.php:48-5562
BadasoTableController::read memakai konversi ini saat menampilkan kolom tabel beserta notnull sebagai is_not_null. src/Controllers/BadasoTableController.php:43-7063 Sebaliknya generateCRUD menyimpan $column['type'] mentah ke DataRow, tanpa melewati DataTypeToComponent. src/Controllers/BadasoTableController.php:91-9764