pixielity/laravel-database

Database utilities and extensions for Laravel

Installs: 0

Dependents: 0

Suggesters: 0

Security: 0

Stars: 0

Forks: 0

pkg:composer/pixielity/laravel-database

dev-main 2026-02-09 14:03 UTC

This package is auto-updated.

Last update: 2026-02-09 10:03:45 UTC


README

Enhanced database utilities and traits for Laravel applications.

Features

1. InMemoryModel Trait

Create Eloquent models backed by array data stored in SQLite (in-memory or file-cached).

2. DataObjectModel Base Class

Eloquent model with DataObject integration for magic getter/setter support.

3. InMemoryDataModel Base Class

Combines InMemoryModel + DataObjectModel for the best of both worlds.

Perfect for:

  • Static/seed data (roles, statuses, countries)
  • Configuration data stored in code
  • Testing with fixture data
  • Small lookup tables
  • Enum-like data structures

Installation

The package is part of the Pixielity framework and is automatically available.

Quick Start

Option 1: InMemoryModel Trait (Basic)

use Pixielity\Database\Traits\InMemoryModel;
use Illuminate\Database\Eloquent\Model;

class Role extends Model
{
    use InMemoryModel;

    protected array $rows = [
        ['id' => 1, 'name' => 'Admin', 'level' => 10],
        ['id' => 2, 'name' => 'Editor', 'level' => 5],
        ['id' => 3, 'name' => 'User', 'level' => 1],
    ];
}

// Use like any Eloquent model
$admin = Role::find(1);
$editors = Role::where('level', '>=', 5)->get();
$all = Role::all();

With Custom Schema

class Status extends Model
{
    use ArrayBackedModel;

    protected array $rows = [
        ['id' => 1, 'name' => 'Active', 'color' => '#00FF00', 'active' => true],
        ['id' => 2, 'name' => 'Inactive', 'color' => '#FF0000', 'active' => false],
    ];

    protected array $schema = [
        'id' => 'integer',
        'name' => 'string',
        'color' => 'string',
        'active' => 'boolean',
    ];
}

With Dynamic Data

class Country extends Model
{
    use ArrayBackedModel;

    public function getRows(): array
    {
        return json_decode(
            file_get_contents(resource_path('data/countries.json')),
            true
        );
    }

    protected array $schema = [
        'id' => 'integer',
        'code' => 'string',
        'name' => 'string',
    ];
}

With Relationships

class Category extends Model
{
    use ArrayBackedModel;

    protected array $rows = [
        ['id' => 1, 'name' => 'Electronics'],
        ['id' => 2, 'name' => 'Books'],
        ['id' => 3, 'name' => 'Clothing'],
    ];

    public function products()
    {
        return $this->hasMany(Product::class);
    }
}

// Use relationships normally
$category = Category::find(1);
$products = $category->products;

Configuration

Global Configuration

// config/database.php

'array_backed' => [
    // Cache directory for SQLite files
    'cache_path' => storage_path('framework/cache/models'),

    // Cache file prefix
    'cache_prefix' => 'array-model',
],

Per-Model Configuration

class Role extends Model
{
    use ArrayBackedModel;

    // Custom insert chunk size (default: 100)
    protected int $insertChunkSize = 50;

    // Disable caching (always use in-memory)
    protected bool $shouldCache = false;

    // Custom data
    protected array $rows = [...];

    // Custom schema
    protected array $schema = [...];
}

Schema Types

Supported column types:

TypeDescriptionExample
integerInteger numbers1, 42, -10
bigIntegerLarge integers9223372036854775807
floatFloating point3.14, 2.5
doubleDouble precision3.141592653589793
decimalDecimal numbers19.99, 100.50
stringText strings"Hello", "World"
textLong textLarge text content
booleanTrue/falsetrue, false
dateDate only2024-01-01
dateTimeDate and time2024-01-01 12:00:00
timestampUnix timestamp1704067200
timeTime only12:00:00
jsonJSON data{"key": "value"}

Automatic Type Inference

If you don't provide a schema, types are automatically inferred:

protected array $rows = [
    [
        'id' => 1,              // → integer
        'name' => 'Admin',      // → string
        'level' => 10,          // → integer
        'rate' => 99.99,        // → float
        'active' => true,       // → boolean
        'created_at' => new DateTime(), // → dateTime
    ],
];

Caching Strategy

Cache States

  1. Cache Hit (Fast - ~1-5ms)

    • Cache file exists
    • Cache is newer than model file
    • Uses existing SQLite file
  2. Cache Miss (Rebuild - ~50-100ms)

    • Cache file doesn't exist OR
    • Cache is older than model file
    • Rebuilds SQLite file
  3. No Cache (In-Memory - ~10-20ms)

    • Caching disabled OR
    • Cache directory not writable
    • Uses in-memory SQLite

