]> git.andy128k.dev Git - missing-tools.git/commitdiff
mixins
authorAndrey Kutejko <andy128k@gmail.com>
Thu, 12 Sep 2013 11:28:19 +0000 (14:28 +0300)
committerAndrey Kutejko <andy128k@gmail.com>
Thu, 12 Sep 2013 11:28:19 +0000 (14:28 +0300)
src/mixins.php [new file with mode: 0644]
t/MixinsTest.php [new file with mode: 0644]

diff --git a/src/mixins.php b/src/mixins.php
new file mode 100644 (file)
index 0000000..78897b8
--- /dev/null
@@ -0,0 +1,91 @@
+<?php
+
+class Mixins
+{
+    private $methods = array();
+
+    public function __construct($object, $mixins)
+    {
+        foreach ($mixins as $class) {
+            if (!class_exists($class))
+                throw new Exception('Tried to inherit non-existant class \''.$class.'\'.');
+
+            $mixin = new $class($object);
+            $methods = get_class_methods($mixin);
+            if (is_array($methods))
+                foreach ($methods as $method)
+                    $this->methods[strtolower($method)][] = $mixin;
+        }
+    }
+
+    private $current_call_method = null;
+    private $current_call_mixins = null;
+
+    public function __call($method, $args = array())
+    {
+        $m = strtolower($method);
+        if (!isset($this->methods[$m]))
+            throw new Exception('Call to undefined method ' . get_class($this) . "::$method()");
+
+        $this->current_call_method = $method;
+        $this->current_call_mixins = $this->methods[$m];
+        $result = call_user_func_array(array($this, 'call_next_method'), $args);
+        $this->current_call_method = null;
+        $this->current_call_mixins = null;
+        return $result;
+    }
+
+    public function call_next_method()
+    {
+        if ($this->current_call_mixins === null)
+            throw new Exception('call_next_method is invoked outside of mixin method.');
+
+        $mixin = array_pop($this->current_call_mixins);
+        if ($mixin === null)
+            throw new Exception('No next method for ' . get_class($this) . "::$method()");
+
+        return call_user_func_array(array($mixin, $this->current_call_method), func_get_args());
+    }
+}
+
+abstract class Mixable
+{
+    protected abstract function __mixins();
+
+    protected $mixins;
+
+    public function __construct()
+    {
+        $this->mixins = new Mixins($this, $this->__mixins());
+    }
+
+    public function __call($method, $args = array())
+    {
+        return call_user_func_array(array($this->mixins, $method), $args);
+    }
+
+    public function call_next_method()
+    {
+        return call_user_func_array(array($this->mixins, 'call_next_method'), func_get_args());
+    }
+
+    public function __clone()
+    {
+    }
+}
+
+class Mixin
+{
+    protected $self;
+
+    public function __construct($self)
+    {
+        $this->self = $self;
+    }
+
+    protected function call_next_method()
+    {
+        return $this->self->call_next_method(func_get_args());
+    }
+}
+
diff --git a/t/MixinsTest.php b/t/MixinsTest.php
new file mode 100644 (file)
index 0000000..d634305
--- /dev/null
@@ -0,0 +1,62 @@
+<?php
+
+class SomeMixin extends Mixin
+{
+    public function getSmthUseful()
+    {
+        return 'a';
+    }
+
+    public function setB($newValue)
+    {
+        $this->self->b = $newValue;
+    }
+}
+
+class WrapMixin extends Mixin
+{
+    public function getSmthUseful()
+    {
+        return '(' . $this->call_next_method() . ')';
+    }
+
+    public function doSmthElse($str)
+    {
+        return strrev($str);
+    }
+}
+
+class Mixture extends Mixable
+{
+    protected function __mixins() { return array('SomeMixin', 'WrapMixin'); }
+
+    public $b = 'b';
+
+    public function doSmthElse($str)
+    {
+        return strtoupper($this->mixins->doSmthElse($str));
+    }
+}
+
+class MixinsTest extends PHPUnit_Framework_TestCase
+{
+    public function testSetter()
+    {
+        $mixture = new Mixture;
+        $mixture->setB('B');
+        $this->assertEquals('B', $mixture->b);
+    }
+
+    public function testWrapping()
+    {
+        $mixture = new Mixture;
+        $this->assertEquals('(a)', $mixture->getSmthUseful());
+    }
+
+    public function testOverriding()
+    {
+        $mixture = new Mixture;
+        $this->assertEquals('LARCH', $mixture->doSmthElse('hCraL'));
+    }
+}
+