macropay-solutions / laravel-crud-wizard-decorator-free
Data composition/decoration for laravel-crud-wizard-free library including url query language
Package info
github.com/macropay-solutions/laravel-crud-wizard-decorator-free
pkg:composer/macropay-solutions/laravel-crud-wizard-decorator-free
Requires
- php: ^8.0
- ext-json: *
- macropay-solutions/laravel-crud-wizard-free: ^7.1.0|^8.0.0
Requires (Dev)
- illuminate/database: ^8.83
- illuminate/http: ^8.83
- illuminate/pagination: ^8.83
- illuminate/routing: ^8.83
- illuminate/validation: ^8.83
- nesbot/carbon: ^2.62
- phpunit/phpunit: ^8.5
- psr/log: ^1.0 || ^2.0 || ^3.0
- symfony/http-foundation: ^5.0
Suggests
- laravel/framework: >= 8.0
- laravel/lumen-framework: >= 8.0
- macropay-solutions/laravel-crud-wizard-client-free: *
- macropay-solutions/laravel-crud-wizard-decorator-demo: dev-production
- macropay-solutions/maravel: dev-production
- macropay-solutions/maravelith: dev-production
README
This is a stripped down version of maravel-rest-wizard-decorator
and can be used for decorating laravel-crud-wizard-free
Decorator High Level Comparison: Freemium vs Pro
The Decorator suite acts as the presentation layer for your microservices, bridging the gap between your internal database structure and your public API contract.
| Feature | Decorator Freemium (laravel-crud-wizard-decorator-free) |
Decorator Pro (maravel-rest-wizard-decorator) |
|---|---|---|
| Schema Masking | ✅ Rename/Map resource & relation columns | ✅ Rename/Map resource & relation columns |
| Relation Wrapping | ✅ Decorate attributes of loaded relations | ✅ Decorate attributes of loaded relations |
| Data Flattening | ✅ Single Table Flattening (Merge resource + relations into one flat response) | ✅ Single Table Flattening (Merge resource + relations into one flat response) |
| Virtual Columns | ❌ | ✅ Composed Columns (Create new fields from multiple resource/relation columns) |
| Memory-Safe Export | ❌ | ✅ Streamed CSV Download (Direct to client; no file saved on server) |
| Filter Transformation | ❌ | ✅ Query Rewriting (Map resource columns to relation filters via URL query string) |
| Response Control | ✅ Standard JSON decoration | ✅ Restricted Columns (Return only requested keys, including in CSV streams) |
| Relational Updates | ❌ | ✅ One-to-One Update (Update related models during resource update) |
| Aggregations | ❌ | ✅ Resource & Relation Aggregations (Sums, Avgs, etc. on children) from maravel-rest-wizard |
| Security (XSS) | ✅ Auto-Sanitization (All JSON string values parsed by htmlspecialchars) |
✅ Auto-Sanitization (All JSON string values parsed by htmlspecialchars) |
Key Advantages of the Pro Decorator Suite
Zero-Footprint CSV Streaming
The Pro decorator can stream millions of rows directly to the client's browser without ever saving a temporary file on the server. This bypasses PHP memory_limit crashes and eliminates "Disk Full" errors during large exports.
Composed & Flattened Data
Build high-performance frontend tables with ease. Pro allows you to merge data from multiple relations and compose new virtual columns (e.g., calculating margin from cost and price fields across different tables) on the fly.
Relation Filter Transformation
Eases the complexity of URL queries. Users can filter by a "public" mapped column name, and the engine automatically transforms it into the correct internal relational logic (joins/where clauses) behind the scenes.
Atomic One-to-One Updates
Maintain data integrity without extra controller boilerplate. When you update a main resource, the Pro decorator handles the simultaneous update of any associated one-to-one relations in a single, clean operation.
AI Integration Note
If you are using the Maravel .cursorrules configuration, use the {--decorated} flag with the generator:
php artisan make:api-resource {resourceName} --decorated
It renames/maps the column names for the resource and its relations.
The reserved words / parameters that will be used as query params are:
- perPage
- page
The withRelations, withRelationsCount, withRelationsExistence query params will be disregarded
I. Install
II. Start using it
III. Crud routes
III.1. Create resource
III.2. Get resource
III.3. List filtered resource
III.4. Update resource (or create)
III.5. Delete resource
I. Install
composer require macropay-solutions/laravel-crud-wizard-decorator-free
II. Start using it
Register your middleware decorators as route middleware for each resource:
$app->routeMiddleware([ 'decorate-' . \MacropaySolutions\LaravelCrudWizardDecorator\Models\ExampleModel::resourceName()=> MacropaySolutions\LaravelCrudWizardDecorator\Http\Middleware\Decorators\ExampleMiddleware::class, ]);
OBS.
- The
withRelations, withRelationsCount, withRelationsExistencequery params will be disregarded. These are to be used only internally with undecorated request. - 202 http response code will not be decorated, and it can be used to send messaged to FE. Example: {"message":"Accepted"}
- 204 http response will be decorated as empty body.
Use the middleware alias as middleware in your crud route definition for each method if you have only few routes:
'decorate-' . $resourceName . ':list' 'decorate-' . $resourceName . ':get' 'decorate-' . $resourceName . ':getRelated' 'decorate-' . $resourceName . ':update' 'decorate-' . $resourceName . ':updateRelated' 'decorate-' . $resourceName . ':create' 'decorate-' . $resourceName . ':delete' 'decorate-' . $resourceName . ':deleteRelated'
If you have many routes that need decoration, it is faster to use the middleware FQN directly in the route definition:
SomeMiddleware::class . ':list' ...
In this way you avoid loading on each request the route middleware array that in some cases can become quite big (hundreds of elements).
Coupled with cached routes (Maravel supports route caching as opposed to Lumen) this solution is faster than the initial one I suggested.
Example:
<?php namespace MacropaySolutions\LaravelCrudWizardDecorator\Models; use Illuminate\Database\Eloquent\Model; use Illuminate\Database\Eloquent\Relations\HasOne; use MacropaySolutions\LaravelCrudWizard\Models\BaseModel; class ExampleModel extends BaseModel { public const RESOURCE_NAME = 'examples'; protected $fillable = [ 'role_id', 'created_at', 'updated_at', ]; public function roleRelation(): HasOne { return $this->hasOne(RelationExampleModel::class, 'id', 'role_id'); } } <?php namespace MacropaySolutions\LaravelCrudWizardDecorator\Models; use Illuminate\Database\Eloquent\Model; use Illuminate\Database\Eloquent\Relations\HasMany; use MacropaySolutions\LaravelCrudWizard\Models\BaseModel; class RelationExampleModel extends BaseModel { public const RESOURCE_NAME = 'relation-examples'; protected $fillable = [ 'name', 'color', 'created_at', 'updated_at', ]; public function exampleModels(): HasMany { return $this->hasMany(ExampleModel::class, 'role_id', 'id'); } } <?php namespace MacropaySolutions\LaravelCrudWizardDecorator\Http\Middleware\Decorators; use MacropaySolutions\LaravelCrudWizardDecorator\Decorators\ExampleDecorator; use MacropaySolutions\LaravelCrudWizardDecorator\Decorators\RelationExampleDecorator; class ExampleMiddleware extends AbstractDecoratorMiddleware { protected string $decoratorClass = ExampleDecorator::class; protected array $relatedDecoratorClassMap = [ 'roleRelation' => RelationExampleDecorator::class ]; public function setResourceModel(): void { $this->resourceModel = new ExampleModel(); } } <?php namespace MacropaySolutions\LaravelCrudWizardDecorator\Http\Middleware\Decorators; use MacropaySolutions\LaravelCrudWizardDecorator\Decorators\ExampleDecorator; use MacropaySolutions\LaravelCrudWizardDecorator\Decorators\RelationExampleDecorator; class RelationExampleMiddleware extends AbstractDecoratorMiddleware { protected string $decoratorClass = RelationExampleDecorator::class; protected array $relatedDecoratorClassMap = [ 'exampleModels' => ExampleDecorator::class ]; public function setResourceModel(): void { $this->resourceModel = new RelationExampleModel(); } } <?php namespace MacropaySolutions\LaravelCrudWizardDecorator\Decorators; class ExampleDecorator extends AbstractResourceDecorator { public function getResourceMappings(): array { return [ 'id' => 'ID', 'updated_at' => 'updatedAt', 'created_at' => 'createdAt', ]; } /** * @inheritDoc */ public function getRelationMappings(): array { return [ 'roleRelation' => [ 'name' => 'roleRelationName', 'color' => 'roleRelationColor', ], ]; } } <?php namespace MacropaySolutions\LaravelCrudWizardDecorator\Decorators; class RelationExampleDecorator extends AbstractResourceDecorator { public array $countRelations = ['exampleModels']; public array $existRelations = ['exampleModels']; public function getResourceMappings(): array { return [ 'id' => 'ID', 'name' => 'roleName', 'color' => 'roleColor', 'updated_at' => 'updatedAt', 'created_at' => 'createdAt', ]; } }
See also Laravel crud wizard decorator demo
III. Crud routes
III.1 Create resource
POST /{resource}
headers:
Authorization: Bearer ... // if needed. not coded in this lib
Accept: application/json
ContentType: application/json
body:
{
"roleID": "1",
}
Json Response:
200:
{
"success": true,
"code": 201,
"locale": "en",
"message": "success",
"data": {
"ID": 3,
"roleID": "1",
"updatedAt": "2022-10-27 09:05:49",
"createdAt": "2022-10-27 09:04:46",
"pki": "3"
}
}
{
"success": false,
"code": 400,
"locale": "en",
"message": "The given data was invalid: The role id field is required.",
"data": {
"roleID": [
"The role id field is required."
]
}
}
III.2 Get resource
GET /{resource}/{identifier}
GET /{resource}/{identifier}/{relation}/{relatedIdentifier}
headers:
Authorization: Bearer ... // if needed. not coded in this lib
Accept: application/json
Json Response:
200:
{
"success": true,
"code": 200,
"locale": "en",
"message": "success",
"data": {
"ID": 3,
"roleID": "1",
"updatedAt": "2022-10-27 09:05:49",
"createdAt": "2022-10-27 09:04:46",
"roleRelationName": "name",
"roleRelationColor": "blue",
"pki": "3"
}
}
{
"success": false,
"code": 400,
"locale": "en",
"message": "Not found",
"data": null
}
III.3 List filtered resource
GET /{resource}?perPage=10&page=2
GET /{resource}/{identifier}/{relation}?... // paid version only
headers:
Authorization: Bearer ... // if needed. not coded in this lib
Accept: application/json or application/xls
The xls will contain undecorated columns and needs special access. See AbstractDecoratorMiddleware::isUserAllowedToDownloadXls
Json Response:
200:
{
"success": true,
"code": 200,
"locale": "en",
"message": "success",
"data": {
"current_page": 1,
"data": [
{
"ID": 3,
"roleID": "1",
"updatedAt": "2022-10-27 09:05:49",
"createdAt": "2022-10-27 09:04:46",
"roleRelationName": "name",
"roleRelationColor": "blue",
"pki": "3"
}
],
"from": 1,
"last_page": 1,
"per_page": 10,
"to": 1,
"total": 1,
"filterable": [
"ID",
"roleID",
"updatedAt",
"createdAt",
],
"sortable": [
"ID",
"roleID"
]
}
}
Binary response for application/xls
III.4 Update resource (or create)
PUT /{resource}/{identifier}
PUT /{resource}/{identifier}/{relation}/{relatedIdentifier}
headers:
Authorization: Bearer ... // if needed. not coded in this lib
Accept: application/json
ContentType: application/json
body:
{
"roleID": "2"
}
Json Response:
200:
{
"success": true,
"code": 200, // or 201 for upsert
"locale": "en",
"message": "success",
"data": {
"ID": 3,
"roleID": "2",
"updatedAt": "2022-10-27 09:05:49",
"createdAt": "2022-10-27 09:04:46",
"pki": "3"
}
}
{
"success": false,
"code": 400,
"locale": "en",
"message": "The given data was invalid: The role id field is required.",
"data": {
"roleID": [
"The role id field is required."
]
}
}
In case of validation errors, only the keys from data object will be decorated!
III.5 Delete resource
DELETE /{resource}/{identifier}
DELETE /{resource}/{identifier}/{relation}/{relatedIdentifier}
headers:
Authorization: Bearer ... // if needed. not coded in this lib
Accept: application/json
Json Response:
200:
{
"success": true,
"code": 204,
"locale": "en",
"message": "success",
"data": null
}
{
"success": false,
"code": 400,
"locale": "en",
"message": "Not found",
"data": null
}