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