Completed
Push — master ( 10f429...3b71b7 )
by Ingo
11:44
created

ClassInfo::table_for_object_field()   A

Complexity

Conditions 1
Paths 1

Size

Total Lines 4
Code Lines 3

Duplication

Lines 0
Ratio 0 %

Importance

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