blackcube/active-record

Scoped queries, elastic dynamic attributes, Hazeltree nested sets — a toolkit for ActiveRecord

Maintainers

Package info

github.com/blackcubeio/active-record

pkg:composer/blackcube/active-record

Statistics

Installs: 3

Dependents: 2

Suggesters: 0

Stars: 0

Open Issues: 0

1.0.0 2026-03-21 17:26 UTC

This package is auto-updated.

Last update: 2026-03-21 17:29:31 UTC


README

Scoped queries, elastic dynamic attributes, Hazeltree nested sets — a toolkit for ActiveRecord.

License Packagist Version

Quickstart

composer require blackcube/active-record
// Scoped queries
$products = Product::query()->active()->language(languageId: 'fr')->all();

// Elastic — dynamic attributes from JSON Schema
$article->author = 'Philippe';
$article->rating = 5;
$article->save();
$found = Article::query()->where(['author' => 'Philippe'])->all();

// Hazeltree — nested sets
$child = new Menu();
$child->name = 'About';
$child->saveInto($homepage);
$breadcrumb = $child->relativeQuery()->parent()->includeAncestors()->includeSelf()->all();

Three building blocks

Scoped queries

Named, composable filters. PHP 8 named arguments flow through __call into typed parameters.

class ProductQuery extends ActiveQuery implements ScopableQueryInterface
{
    use ScopableTrait;
    use QualifyColumnTrait;
}

$products = Product::query()
    ->active(active: false)
    ->language(languageId: 'fr')
    ->andWhere(['like', 'title', 'laptop'])
    ->all();

QualifyColumnTrait auto-prefixes column names with the table qualifier. Scopes write simple names, qualification is transparent. No ambiguous column errors in JOINs.

Elastic — Dynamic JSON Schema attributes

JSON column + JSON Schema = dynamic attributes without EAV.

class Article extends ActiveRecord implements ElasticInterface
{
    use ElasticTrait;  // handles __get/__set dispatch, no MagicCompose needed
}

// Properties come from JSON Schema, not PHP class definition
$article->author = 'Philippe';     // stored in _extras JSON column
$article->rating = 5;
$article->save();

// Query virtual columns — automatically converted to JSON_VALUE()
$top = Article::query()->where(['>', 'rating', 3])->orderBy(['rating' => SORT_DESC])->all();

// Validation from JSON Schema
$resolver = new ElasticRuleResolver();
$rules = $resolver->resolve($article);

Hazeltree — Nested sets with rational numbers

Tree structure in RDBMS. Read branches in one query. Write without global renumbering.

Based on Dan Hazel's research (2008).

class Menu extends ActiveRecord implements HazeltreeInterface
{
    use HazeltreeTrait;  // handles __get/__set dispatch, no MagicCompose needed
}

// Write
$home = new Menu(); $home->name = 'Home'; $home->save();           // path: 1
$about = new Menu(); $about->name = 'About'; $about->saveInto($home); // path: 1.1
$blog = new Menu(); $blog->name = 'Blog'; $blog->saveAfter($about);  // path: 1.2

// Read — one query each
$children   = $home->relativeQuery()->children()->all();
$breadcrumb = $about->relativeQuery()->parent()->includeAncestors()->includeSelf()->all();
$siblings   = $about->relativeQuery()->siblings()->next()->all();
$roots      = Menu::query()->roots()->all();

Combined — Elastic + Hazeltree

For models that need both dynamic attributes and tree structure:

class Content extends ActiveRecord implements ElasticInterface, HazeltreeInterface
{
    use HazeltreeElasticTrait;  // dispatches to both, resolves all collisions
}

Trait architecture

Three layers, zero collision:

Layer Purpose Example
Base traits Prefixed methods, protected, no collision BaseElasticTrait, BaseHazeltreeTrait
Composite traits __get/__set dispatch, one per model ElasticTrait, HazeltreeTrait, HazeltreeElasticTrait
Abstract classes Convenience, just use Trait AbstractElasticActiveRecord, AbstractHazeltreeElasticQuery

Base traits use tryElasticGet() / tryHazeltreeGet() returning bool. Composite traits chain: elastic, then hazeltree, then parent::. No insteadof, no MagicCompose needed.

Tests

vendor/bin/codecept run

500 tests, 2726 assertions across 17 suites.

Documentation

License

BSD-3-Clause. See LICENSE.md.

Author

Philippe Gaultier philippe@blackcube.io