Completed
Push — master ( 9e3f76...51d53f )
by Hamish
10:45
created

ClassInfo::classes_for_file()   A

Complexity

Conditions 3
Paths 3

Size

Total Lines 11
Code Lines 7

Duplication

Lines 0
Ratio 0 %

Importance

Changes 0
Metric Value
cc 3
eloc 7
nc 3
nop 1
dl 0
loc 11
rs 9.4285
c 0
b 0
f 0
1
<?php
2
3
/**
4
 * Provides introspection information about the class tree.
5
 *
6
 * It's a cached wrapper around the built-in class functions.  SilverStripe uses
7
 * class introspection heavily and without the caching it creates an unfortunate
8
 * performance hit.
9
 *
10
 * @package framework
11
 * @subpackage core
12
 */
13
class ClassInfo {
14
15
	/**
16
	 * Wrapper for classes getter.
17
	 *
18
	 * @return array
19
	 */
20
	public static function allClasses() {
21
		return SS_ClassLoader::instance()->getManifest()->getClasses();
22
	}
23
24
	/**
25
	 * Returns true if a class or interface name exists.
26
	 *
27
	 * @param  string $class
28
	 * @return bool
29
	 */
30
	public static function exists($class) {
31
		return class_exists($class, false) || interface_exists($class, false) || SS_ClassLoader::instance()->getItemPath($class);
32
	}
33
34
	/**
35
	 * Cache for {@link hasTable()}
36
	 */
37
	private static $_cache_all_tables = array();
38
39
	/**
40
	 * @var Array Cache for {@link ancestry()}.
41
	 */
42
	private static $_cache_ancestry = array();
43
44
	/**
45
	 * @todo Move this to SS_Database or DB
46
	 */
47
	public static function hasTable($class) {
48
		// Cache the list of all table names to reduce on DB traffic
49
		if(empty(self::$_cache_all_tables) && DB::is_active()) {
50
			self::$_cache_all_tables = DB::get_schema()->tableList();
51
		}
52
		return !empty(self::$_cache_all_tables[strtolower($class)]);
53
	}
54
55
	public static function reset_db_cache() {
56
		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...
57
		self::$_cache_ancestry = array();
58
	}
59
60
	/**
61
	 * Returns the manifest of all classes which are present in the database.
62
	 *
63
	 * @param string $class Class name to check enum values for ClassName field
64
	 * @param boolean $includeUnbacked Flag indicating whether or not to include
65
	 * types that don't exist as implemented classes. By default these are excluded.
66
	 * @return array List of subclasses
67
	 */
68
	public static function getValidSubClasses($class = 'SiteTree', $includeUnbacked = false) {
69
		if(is_string($class) && !class_exists($class)) return array();
70
71
		$class = self::class_name($class);
72
		$classes = DB::get_schema()->enumValuesForField($class, 'ClassName');
73
		if (!$includeUnbacked) $classes = array_filter($classes, array('ClassInfo', 'exists'));
74
		return $classes;
75
	}
76
77
	/**
78
	 * Returns an array of the current class and all its ancestors and children
79
	 * which require a DB table.
80
	 *
81
	 * @todo Move this into {@see DataObjectSchema}
82
	 *
83
	 * @param string|object $class
84
	 * @return array
85
	 */
86
	public static function dataClassesFor($class) {
87
		if(is_string($class) && !class_exists($class)) return array();
88
89
		$result = array();
90
91
		$class = self::class_name($class);
92
93
		$classes = array_merge(
94
			self::ancestry($class),
95
			self::subclassesFor($class)
96
		);
97
98
		foreach ($classes as $class) {
99
			if (DataObject::has_own_table($class)) {
100
				$result[$class] = $class;
101
			}
102
		}
103
104
		return $result;
105
	}
106
107
	/**
108
	 * @deprecated 4.0..5.0
109
	 */
110
	public static function baseDataClass($class) {
111
		Deprecation::notice('5.0', 'Use DataObject::getSchema()->baseDataClass()');
112
		return DataObject::getSchema()->baseDataClass($class);
113
	}
114
115
	/**
116
	 * Returns a list of classes that inherit from the given class.
117
	 * The resulting array includes the base class passed
118
	 * through the $class parameter as the first array value.
119
	 *
120
	 * Example usage:
121
	 * <code>
122
	 * ClassInfo::subclassesFor('BaseClass');
123
	 * 	array(
124
	 * 	'BaseClass' => 'BaseClass',
125
	 * 	'ChildClass' => 'ChildClass',
126
	 * 	'GrandChildClass' => 'GrandChildClass'
127
	 * )
128
	 * </code>
129
	 *
130
	 * @param mixed $class string of the classname or instance of the class
131
	 * @return array Names of all subclasses as an associative array.
132
	 */
133
	public static function subclassesFor($class) {
134
		if(is_string($class) && !class_exists($class)) {
135
			return [];
136
		}
137
138
		//normalise class case
139
		$className = self::class_name($class);
140
		$descendants = SS_ClassLoader::instance()->getManifest()->getDescendantsOf($class);
141
		$result      = array($className => $className);
142
143
		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...
144
			return $result + ArrayLib::valuekey($descendants);
145
		} else {
146
			return $result;
147
		}
148
	}
