Erebot  latest
A modular IRC bot for PHP 5.3+
CallableWrapper.php
1 <?php
2 /*
3  This file is part of Erebot, a modular IRC bot written in PHP.
4 
5  Copyright © 2010 François Poirotte
6 
7  Erebot is free software: you can redistribute it and/or modify
8  it under the terms of the GNU General Public License as published by
9  the Free Software Foundation, either version 3 of the License, or
10  (at your option) any later version.
11 
12  Erebot is distributed in the hope that it will be useful,
13  but WITHOUT ANY WARRANTY; without even the implied warranty of
14  MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
15  GNU General Public License for more details.
16 
17  You should have received a copy of the GNU General Public License
18  along with Erebot. If not, see <http://www.gnu.org/licenses/>.
19 */
20 
21 namespace Erebot;
22 
30 abstract class CallableWrapper implements \Erebot\CallableInterface
31 {
33  protected $callable;
34 
36  protected $representation;
37 
54  protected function __construct($callable)
55  {
56  $this->callable = $callable;
57  }
58 
72  public function __toString()
73  {
74  return static::represent($this->callable);
75  }
76 
90  public static function wrap($callable)
91  {
92  $parts = explode('::', static::represent($callable));
93  if (count($parts) == 1) {
94  // We wrapped a function.
95  $reflector = new \ReflectionFunction($callable);
96  } else {
97  if (!is_array($callable)) {
98  // We wrapped a Closure or some invokable object.
99  $callable = array($callable, $parts[1]);
100  }
101  $reflector = new \ReflectionMethod($callable[0], $callable[1]);
102  }
103 
104  // References are lost, unless __invoke()'s signature also requires them.
105  // We must also make sure default values are preserved.
106  $args = array();
107  foreach ($reflector->getParameters() as $argReflect) {
108  $arg = '';
109  if ($argReflect->isPassedByReference()) {
110  $arg .= '&';
111  }
112  $arg .= '$a' . count($args);
113  if ($argReflect->isOptional()) {
114  $arg .= '=' . var_export($argReflect->getDefaultValue(), true);
115  } else {
116  $arg .= '=null';
117  }
118  $args[] = $arg;
119  }
120 
121  $args = implode(',', $args);
122  $class = 'Wrapped_' . sha1($args);
123  if (!class_exists("\\Erebot\\CallableWrapper\\$class", false)) {
124  $tpl = "
125  namespace Erebot\\CallableWrapper {
126  class $class extends \\Erebot\\CallableWrapper
127  {
128  public function __invoke($args)
129  {
130  // HACK: we use debug_backtrace() to get (and pass along)
131  // references for call_user_func_array().
132 
133  // Starting with PHP 5.4.0, it is possible to limit
134  // the number of stack frames returned.
135  if (version_compare(PHP_VERSION, '5.4', '>='))
136  \$bt = debug_backtrace(0, 1);
137  // Starting with PHP 5.3.6, the first argument
138  // to debug_backtrace() is a bitmask of options.
139  else if (version_compare(PHP_VERSION, '5.3.6', '>='))
140  \$bt = debug_backtrace(0);
141  else
142  \$bt = debug_backtrace(FALSE);
143 
144  if (isset(\$bt[0]['args']))
145  \$args =& \$bt[0]['args'];
146  else
147  \$args = array();
148  return call_user_func_array(\$this->callable, \$args);
149  }
150  }
151  }
152  ";
153  eval($tpl);
154  }
155  $class = "\\Erebot\\CallableWrapper\\$class";
156  return new $class($callable);
157  }
158 
180  public static function initialize()
181  {
182  static $initialized = false;
183  static $structures = array();
184 
185  if (defined('T_CALLABLE'))
186  return;
187 
188  if (!$initialized) {
189  spl_autoload_register(
190  function ($class) {
191  $parts = array_map('strrev', explode('\\', strrev($class), 2));
192  $short = array_shift($parts);
193  $ns = (string) array_shift($parts);
194 
195  if ($short === 'callable') {
196  // The class to load is "callable", inject the alias.
197  if (class_alias('\\Erebot\\CallableInterface', $class, true) !== true) {
198  throw new \RuntimeException('Could not load wrapper');
199  }
200  return true;
201  }
202 
203  // Otherwise, inject the alias in the namespace
204  // of the class being loaded.
205  class_exists("$ns\\callable");
206  return false;
207  },
208  true
209  );
210  $initialized = true;
211  }
212 
213  // Inject the alias in existing namespaces.
214  $funcs = get_defined_functions();
215  $new = array_merge(
216  $funcs['user'],
217  get_declared_classes(),
218  get_declared_interfaces()
219  );
220 
221  if ($new != $structures) {
222  $newNS = array();
223  foreach (array_diff($new, $structures) as $structure) {
224  $parts = explode('\\', strrev($structure), 2);
225  array_shift($parts); // Remove class/interface name.
226  $newNS[] = (string) array_shift($parts);
227  }
228  $structures = $new;
229  $newNS = array_unique($newNS);
230  foreach ($newNS as $ns) {
231  class_exists(strrev($ns) . '\\callable');
232  }
233  }
234  }
235 
256  public static function represent($callable)
257  {
258  if (!is_callable($callable, false, $representation)) {
259  throw new \InvalidArgumentException('Not a valid callable');
260  }
261 
262  // This happens for anonymous functions
263  // created with create_function().
264  if (is_string($callable) && $representation == "") {
265  $representation = $callable;
266  }
267  return $representation;
268  }
269 }
Definition: CLI.php:21
Interface for something that can be called.
static wrap($callable)
$callable
Inner callable object, as used by PHP.
Class used to represent anything that is callable.
static represent($callable)
$representation
Human representation of the inner callable.