Skip to content

Models and Traits

Braden Keith edited this page Sep 20, 2025 · 1 revision

Models and Traits

Reference guide for models, traits, and interfaces in the Romega Software - Availability package.

Core Trait: HasAvailability

The main trait to add availability support to your models.

Basic Usage

<?php

namespace App\Models;

use Illuminate\Database\Eloquent\Model;
use RomegaSoftware\Availability\Traits\HasAvailability;

class Resource extends Model
{
    use HasAvailability;
}

What It Provides

trait HasAvailability
{
    // Polymorphic relationship to availability rules
    public function availabilityRules(): MorphMany

    // Get timezone for evaluation (override as needed)
    public function getAvailabilityTimezone(): string

    // Get default effect when no rules match (override as needed)
    public function getAvailabilityDefaultEffect(): Effect
}

Customizing the Trait

class Resource extends Model
{
    use HasAvailability;

    // Custom timezone logic
    public function getAvailabilityTimezone(): string
    {
        return $this->timezone
            ?? $this->location->timezone
            ?? config('app.timezone');
    }

    // Dynamic default effect
    public function getAvailabilityDefaultEffect(): Effect
    {
        if ($this->is_premium) {
            return Effect::Allow;
        }

        return Effect::Deny;
    }

    // Custom relationship query
    public function availabilityRules()
    {
        return $this->morphMany(
            config('availability.models.rule'),
            'subject'
        )->where('expires_at', '>', now())
         ->orWhereNull('expires_at');
    }
}

AvailabilitySubject Interface

For complete control, implement the interface directly.

<?php

namespace App\Models;

use Illuminate\Database\Eloquent\Model;
use RomegaSoftware\Availability\Contracts\AvailabilitySubject;
use RomegaSoftware\Availability\Support\Effect;

class CustomResource extends Model implements AvailabilitySubject
{
    public function availabilityRules()
    {
        return $this->morphMany(
            \RomegaSoftware\Availability\Models\AvailabilityRule::class,
            'subject'
        );
    }

    public function getAvailabilityTimezone(): string
    {
        return 'UTC';
    }

    public function getAvailabilityDefaultEffect(): Effect
    {
        return Effect::Allow;
    }
}

AvailabilityRule Model

The model that stores availability rules.

Default Model

namespace RomegaSoftware\Availability\Models;

class AvailabilityRule extends Model
{
    protected $fillable = [
        'type',
        'config',
        'effect',
        'priority',
        'enabled',
    ];

    protected $casts = [
        'config' => 'array',
        'effect' => Effect::class,
        'enabled' => 'boolean',
    ];

    public function subject(): MorphTo
    {
        return $this->morphTo();
    }
}

Extending the Model

namespace App\Models;

use RomegaSoftware\Availability\Models\AvailabilityRule as BaseRule;

class CustomAvailabilityRule extends BaseRule
{
    // Add custom attributes
    protected $fillable = [
        'type',
        'config',
        'effect',
        'priority',
        'enabled',
        'name',           // Custom
        'description',    // Custom
        'created_by',     // Custom
        'expires_at',     // Custom
    ];

    // Add custom casts
    protected $casts = [
        'config' => 'array',
        'effect' => Effect::class,
        'enabled' => 'boolean',
        'expires_at' => 'datetime',
    ];

    // Add custom scopes
    public function scopeActive($query)
    {
        return $query->where('enabled', true)
                    ->where(function ($q) {
                        $q->whereNull('expires_at')
                          ->orWhere('expires_at', '>', now());
                    });
    }

    public function scopeByType($query, $type)
    {
        return $query->where('type', $type);
    }

    // Add custom methods
    public function isExpired(): bool
    {
        return $this->expires_at && $this->expires_at->isPast();
    }

    public function getRemainingDays(): ?int
    {
        if (!$this->expires_at) {
            return null;
        }

        return now()->diffInDays($this->expires_at, false);
    }

    // Add relationships
    public function creator()
    {
        return $this->belongsTo(User::class, 'created_by');
    }
}

Registering Custom Model

In config/availability.php:

'models' => [
    'rule' => \App\Models\CustomAvailabilityRule::class,
],

Effect Enum

The Effect enum represents allow/deny states.

namespace RomegaSoftware\Availability\Support;

enum Effect: string
{
    case Allow = 'allow';
    case Deny = 'deny';

    public function isAllow(): bool
    {
        return $this === self::Allow;
    }

    public function isDeny(): bool
    {
        return $this === self::Deny;
    }
}

Using Effect Enum

use RomegaSoftware\Availability\Support\Effect;

// Creating rules
$rule = $resource->availabilityRules()->create([
    'type' => 'weekdays',
    'config' => ['days' => [1,2,3,4,5]],
    'effect' => Effect::Allow,
    'priority' => 10,
]);

// Checking effect
if ($rule->effect->isAllow()) {
    // Handle allow
}

// Comparing
if ($rule->effect === Effect::Deny) {
    // Handle deny
}

// In database queries
$allowRules = AvailabilityRule::where('effect', Effect::Allow)->get();

Model Patterns

Multi-Model Setup

// Different models with different availability behavior
class Product extends Model
{
    use HasAvailability;

