Wire::addHook PHP Method

addHook() public method

Hookable method calls are methods preceded by three underscores. You may also specify a method that doesn't exist already in the class The hook method that you define may be part of a class or a globally scoped function. If you are hooking a procedural function, you may omit the $toObject and instead just call via: $this->addHook($method, 'function_name'); or $this->addHook($method, 'function_name', $options);
public addHook ( string $method, object | null | callable $toObject, string $toMethod = null, array $options = [] ) : string
$method string Method name to hook into, NOT including the three preceding underscores. May also be Class::Method for same result as using the fromClass option.
$toObject object | null | callable Object to call $toMethod from, Or null if $toMethod is a function outside of an object, Or function|callable if $toObject is not applicable or function is provided as a closure.
$toMethod string Method from $toObject, or function name to call on a hook event. Optional.
$options array See self::$defaultHookOptions at the beginning of this class. Optional.
return string A special Hook ID that should be retained if you need to remove the hook later
    public function addHook($method, $toObject, $toMethod = null, $options = array())
    {
        if (is_array($toMethod)) {
            // $options array specified as 3rd argument
            if (count($options)) {
                // combine $options from addHookBefore/After and user specified options
                $options = array_merge($toMethod, $options);
            } else {
                $options = $toMethod;
            }
            $toMethod = null;
        }
        if (is_null($toMethod)) {
            // $toObject has been ommitted and a procedural function specified instead
            // $toObject may also be a closure
            $toMethod = $toObject;
            $toObject = null;
        }
        if (is_null($toMethod)) {
            throw new WireException("Method to call is required and was not specified (toMethod)");
        }
        if (substr($method, 0, 3) == '___') {
            throw new WireException("You must specify hookable methods without the 3 preceding underscores");
        }
        if (method_exists($this, $method)) {
            throw new WireException("Method " . $this->className() . "::{$method} is not hookable");
        }
        $options = array_merge(self::$defaultHookOptions, $options);
        if (strpos($method, '::')) {
            list($fromClass, $method) = explode('::', $method, 2);
            if (strpos($fromClass, '(') !== false) {
                // extract object selector match string
                list($fromClass, $objMatch) = explode('(', $fromClass, 2);
                $objMatch = trim($objMatch, ') ');
                if (Selectors::stringHasSelector($objMatch)) {
                    $objMatch = new Selectors($objMatch);
                }
                if ($objMatch) {
                    $options['objMatch'] = $objMatch;
                }
            }
            $options['fromClass'] = $fromClass;
        }
        $argOpen = strpos($method, '(');
        if ($argOpen && strpos($method, ')') > $argOpen + 1) {
            // extract argument selector match string(s), arg 0: Something::something(selector_string)
            // or: Something::something(1:selector_string, 3:selector_string) matches arg 1 and 3.
            list($method, $argMatch) = explode('(', $method, 2);
            $argMatch = trim($argMatch, ') ');
            if (strpos($argMatch, ':') !== false) {
                // zero-based argument indexes specified, i.e. 0:template=product, 1:order_status
                $args = preg_split('/\\b([0-9]):/', trim($argMatch), -1, PREG_SPLIT_DELIM_CAPTURE);
                if (count($args)) {
                    $argMatch = array();
                    array_shift($args);
                    // blank
                    while (count($args)) {
                        $argKey = (int) trim(array_shift($args));
                        $argVal = trim(array_shift($args), ', ');
                        $argMatch[$argKey] = $argVal;
                    }
                }
            } else {
                // just single argument specified, so argument 0 is assumed
            }
            if (is_string($argMatch)) {
                $argMatch = array(0 => $argMatch);
            }
            foreach ($argMatch as $argKey => $argVal) {
                if (Selectors::stringHasSelector($argVal)) {
                    $argMatch[$argKey] = new Selectors($argVal);
                }
            }
            if (count($argMatch)) {
                $options['argMatch'] = $argMatch;
            }
        }
        if ($options['allInstances'] || $options['fromClass']) {
            // hook all instances of this class
            $hookClass = $options['fromClass'] ? $options['fromClass'] : $this->className();
            if (!isset(self::$staticHooks[$hookClass])) {
                self::$staticHooks[$hookClass] = array();
            }
            $hooks =& self::$staticHooks[$hookClass];
            $options['allInstances'] = true;
            $local = 0;
        } else {
            // hook only this instance
            $hookClass = '';
            $hooks =& $this->localHooks;
            $local = 1;
        }
        $priority = (string) $options['priority'];
        if (!isset($hooks[$method])) {
            if (ctype_digit($priority)) {
                $priority = "{$priority}.0";
            }
            $hooks[$method] = array();
        } else {
            if (strpos($priority, '.')) {
                // priority already specifies a sub value: extract it
                list($priority, $n) = explode('.', $priority);
                $options['priority'] = $priority;
                // without $n
                $priority .= ".{$n}";
            } else {
                $n = 0;
                $priority .= ".0";
            }
            // come up with a priority that is unique for this class/method across both local and static hooks
            while ($hookClass && isset(self::$staticHooks[$hookClass][$method][$priority]) || isset($this->localHooks[$method][$priority])) {
                $n++;
                $priority = "{$options['priority']}.{$n}";
            }
        }
        // Note hookClass is always blank when this is a local hook
        $id = "{$hookClass}:{$priority}:{$method}";
        $options['priority'] = $priority;
        $hooks[$method][$priority] = array('id' => $id, 'method' => $method, 'toObject' => $toObject, 'toMethod' => $toMethod, 'options' => $options);
        // cacheValue is just the method() or property, cacheKey includes optional fromClass::
        $cacheValue = $options['type'] == 'method' ? "{$method}()" : "{$method}";
        $cacheKey = ($options['fromClass'] ? $options['fromClass'] . '::' : '') . $cacheValue;
        self::$hookMethodCache[$cacheKey] = $cacheValue;
        // keep track of all local hooks combined when debug mode is on
        if ($this->wire('config')->debug && $hooks === $this->localHooks) {
            $debugClass = $this->className();
            $debugID = ($local ? $debugClass : '') . $id;
            while (isset(self::$allLocalHooks[$debugID])) {
                $debugID .= "_";
            }
            $debugHook = $hooks[$method][$priority];
            $debugHook['method'] = $debugClass . "->" . $debugHook['method'];
            self::$allLocalHooks[$debugID] = $debugHook;
        }
        // sort by priority, if more than one hook for the method
        if (count($hooks[$method]) > 1) {
            defined("SORT_NATURAL") ? ksort($hooks[$method], SORT_NATURAL) : uksort($hooks[$method], "strnatcmp");
        }
        return $id;
    }