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
Requires
- php: >=8.3
Requires (Dev)
- codeception/codeception: ^5.3
- codeception/module-asserts: ^3.3
- codeception/module-db: ^3.2
- vlucas/phpdotenv: ^5.6
- yiisoft/active-record: ^1.0
- yiisoft/cache: ^3.2
- yiisoft/db: ^2.0
- yiisoft/db-mysql: ^2.0
- yiisoft/di: ^1.2
- yiisoft/event-dispatcher: ^1.1
- yiisoft/factory: ^1.3
- yiisoft/test-support: ^3.1
Suggests
- yiisoft/active-record: Required for MagicComposeActiveRecordTrait
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.
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
- Handlers discovered via reflection (cached)
- Sorted by priority DESC
- First handler that doesn't throw
MagicNotHandledExceptionwins - If all throw → fallback to parent or PHP error
License
BSD-3-Clause. See LICENSE.md.
Author
Philippe Gaultier philippe@blackcube.io