Advanced Features
Auto CRUD
Create, Read, Update, Delete operations are among the most common features in information system development.
Overview
Typical CRUD features include:
- A page containing a list of data, usually displayed in a datatable complete with searching, sorting, and pagination.
- Buttons to add, edit, and delete data.
With Auto CRUD, these features don't need to be coded at all. Simply "define" what fields need to be displayed from an Eloquent Model, and a complete CRUD feature can automatically be accessed through the application without coding.
Installation
Enable Auto CRUD
Make sure auto-crud
has been enabled:
config/laravolt/platform.php
<?phpreturn [ 'middleware' => ['web', 'auth'], 'features' => [ 'auto-crud' => true, ],];
Define Your Model
Define your Eloquent Model including its relationships. The Model here is a regular Eloquent class, just like what we usually define in Laravel. The Model is used to interact with the database in the CRUD process.
class User extends Model{ public function country() { return $this->belongsTo("App\\Models\\Country"); }}
This relationship is optional, but very useful when there are 2 or more models that are interconnected, such as Post and Category. Please read Laravel's official documentation on Eloquent Relationships.
Register Configuration File
Create a new configuration file in the config/laravolt/auto-crud-resources
folder. The configuration file defines which fields will be displayed in:
- Forms, including validation
- Tables, including
searchable
andsortable
flags - Detail pages
Here's a minimal example of a configuration file to create a CRUD based on the User model:
config/laravolt/auto-crud-resources/user.php
return [ 'label' => 'User', 'model' => \App\Models\User::class, // optional, if you want to override the default Table //'table' => \App\Http\Livewire\Table\UserCustomTable::class 'schema' => [ [ 'name' => 'name', 'type' => \Laravolt\Fields\Field::TEXT, 'label' => 'Full Name', ], [ 'name' => 'email', 'type' => \Laravolt\Fields\Field::EMAIL, 'label' => 'Email', ], [ 'name' => 'password', 'type' => \Laravolt\Fields\Field::PASSWORD, 'label' => 'Password', ], ],];
Customization
Table Customization
Create a Table
php artisan make:table UserCustomTable
Define Columns
Modify the skeleton class to look like this:
<?phpnamespace App\Http\Livewire\Table;use Laravolt\AutoCrud\Tables\ResourceTable;use Laravolt\Suitable\Columns\Label;class UserCustomTable extends ResourceTable{ protected function prepareColumns(): array { // keep default columns, call parent method $columns = parent::prepareColumns(); // or override and prepare columns from scratch // $columns = []; // Define additional columns here as needed $columns[] = Label::make('status', 'Status'); return $columns; }}
Column documentation can be found at https://laravolt.dev/docs/v6/table/.
Add the following entry to your configuration file:
config/laravolt/auto-crud-resources/user.php
return [ // ...existing configuration 'table' => \App\Http\Livewire\Table\UserCustomTable::class // ...];
Refresh Your Browser!
Available Options
label
The text that will be displayed in the menu.
model
The Eloquent Model class that will be called for each CRUD action performed.
restful_button
By default, each row will have three buttons: detail, edit, and delete (restful button). To hide these buttons, change the restful_button
option to false
:
'restful_button' => false,
schema
Defines which fields need to be displayed. The minimum entries that must exist are name
, type
, and label
:
'schema' => [ [ 'name' => 'fullname', 'type' => 'text', 'label' => 'Full Name', ], [ // others fields... ]]
You can refer to the \Laravolt\Fields\Field
interface to see the available type
values:
// Input Elements\Laravolt\Fields\Field::BOOLEAN;\Laravolt\Fields\Field::CHECKBOX;\Laravolt\Fields\Field::CHECKBOX_GROUP;\Laravolt\Fields\Field::COLOR;\Laravolt\Fields\Field::DATE;\Laravolt\Fields\Field::DATE_PICKER;\Laravolt\Fields\Field::DATETIME_PICKER;\Laravolt\Fields\Field::DROPDOWN;\Laravolt\Fields\Field::DROPDOWN_COLOR;\Laravolt\Fields\Field::DROPDOWN_DB;\Laravolt\Fields\Field::EMAIL;\Laravolt\Fields\Field::HIDDEN;\Laravolt\Fields\Field::NUMBER;\Laravolt\Fields\Field::MULTIROW;\Laravolt\Fields\Field::PASSWORD;\Laravolt\Fields\Field::RADIO_GROUP;\Laravolt\Fields\Field::REDACTOR;\Laravolt\Fields\Field::RUPIAH;\Laravolt\Fields\Field::TEXT;\Laravolt\Fields\Field::TEXTAREA;\Laravolt\Fields\Field::TIME;\Laravolt\Fields\Field::UPLOADER;// Other Elements\Laravolt\Fields\Field::ACTION;\Laravolt\Fields\Field::BUTTON;\Laravolt\Fields\Field::HTML;\Laravolt\Fields\Field::RESTFUL_BUTTON;\Laravolt\Fields\Field::SEGMENT;\Laravolt\Fields\Field::SUBMIT;// Relationship\Laravolt\Fields\Field::BELONGS_TO;
Visibility
For each field, the following options can be used to determine whether the field needs to be displayed or not. By default all are true.
'show_on_index' => false,'show_on_detail' => true,'show_on_create' => true,'show_on_edit' => true,
Sorting
To determine whether a field can be sorted or not, you can use the sortable
option. For Input Elements type fields, by default sortable
will be true
. To disable sorting, simply set it to false
:
'sortable' => false,
For Relationship type fields, the default value is false
. To enable sorting, we need to set it to the column name that will be used for sorting on the related table:
// sort by "name" column on the "countries" table (or whatever the table name is, according to the relationship definition)'type' => Laravolt\Fields\Field::BELONGS_TO,'name' => 'country','sortable' => 'name','label' => 'Country',
By default, the Laravolt\Fields\Field::BELONGS_TO
field will display data from its relation model in JSON format. To change this, add a public display() method to the relation model.
class Country extends Model{ public function display() { //display data from the name column return $this->name; }}
When adding sorting to a Relationship field, we also need to add the Laravolt\Suitable\AutoSort
trait to the original model, which is \App\Models\User
:
use Laravolt\Suitable\AutoSort;class User extends Model{ use AutoSort; public function country() { return $this->belongsTo("App\\Models\\Country"); }}
The AutoSort
trait will automatically modify the query to be able to sort on related tables using the join mechanism.
Searching
To determine whether a field can be searched or not, you can use the searchable
option. For Input Elements type fields, by default searchable
will be true
. If you want to remove a column from the search list, simply set it to false
:
'searchable' => false,
For Relationship type fields, the default value is false
. To enable searching on the related table, we need to set it to the appropriate column name:
// search will also be done on the countries.name column'type' => Laravolt\Fields\Field::BELONGS_TO,'name' => 'country','searchable' => 'name','label' => 'Country',
Form Validation
To add validation, simply add "rules":
'rules' => ['required', 'size:10'],
Using Rule Objects
You can also use Laravel's Rule objects for more complex validation:
'rules' => ['required', \Illuminate\Validation\Rule::unique('users', 'name')],
However, when using Rule objects in configuration files, be aware of potential serialization issues when running php artisan config:cache
. Laravel's Rule
objects are not directly serializable. To avoid this issue, you have two options:
Use string representation (Recommended for config files):
PHP'rules' => ['required', 'unique:users,name'],Use Rule objects (Available but requires proper handling):
PHP'rules' => ['required', \Illuminate\Validation\Rule::unique('users', 'name')],If you choose to use Rule objects, ensure your application correctly handles the serialization process or avoid using
config:cache
.
For comprehensive validation examples, see the Advanced Configuration section.
All validations available in Laravel can be applied here.
Field Types (Schema)
Boolean
[ 'type' => \Laravolt\Fields\Field::BOOLEAN, 'name' => 'approved', 'label' => 'Approved?',],
Checkbox
[ 'type' => \Laravolt\Fields\Field::CHECKBOX, 'name' => 'remember', 'label' => 'Remember Me',],
Checkbox Group
[ 'type' => \Laravolt\Fields\Field::CHECKBOX_GROUP, 'name' => 'education', 'label' => 'Last Education', 'options' => ['elementary', 'junior high', 'high school', 'undergraduate'], 'inline' => false, // false (default value) or true],
Color
[ 'type' => \Laravolt\Fields\Field::COLOR, 'name' => 'color', 'label' => 'Color',],
Date
[ 'type' => \Laravolt\Fields\Field::DATE, 'name' => 'date', 'label' => 'Date',],
Date Picker
[ 'type' => \Laravolt\Fields\Field::DATE_PICKER, 'name' => 'datepicker', 'label' => 'Date Picker',],
Datetime Picker
[ 'type' => \Laravolt\Fields\Field::DATETIME_PICKER, 'name' => 'datetimepicker', 'label' => 'Datetime Picker',],
Dropdown
[ 'type' => \Laravolt\Fields\Field::DROPDOWN, 'name' => 'dropdown', 'label' => 'Dropdown', 'options' => [1 => 'Elementary', 2 => 'Junior High', 3 => 'High School'],],
Dropdown Color
[ 'type' => \Laravolt\Fields\Field::DROPDOWN_COLOR, 'name' => 'dropdown_color', 'label' => 'Dropdown Color',],
Dropdown DB
Dropdown with option choices obtained from database query results.
[ 'type' => \Laravolt\Fields\Field::DROPDOWN_DB, 'name' => 'dropdown_db', 'label' => 'Dropdown DB', 'query' => 'select id, name from users limit 10'],
[ 'type' => \Laravolt\Fields\Field::EMAIL, 'name' => 'email', 'label' => 'Email',],
Hidden
[ 'type' => \Laravolt\Fields\Field::HIDDEN, 'name' => 'user_id',],
Number
[ 'type' => \Laravolt\Fields\Field::NUMBER, 'name' => 'number', 'label' => 'Number',],
Multirow
//TODO
Password
[ 'type' => \Laravolt\Fields\Field::PASSWORD, 'name' => 'password', 'label' => 'Password',],
Radio Group
[ 'type' => \Laravolt\Fields\Field::RADIO_GROUP, 'name' => 'radio_group', 'label' => 'Radio Group', 'options' => [1 => 'Elementary', 2 => 'Junior High', 3 => 'High School'], 'inline' => false, // false (default value) or true],
Redactor
[ 'type' => \Laravolt\Fields\Field::REDACTOR, 'name' => 'article', 'label' => 'Article',],
Restful Button
[ 'type' => \Laravolt\Fields\Field::RESTFUL_BUTTON, 'show_on_create' => false, 'show_on_edit' => false, 'show_on_detail' => false, 'only' => ['show'], // available options: 'show', 'edit', 'destroy']
Rupiah
[ 'type' => \Laravolt\Fields\Field::RUPIAH, 'name' => 'rupiah', 'label' => 'Rupiah',],
Text
[ 'type' => \Laravolt\Fields\Field::TEXT, 'name' => 'text', 'label' => 'Text',],
Textarea
[ 'type' => \Laravolt\Fields\Field::TEXTAREA, 'name' => 'textarea', 'label' => 'Textarea',],
Time
[ 'type' => \Laravolt\Fields\Field::TIME, 'name' => 'time', 'label' => 'Time (Standard Browser)',],
Uploader
[ 'name' => 'profile_picture', 'type' => \Laravolt\Fields\Field::UPLOADER, 'label' => 'Profile Picture', 'limit' => 1, // maximum number of files that can be uploaded 'extensions' => ['jpg', 'png'], 'fileMaxSize' => 5, // in MB],
Autofill Value
We can add parameters in the URL query string to provide default values to certain fields. For example, if we have a form schema like this:
[ 'type' => \Laravolt\Fields\Field::TEXT, 'name' => 'name', 'label' => 'Name',],
To provide an initial value to the form, we can add a query string as follows:
http://localhost/resource/user/create?name=John
When the user create page is opened for the first time, the name
field will automatically be filled with the value "John".
Advanced Configuration Considerations
Serialization of Configuration Files
When running php artisan config:cache
in Laravel, all configuration files are serialized into a single cached file. This can cause issues with certain object types that are not serializable, such as validation Rule
objects.
Error Symptoms
If you're using Rule objects in your config files, you might encounter this error when running php artisan config:cache
or php artisan optimize
:
LogicException: Your configuration files are not serializable.Error::("Call to undefined method Illuminate\Validation\Rules\Unique::__set_state()")
Handling Non-Serializable Rule Objects
To resolve the serialization issue, consider these solutions:
Use string representation for validation rules (Simplest approach):
PHP// Instead of:'rules' => ['required', \Illuminate\Validation\Rule::unique('users', 'name')],// Use:'rules' => ['required', 'unique:users,name'],This is the most straightforward solution and works in all cases.
Create a custom service provider that converts Rule objects to strings:
PHP<?phpnamespace App\Providers;use Illuminate\Support\ServiceProvider;use Illuminate\Validation\Rules\Unique;class AutoCrudRuleSerializationProvider extends ServiceProvider{public function boot(){if (!$this->app->configurationIsCached()) {$this->processAutoCrudResources();}}protected function processAutoCrudResources(): void{$resources = config('laravolt.auto-crud-resources', []);if (empty($resources)) {return;}$processed = [];foreach ($resources as $key => $resource) {$processed[$key] = $this->processResource($resource);}// Update the config repository with the processed resourcesconfig(['laravolt.auto-crud-resources' => $processed]);}protected function processResource(array $resource): array{if (!isset($resource['schema']) || !is_array($resource['schema'])) {return $resource;}foreach ($resource['schema'] as $index => $field) {if (isset($field['rules']) && is_array($field['rules'])) {$resource['schema'][$index]['rules'] = $this->processRules($field['rules']);}}return $resource;}protected function processRules(array $rules): array{return array_map(function ($rule) {if (is_array($rule)) {return $this->processRules($rule);}if ($rule instanceof Unique) {// Convert Unique rule to string formatreturn $this->uniqueRuleToString($rule);}return $rule;}, $rules);}protected function uniqueRuleToString(Unique $rule): string{// Extract table and column using reflection$table = $this->extractProperty($rule, 'table', 'unknown_table');$column = $this->extractProperty($rule, 'column', 'NULL');return "unique:{$table},{$column}";}protected function extractProperty($object, $property, $default = null){try {$reflector = new \ReflectionClass($object);$prop = $reflector->getProperty($property);$prop->setAccessible(true);$value = $prop->getValue($object);return $value ?: $default;} catch (\Exception $e) {return $default;}}config(['laravolt.auto-crud-resources' => $processed]);}// Additional helper methods...}Then register this provider in your
config/app.php
orbootstrap/providers.php
file.
Best Practices Summary
For optimal experience with Auto CRUD validation rules and Laravel's config cache:
In development environments:
- You can use Rule objects directly for better IDE support and type safety
- Avoid running
php artisan config:cache
or disable it withphp artisan config:clear
In production environments:
- Use string-based validation rules in your configuration files
- Or implement a rule serialization service provider as shown above
- Always test your
php artisan optimize
command before deploying
For rule objects with special features: When you need features like
ignore()
for unique rules during updates, create a helper class that converts string rules back to Rule objects at runtime:PHP<?phpnamespace App\Support;use Illuminate\Support\Str;use Illuminate\Validation\Rule;class RuleHelper{/*** Convert rule strings back to Rule objects during runtime validation.*/public static function resolveRules(array $rules): array{return array_map(function($rule) {if (is_array($rule)) {return self::resolveRules($rule);}if (!is_string($rule)) {return $rule;}// Convert unique:table,column back to Rule::unique() objectif (Str::startsWith($rule, 'unique:')) {$parts = explode(':', $rule, 2);if (isset($parts[1])) {$params = explode(',', $parts[1]);$table = $params[0] ?? null;$column = $params[1] ?? null;if ($table) {return Rule::unique($table, $column);}}}return $rule;}, $rules);}}
This approach gives you the best of both worlds: serializable configurations and the full power of Laravel's validation Rule objects when needed.