149
150
	/**
151
	 * Convert a class name in any case and return it as it was defined in PHP
152
	 *
153
	 * eg: self::class_name('dataobJEct'); //returns 'DataObject'
154
	 *
155
	 * @param string|object $nameOrObject The classname or object you want to normalise
156
	 * @return string The normalised class name
157
	 */
158
	public static function class_name($nameOrObject) {
159
		if (is_object($nameOrObject)) {
160
			return get_class($nameOrObject);
161
		}
162
		$reflection = new ReflectionClass($nameOrObject);
163
		return $reflection->getName();
164
	}
165
166
	/**
167
	 * Returns the passed class name along with all its parent class names in an
168
	 * array, sorted with the root class first.
169
	 *
170
	 * @param  string $class
171
	 * @param  bool $tablesOnly Only return classes that have a table in the db.
172
	 * @return array
173
	 */
174
	public static function ancestry($class, $tablesOnly = false) {
175
		if(is_string($class) && !class_exists($class)) return array();
176
177
		$class = self::class_name($class);
178
179
		$lClass = strtolower($class);
180
181
		$cacheKey = $lClass . '_' . (string)$tablesOnly;
182
		$parent = $class;
183
		if(!isset(self::$_cache_ancestry[$cacheKey])) {
184
			$ancestry = array();
185
			do {
186
				if (!$tablesOnly || DataObject::has_own_table($parent)) {
187
					$ancestry[$parent] = $parent;
188
				}
189
			} while ($parent = get_parent_class($parent));
190
			self::$_cache_ancestry[$cacheKey] = array_reverse($ancestry);
191
		}
192
193
		return self::$_cache_ancestry[$cacheKey];
194
	}
195
196
	/**
197
	 * @return array A self-keyed array of class names. Note that this is only available with Silverstripe
198
	 * classes and not built-in PHP classes.
199
	 */
200
	public static function implementorsOf($interfaceName) {
201
		return SS_ClassLoader::instance()->getManifest()->getImplementorsOf($interfaceName);
202
	}
203
204
	/**
205
	 * Returns true if the given class implements the given interface
206
	 */
207
	public static function classImplements($className, $interfaceName) {
208
		return in_array($className, self::implementorsOf($interfaceName));
209
	}
210
211
	/**
212
	 * Get all classes contained in a file.
213
	 * @uses ManifestBuilder
214
	 *
215
	 * @todo Doesn't return additional classes that only begin
216
	 *  with the filename, and have additional naming separated through underscores.
217
	 *
218
	 * @param string $filePath Path to a PHP file (absolute or relative to webroot)
219
	 * @return array
220
	 */
221
	public static function classes_for_file($filePath) {
222
		$absFilePath    = Director::getAbsFile($filePath);
223
		$matchedClasses = array();
224
		$manifest       = SS_ClassLoader::instance()->getManifest()->getClasses();
225
226
		foreach($manifest as $class => $compareFilePath) {
227
			if($absFilePath == $compareFilePath) $matchedClasses[] = $class;
228
		}
229
230
		return $matchedClasses;
231
	}
232
233
	/**
234
	 * Returns all classes contained in a certain folder.
235
	 *
236
	 * @todo Doesn't return additional classes that only begin
237
	 *  with the filename, and have additional naming separated through underscores.
238
	 *
239
	 * @param string $folderPath Relative or absolute folder path
240
	 * @return array Array of class names
241
	 */
242
	public static function classes_for_folder($folderPath) {
243
		$absFolderPath  = Director::getAbsFile($folderPath);
244
		$matchedClasses = array();
245
		$manifest       = SS_ClassLoader::instance()->getManifest()->getClasses();
246
247
		foreach($manifest as $class => $compareFilePath) {
248
			if(stripos($compareFilePath, $absFolderPath) === 0) $matchedClasses[] = $class;
249
		}
250
251
		return $matchedClasses;
252
	}
253
254
	private static $method_from_cache = array();
255
256
	public static function has_method_from($class, $method, $compclass) {
257
		$lClass = strtolower($class);
258
		$lMethod = strtolower($method);
259
		$lCompclass = strtolower($compclass);
260
		if (!isset(self::$method_from_cache[$lClass])) self::$method_from_cache[$lClass] = array();
261
262
		if (!array_key_exists($lMethod, self::$method_from_cache[$lClass])) {
263
			self::$method_from_cache[$lClass][$lMethod] = false;
264
265
			$classRef = new ReflectionClass($class);
266
267
			if ($classRef->hasMethod($method)) {
268
				$methodRef = $classRef->getMethod($method);
269
				self::$method_from_cache[$lClass][$lMethod] = $methodRef->getDeclaringClass()->getName();
270
			}
271
		}
272
273
		return strtolower(self::$method_from_cache[$lClass][$lMethod]) == $lCompclass;
274
	}
275
276
	/**
277
	 * @deprecated 4.0..5.0
278
	 */
279
	public static function table_for_object_field($candidateClass, $fieldName) {
280
		Deprecation::notice('5.0', 'Use DataObject::getSchema()->tableForField()');
281
		return DataObject::getSchema()->tableForField($candidateClass, $fieldName);
282
	}
283
}
284
285