    public function getAvailabilityDefaultEffect(): Effect
    {
        return $this->in_stock ? Effect::Allow : Effect::Deny;
    }
}

class Service extends Model
{
    use HasAvailability;

    public function getAvailabilityDefaultEffect(): Effect
    {
        return Effect::Allow; // Services default to available
    }
}

class Room extends Model
{
    use HasAvailability;

    public function getAvailabilityDefaultEffect(): Effect
    {
        return Effect::Deny; // Rooms must be explicitly made available
    }
}

Nested Resources

class Hotel extends Model
{
    use HasAvailability;

    public function rooms()
    {
        return $this->hasMany(Room::class);
    }

    // Check if any room is available
    public function hasAvailableRoom($moment)
    {
        $engine = app(AvailabilityEngine::class);

        return $this->rooms->contains(function ($room) use ($engine, $moment) {
            return $engine->isAvailable($room, $moment);
        });
    }
}

class Room extends Model
{
    use HasAvailability;

    public function hotel()
    {
        return $this->belongsTo(Hotel::class);
    }

    // Inherit hotel's timezone
    public function getAvailabilityTimezone(): string
    {
        return $this->hotel->timezone ?? config('app.timezone');
    }
}

Composite Availability

class Event extends Model
{
    use HasAvailability;

    public function venue()
    {
        return $this->belongsTo(Venue::class);
    }

    public function equipment()
    {
        return $this->belongsToMany(Equipment::class);
    }

    // Event is available only if venue and all equipment are available
    public function isFullyAvailable($moment)
    {
        $engine = app(AvailabilityEngine::class);

        // Check venue
        if (!$engine->isAvailable($this->venue, $moment)) {
            return false;
        }

        // Check all equipment
        foreach ($this->equipment as $item) {
            if (!$engine->isAvailable($item, $moment)) {
                return false;
            }
        }

        // Check event itself
        return $engine->isAvailable($this, $moment);
    }
}

Model Observers

Automatic Rule Creation

namespace App\Observers;

use App\Models\Resource;
use RomegaSoftware\Availability\Support\Effect;

class ResourceObserver
{
    public function created(Resource $resource)
    {
        // Automatically create default rules
        if ($resource->type === 'meeting_room') {
            $resource->availabilityRules()->createMany([
                [
                    'type' => 'weekdays',
                    'config' => ['days' => [1,2,3,4,5]],
                    'effect' => Effect::Allow,
                    'priority' => 10,
                ],
                [
                    'type' => 'time_of_day',
                    'config' => ['from' => '08:00', 'to' => '18:00'],
                    'effect' => Effect::Allow,
                    'priority' => 20,
                ],
            ]);
        }
    }

    public function deleting(Resource $resource)
    {
        // Clean up availability rules
        $resource->availabilityRules()->delete();
    }
}

Cache Invalidation

class AvailabilityRuleObserver
{
    public function saved(AvailabilityRule $rule)
    {
        $this->invalidateCache($rule);
    }

    public function deleted(AvailabilityRule $rule)
    {
        $this->invalidateCache($rule);
    }

    private function invalidateCache(AvailabilityRule $rule)
    {
        Cache::tags([
            'availability',
            "subject-{$rule->subject_type}-{$rule->subject_id}",
        ])->flush();
    }
}

Database Schema

Basic Schema

Schema::create('availability_rules', function (Blueprint $table) {
    $table->id();
    $table->morphs('subject'); // subject_type, subject_id
    $table->string('type', 50);
    $table->json('config')->nullable();
    $table->string('effect', 10);
    $table->integer('priority')->default(50);
    $table->boolean('enabled')->default(true);
    $table->timestamps();

    $table->index(['subject_type', 'subject_id', 'enabled', 'priority']);
});

Extended Schema

Schema::create('availability_rules', function (Blueprint $table) {
    $table->id();
    $table->morphs('subject');
    $table->string('type', 50);
    $table->json('config')->nullable();
    $table->string('effect', 10);
    $table->integer('priority')->default(50);
    $table->boolean('enabled')->default(true);

    // Additional fields
    $table->string('name')->nullable();
    $table->text('description')->nullable();
    $table->timestamp('starts_at')->nullable();
    $table->timestamp('expires_at')->nullable();
    $table->foreignId('created_by')->nullable()->constrained('users');
    $table->foreignId('updated_by')->nullable()->constrained('users');
    $table->json('metadata')->nullable();

    $table->timestamps();
    $table->softDeletes();

    // Indexes
    $table->index(['subject_type', 'subject_id', 'enabled', 'priority']);
    $table->index(['starts_at', 'expires_at']);
    $table->index('type');
});

Next Steps

Getting Started

Installation
Set up the package in your Laravel app

Quick Start
Get running in 5 minutes

Basic Usage
Common patterns and examples


Core Concepts

How It Works
Understanding the evaluation engine

Rule Types
Available rule types and configurations

Priority System
How rule priority affects evaluation


Advanced Topics

Inventory Gates
Dynamic availability based on stock

Custom Evaluators
Build your own rule types

Complex Scenarios
Real-world implementation patterns

Performance Tips
Optimization strategies


API Reference

Configuration
Package configuration options

Models & Traits
Available models and traits

Testing
Testing your availability rules

Clone this wiki locally