Cache Invalidation

Cache is automatically invalidated when:

  • Model file is modified
  • $rows property changes
  • getRows() method changes

Manual Cache Clear

# Clear all array-backed model caches
rm storage/framework/cache/array-model-*.sqlite

# Clear specific model cache
rm storage/framework/cache/array-model-app-models-role.sqlite

Advanced Usage

Custom Table Modifications

class Role extends Model
{
    use ArrayBackedModel;

    protected array $rows = [...];

    protected function afterMigrate(Blueprint $table): void
    {
        // Add indexes
        $table->index('name');
        $table->index('level');

        // Add unique constraints
        $table->unique('name');
    }
}

Loading from External Sources

class Currency extends Model
{
    use ArrayBackedModel;

    public function getRows(): array
    {
        // From API with caching
        return cache()->remember('currencies', 3600, function () {
            return Http::get('https://api.example.com/currencies')->json();
        });
    }

    protected array $schema = [
        'code' => 'string',
        'name' => 'string',
        'symbol' => 'string',
        'rate' => 'float',
    ];
}

With Timestamps

class Status extends Model
{
    use ArrayBackedModel;

    // Enable timestamps
    public $timestamps = true;

    protected array $rows = [
        [
            'id' => 1,
            'name' => 'Active',
            'created_at' => '2024-01-01 00:00:00',
            'updated_at' => '2024-01-01 00:00:00',
        ],
    ];
}

Disable Caching

class DynamicData extends Model
{
    use ArrayBackedModel;

    // Disable caching (always use in-memory)
    protected bool $shouldCache = false;

    public function getRows(): array
    {
        // Fetch fresh data every time
        return Http::get('https://api.example.com/data')->json();
    }
}

Performance Tips

✅ DO

  • Use for static data (< 10,000 rows)
  • Define explicit schema for better performance
  • Use appropriate chunk sizes for large datasets
  • Enable caching for static data
  • Use indexes for frequently queried columns

❌ DON'T

  • Use for large datasets (> 10,000 rows)
  • Use for frequently changing data
  • Use for user-generated content
  • Use as replacement for proper database tables
  • Disable caching for static data

Benchmarks

OperationCache HitCache MissIn-Memory
First Load-~50-100ms~10-20ms
Subsequent~1-5ms-~10-20ms
Query (find)~0.1ms~0.1ms~0.1ms
Query (where)~0.5ms~0.5ms~0.5ms

Benchmarks based on 1,000 rows on average hardware

Testing

use Tests\TestCase;

class RoleTest extends TestCase
{
    public function test_can_query_roles()
    {
        $admin = Role::where('name', 'Admin')->first();

        $this->assertNotNull($admin);
        $this->assertEquals(10, $admin->level);
    }

    public function test_can_use_relationships()
    {
        $category = Category::find(1);
        $products = $category->products;

        $this->assertInstanceOf(Collection::class, $products);
    }
}

Troubleshooting

Cache Directory Not Writable

Error: Falls back to in-memory database

Solution:

chmod 755 storage/framework/cache

Table Already Exists

Error: QueryException: table "..." already exists

Solution: This is usually a race condition and is handled automatically. If it persists:

rm storage/framework/cache/array-model-*.sqlite

Memory Issues with Large Datasets

Error: Allowed memory size exhausted

Solution:

  • Reduce dataset size
  • Increase insertChunkSize
  • Use proper database table instead

Comparison with Alternatives

vs. Database Seeders

FeatureArrayBackedModelSeeders
SetupAutomaticManual
PerformanceFast (cached)Slow (runs every time)
TestingEasyComplex
MaintenanceSimpleRequires migrations

vs. Config Files

FeatureArrayBackedModelConfig
QueryingFull EloquentManual array search
RelationshipsYesNo
CachingAutomaticManual
Type SafetyYesNo

vs. Regular Models

FeatureArrayBackedModelRegular Model
SetupSimpleRequires migrations
PerformanceFastDepends on DB
ScalabilityLimitedUnlimited
Best ForStatic dataDynamic data

Examples

See the examples/ directory for complete working examples:

  • examples/RoleModel.php - Basic usage
  • examples/CountryModel.php - With external data
  • examples/StatusModel.php - With custom schema
  • examples/CategoryModel.php - With relationships

License

MIT License

Credits

Inspired by calebporzio/sushi with enhancements:

  • Better documentation
  • Enhanced caching strategy
  • Improved error handling
  • More configuration options
  • Better type inference
  • Laravel 11+ compatibility