blackcube/yii-magic-compose

PHP 8.3+ simple solution for magic methods and method composition using attributes

Installs: 7

Dependents: 2

Suggesters: 0

Security: 0

Stars: 0

Watchers: 0

Forks: 0

Open Issues: 0

pkg:composer/blackcube/yii-magic-compose

1.0.0 2026-01-24 14:46 UTC

This package is auto-updated.

Last update: 2026-01-24 17:45:48 UTC


README

PHP 8.3+ simple solution for magic methods and method composition using attributes.

License PHP Version Packagist Version

Installation

composer require blackcube/yii-magic-compose

Problem

PHP traits can't compose magic methods. When multiple traits define __get, only one wins:

class MyClass {
    use TraitA, TraitB; // TraitB::__get shadows TraitA::__get
}

Solution

Mark handlers with attributes, let MagicComposeTrait dispatch:

class MyClass {
    use MagicComposeTrait, TraitA, TraitB;
}

trait TraitA {
    #[MagicGetter]
    protected function getFromA(string $name): mixed {
        if ($name === 'foo') return 'from A';
        throw new MagicNotHandledException();
    }
}

trait TraitB {
    #[MagicGetter(priority: Priority::HIGH)]
    protected function getFromB(string $name): mixed {
        if ($name === 'bar') return 'from B';
        throw new MagicNotHandledException();
    }
}

$obj = new MyClass();
$obj->foo; // 'from A'
$obj->bar; // 'from B'

Attributes

Attribute Magic method Handler signature
#[MagicGetter] __get (string $name): mixed
#[MagicSetter] __set (string $name, mixed $value): void
#[MagicIsset] __isset (string $name): bool
#[MagicUnset] __unset (string $name): void
#[MagicCall] __call (string $name, array $arguments): mixed

Priority

Handlers are sorted by priority (highest first):

use Blackcube\MagicCompose\Attributes\Priority;

#[MagicGetter(priority: Priority::HIGH)]  // 90 - runs first
#[MagicGetter(priority: Priority::NORMAL)] // 50 - default
#[MagicGetter(priority: Priority::LOW)]    // 10 - runs last

Constants: CRITICAL (100), HIGH (90), NORMAL (50), LOW (10).

MagicExtend

Intercept parent class methods with chainable $this->next():

trait AuditTrait {
    use MagicComposeMethodsBaseTrait;

    #[MagicExtend(method: 'save')]
    protected function auditSave(): bool {
        $this->log('before save');
        $result = $this->next(); // call parent::save() or next handler
        $this->log('after save');
        return $result;
    }
}

Yii3 ActiveRecord

For Yii3 ActiveRecord, use MagicComposeActiveRecordTrait:

use Yiisoft\ActiveRecord\ActiveRecord;
use Blackcube\MagicCompose\MagicComposeActiveRecordTrait;

class User extends ActiveRecord {
    use MagicComposeActiveRecordTrait;
    use SomeCustomTrait;
}

Pre-wired methods: propertyValuesInternal, refreshInternal, populateProperty, deleteInternal, populateRecord.

Handler Flow

  1. Handlers discovered via reflection (cached)
  2. Sorted by priority DESC
  3. First handler that doesn't throw MagicNotHandledException wins
  4. If all throw → fallback to parent or PHP error

License

BSD-3-Clause. See LICENSE.md.

Author

Philippe Gaultier philippe@blackcube.io