Code Coverage
 
Lines
Functions and Methods
Classes and Traits
Total
100.00% covered (success)
100.00%
101 / 101
100.00% covered (success)
100.00%
4 / 4
CRAP
100.00% covered (success)
100.00%
1 / 1
CallableObject
100.00% covered (success)
100.00%
101 / 101
100.00% covered (success)
100.00%
4 / 4
46
100.00% covered (success)
100.00%
1 / 1
 __construct
100.00% covered (success)
100.00%
5 / 5
100.00% covered (success)
100.00%
1 / 1
3
 prepare
100.00% covered (success)
100.00%
35 / 35
100.00% covered (success)
100.00%
1 / 1
16
 prepareParameters
100.00% covered (success)
100.00%
10 / 10
100.00% covered (success)
100.00%
1 / 1
7
 call
100.00% covered (success)
100.00%
51 / 51
100.00% covered (success)
100.00%
1 / 1
20
1<?php
2/**
3 * Pop PHP Framework (http://www.popphp.org/)
4 *
5 * @link       https://github.com/popphp/popphp-framework
6 * @author     Nick Sagona, III <dev@nolainteractive.com>
7 * @copyright  Copyright (c) 2009-2024 NOLA Interactive, LLC. (http://www.nolainteractive.com)
8 * @license    http://www.popphp.org/license     New BSD License
9 */
10
11/**
12 * @namespace
13 */
14namespace Pop\Utils;
15
16use ReflectionClass;
17use ReflectionException;
18
19/**
20 * Pop utils callable object class
21 *
22 * @category   Pop
23 * @package    Pop\Utils
24 * @author     Nick Sagona, III <dev@nolainteractive.com>
25 * @copyright  Copyright (c) 2009-2024 NOLA Interactive, LLC. (http://www.nolainteractive.com)
26 * @license    http://www.popphp.org/license     New BSD License
27 * @version    2.1.0
28 */
29class CallableObject extends AbstractCallable
30{
31
32    /**
33     * Constructor
34     *
35     * Instantiate the callable object
36     *
37     * @param  mixed $callable
38     * @param  mixed $parameters
39     */
40    public function __construct(mixed $callable, mixed $parameters = null)
41    {
42        $this->setCallable($callable);
43        if ($parameters !== null) {
44            if (is_array($parameters)) {
45                $this->setParameters($parameters);
46            } else {
47                $this->addParameter($parameters);
48            }
49        }
50    }
51
52    /**
53     * Prepare object for call
54     *
55     * @throws Exception
56     * @return CallableObject
57     */
58    public function prepare(): CallableObject
59    {
60        $class  = null;
61        $method = null;
62
63        if ($this->callable instanceof \Closure) {
64            $this->callableType = self::CLOSURE;
65        } else if (is_object($this->callable)) {
66            $this->callableType = self::OBJECT;
67        } else if (is_string($this->callable)) {
68            if (str_contains($this->callable, '::')) {
69                $this->callableType = self::STATIC_CALL;
70                [$class, $method]   = explode('::', $this->callable);
71            } else if (str_contains($this->callable, '->')) {
72                $this->callableType = self::INSTANCE_CALL;
73                [$class, $method]   = explode('->', $this->callable);
74            } else if (str_starts_with($this->callable, 'new ')) {
75                $this->callableType = self::NEW_OBJECT;
76                $class = substr($this->callable, 4);
77            } else if (class_exists($this->callable)) {
78                $this->callableType = self::CONSTRUCTOR_CALL;
79            } else if (function_exists($this->callable)) {
80                $this->callableType = self::FUNCTION;
81            }
82        } else if (is_callable($this->callable)) {
83            $this->callableType = self::IS_CALLABLE;
84        }
85
86        if ($class !== null) {
87            if (!class_exists($class)) {
88                throw new Exception("Error: The class '" . $class . "' does not exist.");
89            }
90            if ($method !== null) {
91                if (!method_exists($class, $method)) {
92                    throw new Exception("Error: The method '" . $method  . "' does not exist in the class '" . $class . "'.");
93                }
94            }
95        }
96
97        if ($this->callableType === null) {
98            throw new Exception('Error: Unable to prepare the callable object for execution.');
99        } else if (!empty($this->parameters)) {
100            $this->callableType .= '_PARAMS';
101        }
102
103        $this->class  = $class;
104        $this->method = $method;
105
106        return $this;
107    }
108
109    /**
110     * Prepare parameters
111     *
112     * @throws Exception|ReflectionException
113     * @return CallableObject
114     */
115    public function prepareParameters(): CallableObject
116    {
117        foreach ($this->parameters as $key => $value) {
118            if ($value instanceof self) {
119                $this->parameters[$key] = $value->call();
120            } else if (is_callable($value)) {
121                $this->parameters[$key] = call_user_func($value);
122            } else if (is_array($value) && isset($value[0]) && is_callable($value[0])) {
123                $callable = $value[0];
124                unset($value[0]);
125                $this->parameters[$key] = call_user_func_array($callable, array_values($value));
126            }
127        }
128
129        return $this;
130    }
131
132    /**
133     * Execute the callable
134     *
135     * @param  mixed $parameters
136     * @return mixed
137     * @throws Exception|ReflectionException
138     */
139    public function call(mixed $parameters = null): mixed
140    {
141        if ($parameters !== null) {
142            if (!is_array($parameters)) {
143                $this->addParameter($parameters);
144            } else {
145                $this->addParameters($parameters);
146            }
147        }
148
149        if ($this->callableType === null) {
150            $this->prepare();
151        }
152
153        $this->prepareParameters();
154
155        $result = null;
156
157        switch ($this->callableType) {
158            case self::FUNCTION:
159            case self::CLOSURE:
160            case self::STATIC_CALL:
161            case self::IS_CALLABLE:
162                $result = call_user_func($this->callable);
163                break;
164            case self::FUNCTION_PARAMS:
165            case self::CLOSURE_PARAMS:
166            case self::STATIC_CALL_PARAMS:
167            case self::IS_CALLABLE_PARAMS:
168                $result = call_user_func_array($this->callable, array_values($this->parameters));
169                break;
170            case self::INSTANCE_CALL:
171                $class  = $this->class;
172                $object = (!empty($this->constructorParams)) ?
173                    (new ReflectionClass($class))->newInstanceArgs($this->constructorParams) :
174                    new $class();
175                $result = call_user_func([$object, $this->method]);
176                break;
177            case self::INSTANCE_CALL_PARAMS:
178                $class  = $this->class;
179                $object = (!empty($this->constructorParams)) ?
180                    (new ReflectionClass($class))->newInstanceArgs($this->constructorParams) :
181                    new $class();
182                $result = call_user_func_array([$object, $this->method], array_values($this->parameters));
183                break;
184            case self::CONSTRUCTOR_CALL:
185                $class  = $this->callable;
186                $result = new $class();
187                break;
188            case self::CONSTRUCTOR_CALL_PARAMS:
189                $result = (new ReflectionClass($this->callable))->newInstanceArgs($this->parameters);
190                break;
191            case self::NEW_OBJECT:
192                $class  = $this->class;
193                $result = new $class();
194                break;
195            case self::OBJECT:
196                $result = $this->callable;
197                break;
198        }
199
200        $this->wasCalled = true;
201
202        return $result;
203    }
204
205}