--- /dev/null
+<?php
+
+if (!extension_loaded("overload")) {
+ // die hard without ext/overload
+ die("Rebuild PHP with the `overload' extension to use PEAR_Autoloader");
+}
+
+require_once "PEAR.php";
+
+/**
+ * This class is for objects where you want to separate the code for
+ * some methods into separate classes. This is useful if you have a
+ * class with not-frequently-used methods that contain lots of code
+ * that you would like to avoid always parsing.
+ *
+ * The PEAR_Autoloader class provides autoloading and aggregation.
+ * The autoloading lets you set up in which classes the separated
+ * methods are found. Aggregation is the technique used to import new
+ * methods, an instance of each class providing separated methods is
+ * stored and called every time the aggregated method is called.
+ *
+ * @author Stig Sæther Bakken <ssb@fast.no>
+ */
+class PEAR_Autoloader extends PEAR
+{
+ /**
+ * Map of methods and classes where they are defined
+ *
+ * @var array
+ *
+ * @access private
+ */
+ var $_autoload_map = array();
+
+ /**
+ * Map of methods and aggregate objects
+ *
+ * @var array
+ *
+ * @access private
+ */
+ var $_method_map = array();
+
+ /**
+ * Add one or more autoload entries.
+ *
+ * @param $method string which method to autoload
+ *
+ * @param $classname string (optional) which class to find the method in.
+ * If the $method parameter is an array, this
+ * parameter may be omitted (and will be ignored
+ * if not), and the $method parameter will be
+ * treated as an associative array with method
+ * names as keys and class names as values.
+ *
+ * @return void
+ *
+ * @access public
+ */
+ function addAutoload($method, $classname = null)
+ {
+ if (is_array($method)) {
+ $this->_autoload_map = array_merge($this->_autoload_map, $method);
+ } else {
+ $this->_autoload_map[$method] = $classname;
+ }
+ }
+
+ /**
+ * Remove an autoload entry.
+ *
+ * @param $method string which method to remove the autoload entry for
+ *
+ * @return bool TRUE if an entry was removed, FALSE if not
+ *
+ * @access public
+ */
+ function removeAutoload($method)
+ {
+ $ok = isset($this->_autoload_map[$method]);
+ unset($this->_autoload_map[$method]);
+ return $ok;
+ }
+
+ /**
+ * Add an aggregate object to this object. If the specified class
+ * is not defined, loading it will be attempted following PEAR's
+ * file naming scheme. All the methods in the class will be
+ * aggregated, except private ones (name starting with an
+ * underscore) and constructors.
+ *
+ * @param $classname string what class to instantiate for the object.
+ *
+ * @return void
+ *
+ * @access public
+ */
+ function addAggregateObject($classname)
+ {
+ $classname = strtolower($classname);
+ if (!class_exists($classname)) {
+ $include_file = preg_replace('/[^a-z0-9]/i', '_', $classname);
+ include_once $include_file;
+ }
+ $obj =& new $classname;
+ $methods = get_class_methods($classname);
+ foreach ($methods as $method) {
+ // don't import priviate methods and constructors
+ if ($method{0} != '_' && $method != $classname) {
+ $this->_method_map[$method] = $obj;
+ }
+ }
+ }
+
+ /**
+ * Remove an aggregate object.
+ *
+ * @param $classname string the class of the object to remove
+ *
+ * @return bool TRUE if an object was removed, FALSE if not
+ *
+ * @access public
+ */
+ function removeAggregateObject($classname)
+ {
+ $ok = false;
+ $classname = strtolower($classname);
+ reset($this->_method_map);
+ while (list($method, $obj) = each($this->_method_map)) {
+ if (get_class($obj) == $classname) {
+ unset($this->_method_map[$method]);
+ $ok = true;
+ }
+ }
+ return $ok;
+ }
+
+ /**
+ * Overloaded object call handler, called each time an
+ * undefined/aggregated method is invoked. This method repeats
+ * the call in the right aggregate object and passes on the return
+ * value.
+ *
+ * @param $method string which method that was called
+ *
+ * @param $args array An array of the parameters passed in the
+ * original call
+ *
+ * @return mixed The return value from the aggregated method, or a PEAR
+ * error if the called method was unknown.
+ */
+ function __call($method, $args)
+ {
+ if (empty($this->_method_map[$method]) && isset($this->_autoload_map[$method])) {
+ $this->addAggregateObject($this->_autoload_map[$method]);
+ }
+ if (isset($this->_method_map[$method])) {
+ return call_user_func_array(array($this->_method_map[$method], $method), $args);
+ }
+ return $this->raiseError("undefined method: $method");
+ }
+}
+
+overload("PEAR_Autoloader");
+
+?>
--- /dev/null
+--TEST--
+PEAR_Autoloader
+--SKIPIF--
+<?php if (!extension_loaded("overload")) die("skip\n"); ?>
+--FILE--
+<?php
+
+require "../PEAR/Autoloader.php";
+
+class test1 extends PEAR_Autoloader {
+ function test1() {
+ $this->addAutoload(array(
+ 'testfunc1' => 'testclass1',
+ 'testfunca' => 'testclass1',
+ 'testfunc2' => 'testclass2',
+ 'testfuncb' => 'testclass2',
+ ));
+ }
+}
+
+class testclass1 {
+ function testfunc1($a) {
+ print "testfunc1 arg=";var_dump($a);
+ return 1;
+ }
+ function testfunca($a) {
+ print "testfunca arg=";var_dump($a);
+ return 2;
+ }
+}
+
+class testclass2 {
+ function testfunc2($b) {
+ print "testfunc2 arg=";var_dump($b);
+ return 3;
+ }
+ function testfuncb($b) {
+ print "testfuncb arg=";var_dump($b);
+ return 4;
+ }
+}
+
+function dump($obj) {
+ print "mapped methods:";
+ foreach ($obj->_method_map as $method => $object) {
+ print " $method";
+ }
+ print "\n";
+}
+
+function call($msg, $retval) {
+ print "calling $msg returned $retval\n";
+}
+
+$obj = new test1;
+dump($obj);
+call("testfunc1", $obj->testfunc1(2));
+dump($obj);
+call("testfunca", $obj->testfunca(2));
+dump($obj);
+call("testfunc2", $obj->testfunc2(2));
+dump($obj);
+call("testfuncb", $obj->testfuncb(2));
+dump($obj);
+
+?>
+--EXPECT--
+mapped methods:
+testfunc1 arg=int(2)
+calling testfunc1 returned 1
+mapped methods: testfunc1 testfunca
+testfunca arg=int(2)
+calling testfunca returned 2
+mapped methods: testfunc1 testfunca
+testfunc2 arg=int(2)
+calling testfunc2 returned 3
+mapped methods: testfunc1 testfunca testfunc2 testfuncb
+testfuncb arg=int(2)
+calling testfuncb returned 4
+mapped methods: testfunc1 testfunca testfunc2 testfuncb