Completed
Push — master ( 644ae6...bba86b )
by Daniel
10:38
created

ClassInfo::classImplements()   A

Complexity

Conditions 1
Paths 1

Size

Total Lines 4
Code Lines 2

Duplication

Lines 0
Ratio 0 %

Importance

Changes 0
Metric Value
cc 1
eloc 2
nc 1
nop 2
dl 0
loc 4
rs 10
c 0
b 0
f 0
1
<?php
2
3
namespace SilverStripe\Core;
4
5
use SilverStripe\Control\Director;
6
use SilverStripe\Core\Manifest\ClassLoader;
7
use SilverStripe\Dev\Deprecation;
8
use SilverStripe\ORM\ArrayLib;
9
use SilverStripe\ORM\DB;
10
use SilverStripe\ORM\DataObject;
11
use ReflectionClass;
12
13
/**
14
 * Provides introspection information about the class tree.
15
 *
16
 * It's a cached wrapper around the built-in class functions.  SilverStripe uses
17
 * class introspection heavily and without the caching it creates an unfortunate
18
 * performance hit.
19
 */
20
class ClassInfo
21
{
22
23
    /**
24
     * Wrapper for classes getter.
25
     *
26
     * @return array
27
     */
28
    public static function allClasses()
29
    {
30
        return ClassLoader::instance()->getManifest()->getClasses();
31
    }
32
33
    /**
34
     * Returns true if a class or interface name exists.
35
     *
36
     * @param  string $class
37
     * @return bool
38
     */
39
    public static function exists($class)
40
    {
41
        return class_exists($class, false) || interface_exists($class, false) || ClassLoader::instance()->getItemPath($class);
0 ignored issues
show
Bug Best Practice introduced by
The expression \SilverStripe\Core\Manif...()->getItemPath($class) of type string|false is loosely compared to true; this is ambiguous if the string can be empty. You might want to explicitly use !== false instead.

In PHP, under loose comparison (like ==, or !=, or switch conditions), values of different types might be equal.

For string values, the empty string '' is a special case, in particular the following results might be unexpected:

''   == false // true
''   == null  // true
'ab' == false // false
'ab' == null  // false

// It is often better to use strict comparison
'' === false // false
'' === null  // false
Loading history...
42
    }
43
44
    /**
45
     * Cache for {@link hasTable()}
46
     */
47
    private static $_cache_all_tables = array();
48
49
    /**
50
     * @var array Cache for {@link ancestry()}.
51
     */
52
    private static $_cache_ancestry = array();
53
54
    /**
55
     * @todo Move this to SS_Database or DB
56
     *
57
     * @param string $tableName
58
     * @return bool
59
     */
60
    public static function hasTable($tableName)
61
    {
62
        // Cache the list of all table names to reduce on DB traffic
63
        if (empty(self::$_cache_all_tables) && DB::is_active()) {
64
            self::$_cache_all_tables = DB::get_schema()->tableList();
65
        }
66
        return !empty(self::$_cache_all_tables[strtolower($tableName)]);
67
    }
68
69
    public static function reset_db_cache()
70
    {
71
        self::$_cache_all_tables = null;
0 ignored issues
show
Documentation Bug introduced by
It seems like null of type null is incompatible with the declared type array of property $_cache_all_tables.

Our type inference engine has found an assignment to a property that is incompatible with the declared type of that property.

Either this assignment is in error or the assigned type should be added to the documentation/type hint for that property..

Loading history...
72
        self::$_cache_ancestry = array();
73
    }
74
75
    /**
76
     * Returns the manifest of all classes which are present in the database.
77
     *
78
     * @param string $class Class name to check enum values for ClassName field
79
     * @param boolean $includeUnbacked Flag indicating whether or not to include
80
     * types that don't exist as implemented classes. By default these are excluded.
81
     * @return array List of subclasses
82
     */
83
    public static function getValidSubClasses($class = 'SilverStripe\\CMS\\Model\\SiteTree', $includeUnbacked = false)
84
    {
85
        if (is_string($class) && !class_exists($class)) {
86
            return array();
87
        }
88
89
        $class = self::class_name($class);
90
        if ($includeUnbacked) {
91
            $table = DataObject::getSchema()->tableName($class);
92
            $classes = DB::get_schema()->enumValuesForField($table, 'ClassName');
93
        } else {
94
            $classes = static::subclassesFor($class);
95
        }
96
        return $classes;
97
    }
98
99
    /**
100
     * Returns an array of the current class and all its ancestors and children
101
     * which require a DB table.
102
     *
103
     * @todo Move this into {@see DataObjectSchema}
104
     *
105
     * @param string|object $nameOrObject Class or object instance
106
     * @return array
107
     */
108
    public static function dataClassesFor($nameOrObject)
109
    {
110
        if (is_string($nameOrObject) && !class_exists($nameOrObject)) {
111
            return array();
112
        }
113
114
        $result = array();
115
116
        $class = self::class_name($nameOrObject);
117
118
        $classes = array_merge(
119
            self::ancestry($class),
120
            self::subclassesFor($class)
121
        );
122
123
        foreach ($classes as $class) {
124
            if (DataObject::getSchema()->classHasTable($class)) {
125
                $result[$class] = $class;
126
            }
127
        }
128
129
        return $result;
130
    }
131
132
    /**
133
     * @deprecated 4.0..5.0
134
     */
135
    public static function baseDataClass($class)
136
    {
137
        Deprecation::notice('5.0', 'Use DataObject::getSchema()->baseDataClass()');
138
        return DataObject::getSchema()->baseDataClass($class);
139
    }
140
141
    /**
142
     * Returns a list of classes that inherit from the given class.
143
     * The resulting array includes the base class passed
144
     * through the $class parameter as the first array value.
145
     *
146
     * Example usage:
147
     * <code>
148
     * ClassInfo::subclassesFor('BaseClass');
149
     *  array(
150
     *  'BaseClass' => 'BaseClass',
151
     *  'ChildClass' => 'ChildClass',
152
     *  'GrandChildClass' => 'GrandChildClass'
153
     * )
154
     * </code>
155
     *
156
     * @param string|object $nameOrObject The classname or object
157
     * @return array Names of all subclasses as an associative array.
158
     */
159
    public static function subclassesFor($nameOrObject)
160
    {
161
        if (is_string($nameOrObject) && !class_exists($nameOrObject)) {
162
            return [];
163
        }
164
165
        //normalise class case
166
        $className = self::class_name($nameOrObject);
167
        $descendants = ClassLoader::instance()->getManifest()->getDescendantsOf($className);
168
        $result      = array($className => $className);
169
170
        if ($descendants) {
0 ignored issues
show
Bug Best Practice introduced by
The expression $descendants of type array is implicitly converted to a boolean; are you sure this is intended? If so, consider using ! empty($expr) instead to make it clear that you intend to check for an array without elements.

This check marks implicit conversions of arrays to boolean values in a comparison. While in PHP an empty array is considered to be equal (but not identical) to false, this is not always apparent.

Consider making the comparison explicit by using empty(..) or ! empty(...) instead.

Loading history...
171
            return $result + ArrayLib::valuekey($descendants);
172
        } else {
173
            return $result;
174
        }
175
    }
176
177
    /**
178
     * Convert a class name in any case and return it as it was defined in PHP
179
     *
180
     * eg: self::class_name('dataobJEct'); //returns 'DataObject'
181
     *
182
     * @param string|object $nameOrObject The classname or object you want to normalise
183
     * @return string The normalised class name
184
     */
185
    public static function class_name($nameOrObject)
186
    {
187
        if (is_object($nameOrObject)) {
188
            return get_class($nameOrObject);
189
        }
190
        $reflection = new ReflectionClass($nameOrObject);
191
        return $reflection->getName();
192
    }
193
194
    /**
195
     * Returns the passed class name along with all its parent class names in an
196
     * array, sorted with the root class first.
197
     *
198
     * @param string|object $nameOrObject Class or object instance
199
     * @param bool $tablesOnly Only return classes that have a table in the db.
200
     * @return array
201
     */
202
    public static function ancestry($nameOrObject, $tablesOnly = false)
203
    {
204
        if (is_string($nameOrObject) && !class_exists($nameOrObject)) {
205
            return array();
206
        }
207
208
        $class = self::class_name($nameOrObject);
209
210
        $lClass = strtolower($class);
211
212
        $cacheKey = $lClass . '_' . (string)$tablesOnly;
213
        $parent = $class;
214
        if (!isset(self::$_cache_ancestry[$cacheKey])) {
215
            $ancestry = array();
216
            do {
217
                if (!$tablesOnly || DataObject::getSchema()->classHasTable($parent)) {
218
                    $ancestry[$parent] = $parent;
219
                }
220
            } while ($parent = get_parent_class($parent));
221
            self::$_cache_ancestry[$cacheKey] = array_reverse($ancestry);
222
        }
223
224
        return self::$_cache_ancestry[$cacheKey];
225
    }
226
227
    /**
228
     * @param string $interfaceName
229
     * @return array A self-keyed array of class names. Note that this is only available with Silverstripe
230
     * classes and not built-in PHP classes.
231
     */
232
    public static function implementorsOf($interfaceName)
233
    {
234
        return ClassLoader::instance()->getManifest()->getImplementorsOf($interfaceName);
235
    }
236
237
    /**
238
     * Returns true if the given class implements the given interface
239
     *
240
     * @param string $className
241
     * @param string $interfaceName
242
     * @return bool
243
     */
244
    public static function classImplements($className, $interfaceName)
245
    {
246
        return in_array($className, self::implementorsOf($interfaceName));
247
    }
248
249
    /**
250
     * Get all classes contained in a file.
251
     * @uses ManifestBuilder
252
     *
253
     * @todo Doesn't return additional classes that only begin
254
     *  with the filename, and have additional naming separated through underscores.
255
     *
256
     * @param string $filePath Path to a PHP file (absolute or relative to webroot)
257
     * @return array
258
     */
259
    public static function classes_for_file($filePath)
260
    {
261
        $absFilePath    = Director::getAbsFile($filePath);
262
        $matchedClasses = array();
263
        $manifest       = ClassLoader::instance()->getManifest()->getClasses();
264
265
        foreach ($manifest as $class => $compareFilePath) {
266
            if ($absFilePath == $compareFilePath) {
267
                $matchedClasses[] = $class;
268
            }
269
        }
270
271
        return $matchedClasses;
272
    }
273
274
    /**
275
     * Returns all classes contained in a certain folder.
276
     *
277
     * @todo Doesn't return additional classes that only begin
278
     *  with the filename, and have additional naming separated through underscores.
279
     *
280
     * @param string $folderPath Relative or absolute folder path
281
     * @return array Array of class names
282
     */
283
    public static function classes_for_folder($folderPath)
284
    {
285
        $absFolderPath  = Director::getAbsFile($folderPath);
286
        $matchedClasses = array();
287
        $manifest       = ClassLoader::instance()->getManifest()->getClasses();
288
289
        foreach ($manifest as $class => $compareFilePath) {
290
            if (stripos($compareFilePath, $absFolderPath) === 0) {
291
                $matchedClasses[] = $class;
292
            }
293
        }
294
295
        return $matchedClasses;
296
    }
297
298
    private static $method_from_cache = array();
299
300
    public static function has_method_from($class, $method, $compclass)
301
    {
302
        $lClass = strtolower($class);
303
        $lMethod = strtolower($method);
304
        $lCompclass = strtolower($compclass);
305
        if (!isset(self::$method_from_cache[$lClass])) {
306
            self::$method_from_cache[$lClass] = array();
307
        }
308
309
        if (!array_key_exists($lMethod, self::$method_from_cache[$lClass])) {
310
            self::$method_from_cache[$lClass][$lMethod] = false;
311
312
            $classRef = new ReflectionClass($class);
313
314
            if ($classRef->hasMethod($method)) {
315
                $methodRef = $classRef->getMethod($method);
316
                self::$method_from_cache[$lClass][$lMethod] = $methodRef->getDeclaringClass()->getName();
317
            }
318
        }
319
320
        return strtolower(self::$method_from_cache[$lClass][$lMethod]) == $lCompclass;
321
    }
322
323
    /**
324
     * @deprecated 4.0..5.0
325
     */
326
    public static function table_for_object_field($candidateClass, $fieldName)
327
    {
328
        Deprecation::notice('5.0', 'Use DataObject::getSchema()->tableForField()');
329
        return DataObject::getSchema()->tableForField($candidateClass, $fieldName);
330
    }
331
332
    /**
333
     * Strip namespace from class
334
     *
335
     * @param string|object $nameOrObject Name of class, or instance
336
     * @return string Name of class without namespace
337
     */
338
    public static function shortName($nameOrObject)
339
    {
340
        $reflection = new ReflectionClass($nameOrObject);
341
        return $reflection->getShortName();
342
    }
343
344
    /**
345
     * Helper to determine if the given object has a method
346
     *
347
     * @param object $object
348
     * @param string $method
349
     * @return bool
350
     */
351
    public static function hasMethod($object, $method)
352
    {
353
        if (empty($object)) {
354
            return false;
355
        }
356
        if (method_exists($object, $method)) {
357
            return true;
358
        }
359
        return method_exists($object, 'hasMethod') && $object->hasMethod($method);
360
    }
361
}
362