Hook::hookObject()   F
last analyzed

Complexity

Conditions 21
Paths 14

Size

Total Lines 109

Duplication

Lines 0
Ratio 0 %

Importance

Changes 0
Metric Value
dl 0
loc 109
rs 3.3333
c 0
b 0
f 0
cc 21
nc 14
nop 3

How to fix   Long Method    Complexity   

Long Method

Small methods make your code easier to understand, in particular if combined with a good name. Besides, if your method is small, finding a good name is usually much easier.

For example, if you find yourself adding comments to a method's body, this is usually a good sign to extract the commented part to a new method, and use the comment as a starting point when coming up with a good name for this new method.

Commonly applied refactorings include:

1
<?php
2
namespace SciActive;
3
4
/**
5
 * HookPHP
6
 *
7
 * An object method hooking system.
8
 *
9
 * Hooks are used to call a callback when a method is called and optionally
10
 * manipulate the arguments/function call/return value.
11
 *
12
 * @version 2.1.0
13
 * @license https://www.gnu.org/licenses/lgpl.html
14
 * @author Hunter Perrin <[email protected]>
15
 * @copyright SciActive.com
16
 * @link http://requirephp.org
17
 */
18
19
if (!class_exists('\SciActive\HookOverride')) {
20
  include_once(__DIR__.DIRECTORY_SEPARATOR.'HookOverride.php');
21
}
22
23
class Hook {
24
  /**
25
   * An array of the callbacks for each hook.
26
   * @var array
27
   */
28
  protected static $hooks = array();
29
  /**
30
   * A copy of the HookOverride_extend file.
31
   * @var string
32
   */
33
  private static $hookFile;
34
35
  /**
36
   * Add a callback.
37
   *
38
   * A callback is called either before a method runs or after. The callback
39
   * is passed an array of arguments or return value which it can freely
40
   * manipulate. If the callback runs before the method and sets the arguments
41
   * array to false (or causes an error), the method will not be run.
42
   * Callbacks before a method are passed the arguments given when the method
43
   * was called, while callbacks after a method are passed the return value
44
   * (in an array) of that method.
45
   *
46
   * The callback can receive up to 5 arguments, in this order:
47
   *
48
   * - &$arguments - An array of either arguments or a return value.
49
   * - $name - The name of the hook.
50
   * - &$object - The object on which the hook caught a method call.
51
   * - &$function - A callback for the method call which was caught. Altering
52
   *   this will cause a different function/method to run.
53
   * - &$data - An array in which callbacks can store data to communicate with
54
   *   later callbacks.
55
   *
56
   * A hook is the name of whatever method it should catch. A hook can also
57
   * have an arbitrary name, but be wary that it may already exist and it may
58
   * result in your callback being falsely called. In order to reduce the
59
   * chance of this, always use a plus sign (+) and your component's name to
60
   * begin arbitrary hook names. E.g. "+com_games_player_bonus".
61
   *
62
   * If the hook is called explicitly, callbacks defined to run before the
63
   * hook will run immediately followed by callbacks defined to run after.
64
   *
65
   * A negative $order value means the callback will be run before the method,
66
   * while a positive value means it will be run after. The smaller the order
67
   * number, the sooner the callback will be run. You can think of the order
68
   * value as a timeline of callbacks, zero (0) being the actual method being
69
   * hooked.
70
   *
71
   * Additional identical callbacks can be added in order to have a callback
72
   * called multiple times for one hook.
73
   *
74
   * The hook "all" is a pseudo hook which will run regardless of what was
75
   * actually caught. Callbacks attached to the "all" hook will run before
76
   * callbacks attached to the actual hook.
77
   *
78
   * Note: Be careful to avoid recursive callbacks, as they may result in an
79
   * infinite loop. All methods under $_ are automatically hooked.
80
   *
81
   * @param string $hook The name of the hook to catch.
82
   * @param int $order The order can be negative, which will run before the method, or positive, which will run after the method. It cannot be zero.
83
   * @param callback The callback.
84
   * @return array An array containing the IDs of the new callback and all matching callbacks.
85
   * @uses \SciActive\Hook::sortCallbacks() To resort the callback array in the correct order.
86
   */
87
  public static function addCallback($hook, $order, $function) {
88
    $callback = array($order, $function);
89
    if (!isset(Hook::$hooks[$hook])) {
90
      Hook::$hooks[$hook] = array();
91
    }
92
    Hook::$hooks[$hook][] = $callback;
93
    uasort(Hook::$hooks[$hook], '\\SciActive\\Hook::sortCallbacks');
94
    return array_keys(Hook::$hooks[$hook], $callback);
95
  }
96
97
  /**
98
   * Delete a callback by its ID.
99
   *
100
   * @param string $hook The name of the callback's hook.
101
   * @param int $id The ID of the callback.
102
   * @return int 1 if the callback was deleted, 2 if it didn't exist.
103
   */
104
  public static function delCallbackByID($hook, $id) {
105
    if (!isset(Hook::$hooks[$hook][$id])) {
106
      return 2;
107
    }
108
    unset(Hook::$hooks[$hook][$id]);
109
    return 1;
110
  }
111
112
  /**
113
   * Get the array of callbacks.
114
   *
115
   * Callbacks are stored in arrays inside this array. The keys of this array
116
   * are the name of the hook whose callbacks are contained in its value as an
117
   * array. Each array contains the values $order, $function, in that order.
118
   *
119
   * @return array An array of callbacks.
120
   */
121
  public static function getCallbacks() {
122
    return Hook::$hooks;
123
  }
124
125
  /**
126
   * Hook an object.
127
   *
128
   * This hooks all (public) methods defined in the given object.
129
   *
130
   * @param object &$object The object to hook.
131
   * @param string $prefix The prefix used to call the object's methods. Usually something like "$object->".
132
   * @param bool $recursive Whether to hook objects recursively.
133
   * @return bool True on success, false on failure.
134
   */
135
  public static function hookObject(&$object, $prefix = '', $recursive = true) {
136
    if ((object) $object === $object) {
137
      $isString = false;
138
    } else {
139
      $isString = true;
140
    }
141
142
    // Make sure we don't take over the hook object, or we'll end up
143
    // recursively calling ourself. Some system classes shouldn't be hooked.
144
    $className = str_replace('\\', '_', $isString ? $object : get_class($object));
145
    global $_;
146
    if (isset($_) && in_array($className, array('\SciActive\Hook', 'depend', 'config', 'info'))) {
147
      return false;
148
    }
149
150
    if ($recursive && !$isString) {
151
      foreach ($object as $curName => &$curProperty) {
152
        if ((object) $curProperty === $curProperty) {
153
          Hook::hookObject($curProperty, $prefix.$curName.'->');
154
        }
155
      }
156
    }
157
158
    if (!class_exists("\SciActive\HookOverride_$className")) {
159
      if ($isString) {
160
        $reflection = new \ReflectionClass($object);
161
      } else {
162
        $reflection = new \ReflectionObject($object);
163
      }
164
      $methods = $reflection->getMethods(\ReflectionMethod::IS_PUBLIC);
165
166
      $code = '';
167
      foreach ($methods as &$curMethod) {
168
        $fname = $curMethod->getName();
169
        if (in_array($fname, array('__construct', '__destruct', '__get', '__set', '__isset', '__unset', '__toString', '__invoke', '__set_state', '__clone', '__sleep', 'jsonSerialize'))) {
170
          continue;
171
        }
172
173
        //$fprefix = $curMethod->isFinal() ? 'final ' : '';
174
        $fprefix = $curMethod->isStatic() ? 'static ' : '';
175
        $params = $curMethod->getParameters();
176
        $paramArray = $paramNameArray = array();
177
        foreach ($params as &$curParam) {
178
          $paramName = $curParam->getName();
179
          $paramPrefix = $curParam->isVariadic() ? '...' : '';
180
          $paramPrefix .= $curParam->isPassedByReference() ? '&' : '';
181
          if ($curParam->isDefaultValueAvailable()) {
182
            $paramSuffix = ' = '.var_export($curParam->getDefaultValue(), true);
183
          } else {
184
            $paramSuffix = '';
185
          }
186
          $paramArray[] = "{$paramPrefix}\${$paramName}{$paramSuffix}";
187
          $paramNameArray[] = "{$paramPrefix}\${$paramName}";
188
        }
189
        unset($curParam);
190
        $code .= $fprefix."function $fname(".implode(', ', $paramArray).") {\n"
191
        .(defined('HHVM_VERSION') ?
192
          (
193
            // There is bad behavior in HHVM where debug_backtrace
194
            // won't return arguments, but calling func_get_args
195
            // somewhere in the function changes that behavior to be
196
            // consistent with official PHP. However, it also
197
            // returns arguments by value, instead of by reference.
198
            // So, we must use a more direct method.
199
            "  \$_HOOK_arguments = array();\n"
200
            .(count($paramNameArray) > 0 ?
201
              "  \$_HOOK_arguments[] = ".implode('; $_HOOK_arguments[] = ', $paramNameArray).";\n" :
202
              ''
203
            )
204
            ."  \$_HOOK_real_arg_count = func_num_args();\n"
205
            ."  \$_HOOK_arg_count = count(\$_HOOK_arguments);\n"
206
            ."  if (\$_HOOK_real_arg_count > \$_HOOK_arg_count) {\n"
207
            ."    for (\$i = \$_HOOK_arg_count; \$i < \$_HOOK_real_arg_count; \$i++)\n"
208
            ."      \$_HOOK_arguments[] = func_get_arg(\$i);\n"
209
            ."  }\n"
210
          ) : (
211
            // We must use a debug_backtrace, because that's the
212
            // best way to get all the passed arguments, by
213
            // reference. 5.4 and up lets us limit it to 1 frame.
214
            (version_compare(PHP_VERSION, '5.4.0') >= 0 ?
215
              "  \$_HOOK_arguments = debug_backtrace(false, 1);\n" :
216
              "  \$_HOOK_arguments = debug_backtrace(false);\n"
217
            )
218
            ."  \$_HOOK_arguments = \$_HOOK_arguments[0]['args'];\n"
219
          )
220
        )
221
        ."  \$_HOOK_function = array(\$this->_hookObject, '$fname');\n"
222
        ."  \$_HOOK_data = array();\n"
223
        ."  \\SciActive\\Hook::runCallbacks(\$this->_hookPrefix.'$fname', \$_HOOK_arguments, 'before', \$this->_hookObject, \$_HOOK_function, \$_HOOK_data);\n"
224
        ."  if (\$_HOOK_arguments !== false) {\n"
225
        ."    \$_HOOK_return = call_user_func_array(\$_HOOK_function, \$_HOOK_arguments);\n"
226
        ."    if ((object) \$_HOOK_return === \$_HOOK_return && get_class(\$_HOOK_return) === '$className')\n"
227
        ."      \\SciActive\\Hook::hookObject(\$_HOOK_return, '$prefix', false);\n"
228
        ."    \$_HOOK_return = array(\$_HOOK_return);\n"
229
        ."    \\SciActive\\Hook::runCallbacks(\$this->_hookPrefix.'$fname', \$_HOOK_return, 'after', \$this->_hookObject, \$_HOOK_function, \$_HOOK_data);\n"
230
        ."    if ((array) \$_HOOK_return === \$_HOOK_return)\n"
231
        ."      return \$_HOOK_return[0];\n"
232
        ."  }\n"
233
        ."}\n\n";
234
      }
235
      unset($curMethod);
236
      // Build a HookOverride class.
237
      $include = str_replace(array('_NAMEHERE_', '//#CODEHERE#', '<?php', '?>'), array($className, $code, '', ''), Hook::$hookFile);
238
      eval($include);
239
    }
240
241
    eval('$object = new \SciActive\HookOverride_'.$className.' ($object, $prefix);');
242
    return true;
243
  }
244
245
  /**
246
   * Run the callbacks for a given hook.
247
   *
248
   * Each callback is run and passed the array of arguments, and the name of
249
   * the given hook. If any callback changes $arguments to FALSE, the
250
   * following callbacks will not be called, and FALSE will be returned.
251
   *
252
   * @param string $name The name of the hook.
253
   * @param array &$arguments An array of arguments to be passed to the callbacks.
254
   * @param string $type The type of callbacks to run. 'before', 'after', or 'all'.
255
   * @param mixed &$object The object on which the hook was called.
256
   * @param callback &$function The function which is called at "0". You can change this in the "before" callbacks to effectively takeover a function.
257
   * @param array &$data A data array for callback communication.
258
   */
259
  public static function runCallbacks($name, &$arguments = array(), $type = 'all', &$object = null, &$function = null, &$data = array()) {
260 View Code Duplication
    if (isset(Hook::$hooks['all'])) {
261
      foreach (Hook::$hooks['all'] as $curCallback) {
262
        if (($type == 'all' && $curCallback[0] != 0) || ($type == 'before' && $curCallback[0] < 0) || ($type == 'after' && $curCallback[0] > 0)) {
263
          call_user_func_array($curCallback[1], array(&$arguments, $name, &$object, &$function, &$data));
264
          if ($arguments === false) {
265
            return;
266
          }
267
        }
268
      }
269
    }
270 View Code Duplication
    if (isset(Hook::$hooks[$name])) {
271
      foreach (Hook::$hooks[$name] as $curCallback) {
272
        if (($type == 'all' && $curCallback[0] != 0) || ($type == 'before' && $curCallback[0] < 0) || ($type == 'after' && $curCallback[0] > 0)) {
273
          call_user_func_array($curCallback[1], array(&$arguments, $name, &$object, &$function, &$data));
274
          if ($arguments === false) {
275
            return;
276
          }
277
        }
278
      }
279
    }
280
  }
281
282
  /**
283
   * Sort function for callback sorting.
284
   *
285
   * This assures that callbacks are executed in the correct order. Callback
286
   * IDs are preserved as long as uasort() is used.
287
   *
288
   * @param array $a The first callback in the comparison.
289
   * @param array $b The second callback in the comparison.
290
   * @return int 0 for equal, -1 for less than, 1 for greater than.
291
   * @access private
292
   */
293
  private static function sortCallbacks($a, $b) {
0 ignored issues
show
Unused Code introduced by
This method is not used, and could be removed.
Loading history...
294
    if ($a[0] == $b[0]) {
295
      return 0;
296
    }
297
    return ($a[0] < $b[0]) ? -1 : 1;
298
  }
299
300
  public static function getHookFile() {
301
    Hook::$hookFile = file_get_contents(__DIR__.DIRECTORY_SEPARATOR.'HookOverride_extend.php');
302
  }
303
}
304
305
Hook::getHookFile();
306