Skip to content

Instantly share code, notes, and snippets.

@mikesimons
Created July 12, 2010 11:55
Show Gist options
  • Save mikesimons/472392 to your computer and use it in GitHub Desktop.
Save mikesimons/472392 to your computer and use it in GitHub Desktop.
<?php
class Object {
private $__instanceMixins = array();
private static $__classMixins = array();
public function mixinInstanceMethod($class, $method = null) {
$reflection = self::reflect($class, $method);
$method = $reflection->getName();
$toAppend = self::methodToAppend($reflection);
$this->appendInstanceMethod($method, $toAppend[0], $toAppend[1]);
}
public static function mixinClassMethod($class, $method = null) {
$reflection = self::reflect($class, $method);
$method = $reflection->getName();
$toAppend = self::methodToAppend($reflection);
self::appendClassMethod($method, $toAppend[0], $toAppend[1]);
}
public function __call($method, $args) {
if(isset($this->__instanceMixins[$method])) {
$appendedMethod = $this->__instanceMixins[$method];
} elseif(isset(self::$__classMixins[get_called_class()][$method])) {
$appendedMethod = self::$__classMixins[get_called_class()][$method];
}
if(isset($appendedMethod)) {
if(is_string($appendedMethod[0])) {
if(count($args)) {
$args = array_combine($appendedMethod[1], $args);
extract($args);
}
return eval($appendedMethod[0]);
} else {
return call_user_func_array($appendedMethod[0], $args);
}
}
throw new \Exception("Method not found {$method}");
}
public static function __callStatic($method, $args) {
$class = get_called_class();
if(isset(self::$__classMixins[$class][$method])) {
$appendedMethod = self::$__classMixins[$class][$method];
if(is_string($appendedMethod[0])) {
if(count($args)) {
$args = array_combine($appendedMethod[1], $args);
extract($args);
}
return eval($appendedMethod[0]);
} else {
return call_user_func_array($appendedMethod[0], $args);
}
}
throw new \Exception("Method not found {$method}");
}
private function appendInstanceMethod($name, $body, $params) {
$this->__instanceMixins[$name] = array($body, $params);
}
private function appendClassMethod($name, $body, $params) {
$class = get_called_class();
if(!isset(self::$__classMixins[$class])) {
self::$__classMixins[$class] = array();
}
self::$__classMixins[$class][$name] = array($body, $params);
}
private static function methodToAppend($reflection) {
$export = $reflection->__toString();
$params = array();
foreach($reflection->getParameters() as $param) {
$params[] = $param->getName();
}
preg_match('/(.*)@@ (?<file>.*) (?<start>[0-9]+) - (?<end>[0-9]+)/', $export, $matches);
$code = self::loadBlockFromFile($matches['file'], $matches['start'], $matches['end']);
return array($code, $params);
}
private static function reflect($class, $method) {
if(!$method && is_string($class)) {
$reflection = new \ReflectionFunction($class);
} elseif(!$method && is_array($class)) {
$reflection = new \ReflectionMethod($class[0], $class[1]);
} else {
$reflection = new \ReflectionMethod($class, $method);
}
return $reflection;
}
private static function loadBlockFromFile($file, $start, $end) {
$file = file($file);
$code = array_slice($file, $start, $end - $start);
$code = trim(implode("\n", $code));
if(strrpos($code, '}') === strlen($code) - 1) {
if(strpos($code, '{') !== 0) {
$code = '{' . $code;
}
}
return $code;
}
}
class Test1 extends Object {
public function put($str) {
echo get_class($this) . ": " . $str . "\n";
}
public function aha() {
$this->put("AHA");
}
}
class Test2 extends Object {
public function put($str) {
echo "Test2: {$str}\n";
}
public function blargh($xxx) {
echo "BLARGH {$xxx}\n";
}
public function moo() {
$this->put("MOO");
}
public function ehe() {
$this->put("EHE");
}
}
class Test3 extends Object {
}
// Normal method
$c1 = new Test1;
$c1->aha();
// Method mixed in from Test2
$c1->mixinInstanceMethod('Test2', 'ehe');
$c1->ehe();
// Class method mixed in from Test2
Test1::mixinClassMethod('Test2', 'moo');
$c1->moo();
// Since it's a class method it doesn't need mixing again
$c2 = new Test1;
$c2->moo();
// Class methods can be called statically (although some might not be suitable)
Test1::mixinClassMethod('Test2', 'blargh');
Test1::blargh("123");
// moo requires put which was defined in Test1
Test2::mixinClassMethod('Test1', 'put');
$c3 = new Test2;
$c3->moo();
/**
Output:
Test1: AHA
Test1: EHE
Test1: MOO
Test1: MOO
BLARGH 123
Test2: MOO
**/
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment