Milk Admin

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

📘 Important: Relationships are defined on the field that acts as the key. For more details, see Relationships Documentation.
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

Loading...