Mixins

Mixins

FIXME Cannot reference $this in a function that is mixed into another class.

A Mixin provides some of the features of multiple inheritance.

There is no direct conversion capability from a Ruby Mixin to PHP, but here is code that permits you to do PHP Mixins.

As with the Ruby Mixin, this code involves no physical includes or copies, but references are made to the methods in other classes so that one may execute a method in another class just as though it was in the current class. In Ruby, mixing in is done with modules, but PHP has no modules, so it is done with classes. Differences to note are:

  • A Ruby module cannot be instantiated, while a PHP class can be.
  • A Ruby module can extend over more than one file, while a PHP class cannot, so if you're porting a Ruby module to PHP, you may need more than one class to do so. If the Ruby module is mixed into a Ruby class, you will need to mix in all of the PHP classes made from the Ruby module.
  • Ruby modules may be nested into other Ruby modules, but PHP classes cannot be nested.

In the following example, Test1 contains the code that permits the functions of other classes to be mixed into it. That code is the class variable $methods, code in the constructor (whose name may be made PHP 4 compatible) and the functions mixin() and call_method().

The class variable $methods in class Test1 will contain references to Mappable→included(), GeoKit→to_lat_lng() and GeoKit→distance_to() as well as to Test1→function_1(). Functions in this class, such as function_1(), may be called directly as

$test_1->function_1();

or through the call_method() as you will mixed in functions:

$test_1->call_method("function_1(\"$f1\")");

Caveat: This code has been tested with only the parameter types of string, array and integer.

class Mappable {
    function included($param1, $param2) {
        echo "\nThis is Mappable->included.  Parameters:\n    $param1\n    $param2\n";    
    }
    function to_lat_lng() {
        echo "\nThis is Mappable->to_lat_lng\n";
    }
}
 
class GeoKit {
    function to_lat_lng($str,$ary=array()) {
        echo "\nThis is GeoKit->to_lat_lng.  Parameters:\n    $str\n    ";var_dump($ary);
    }
    function distance_to() {
        echo "\nThis is GeoKit->distance_to.  It has no parameters.\n";
    }
}
 
class Test1 {
    var $methods;
 
    function __construct() {
        $class_name = get_class($this);
        foreach(get_class_methods($class_name) as $method) {
            if(get_class($this) != $method && $method != '__construct' &&
               $method != 'mixin'          && $method != 'call_method') {
                $this->methods[] = array($method,array($class_name,$method));
            } 
        }
    }
 
    function mixin(&$instance) {
        $class_name = get_class($instance);
        foreach(get_class_methods($class_name) as $method) {
            $dup = false;
            foreach($this->methods as $registered_method) {
                if($method == $registered_method[0]) {
                    $existing_method = $registered_method[1];
                    echo("Error: Duplicate method names.  ".
                        "\"$class_name->$method\" can't be mixed in because".
                        " \"$existing_method[0]->$method\" has already been mixed in.\n");
                    $dup = true;
                }
            }
            if(!$dup) {
                $this->methods[] = array($method,array($class_name,$method));
            }
        }           
    }
 
    function call_method($method_and_parameters) {
        if(strpos($method_and_parameters,'(') === false) {
            $method = $method_and_parameters;
        } else {
            $method = substr($method_and_parameters,0,strpos($method_and_parameters,'('));
        }    
        foreach($this->methods as $registered_method) {
            if($method == $registered_method[0]) {
                $user_func = "call_user_func(\$registered_method[1]";
                break;
            }
        }
        if(isset($user_func)) {
            if(strlen($method_and_parameters) > strlen($method)) {
                $params = substr($method_and_parameters,strlen($method));
                $params = substr($params,1,-1);
            }
            if(isset($params)) {
                $user_func .= ','.$params.');';
            } else {
                $user_func .= ');';
            }
            eval($user_func);
        } else {
            $name = get_class($this);
            echo("Error: The method \"$method\" is not registered in the class \"$name\".\n");
        }
    }
 
    function function_1($param='default') {
      echo "\nThis is Test1->function_1.  It has the parameter: $param.\n";
    }
}
 
$geo_kit = new GeoKit;
$mappable = new Mappable;
$test_1 = new Test1;
 
$test_1->mixin($geo_kit);
$test_1->mixin($mappable);
 
$test_1->function_1();
 
$f1 = 'the quick';
$test_1->call_method("function_1(\"$f1\")");
$test_1->call_method('distance_to');
$test_1->call_method('to_lat_lng("brown",array(\'fox jumped\',\'over the\'))');
$test_1->call_method('no_such_function');
$test_1->call_method('included(\'lazy\',670)');
 
mixins.txt · Last modified: 2010/03/06 08:52 by 164.67.235.148
 

The Akelos Framework was created by Bermi Ferrer and other contributors.
Potions of the code and documentation have been ported from Ruby on Rails.

The Akelos Framework is released under the LGPL license.

"Akelos", "Akelos Framework", and the Akelos logo are trademarks of Bermi Labs All rights reserved.

Wiki driven by DokuWiki