PHP 5.4+ magic interception system using traits
magery provides a trait framework with which to hook into object magic, namely __get, __set, __unset, __isset, and __call.
Install via cli using composer:
php composer.phar require jgswift/magery:0.1.*
Install via composer.json using composer:
{
"require": {
"jgswift/magery": "0.1.*"
}
}
- php 5.4+
class Foo
{
use Magery\Mage;
private $bar;
public function __construct()
{
$this->read('bar', function(){
throw new \Exception('Don\'t touch my bar!');
});
}
public function touchBar()
{
$this->bar;
}
}
$foo = new Foo();
$foo->touchBar(); // Fatal error: Uncaught exception 'Exception' with message 'Don't touch my bar!'
A write callback could be registered to protect a variable from being overwritten (even from within the scope of your class)
$this->write('bar', function() {
throw new \Exception('Don\'t write to my bar!');
});
public function writeToBar() {
$this->bar = 'somethingElse';
}
$foo = new Foo();
$foo->writeToBar(); // Fatal error: Uncaught exception 'Exception' with message 'Don't write to my bar!'
It is possible to intercept any object property or method with an event.
Note: Multiple registered events will fire in the order they were added (FIFO) until an event returns a response value.
class Foo {
use magery\Mage;
public function __construct() {
$this->read('bar', function(){
return 'baz';
});
// Shortcut method
$this->read('buzz', function() {
return 'bar';
});
}
}
$foo = new Foo();
echo $foo->bar; // 'baz'
echo $foo->buzz; // 'bar'
class User {
private $firstName;
private $lastName;
function __construct($firstName, $lastName) { /* ... */ }
}
$user = new User('John', 'Doe');
$user->exists('lastName', function()use(&$c) {
return isset($this->lastName);
// do something extra for existence check
});
var_dump(isset($user->lastName)); // true
$user = new User('Joe','Smith');
$user->remove('name', function()use(&$c) {
unset($this->name);
// do something extra for remove
});
unset($user->name);
var_dump(isset($user->name)); // false
If the event returns a response, this may be cached to reduce execution time on future reads.
public $bar = 'Bill';
public function __construct()
{
$this->read('bar', function(){
sleep(1);
return microtime();
}, true); // pass in "true" here (defaults to false)
}
$foo = new Foo();
var_dump($foo->bar === $foo->bar); // true
All events are registered globally except the magery function is protected unless otherwise specified.
<?php
class Foo
{
use Magery\Mage {magery as public;} // allow public event registration
public $bar;
}
$foo = new Foo();
$foo->read('bar', function(){
throw new \Exception('Don\'t touch my bar!');
});
$foo->bar; // Fatal error: Uncaught exception 'Exception' with message 'Don't touch my bar!'
Like read, call magic also may also cache the result to reduce execution time on future calls.
<?php
class Foo
{
use Magery\Mage;
}
$foo = new Foo();
$foo->call('bar', function() {
sleep(1);
return microtime();
},true);
var_dump($foo->bar() === $foo->bar()); // true
For arrays instead of objects, using a combination of ArrayAccess and the MageAccess trait will provide the same magic opportunities with array syntax
<?php
class Foo implements ArrayAccess {
use Magery\MageAccess;
}
$foo = new Foo();
$foo->read('bar', function() {
return 'baz';
});
var_dump($foo['bar']); // baz
You can create custom handlers by using traits selectively.
This example class only includes write magic and all other operations are performed natively without interruption.
Custom objects must use Magery\Object
and you may additionally choose any combination of Traits/Read
, Traits/Write
, Traits/Remove
, Traits/Exists
traits to apply specific magery functionality.
<?php
class Foo
{
use Magery\Object, Magery\Traits\Write;
}
$foo = new Foo();
$foo->write('bar', function($v){
$this->baz = $v;
});
$foo->bar = 'somethingImportant';
var_dump($foo->baz); // somethingImportant