RuleBuilder - Schema Configuration
Revision: 2025/10/31
The RuleBuilder class provides a fluent interface for defining model schemas in the configure() method. It allows you to configure table structure, field types, validation rules, form behaviors, and relationships.
💡 Key Concept: RuleBuilder is used inside the configure($rule) method of your Model to define all field properties, database schema, and UI behaviors in one place.
Basic Usage
protected function configure($rule): void {
$rule->table('#__products') // Define table name
->id() // Auto-increment primary key
->string('name', 100)->required() // VARCHAR(100) NOT NULL
->decimal('price', 10, 2)->default(0) // DECIMAL(10,2) DEFAULT 0
->text('description')->nullable() // TEXT NULL
->boolean('in_stock')->default(true) // TINYINT(1) DEFAULT 1
->created_at(); // DATETIME with auto-preservation
}
Configuration Methods
Core Configuration
| Method |
Description |
Example |
table(string $name) |
Define the database table name (use #__ prefix) |
$rule->table('#__products') |
db(string $name) |
Set database connection type ('db' or 'db2') |
$rule->db('db2') |
renameField(string $from, string $to) |
Rename a column during schema updates |
$rule->renameField('full_name', 'name') |
Field Type Methods
Basic Types
| Method |
SQL Type |
Description |
Example |
id(string $name = 'id') |
INT AUTO_INCREMENT |
Primary key (auto-increment, hidden in forms) |
$rule->id() |
string(string $name, int $length = 255) |
VARCHAR(length) |
String field with max length |
$rule->string('name', 100) |
title(string $name = 'title', int $length = 255) |
VARCHAR(length) |
Title field (auto-used in belongsTo relationships) |
$rule->title() |
text(string $name) |
TEXT |
Long text field |
$rule->text('description') |
int(string $name) |
INT |
Integer number |
$rule->int('quantity') |
decimal(string $name, int $length = 10, int $precision = 2) |
DECIMAL(length, precision) |
Decimal number with precision |
$rule->decimal('price', 10, 2) |
boolean(string $name) |
TINYINT(1) |
Boolean/checkbox field |
$rule->boolean('is_active') |
array(string $name) |
TEXT (JSON) |
Array stored as JSON |
$rule->array('metadata') |
Date/Time Types
| Method |
SQL Type |
Description |
Example |
datetime(string $name) |
DATETIME |
Date and time |
$rule->datetime('published_at') |
date(string $name) |
DATE |
Date only |
$rule->date('birth_date') |
time(string $name) |
TIME |
Time only |
$rule->time('open_time') |
timestamp(string $name) |
DATETIME |
Timestamp (alias for datetime) |
$rule->timestamp('created') |
created_at(string $name = 'created_at') |
DATETIME |
Auto-preserved creation timestamp (hidden from edit) |
$rule->created_at() |
Special Input Types
| Method |
Description |
Example |
email(string $name) |
Email field with validation |
$rule->email('email') |
tel(string $name) |
Telephone field |
$rule->tel('phone') |
url(string $name) |
URL field with validation |
$rule->url('website') |
file(string $name) |
File upload field |
$rule->file('attachment') |
image(string $name) |
Image upload field (accepts images only) |
$rule->image('photo') |
Selection Types
| Method |
Description |
Example |
list(string $name, array $options) |
Dropdown/select field |
$rule->list('status', ['active' => 'Active', 'inactive' => 'Inactive']) |
select(string $name, array $options) |
Alias for list() |
$rule->select('category', $categories) |
enum(string $name, array $options) |
Enum field (database-level constraint) |
$rule->enum('size', ['S', 'M', 'L', 'XL']) |
radio(string $name, array $options) |
Radio buttons |
$rule->radio('gender', ['M' => 'Male', 'F' => 'Female']) |
checkbox(string $name) |
Single checkbox (alias for boolean) |
$rule->checkbox('agree_terms') |
checkboxes(string $name, array $options) |
Multiple checkboxes (stored as array) |
$rule->checkboxes('features', ['wifi' => 'WiFi', 'parking' => 'Parking']) |
Field Configuration Methods
Validation & Constraints
| Method |
Description |
Example |
required() |
Make field required |
$rule->string('name', 100)->required() |
nullable(bool $nullable = true) |
Allow NULL values |
$rule->text('bio')->nullable() |
default($value) |
Set default value |
$rule->int('views')->default(0) |
saveValue($value) |
Set a value that will always be saved |
$rule->datetime('updated_at')->saveValue(date('Y-m-d H:i:s')) |
unique() |
Add UNIQUE constraint |
$rule->email('email')->unique() |
index() |
Add database index |
$rule->int('user_id')->index() |
unsigned() |
Make numeric field unsigned |
$rule->int('quantity')->unsigned() |
min($value) |
Set minimum value (numeric/date/time) or minimum length (string/text). You can also pass another field name for backend comparison. |
$rule->int('age')->min(18)
$rule->string('name', 100)->min(3)
$rule->date('start')->max('end') |
max($value) |
Set maximum value (numeric/date/time) or maximum length (string/text). You can also pass another field name for backend comparison. |
$rule->int('quantity')->max(100)
$rule->string('title', 200)->max(120)
$rule->int('min_members')->max('max_members') |
step($value) |
Set step value for numeric inputs |
$rule->decimal('price', 10, 2)->step(0.01) |
Display & Labeling
| Method |
Description |
Example |
label(string $label) |
Set display label |
$rule->string('first_name', 50)->label('First Name') |
formLabel(string $label) |
Set form-specific label |
$rule->text('content')->formLabel('Article Content') |
hide() |
Hide from list/table view, edit form and detail view |
$rule->text('notes')->hide() |
hideFromList() |
Hide from list/table view |
$rule->text('notes')->hideFromList() |
hideFromEdit() |
Hide from edit form |
$rule->created_at()->hideFromEdit() |
hideFromView() |
Hide from detail view |
$rule->string('password', 255)->hideFromView() |
excludeFromDatabase() |
Don't create in database (virtual field) |
$rule->string('computed_value', 100)->excludeFromDatabase() |
Form Configuration
| Method |
Description |
Example |
formType(string $type) |
Set HTML form input type |
$rule->string('slug', 100)->formType('hidden') |
formParams(array $params) |
Set form parameters (HTML attributes) |
$rule->text('bio')->formParams(['rows' => 5, 'cols' => 50]) |
error(string $message) |
Set validation error message |
$rule->email('email')->error('Please enter a valid email address') |
File Upload Configuration
| Method |
Description |
Example |
multiple(bool|int $multiple = true) |
Allow multiple file uploads |
$rule->image('photos')->multiple(5) |
maxFiles(int $max) |
Set maximum number of files |
$rule->file('documents')->maxFiles(10) |
accept(string $accept) |
Set accepted file types |
$rule->file('doc')->accept('.pdf,.doc,.docx') |
maxSize(int $size) |
Set max file size in bytes |
$rule->image('avatar')->maxSize(2097152) (2MB) |
uploadDir(string $dir) |
Set upload directory |
$rule->image('photo')->uploadDir('/uploads/photos') |
Dynamic Options
| Method |
Description |
Example |
options(array $options) |
Set options for list/select/enum fields |
$rule->list('status', [])->options($this->getStatusOptions()) |
apiUrl(string $url, ?string $display_field = null) |
Set API URL for dynamic options loading |
$rule->int('category_id')->apiUrl('/api/categories', 'name') |
Relationships
| Method |
Description |
Example |
belongsTo(string $alias, string $related_model, ?string $related_key = 'id') |
Define a belongsTo relationship (foreign key in THIS table) |
$rule->int('user_id')->belongsTo('user', UserModel::class) |
hasOne(string $alias, string $related_model, string $foreign_key_in_related, string $onDelete = 'CASCADE') |
Define a hasOne relationship (foreign key in RELATED table) |
$rule->id()->hasOne('profile', ProfileModel::class, 'user_id') |
hasMany(string $alias, string $related_model, string $foreign_key_in_related, string $onDelete = 'CASCADE') |
Define a hasMany relationship (foreign key in RELATED table) |
$rule->id()->hasMany('posts', PostModel::class, 'user_id') |
withCount(string $alias, string $related_model, string $foreign_key_in_related) |
Add a COUNT subquery (virtual field, read-only, always included in queries) |
$rule->id()->withCount('posts_count', PostModel::class, 'user_id') |
Relationship Example
// PostsModel
protected function configure($rule): void {
$rule->table('#__posts')
->id()
->string('title', 200)->required()
->int('user_id')->belongsTo('author', UserModel::class) // Foreign key here
->text('content');
}
// UserModel
protected function configure($rule): void {
$rule->table('#__users')
->id()
->hasMany('posts', PostModel::class, 'user_id') // Load actual posts
->withCount('posts_count', PostModel::class, 'user_id') // Count posts efficiently
->title('username', 50)->required()
->email('email');
}
// Usage
$user = $userModel->getById(1);
echo $user->posts_count; // 15 (fast COUNT subquery)
$posts = $user->posts; // Array of PostModel (lazy loaded)
Advanced Customization
Custom Getters, Setters & Editors
| Method |
Description |
Example |
getter(callable $fn) |
Custom getter function (for formatted display) |
$rule->string('name', 100)->getter(fn($v) => strtoupper($v)) |
setter(callable $fn) |
Custom setter function (before save to DB) |
$rule->string('slug', 100)->setter(fn($v) => slugify($v)) |
editor(callable $fn) |
Custom editor function (for form display) |
$rule->text('bio')->editor(fn($v) => htmlspecialchars($v)) |
Custom Properties
| Method |
Description |
Example |
property(string $key, $value) |
Set a custom property |
$rule->string('code', 20)->property('uppercase', true) |
properties(array $properties) |
Set multiple custom properties at once |
$rule->text('content')->properties(['editor' => 'wysiwyg', 'toolbar' => 'full']) |
customize(callable $callback) |
Customize field with a callback |
$rule->int('age')->customize(fn($field) => [...$field, 'custom' => true]) |
Utility Methods
| Method |
Description |
Returns |
getRules(): array |
Get all defined field rules |
Array of field configurations |
setRules(array $rules): self |
Set all rules at once |
RuleBuilder instance |
clear(): self |
Clear all rules |
RuleBuilder instance |
getTable(): ?string |
Get the table name |
Table name or null |
getPrimaryKey(): ?string |
Get the primary key name |
Primary key field name or null |
getDbType(): ?string |
Get database connection type |
'db' or 'db2' |
changeCurrentField(string $name) |
Switch to configuring a different field |
void |
changeType(string $name, string $type): self |
Change the type of an existing field |
RuleBuilder instance |
Complete Example
namespace Modules\Shop;
use App\Abstracts\AbstractModel;
use Modules\Users\UsersModel;
use Modules\Categories\CategoriesModel;
class ProductsModel extends AbstractModel
{
protected function configure($rule): void {
$rule->table('#__shop_products')
// Primary key
->id()
// Basic fields
->title('name', 200)->required()
->string('sku', 50)->unique()->required()
// Relationships
->int('category_id')
->belongsTo('category', CategoriesModel::class)
->required()
->index()
->int('created_by')
->belongsTo('author', UsersModel::class)
->hideFromEdit()
// Pricing
->decimal('price', 10, 2)
->default(0)
->min(0)
->step(0.01)
->required()
->decimal('sale_price', 10, 2)
->nullable()
->min(0)
// Description
->text('description')->nullable()
->text('short_description')->nullable()->hideFromList()
// Stock management
->int('stock_quantity')->default(0)->unsigned()
->boolean('track_inventory')->default(true)
->boolean('in_stock')->default(true)
// Status
->enum('status', ['draft', 'published', 'archived'])
->default('draft')
// Media
->image('featured_image')
->accept('image/*')
->maxSize(5242880) // 5MB
->uploadDir('/uploads/products')
->nullable()
->file('gallery')
->multiple(10)
->accept('image/*')
->nullable()
// Metadata
->array('meta_data')->nullable()->excludeFromDatabase()
// Timestamps
->created_at()
->datetime('updated_at')->nullable()->hideFromEdit();
}
}
Field Rule Structure
Each field rule contains the following properties:
[
'type' => 'string', // PHP and SQL type
'length' => 100, // Max length for strings
'precision' => 2, // Precision for floats/decimals
'nullable' => true, // Can be NULL
'default' => null, // Default value
'primary' => false, // Is primary key
'label' => 'Field Name', // Display label
'options' => [], // Options for list/enum
'index' => false, // Create database index
'unique' => false, // UNIQUE constraint
'unsigned' => false, // Unsigned numeric
'list' => true, // Show in list view
'edit' => true, // Show in edit form
'view' => true, // Show in detail view
'sql' => true, // Create in database
'form-type' => 'text', // HTML input type
'form-label' => null, // Form-specific label
'form-params' => [], // HTML attributes
'relationship' => [], // Relationship config
'api_url' => null, // API endpoint for options
'save_value' => null, // Value to always save
'_auto_created_at' => false, // Auto-preserve on update
'_is_title_field' => false, // Used in belongsTo display
'_get' => callable, // Custom getter
'_set' => callable, // Custom setter
'_edit' => callable, // Custom editor
]
Tests
RuleBuilder has a dedicated unit test suite:
php vendor/bin/phpunit tests/Unit/Builders/RuleBuilderMethodsTest.php
Next Steps
📚 Related Documentation: