CallableMethodsList::fromObject()   A
last analyzed

Complexity

Conditions 3
Paths 3

Size

Total Lines 21
Code Lines 8

Duplication

Lines 0
Ratio 0 %

Importance

Changes 3
Bugs 3 Features 0
Metric Value
c 3
b 3
f 0
dl 0
loc 21
rs 9.3142
cc 3
eloc 8
nc 3
nop 1
1
<?php
2
3
/**
4
 * Copyright (c) 2015-present Ganbaro Digital Ltd
5
 * All rights reserved.
6
 *
7
 * Redistribution and use in source and binary forms, with or without
8
 * modification, are permitted provided that the following conditions
9
 * are met:
10
 *
11
 *   * Redistributions of source code must retain the above copyright
12
 *     notice, this list of conditions and the following disclaimer.
13
 *
14
 *   * Redistributions in binary form must reproduce the above copyright
15
 *     notice, this list of conditions and the following disclaimer in
16
 *     the documentation and/or other materials provided with the
17
 *     distribution.
18
 *
19
 *   * Neither the names of the copyright holders nor the names of his
20
 *     contributors may be used to endorse or promote products derived
21
 *     from this software without specific prior written permission.
22
 *
23
 * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
24
 * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
25
 * LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS
26
 * FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE
27
 * COPYRIGHT OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT,
28
 * INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING,
29
 * BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES;
30
 * LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER
31
 * CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT
32
 * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN
33
 * ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE
34
 * POSSIBILITY OF SUCH DAMAGE.
35
 *
36
 * @category  Libraries
37
 * @package   Reflection/ValueBuilders
38
 * @author    Stuart Herbert <[email protected]>
39
 * @copyright 2015-present Ganbaro Digital Ltd www.ganbarodigital.com
40
 * @license   http://www.opensource.org/licenses/bsd-license.php  BSD License
41
 * @link      http://code.ganbarodigital.com/php-file-system
42
 */
43
44
namespace GanbaroDigital\Reflection\ValueBuilders;
45
46
use ReflectionClass;
47
use ReflectionObject;
48
use ReflectionMethod;
49
50
use GanbaroDigital\DataContainers\Caches\StaticDataCache;
51
use GanbaroDigital\Reflection\Exceptions\E4xx_NoSuchClass;
52
use GanbaroDigital\Reflection\Exceptions\E4xx_UnsupportedType;
53
54
class CallableMethodsList
55
{
56
    // we are going to cache the results for performance
57
    use StaticDataCache;
58
59
    /**
60
     * extract an indexed list of methods from an object
61
     *
62
     * @param  object $obj
63
     *         the object to obtain method names from
64
     * @return array
65
     *         a list of the matching method names, indexed by method name
66
     *         for quick look up
67
     */
68
    public static function fromObject($obj)
69
    {
70
        // robustness!
71
        if (!is_object($obj)) {
72
            throw new E4xx_UnsupportedType(gettype($obj));
73
        }
74
75
        // do we already have the answer?
76
        if (($retval = self::getObjectFromCache($obj)) !== null) {
77
            return $retval;
78
        }
79
80
        // what can be called?
81
        $retval = self::buildListOfObjectMethods($obj);
82
83
        // cache it for next time
84
        self::setObjectInCache($obj, $retval);
85
86
        // all done
87
        return $retval;
88
    }
89
90
    /**
91
     * extract an indexed list of methods from a class or object
92
     *
93
     * @param  string $className
94
     *         the class to obtain method names from
95
     * @return array
96
     *         a list of the matching method names, indexed by method name
97
     *         for quick look up
98
     */
99
    protected static function fromClassName($className)
100
    {
101
        // make sure that we have an actual class
102
        if (!class_exists($className)) {
103
            throw new E4xx_NoSuchClass($className);
104
        }
105
106
        // do we already have this?
107
        if (($retval = self::getClassFromCache($className)) !== null) {
108
            return $retval;
109
        }
110
111
        // what can be called?
112
        $retval = self::buildListOfClassMethods($className);
113
114
        // cache it
115
        self::setClassInCache($className, $retval);
116
117
        // all done
118
        return $retval;
119
    }
120
121
    /**
122
     * return a list of public methods on a class
123
     *
124
     * this will return both static and non-static methods
125
     *
126
     * @param  string $className
127
     *         the class to check
128
     * @return array
129
     */
130
    private static function getPublicMethodsFromClass($className)
131
    {
132
        // get the list of methods from reflection
133
        $refClass = new ReflectionClass($className);
134
        return $refClass->getMethods(ReflectionMethod::IS_PUBLIC);
135
    }
136
137
    /**
138
     * build a list of methods, based on whether they are static or not
139
     *
140
     * @param  array $rawMethods
141
     *         a list of ReflectionMethod objects to filter on
142
     * @param  boolean $isStatic
143
     *         TRUE if you want only static methods
144
     *         FALSE if you want only non-static methods
145
     * @return array
146
     *         the method names that have passed the filter
147
     */
148
    private static function filterMethodsByStaticness($rawMethods, $isStatic)
149
    {
150
        // our return value
151
        $retval = [];
152
153
        // eventually, we'll move this out into a separate class that can
154
        // be combined as a data stream
155
        foreach ($rawMethods as $rawMethod) {
156
            // skip over static methods
157
            if (!$rawMethod->isStatic() === $isStatic) {
158
                continue;
159
            }
160
161
            // we like this one :)
162
            $methodName = $rawMethod->getName();
163
            $retval[$methodName] = $methodName;
164
        }
165
166
        // all done
167
        return $retval;
168
    }
169
170
    /**
171
     * extract an indexed list of methods from a class or object
172
     *
173
     * @param  string $className
174
     *         the class to obtain method names from
175
     * @return array
176
     *         a list of the matching method names, indexed by method name
177
     *         for quick look up
178
     */
179
    public static function fromString($className)
180
    {
181
        // robustness!
182
        if (!is_string($className)) {
183
            throw new E4xx_UnsupportedType(gettype($className));
184
        }
185
186
        return self::fromClassName($className);
187
    }
188
189
    /**
190
     * extract an indexed list of methods from a class or object
191
     *
192
     * @param  mixed $data
193
     *         the class or object to obtain method names from
194
     * @return array
195
     *         a list of the matching method names, indexed by method name
196
     *         for quick look up
197
     *
198
     * @throws E4xx_UnsupportedType
199
     */
200
    public static function from($data)
201
    {
202
        // we do this old-skool style because CallableMethodsList (the way
203
        // we tell everyone to do this kind of matching) actually depends
204
        // on us to work
205
        if (is_object($data)) {
206
            return self::fromObject($data);
207
        }
208
209
        if (is_string($data)) {
210
            return self::fromString($data);
211
        }
212
213
        // don't know what you are, don't care
214
        throw new E4xx_UnsupportedType(gettype($data));
215
    }
216
217
    /**
218
     * extract an indexed list of methods from a class or object
219
     *
220
     * @deprecated since 2.10.0
221
     * @codeCoverageIgnore
222
     * @param  mixed $data
223
     *         the class or object to obtain method names from
224
     * @return array
225
     *         a list of the matching method names, indexed by method name
226
     *         for quick look up
227
     *
228
     * @throws E4xx_UnsupportedType
229
     */
230
    public static function fromMixed($data)
231
    {
232
        return self::from($data);
233
    }
234
235
    /**
236
     * extract an indexed list of methods from a class or object
237
     *
238
     * @param  mixed $data
239
     *         the class or object to obtain method names from
240
     * @return array
241
     *         a list of the matching method names, indexed by method name
242
     *         for quick look up
243
     *
244
     * @throws E4xx_UnsupportedType
245
     */
246
    public function __invoke($data)
247
    {
248
        return self::from($data);
249
    }
250
251
    /**
252
     * get the cache key to use for a given classname
253
     *
254
     * @param  string $className
255
     *         the class we want to cache data about
256
     * @return string
257
     */
258
    private static function getClassCacheName($className)
259
    {
260
        return $className . '::class';
261
    }
262
263
    /**
264
     * get the cache key to use for a given object
265
     *
266
     * @param  object $obj
267
     *         the object we want to cache data about
268
     * @return string
269
     */
270
    private static function getObjectCacheName($obj)
271
    {
272
        return get_class($obj) . '::object';
273
    }
274
275
    /**
276
     * get cached data about a class
277
     *
278
     * @param  string $className
279
     *         the class we want cached data for
280
     * @return array|null
281
     */
282
    private static function getClassFromCache($className)
283
    {
284
        $cacheKey = self::getClassCacheName($className);
285
        return self::getFromCache($cacheKey);
286
    }
287
288
    /**
289
     * write data about a class into our cache
290
     *
291
     * @param  string $className
292
     *         the class we want to cache data for
293
     * @param  array $methodsList
294
     *         the data we want to cache
295
     * @return void
296
     */
297
    private static function setClassInCache($className, array $methodsList)
298
    {
299
        $cacheKey = self::getClassCacheName($className);
300
        self::setInCache($cacheKey, $methodsList);
301
    }
302
303
    /**
304
     * get cached data about an object
305
     *
306
     * @param  object $obj
307
     *         the object we want cached data for
308
     * @return array|null
309
     */
310
    private static function getObjectFromCache($obj)
311
    {
312
        $cacheKey = self::getObjectCacheName($obj);
313
        return self::getFromCache($cacheKey);
314
    }
315
316
    /**
317
     * write data about an object into our cache
318
     *
319
     * @param  object $obj
320
     *         the obj we want to cache data for
321
     * @param  array $methodsList
322
     *         the data we want to cache
323
     * @return void
324
     */
325
    private static function setObjectInCache($obj, array $methodsList)
326
    {
327
        $cacheKey = self::getObjectCacheName($obj);
328
        self::setInCache($cacheKey, $methodsList);
329
    }
330
331
    /**
332
     * return a list of methods that can be called on a class
333
     *
334
     * @param  string $className
335
     *         the class to examine
336
     * @return array
337
     *         a list of methods that can be called without having to
338
     *         instantiate an object
339
     */
340
    private static function buildListOfClassMethods($className)
341
    {
342
        // get the methods
343
        $rawMethods = self::getPublicMethodsFromClass($className);
344
345
        // unfortunately, getMethods() returns an array indexed by number,
346
        // and not an array indexed by method name, so we now need to
347
        // transform the array
348
        $retval = self::filterMethodsByStaticness($rawMethods, true);
349
350
        // all done
351
        return $retval;
352
    }
353
354
    /**
355
     * return a list of methods that can be called on an object
356
     *
357
     * @param  object $obj
358
     *         the object to examine
359
     * @return array
360
     *         a list of methods that can be called on the object
361
     */
362
    private static function buildListOfObjectMethods($obj)
363
    {
364
        // get the methods
365
        $rawMethods = self::getPublicMethodsFromClass(get_class($obj));
366
367
        // unfortunately, getMethods() returns an array indexed by number,
368
        // and not an array indexed by method name, so we now need to
369
        // transform the array
370
        $retval = self::filterMethodsByStaticness($rawMethods, false);
371
372
        // all done
373
        return $retval;
374
    }
375
}