ENHANCEMENT Don't force all class names to lowercase

Speeds up autoloading because composer psr-4 works properly now
This commit is contained in:
Damian Mooyman 2017-09-19 16:55:39 +12:00
parent 9a7adc46f8
commit 261302a121
No known key found for this signature in database
GPG Key ID: 78B823A10DE27D1A
16 changed files with 581 additions and 348 deletions

View File

@ -116,7 +116,7 @@ class ContentNegotiator
$chosenFormat = "xhtml"; $chosenFormat = "xhtml";
} else { } else {
foreach ($mimes as $format => $mime) { foreach ($mimes as $format => $mime) {
$regExp = '/' . str_replace(array('+','/'), array('\+','\/'), $mime) . '(;q=(\d+\.\d+))?/i'; $regExp = '/' . str_replace(array('+', '/'), array('\+', '\/'), $mime) . '(;q=(\d+\.\d+))?/i';
if (isset($_SERVER['HTTP_ACCEPT']) && preg_match($regExp, $_SERVER['HTTP_ACCEPT'], $matches)) { if (isset($_SERVER['HTTP_ACCEPT']) && preg_match($regExp, $_SERVER['HTTP_ACCEPT'], $matches)) {
$preference = isset($matches[2]) ? $matches[2] : 1; $preference = isset($matches[2]) ? $matches[2] : 1;
if (!isset($q[$preference])) { if (!isset($q[$preference])) {
@ -130,13 +130,13 @@ class ContentNegotiator
krsort($q); krsort($q);
$chosenFormat = reset($q); $chosenFormat = reset($q);
} else { } else {
$chosenFormat = Config::inst()->get('SilverStripe\\Control\\ContentNegotiator', 'default_format'); $chosenFormat = Config::inst()->get(static::class, 'default_format');
} }
} }
} }
$negotiator = new ContentNegotiator(); $negotiator = new ContentNegotiator();
$negotiator->$chosenFormat( $response ); $negotiator->$chosenFormat($response);
} }
/** /**
@ -202,7 +202,7 @@ class ContentNegotiator
$response->addHeader("Vary", "Accept"); $response->addHeader("Vary", "Accept");
$content = $response->getBody(); $content = $response->getBody();
$hasXMLHeader = (substr($content, 0, 5) == '<' . '?xml' ); $hasXMLHeader = (substr($content, 0, 5) == '<' . '?xml');
// Fix base tag // Fix base tag
$content = preg_replace( $content = preg_replace(
@ -212,7 +212,11 @@ class ContentNegotiator
); );
$content = preg_replace("#<\\?xml[^>]+\\?>\n?#", '', $content); $content = preg_replace("#<\\?xml[^>]+\\?>\n?#", '', $content);
$content = str_replace(array('/>','xml:lang','application/xhtml+xml'), array('>','lang','text/html'), $content); $content = str_replace(
array('/>', 'xml:lang', 'application/xhtml+xml'),
array('>', 'lang', 'text/html'),
$content
);
// Only replace the doctype in templates with the xml header // Only replace the doctype in templates with the xml header
if ($hasXMLHeader) { if ($hasXMLHeader) {

View File

@ -3,13 +3,13 @@
namespace SilverStripe\Core; namespace SilverStripe\Core;
use Exception; use Exception;
use ReflectionClass;
use SilverStripe\CMS\Model\SiteTree;
use SilverStripe\Control\Director; use SilverStripe\Control\Director;
use SilverStripe\Core\Manifest\ClassLoader; use SilverStripe\Core\Manifest\ClassLoader;
use SilverStripe\Dev\Deprecation; use SilverStripe\Dev\Deprecation;
use SilverStripe\ORM\ArrayLib;
use SilverStripe\ORM\DB;
use SilverStripe\ORM\DataObject; use SilverStripe\ORM\DataObject;
use ReflectionClass; use SilverStripe\ORM\DB;
/** /**
* Provides introspection information about the class tree. * Provides introspection information about the class tree.
@ -20,28 +20,6 @@ use ReflectionClass;
*/ */
class ClassInfo class ClassInfo
{ {
/**
* Wrapper for classes getter.
*
* @return array
*/
public static function allClasses()
{
return ClassLoader::inst()->getManifest()->getClasses();
}
/**
* Returns true if a class or interface name exists.
*
* @param string $class
* @return bool
*/
public static function exists($class)
{
return class_exists($class, false) || interface_exists($class, false) || ClassLoader::inst()->getItemPath($class);
}
/** /**
* Cache for {@link hasTable()} * Cache for {@link hasTable()}
* *
@ -64,6 +42,14 @@ class ClassInfo
*/ */
private static $_cache_parse = []; private static $_cache_parse = [];
/**
* Cache for has_method_from
*
* @internal
* @var array
*/
private static $_cache_methods = array();
/** /**
* Cache for class_name * Cache for class_name
* *
@ -72,6 +58,29 @@ class ClassInfo
*/ */
private static $_cache_class_names = []; private static $_cache_class_names = [];
/**
* Wrapper for classes getter.
*
* @return array List of all class names
*/
public static function allClasses()
{
return ClassLoader::inst()->getManifest()->getClassNames();
}
/**
* Returns true if a class or interface name exists.
*
* @param string $class
* @return bool
*/
public static function exists($class)
{
return class_exists($class, false)
|| interface_exists($class, false)
|| ClassLoader::inst()->getItemPath($class);
}
/** /**
* @todo Move this to SS_Database or DB * @todo Move this to SS_Database or DB
* *
@ -101,7 +110,7 @@ class ClassInfo
* types that don't exist as implemented classes. By default these are excluded. * types that don't exist as implemented classes. By default these are excluded.
* @return array List of subclasses * @return array List of subclasses
*/ */
public static function getValidSubClasses($class = 'SilverStripe\\CMS\\Model\\SiteTree', $includeUnbacked = false) public static function getValidSubClasses($class = SiteTree::class, $includeUnbacked = false)
{ {
if (is_string($class) && !class_exists($class)) { if (is_string($class) && !class_exists($class)) {
return array(); return array();
@ -129,29 +138,26 @@ class ClassInfo
public static function dataClassesFor($nameOrObject) public static function dataClassesFor($nameOrObject)
{ {
if (is_string($nameOrObject) && !class_exists($nameOrObject)) { if (is_string($nameOrObject) && !class_exists($nameOrObject)) {
return array(); return [];
} }
$result = array(); // Get all classes
$class = self::class_name($nameOrObject); $class = self::class_name($nameOrObject);
$classes = array_merge( $classes = array_merge(
self::ancestry($class), self::ancestry($class),
self::subclassesFor($class) self::subclassesFor($class)
); );
foreach ($classes as $class) { // Filter by table
if (DataObject::getSchema()->classHasTable($class)) { return array_filter($classes, function ($next) {
$result[$class] = $class; return DataObject::getSchema()->classHasTable($next);
} });
}
return $result;
} }
/** /**
* @deprecated 4.0..5.0 * @deprecated 4.0..5.0
* @param string $class
* @return string
*/ */
public static function baseDataClass($class) public static function baseDataClass($class)
{ {
@ -163,19 +169,20 @@ class ClassInfo
* Returns a list of classes that inherit from the given class. * Returns a list of classes that inherit from the given class.
* The resulting array includes the base class passed * The resulting array includes the base class passed
* through the $class parameter as the first array value. * through the $class parameter as the first array value.
* Note that keys are lowercase, while the values are correct case.
* *
* Example usage: * Example usage:
* <code> * <code>
* ClassInfo::subclassesFor('BaseClass'); * ClassInfo::subclassesFor('BaseClass');
* array( * array(
* 'BaseClass' => 'BaseClass', * 'baseclass' => 'BaseClass',
* 'ChildClass' => 'ChildClass', * 'childclass' => 'ChildClass',
* 'GrandChildClass' => 'GrandChildClass' * 'grandchildclass' => 'GrandChildClass'
* ) * )
* </code> * </code>
* *
* @param string|object $nameOrObject The classname or object * @param string|object $nameOrObject The classname or object
* @return array Names of all subclasses as an associative array. * @return array List of class names with lowercase keys and correct-case values
*/ */
public static function subclassesFor($nameOrObject) public static function subclassesFor($nameOrObject)
{ {
@ -183,16 +190,16 @@ class ClassInfo
return []; return [];
} }
//normalise class case // Get class names
$className = self::class_name($nameOrObject); $className = self::class_name($nameOrObject);
$descendants = ClassLoader::inst()->getManifest()->getDescendantsOf($className); $lowerClassName = strtolower($className);
$result = array($className => $className);
if ($descendants) { // Merge with descendants
return $result + ArrayLib::valuekey($descendants); $descendants = ClassLoader::inst()->getManifest()->getDescendantsOf($className);
} else { return array_merge(
return $result; [ $lowerClassName => $className ],
} $descendants
);
} }
/** /**
@ -211,8 +218,15 @@ class ClassInfo
$key = strtolower($nameOrObject); $key = strtolower($nameOrObject);
if (!isset(static::$_cache_class_names[$key])) { if (!isset(static::$_cache_class_names[$key])) {
$reflection = new ReflectionClass($nameOrObject); // Get manifest name
static::$_cache_class_names[$key] = $reflection->getName(); $name = ClassLoader::inst()->getManifest()->getItemName($nameOrObject);
// Use reflection for non-manifest classes
if (!$name) {
$reflection = new ReflectionClass($nameOrObject);
$name = $reflection->getName();
}
static::$_cache_class_names[$key] = $name;
} }
return static::$_cache_class_names[$key]; return static::$_cache_class_names[$key];
@ -224,25 +238,25 @@ class ClassInfo
* *
* @param string|object $nameOrObject Class or object instance * @param string|object $nameOrObject Class or object instance
* @param bool $tablesOnly Only return classes that have a table in the db. * @param bool $tablesOnly Only return classes that have a table in the db.
* @return array * @return array List of class names with lowercase keys and correct-case values
*/ */
public static function ancestry($nameOrObject, $tablesOnly = false) public static function ancestry($nameOrObject, $tablesOnly = false)
{ {
if (is_string($nameOrObject) && !class_exists($nameOrObject)) { if (is_string($nameOrObject) && !class_exists($nameOrObject)) {
return array(); return [];
} }
$class = self::class_name($nameOrObject); $class = self::class_name($nameOrObject);
$lClass = strtolower($class); $lowerClass = strtolower($class);
$cacheKey = $lClass . '_' . (string)$tablesOnly; $cacheKey = $lowerClass . '_' . (string)$tablesOnly;
$parent = $class; $parent = $class;
if (!isset(self::$_cache_ancestry[$cacheKey])) { if (!isset(self::$_cache_ancestry[$cacheKey])) {
$ancestry = array(); $ancestry = [];
do { do {
if (!$tablesOnly || DataObject::getSchema()->classHasTable($parent)) { if (!$tablesOnly || DataObject::getSchema()->classHasTable($parent)) {
$ancestry[$parent] = $parent; $ancestry[strtolower($parent)] = $parent;
} }
} while ($parent = get_parent_class($parent)); } while ($parent = get_parent_class($parent));
self::$_cache_ancestry[$cacheKey] = array_reverse($ancestry); self::$_cache_ancestry[$cacheKey] = array_reverse($ancestry);
@ -253,8 +267,8 @@ class ClassInfo
/** /**
* @param string $interfaceName * @param string $interfaceName
* @return array A self-keyed array of class names. Note that this is only available with Silverstripe * @return array A self-keyed array of class names with lowercase keys and correct-case values.
* classes and not built-in PHP classes. * Note that this is only available with Silverstripe classes and not built-in PHP classes.
*/ */
public static function implementorsOf($interfaceName) public static function implementorsOf($interfaceName)
{ {
@ -270,28 +284,28 @@ class ClassInfo
*/ */
public static function classImplements($className, $interfaceName) public static function classImplements($className, $interfaceName)
{ {
return in_array($className, self::implementorsOf($interfaceName)); $lowerClassName = strtolower($className);
$implementors = self::implementorsOf($interfaceName);
return isset($implementors[$lowerClassName]);
} }
/** /**
* Get all classes contained in a file. * Get all classes contained in a file.
* @uses ManifestBuilder
*
* @todo Doesn't return additional classes that only begin
* with the filename, and have additional naming separated through underscores.
* *
* @param string $filePath Path to a PHP file (absolute or relative to webroot) * @param string $filePath Path to a PHP file (absolute or relative to webroot)
* @return array * @return array Map of lowercase class names to correct class name
*/ */
public static function classes_for_file($filePath) public static function classes_for_file($filePath)
{ {
$absFilePath = Director::getAbsFile($filePath); $absFilePath = Director::getAbsFile($filePath);
$matchedClasses = array(); $classManifest = ClassLoader::inst()->getManifest();
$manifest = ClassLoader::inst()->getManifest()->getClasses(); $classes = $classManifest->getClasses();
$classNames = $classManifest->getClassNames();
foreach ($manifest as $class => $compareFilePath) { $matchedClasses = [];
if ($absFilePath == $compareFilePath) { foreach ($classes as $lowerClass => $compareFilePath) {
$matchedClasses[] = $class; if (strcasecmp($absFilePath, $compareFilePath) === 0) {
$matchedClasses[$lowerClass] = $classNames[$lowerClass];
} }
} }
@ -301,50 +315,55 @@ class ClassInfo
/** /**
* Returns all classes contained in a certain folder. * Returns all classes contained in a certain folder.
* *
* @todo Doesn't return additional classes that only begin
* with the filename, and have additional naming separated through underscores.
*
* @param string $folderPath Relative or absolute folder path * @param string $folderPath Relative or absolute folder path
* @return array Array of class names * @return array Map of lowercase class names to correct class name
*/ */
public static function classes_for_folder($folderPath) public static function classes_for_folder($folderPath)
{ {
$absFolderPath = Director::getAbsFile($folderPath); $absFolderPath = Director::getAbsFile($folderPath);
$matchedClasses = array(); $classManifest = ClassLoader::inst()->getManifest();
$manifest = ClassLoader::inst()->getManifest()->getClasses(); $classes = $classManifest->getClasses();
$classNames = $classManifest->getClassNames();
foreach ($manifest as $class => $compareFilePath) { $matchedClasses = [];
foreach ($classes as $lowerClass => $compareFilePath) {
if (stripos($compareFilePath, $absFolderPath) === 0) { if (stripos($compareFilePath, $absFolderPath) === 0) {
$matchedClasses[] = $class; $matchedClasses[$lowerClass] = $classNames[$lowerClass];
} }
} }
return $matchedClasses; return $matchedClasses;
} }
private static $method_from_cache = array(); /**
* Determine if the given class method is implemented at the given comparison class
*
* @param string $class Class to get methods from
* @param string $method Method name to lookup
* @param string $compclass Parent class to test if this is the implementor
* @return bool True if $class::$method is declared in $compclass
*/
public static function has_method_from($class, $method, $compclass) public static function has_method_from($class, $method, $compclass)
{ {
$lClass = strtolower($class); $lClass = strtolower($class);
$lMethod = strtolower($method); $lMethod = strtolower($method);
$lCompclass = strtolower($compclass); $lCompclass = strtolower($compclass);
if (!isset(self::$method_from_cache[$lClass])) { if (!isset(self::$_cache_methods[$lClass])) {
self::$method_from_cache[$lClass] = array(); self::$_cache_methods[$lClass] = array();
} }
if (!array_key_exists($lMethod, self::$method_from_cache[$lClass])) { if (!array_key_exists($lMethod, self::$_cache_methods[$lClass])) {
self::$method_from_cache[$lClass][$lMethod] = false; self::$_cache_methods[$lClass][$lMethod] = false;
$classRef = new ReflectionClass($class); $classRef = new ReflectionClass($class);
if ($classRef->hasMethod($method)) { if ($classRef->hasMethod($method)) {
$methodRef = $classRef->getMethod($method); $methodRef = $classRef->getMethod($method);
self::$method_from_cache[$lClass][$lMethod] = $methodRef->getDeclaringClass()->getName(); self::$_cache_methods[$lClass][$lMethod] = $methodRef->getDeclaringClass()->getName();
} }
} }
return strtolower(self::$method_from_cache[$lClass][$lMethod]) == $lCompclass; return strtolower(self::$_cache_methods[$lClass][$lMethod]) === $lCompclass;
} }
/** /**
@ -364,8 +383,9 @@ class ClassInfo
*/ */
public static function shortName($nameOrObject) public static function shortName($nameOrObject)
{ {
$reflection = new ReflectionClass($nameOrObject); $name = static::class_name($nameOrObject);
return $reflection->getShortName(); $parts = explode('\\', $name);
return end($parts);
} }
/** /**

View File

@ -112,8 +112,9 @@ class CoreConfigFactory
public function buildStaticTransformer() public function buildStaticTransformer()
{ {
return new PrivateStaticTransformer(function () { return new PrivateStaticTransformer(function () {
$classes = ClassLoader::inst()->getManifest()->getClasses(); return ClassLoader::inst()
return array_keys($classes); ->getManifest()
->getClassNames();
}); });
} }

View File

@ -31,6 +31,7 @@ class InheritanceMiddleware implements Middleware
$nextConfig = $next($nextClass, $excludeMiddleware); $nextConfig = $next($nextClass, $excludeMiddleware);
$config = Priority::mergeArray($nextConfig, $config); $config = Priority::mergeArray($nextConfig, $config);
} }
return $config; return $config;
} }
} }

View File

@ -19,6 +19,8 @@ use SilverStripe\Dev\TestOnly;
* - Class and interface names and paths. * - Class and interface names and paths.
* - All direct and indirect descendants of a class. * - All direct and indirect descendants of a class.
* - All implementors of an interface. * - All implementors of an interface.
*
* To be consistent; In general all array keys are lowercase, and array values are correct-case
*/ */
class ClassManifest class ClassManifest
{ {
@ -51,21 +53,51 @@ class ClassManifest
protected $cacheKey; protected $cacheKey;
/** /**
* Map of classes to paths * Array of properties to cache
*
* @var array
*/
protected $serialisedProperties = [
'classes',
'classNames',
'descendants',
'interfaces',
'interfaceNames',
'implementors',
'traits',
'traitNames',
];
/**
* Map of lowercase class names to paths
* *
* @var array * @var array
*/ */
protected $classes = array(); protected $classes = array();
/**
* Map of lowercase class names to case-correct names
*
* @var array
*/
protected $classNames = [];
/** /**
* List of root classes with no parent class * List of root classes with no parent class
* Keys are lowercase, values are correct case.
*
* Note: Only used while regenerating cache
* *
* @var array * @var array
*/ */
protected $roots = array(); protected $roots = array();
/** /**
* List of direct children for any class * List of direct children for any class.
* Keys are lowercase, values are arrays.
* Each item-value array has lowercase keys and correct case for values.
*
* Note: Only used while regenerating cache
* *
* @var array * @var array
*/ */
@ -73,31 +105,49 @@ class ClassManifest
/** /**
* List of descendents for any class (direct + indirect children) * List of descendents for any class (direct + indirect children)
* Keys are lowercase, values are arrays.
* Each item-value array has lowercase keys and correct case for values.
* *
* @var array * @var array
*/ */
protected $descendants = array(); protected $descendants = array();
/** /**
* List of interfaces and paths to those files * Map of lowercase interface name to path those files
* *
* @var array * @var array
*/ */
protected $interfaces = array(); protected $interfaces = [];
/**
* Map of lowercase interface name to proper case
*
* @var array
*/
protected $interfaceNames = [];
/** /**
* List of direct implementors of any interface * List of direct implementors of any interface
* Keys are lowercase, values are arrays.
* Each item-value array has lowercase keys and correct case for values.
* *
* @var array * @var array
*/ */
protected $implementors = array(); protected $implementors = array();
/** /**
* Map of traits to paths * Map of lowercase trait names to paths
* *
* @var array * @var array
*/ */
protected $traits = array(); protected $traits = [];
/**
* Map of lowercase trait names to proper case
*
* @var array
*/
protected $traitNames = [];
/** /**
* PHP Parser for parsing found files * PHP Parser for parsing found files
@ -141,20 +191,22 @@ class ClassManifest
// build cache from factory // build cache from factory
if ($this->cacheFactory) { if ($this->cacheFactory) {
$this->cache = $this->cacheFactory->create( $this->cache = $this->cacheFactory->create(
CacheInterface::class.'.classmanifest', CacheInterface::class . '.classmanifest',
[ 'namespace' => 'classmanifest' . ($includeTests ? '_tests' : '') ] ['namespace' => 'classmanifest' . ($includeTests ? '_tests' : '')]
); );
} }
if (!$forceRegen && $this->cache && ($data = $this->cache->get($this->cacheKey))) { // Check if cache is safe to use
$this->classes = $data['classes']; if (!$forceRegen
$this->descendants = $data['descendants']; && $this->cache
$this->interfaces = $data['interfaces']; && ($data = $this->cache->get($this->cacheKey))
$this->implementors = $data['implementors']; && $this->loadState($data)
$this->traits = $data['traits']; ) {
} else { return;
$this->regenerate($includeTests);
} }
// Build
$this->regenerate($includeTests);
} }
/** /**
@ -171,6 +223,11 @@ class ClassManifest
return $this->parser; return $this->parser;
} }
/**
* Get node traverser for parsing class files
*
* @return NodeTraverser
*/
public function getTraverser() public function getTraverser()
{ {
if (!$this->traverser) { if (!$this->traverser) {
@ -182,6 +239,11 @@ class ClassManifest
return $this->traverser; return $this->traverser;
} }
/**
* Get visitor for parsing class files
*
* @return ClassManifestVisitor
*/
public function getVisitor() public function getVisitor()
{ {
if (!$this->visitor) { if (!$this->visitor) {
@ -200,15 +262,35 @@ class ClassManifest
*/ */
public function getItemPath($name) public function getItemPath($name)
{ {
$name = strtolower($name); $lowerName = strtolower($name);
foreach ([ foreach ([
$this->classes, $this->classes,
$this->interfaces, $this->interfaces,
$this->traits $this->traits,
] as $source) { ] as $source) {
if (isset($source[$name]) && file_exists($source[$name])) { if (isset($source[$lowerName]) && file_exists($source[$lowerName])) {
return $source[$name]; return $source[$lowerName];
}
}
return null;
}
/**
* Return correct case name
*
* @param string $name
* @return string Correct case name
*/
public function getItemName($name)
{
$lowerName = strtolower($name);
foreach ([
$this->classNames,
$this->interfaceNames,
$this->traitNames,
] as $source) {
if (isset($source[$lowerName])) {
return $source[$lowerName];
} }
} }
return null; return null;
@ -225,23 +307,33 @@ class ClassManifest
} }
/** /**
* Returns a lowercase array of all the class names in the manifest. * Returns a map of lowercase class names to proper class names in the manifest
* *
* @return array * @return array
*/ */
public function getClassNames() public function getClassNames()
{ {
return array_keys($this->classes); return $this->classNames;
} }
/** /**
* Returns a lowercase array of all trait names in the manifest * Returns a map of lowercased trait names to file paths.
*
* @return array
*/
public function getTraits()
{
return $this->traits;
}
/**
* Returns a map of lowercase trait names to proper trait names in the manifest
* *
* @return array * @return array
*/ */
public function getTraitNames() public function getTraitNames()
{ {
return array_keys($this->traits); return $this->traitNames;
} }
/** /**
@ -268,12 +360,11 @@ class ClassManifest
} }
$lClass = strtolower($class); $lClass = strtolower($class);
if (array_key_exists($lClass, $this->descendants)) { if (array_key_exists($lClass, $this->descendants)) {
return $this->descendants[$lClass]; return $this->descendants[$lClass];
} else {
return array();
} }
return [];
} }
/** /**
@ -286,6 +377,16 @@ class ClassManifest
return $this->interfaces; return $this->interfaces;
} }
/**
* Return map of lowercase interface names to proper case names in the manifest
*
* @return array
*/
public function getInterfaceNames()
{
return $this->interfaceNames;
}
/** /**
* Returns a map of lowercased interface names to the classes the implement * Returns a map of lowercased interface names to the classes the implement
* them. * them.
@ -301,15 +402,14 @@ class ClassManifest
* Returns an array containing the class names that implement a certain * Returns an array containing the class names that implement a certain
* interface. * interface.
* *
* @param string $interface * @param string $interface
* @return array * @return array
*/ */
public function getImplementorsOf($interface) public function getImplementorsOf($interface)
{ {
$interface = strtolower($interface); $lowerInterface = strtolower($interface);
if (array_key_exists($lowerInterface, $this->implementors)) {
if (array_key_exists($interface, $this->implementors)) { return $this->implementors[$lowerInterface];
return $this->implementors[$interface];
} else { } else {
return array(); return array();
} }
@ -334,21 +434,16 @@ class ClassManifest
*/ */
public function regenerate($includeTests) public function regenerate($includeTests)
{ {
$resets = array(
'classes', 'roots', 'children', 'descendants', 'interfaces',
'implementors', 'traits'
);
// Reset the manifest so stale info doesn't cause errors. // Reset the manifest so stale info doesn't cause errors.
foreach ($resets as $reset) { $this->loadState([]);
$this->$reset = array(); $this->roots = [];
} $this->children = [];
$finder = new ManifestFileFinder(); $finder = new ManifestFileFinder();
$finder->setOptions(array( $finder->setOptions(array(
'name_regex' => '/^[^_].*\\.php$/', 'name_regex' => '/^[^_].*\\.php$/',
'ignore_files' => array('index.php', 'main.php', 'cli-script.php'), 'ignore_files' => array('index.php', 'main.php', 'cli-script.php'),
'ignore_tests' => !$includeTests, 'ignore_tests' => !$includeTests,
'file_callback' => function ($basename, $pathname) use ($includeTests) { 'file_callback' => function ($basename, $pathname) use ($includeTests) {
$this->handleFile($basename, $pathname, $includeTests); $this->handleFile($basename, $pathname, $includeTests);
}, },
@ -360,23 +455,21 @@ class ClassManifest
} }
if ($this->cache) { if ($this->cache) {
$data = array( $data = $this->getState();
'classes' => $this->classes,
'descendants' => $this->descendants,
'interfaces' => $this->interfaces,
'implementors' => $this->implementors,
'traits' => $this->traits,
);
$this->cache->set($this->cacheKey, $data); $this->cache->set($this->cacheKey, $data);
} }
} }
/**
* Visit a file to inspect for classes, interfaces and traits
*
* @param string $basename
* @param string $pathname
* @param bool $includeTests
* @throws Exception
*/
public function handleFile($basename, $pathname, $includeTests) public function handleFile($basename, $pathname, $includeTests)
{ {
$classes = null;
$interfaces = null;
$traits = null;
// The results of individual file parses are cached, since only a few // The results of individual file parses are cached, since only a few
// files will have changed and TokenisedRegularExpression is quite // files will have changed and TokenisedRegularExpression is quite
// slow. A combination of the file name and file contents hash are used, // slow. A combination of the file name and file contents hash are used,
@ -384,6 +477,7 @@ class ClassManifest
$key = preg_replace('/[^a-zA-Z0-9_]/', '_', $basename) . '_' . md5_file($pathname); $key = preg_replace('/[^a-zA-Z0-9_]/', '_', $basename) . '_' . md5_file($pathname);
// Attempt to load from cache // Attempt to load from cache
// Note: $classes, $interfaces and $traits arrays have correct-case keys, not lowercase
$changed = false; $changed = false;
if ($this->cache if ($this->cache
&& ($data = $this->cache->get($key)) && ($data = $this->cache->get($key))
@ -409,64 +503,64 @@ class ClassManifest
$traits = $this->getVisitor()->getTraits(); $traits = $this->getVisitor()->getTraits();
} }
// Merge this data into the global list // Merge raw class data into global list
foreach ($classes as $className => $classInfo) { foreach ($classes as $className => $classInfo) {
$extends = !empty($classInfo['extends']) $lowerClassName = strtolower($className);
? array_map('strtolower', $classInfo['extends']) if (array_key_exists($lowerClassName, $this->classes)) {
: [];
$implements = !empty($classInfo['interfaces'])
? array_map('strtolower', $classInfo['interfaces'])
: [];
$lowercaseName = strtolower($className);
if (array_key_exists($lowercaseName, $this->classes)) {
throw new Exception(sprintf( throw new Exception(sprintf(
'There are two files containing the "%s" class: "%s" and "%s"', 'There are two files containing the "%s" class: "%s" and "%s"',
$className, $className,
$this->classes[$lowercaseName], $this->classes[$lowerClassName],
$pathname $pathname
)); ));
} }
// Skip if implements TestOnly, but doesn't include tests // Skip if implements TestOnly, but doesn't include tests
if (!$includeTests $lowerInterfaces = array_map('strtolower', $classInfo['interfaces']);
&& $implements if (!$includeTests && in_array(strtolower(TestOnly::class), $lowerInterfaces)) {
&& in_array(strtolower(TestOnly::class), $implements)
) {
$changed = true; $changed = true;
unset($classes[$className]); unset($classes[$className]);
continue; continue;
} }
$this->classes[$lowercaseName] = $pathname; $this->classes[$lowerClassName] = $pathname;
$this->classNames[$lowerClassName] = $className;
if ($extends) { // Add to children
foreach ($extends as $ancestor) { if ($classInfo['extends']) {
if (!isset($this->children[$ancestor])) { foreach ($classInfo['extends'] as $ancestor) {
$this->children[$ancestor] = array($className); $lowerAncestor = strtolower($ancestor);
} else { if (!isset($this->children[$lowerAncestor])) {
$this->children[$ancestor][] = $className; $this->children[$lowerAncestor] = [];
} }
$this->children[$lowerAncestor][$lowerClassName] = $className;
} }
} else { } else {
$this->roots[] = $className; $this->roots[$lowerClassName] = $className;
} }
if ($implements) { // Load interfaces
foreach ($implements as $interface) { foreach ($classInfo['interfaces'] as $interface) {
if (!isset($this->implementors[$interface])) { $lowerInterface = strtolower($interface);
$this->implementors[$interface] = array($className); if (!isset($this->implementors[$lowerInterface])) {
} else { $this->implementors[$lowerInterface] = [];
$this->implementors[$interface][] = $className;
}
} }
$this->implementors[$lowerInterface][$lowerClassName] = $className;
} }
} }
// Merge all found interfaces into list
foreach ($interfaces as $interfaceName => $interfaceInfo) { foreach ($interfaces as $interfaceName => $interfaceInfo) {
$this->interfaces[strtolower($interfaceName)] = $pathname; $lowerInterface = strtolower($interfaceName);
$this->interfaces[$lowerInterface] = $pathname;
$this->interfaceNames[$lowerInterface] = $interfaceName;
} }
// Merge all traits
foreach ($traits as $traitName => $traitInfo) { foreach ($traits as $traitName => $traitInfo) {
$this->traits[strtolower($traitName)] = $pathname; $lowerTrait = strtolower($traitName);
$this->traits[$lowerTrait] = $pathname;
$this->traitNames[$lowerTrait] = $traitName;
} }
// Save back to cache if configured // Save back to cache if configured
@ -489,23 +583,57 @@ class ClassManifest
*/ */
protected function coalesceDescendants($class) protected function coalesceDescendants($class)
{ {
$lClass = strtolower($class); // Reset descendents to immediate children initially
$lowerClass = strtolower($class);
if (array_key_exists($lClass, $this->children)) { if (empty($this->children[$lowerClass])) {
$this->descendants[$lClass] = array(); return [];
foreach ($this->children[$lClass] as $class) {
$this->descendants[$lClass] = array_merge(
$this->descendants[$lClass],
array($class),
$this->coalesceDescendants($class)
);
}
return $this->descendants[$lClass];
} else {
return array();
} }
// Coalasce children into descendent list
$this->descendants[$lowerClass] = $this->children[$lowerClass];
foreach ($this->children[$lowerClass] as $childClass) {
// Merge all nested descendants
$this->descendants[$lowerClass] = array_merge(
$this->descendants[$lowerClass],
$this->coalesceDescendants($childClass)
);
}
return $this->descendants[$lowerClass];
}
/**
* Reload state from given cache data
*
* @param array $data
* @return bool True if cache was valid and successfully loaded
*/
protected function loadState($data)
{
$success = true;
foreach ($this->serialisedProperties as $property) {
if (!isset($data[$property]) || !is_array($data[$property])) {
$success = false;
$value = [];
} else {
$value = $data[$property];
}
$this->$property = $value;
}
return $success;
}
/**
* Load current state into an array of data
*
* @return array
*/
protected function getState()
{
$data = [];
foreach ($this->serialisedProperties as $property) {
$data[$property] = $this->$property;
}
return $data;
} }
/** /**
@ -516,6 +644,9 @@ class ClassManifest
*/ */
protected function validateItemCache($data) protected function validateItemCache($data)
{ {
if (!$data || !is_array($data)) {
return false;
}
foreach (['classes', 'interfaces', 'traits'] as $key) { foreach (['classes', 'interfaces', 'traits'] as $key) {
// Must be set // Must be set
if (!isset($data[$key])) { if (!isset($data[$key])) {

View File

@ -5,6 +5,7 @@ namespace SilverStripe\Core\Manifest;
use LogicException; use LogicException;
use Psr\SimpleCache\CacheInterface; use Psr\SimpleCache\CacheInterface;
use SilverStripe\Core\Cache\CacheFactory; use SilverStripe\Core\Cache\CacheFactory;
use SilverStripe\Core\Config\Config;
use SilverStripe\Core\Config\Configurable; use SilverStripe\Core\Config\Configurable;
use SilverStripe\Core\Injector\Injector; use SilverStripe\Core\Injector\Injector;
@ -50,6 +51,22 @@ class ModuleManifest
*/ */
protected $modules = []; protected $modules = [];
/**
* List of modules sorted by priority
*
* @config
* @var array
*/
private static $module_priority = [];
/**
* Project name
*
* @config
* @var string
*/
private static $project = null;
/** /**
* Adds a path as a module * Adds a path as a module
* *
@ -244,6 +261,7 @@ class ModuleManifest
{ {
$order = static::config()->uninherited('module_priority'); $order = static::config()->uninherited('module_priority');
$project = static::config()->get('project'); $project = static::config()->get('project');
/* @var PrioritySorter $sorter */ /* @var PrioritySorter $sorter */
$sorter = Injector::inst()->createWithArgs( $sorter = Injector::inst()->createWithArgs(
PrioritySorter::class . '.modulesorter', PrioritySorter::class . '.modulesorter',

View File

@ -116,7 +116,7 @@ class TaskRunner extends Controller
{ {
$availableTasks = array(); $availableTasks = array();
$taskClasses = ClassInfo::subclassesFor('SilverStripe\\Dev\\BuildTask'); $taskClasses = ClassInfo::subclassesFor(BuildTask::class);
// remove the base class // remove the base class
array_shift($taskClasses); array_shift($taskClasses);

View File

@ -167,7 +167,7 @@ class DatabaseAdmin extends Controller
*/ */
public function buildDefaults() public function buildDefaults()
{ {
$dataClasses = ClassInfo::subclassesFor('SilverStripe\ORM\DataObject'); $dataClasses = ClassInfo::subclassesFor(DataObject::class);
array_shift($dataClasses); array_shift($dataClasses);
if (!Director::is_cli()) { if (!Director::is_cli()) {
@ -260,7 +260,7 @@ class DatabaseAdmin extends Controller
} }
// Build the database. Most of the hard work is handled by DataObject // Build the database. Most of the hard work is handled by DataObject
$dataClasses = ClassInfo::subclassesFor('SilverStripe\ORM\DataObject'); $dataClasses = ClassInfo::subclassesFor(DataObject::class);
array_shift($dataClasses); array_shift($dataClasses);
if (!$quiet) { if (!$quiet) {

View File

@ -129,8 +129,9 @@ class DBClassName extends DBEnum
public function getEnum() public function getEnum()
{ {
$classNames = ClassInfo::subclassesFor($this->getBaseClass()); $classNames = ClassInfo::subclassesFor($this->getBaseClass());
unset($classNames[DataObject::class]); $dataobject = strtolower(DataObject::class);
return $classNames; unset($classNames[$dataobject]);
return array_values($classNames);
} }
/** /**

View File

@ -37,9 +37,8 @@ class PolymorphicHasManyList extends HasManyList
* to generate the ID and Class foreign keys. * to generate the ID and Class foreign keys.
* @param string $foreignClass Name of the class filter this relation is filtered against * @param string $foreignClass Name of the class filter this relation is filtered against
*/ */
function __construct($dataClass, $foreignField, $foreignClass) public function __construct($dataClass, $foreignField, $foreignClass)
{ {
// Set both id foreign key (as in HasManyList) and the class foreign key // Set both id foreign key (as in HasManyList) and the class foreign key
parent::__construct($dataClass, "{$foreignField}ID"); parent::__construct($dataClass, "{$foreignField}ID");
$this->classForeignKey = "{$foreignField}Class"; $this->classForeignKey = "{$foreignField}Class";
@ -120,7 +119,8 @@ class PolymorphicHasManyList extends HasManyList
$foreignClass = $this->getForeignClass(); $foreignClass = $this->getForeignClass();
$classNames = ClassInfo::subclassesFor($foreignClass); $classNames = ClassInfo::subclassesFor($foreignClass);
$classForeignKey = $this->classForeignKey; $classForeignKey = $this->classForeignKey;
if (!in_array($item->$classForeignKey, $classNames)) { $classValueLower = strtolower($item->$classForeignKey);
if (!array_key_exists($classValueLower, $classNames)) {
return; return;
} }

View File

@ -3,6 +3,7 @@
namespace SilverStripe\Core\Tests; namespace SilverStripe\Core\Tests;
use ReflectionException; use ReflectionException;
use SilverStripe\Core\ClassInfo;
use SilverStripe\Core\Tests\ClassInfoTest\BaseClass; use SilverStripe\Core\Tests\ClassInfoTest\BaseClass;
use SilverStripe\Core\Tests\ClassInfoTest\BaseDataClass; use SilverStripe\Core\Tests\ClassInfoTest\BaseDataClass;
use SilverStripe\Core\Tests\ClassInfoTest\ChildClass; use SilverStripe\Core\Tests\ClassInfoTest\ChildClass;
@ -11,8 +12,6 @@ use SilverStripe\Core\Tests\ClassInfoTest\HasFields;
use SilverStripe\Core\Tests\ClassInfoTest\NoFields; use SilverStripe\Core\Tests\ClassInfoTest\NoFields;
use SilverStripe\Core\Tests\ClassInfoTest\WithCustomTable; use SilverStripe\Core\Tests\ClassInfoTest\WithCustomTable;
use SilverStripe\Core\Tests\ClassInfoTest\WithRelation; use SilverStripe\Core\Tests\ClassInfoTest\WithRelation;
use SilverStripe\ORM\ArrayLib;
use SilverStripe\Core\ClassInfo;
use SilverStripe\Dev\SapphireTest; use SilverStripe\Dev\SapphireTest;
use SilverStripe\ORM\DataObject; use SilverStripe\ORM\DataObject;
use SilverStripe\View\ViewableData; use SilverStripe\View\ViewableData;
@ -50,22 +49,19 @@ class ClassInfoTest extends SapphireTest
public function testSubclassesFor() public function testSubclassesFor()
{ {
$subclasses = [
'silverstripe\\core\\tests\\classinfotest\\baseclass' => BaseClass::class,
'silverstripe\\core\\tests\\classinfotest\\childclass' => ChildClass::class,
'silverstripe\\core\\tests\\classinfotest\\grandchildclass' => GrandChildClass::class,
];
$this->assertEquals( $this->assertEquals(
array( $subclasses,
BaseClass::class => BaseClass::class,
ChildClass::class => ChildClass::class,
GrandChildClass::class => GrandChildClass::class
),
ClassInfo::subclassesFor(BaseClass::class), ClassInfo::subclassesFor(BaseClass::class),
'ClassInfo::subclassesFor() returns only direct subclasses and doesnt include base class' 'ClassInfo::subclassesFor() returns only direct subclasses and doesnt include base class'
); );
ClassInfo::reset_db_cache(); ClassInfo::reset_db_cache();
$this->assertEquals( $this->assertEquals(
array( $subclasses,
BaseClass::class => BaseClass::class,
ChildClass::class => ChildClass::class,
GrandChildClass::class => GrandChildClass::class
),
ClassInfo::subclassesFor('silverstripe\\core\\tests\\classinfotest\\baseclass'), ClassInfo::subclassesFor('silverstripe\\core\\tests\\classinfotest\\baseclass'),
'ClassInfo::subclassesFor() is acting in a case sensitive way when it should not' 'ClassInfo::subclassesFor() is acting in a case sensitive way when it should not'
); );
@ -96,20 +92,27 @@ class ClassInfoTest extends SapphireTest
public function testClassesForFolder() public function testClassesForFolder()
{ {
//$baseFolder = Director::baseFolder() . '/' . FRAMEWORK_DIR . '/tests/_ClassInfoTest';
//$manifestInfo = ManifestBuilder::get_manifest_info($baseFolder);
$classes = ClassInfo::classes_for_folder(ltrim(FRAMEWORK_DIR . '/tests', '/')); $classes = ClassInfo::classes_for_folder(ltrim(FRAMEWORK_DIR . '/tests', '/'));
$this->assertContains( $this->assertArrayHasKey(
'silverstripe\\core\\tests\\classinfotest', 'silverstripe\\core\\tests\\classinfotest',
$classes, $classes,
'ClassInfo::classes_for_folder() returns classes matching the filename' 'ClassInfo::classes_for_folder() returns classes matching the filename'
); );
$this->assertContains( $this->assertContains(
ClassInfoTest::class,
$classes,
'ClassInfo::classes_for_folder() returns classes matching the filename'
);
$this->assertArrayHasKey(
'silverstripe\\core\\tests\\classinfotest\\baseclass', 'silverstripe\\core\\tests\\classinfotest\\baseclass',
$classes, $classes,
'ClassInfo::classes_for_folder() returns additional classes not matching the filename' 'ClassInfo::classes_for_folder() returns additional classes not matching the filename'
); );
$this->assertContains(
BaseClass::class,
$classes,
'ClassInfo::classes_for_folder() returns additional classes not matching the filename'
);
} }
/** /**
@ -118,12 +121,12 @@ class ClassInfoTest extends SapphireTest
public function testAncestry() public function testAncestry()
{ {
$ancestry = ClassInfo::ancestry(ChildClass::class); $ancestry = ClassInfo::ancestry(ChildClass::class);
$expect = ArrayLib::valuekey([ $expect = [
ViewableData::class, 'silverstripe\\view\\viewabledata' => ViewableData::class,
DataObject::class, 'silverstripe\\orm\\dataobject' => DataObject::class,
BaseClass::class, 'silverstripe\\core\tests\classinfotest\\baseclass' => BaseClass::class,
ChildClass::class, 'silverstripe\\core\tests\classinfotest\\childclass' => ChildClass::class,
]); ];
$this->assertEquals($expect, $ancestry); $this->assertEquals($expect, $ancestry);
ClassInfo::reset_db_cache(); ClassInfo::reset_db_cache();
@ -135,7 +138,9 @@ class ClassInfoTest extends SapphireTest
ClassInfo::reset_db_cache(); ClassInfo::reset_db_cache();
$ancestry = ClassInfo::ancestry(ChildClass::class, true); $ancestry = ClassInfo::ancestry(ChildClass::class, true);
$this->assertEquals( $this->assertEquals(
array(BaseClass::class => BaseClass::class), [
'silverstripe\\core\tests\classinfotest\\baseclass' => BaseClass::class
],
$ancestry, $ancestry,
'$tablesOnly option excludes memory-only inheritance classes' '$tablesOnly option excludes memory-only inheritance classes'
); );
@ -146,13 +151,12 @@ class ClassInfoTest extends SapphireTest
*/ */
public function testDataClassesFor() public function testDataClassesFor()
{ {
$expect = array( $expect = [
BaseDataClass::class => BaseDataClass::class, 'silverstripe\\core\\tests\\classinfotest\\basedataclass' => BaseDataClass::class,
HasFields::class => HasFields::class, 'silverstripe\\core\\tests\\classinfotest\\hasfields' => HasFields::class,
WithRelation::class => WithRelation::class, 'silverstripe\\core\\tests\\classinfotest\\withrelation' => WithRelation::class,
WithCustomTable::class => WithCustomTable::class, 'silverstripe\\core\\tests\\classinfotest\\withcustomtable' => WithCustomTable::class,
); ];
$classes = array( $classes = array(
BaseDataClass::class, BaseDataClass::class,
NoFields::class, NoFields::class,
@ -166,10 +170,10 @@ class ClassInfoTest extends SapphireTest
ClassInfo::reset_db_cache(); ClassInfo::reset_db_cache();
$this->assertEquals($expect, ClassInfo::dataClassesFor($classes[1])); $this->assertEquals($expect, ClassInfo::dataClassesFor($classes[1]));
$expect = array( $expect = [
BaseDataClass::class => BaseDataClass::class, 'silverstripe\\core\\tests\\classinfotest\\basedataclass' => BaseDataClass::class,
HasFields::class => HasFields::class, 'silverstripe\\core\\tests\\classinfotest\\hasfields' => HasFields::class,
); ];
ClassInfo::reset_db_cache(); ClassInfo::reset_db_cache();
$this->assertEquals($expect, ClassInfo::dataClassesFor($classes[2])); $this->assertEquals($expect, ClassInfo::dataClassesFor($classes[2]));

View File

@ -71,7 +71,13 @@ class ClassManifestTest extends SapphireTest
public function testGetClassNames() public function testGetClassNames()
{ {
$this->assertEquals( $this->assertEquals(
['classa', 'classb', 'classc', 'classd', 'classe'], [
'classa' => 'ClassA',
'classb' => 'ClassB',
'classc' => 'ClassC',
'classd' => 'ClassD',
'classe' => 'ClassE',
],
$this->manifest->getClassNames() $this->manifest->getClassNames()
); );
} }
@ -79,28 +85,36 @@ class ClassManifestTest extends SapphireTest
public function testGetTraitNames() public function testGetTraitNames()
{ {
$this->assertEquals( $this->assertEquals(
array('testtraita', 'testnamespace\testing\testtraitb'), array(
'testtraita' => 'TestTraitA',
'testnamespace\testing\testtraitb' => 'TestNamespace\Testing\TestTraitB',
),
$this->manifest->getTraitNames() $this->manifest->getTraitNames()
); );
} }
public function testGetDescendants() public function testGetDescendants()
{ {
$expect = array( $expect = [
'classa' => array('ClassC', 'ClassD'), 'classa' => [
'classc' => array('ClassD') 'classc' => 'ClassC',
); 'classd' => 'ClassD',
],
'classc' => [
'classd' => 'ClassD',
],
];
$this->assertEquals($expect, $this->manifest->getDescendants()); $this->assertEquals($expect, $this->manifest->getDescendants());
} }
public function testGetDescendantsOf() public function testGetDescendantsOf()
{ {
$expect = array( $expect = [
'CLASSA' => array('ClassC', 'ClassD'), 'CLASSA' => ['classc' => 'ClassC', 'classd' => 'ClassD'],
'classa' => array('ClassC', 'ClassD'), 'classa' => ['classc' => 'ClassC', 'classd' => 'ClassD'],
'CLASSC' => array('ClassD'), 'CLASSC' => ['classd' => 'ClassD'],
'classc' => array('ClassD') 'classc' => ['classd' => 'ClassD'],
); ];
foreach ($expect as $class => $desc) { foreach ($expect as $class => $desc) {
$this->assertEquals($desc, $this->manifest->getDescendantsOf($class)); $this->assertEquals($desc, $this->manifest->getDescendantsOf($class));
@ -118,21 +132,21 @@ class ClassManifestTest extends SapphireTest
public function testGetImplementors() public function testGetImplementors()
{ {
$expect = array( $expect = [
'interfacea' => array('ClassB'), 'interfacea' => ['classb' => 'ClassB'],
'interfaceb' => array('ClassC') 'interfaceb' => ['classc' => 'ClassC'],
); ];
$this->assertEquals($expect, $this->manifest->getImplementors()); $this->assertEquals($expect, $this->manifest->getImplementors());
} }
public function testGetImplementorsOf() public function testGetImplementorsOf()
{ {
$expect = array( $expect = [
'INTERFACEA' => array('ClassB'), 'INTERFACEA' => ['classb' => 'ClassB'],
'interfacea' => array('ClassB'), 'interfacea' => ['classb' => 'ClassB'],
'INTERFACEB' => array('ClassC'), 'INTERFACEB' => ['classc' => 'ClassC'],
'interfaceb' => array('ClassC') 'interfaceb' => ['classc' => 'ClassC'],
); ];
foreach ($expect as $interface => $impl) { foreach ($expect as $interface => $impl) {
$this->assertEquals($impl, $this->manifest->getImplementorsOf($interface)); $this->assertEquals($impl, $this->manifest->getImplementorsOf($interface));
@ -141,13 +155,13 @@ class ClassManifestTest extends SapphireTest
public function testTestManifestIncludesTestClasses() public function testTestManifestIncludesTestClasses()
{ {
$this->assertNotContains('testclassa', array_keys($this->manifest->getClasses())); $this->assertArrayNotHasKey('testclassa', $this->manifest->getClasses());
$this->assertContains('testclassa', array_keys($this->manifestTests->getClasses())); $this->assertArrayHasKey('testclassa', $this->manifestTests->getClasses());
} }
public function testManifestExcludeFilesPrefixedWithUnderscore() public function testManifestExcludeFilesPrefixedWithUnderscore()
{ {
$this->assertNotContains('ignore', array_keys($this->manifest->getClasses())); $this->assertArrayNotHasKey('ignore', $this->manifest->getClasses());
} }
/** /**

View File

@ -2,11 +2,13 @@
namespace SilverStripe\Core\Tests\Manifest; namespace SilverStripe\Core\Tests\Manifest;
use SilverStripe\Admin\ModelAdmin;
use SilverStripe\Core\ClassInfo; use SilverStripe\Core\ClassInfo;
use SilverStripe\Core\Manifest\ClassManifest; use SilverStripe\Core\Manifest\ClassManifest;
use SilverStripe\Core\Manifest\ClassLoader; use SilverStripe\Core\Manifest\ClassLoader;
use SilverStripe\Dev\SapphireTest; use SilverStripe\Dev\SapphireTest;
use ReflectionMethod; use ReflectionMethod;
use SilverStripe\Security\PermissionProvider;
/** /**
* Tests for the {@link ClassManifest} class. * Tests for the {@link ClassManifest} class.
@ -41,27 +43,29 @@ class NamespacedClassManifestTest extends SapphireTest
public function testClassInfoIsCorrect() public function testClassInfoIsCorrect()
{ {
$this->assertContains('SilverStripe\Framework\Tests\ClassI', ClassInfo::implementorsOf('SilverStripe\\Security\\PermissionProvider')); $this->assertContains(
'SilverStripe\\Framework\\Tests\\ClassI',
ClassInfo::implementorsOf(PermissionProvider::class)
);
// because we're using a nested manifest we have to "coalesce" the descendants again to correctly populate the // because we're using a nested manifest we have to "coalesce" the descendants again to correctly populate the
// descendants of the core classes we want to test against - this is a limitation of the test manifest not // descendants of the core classes we want to test against - this is a limitation of the test manifest not
// including all core classes // including all core classes
$method = new ReflectionMethod($this->manifest, 'coalesceDescendants'); $method = new ReflectionMethod($this->manifest, 'coalesceDescendants');
$method->setAccessible(true); $method->setAccessible(true);
$method->invoke($this->manifest, 'SilverStripe\\Admin\\ModelAdmin'); $method->invoke($this->manifest, ModelAdmin::class);
$this->assertContains('SilverStripe\\Framework\\Tests\\ClassI', ClassInfo::subclassesFor(ModelAdmin::class));
$this->assertContains('SilverStripe\Framework\Tests\ClassI', ClassInfo::subclassesFor('SilverStripe\\Admin\\ModelAdmin'));
} }
public function testGetItemPath() public function testGetItemPath()
{ {
$expect = array( $expect = array(
'SILVERSTRIPE\TEST\CLASSA' => 'module/classes/ClassA.php', 'SILVERSTRIPE\\TEST\\CLASSA' => 'module/classes/ClassA.php',
'Silverstripe\Test\ClassA' => 'module/classes/ClassA.php', 'Silverstripe\\Test\\ClassA' => 'module/classes/ClassA.php',
'silverstripe\test\classa' => 'module/classes/ClassA.php', 'silverstripe\\test\\classa' => 'module/classes/ClassA.php',
'SILVERSTRIPE\TEST\INTERFACEA' => 'module/interfaces/InterfaceA.php', 'SILVERSTRIPE\\TEST\\INTERFACEA' => 'module/interfaces/InterfaceA.php',
'Silverstripe\Test\InterfaceA' => 'module/interfaces/InterfaceA.php', 'Silverstripe\\Test\\InterfaceA' => 'module/interfaces/InterfaceA.php',
'silverstripe\test\interfacea' => 'module/interfaces/InterfaceA.php' 'silverstripe\\test\\interfacea' => 'module/interfaces/InterfaceA.php'
); );
foreach ($expect as $name => $path) { foreach ($expect as $name => $path) {
@ -72,15 +76,15 @@ class NamespacedClassManifestTest extends SapphireTest
public function testGetClasses() public function testGetClasses()
{ {
$expect = array( $expect = array(
'silverstripe\test\classa' => "{$this->base}/module/classes/ClassA.php", 'silverstripe\\test\\classa' => "{$this->base}/module/classes/ClassA.php",
'silverstripe\test\classb' => "{$this->base}/module/classes/ClassB.php", 'silverstripe\\test\\classb' => "{$this->base}/module/classes/ClassB.php",
'silverstripe\test\classc' => "{$this->base}/module/classes/ClassC.php", 'silverstripe\\test\\classc' => "{$this->base}/module/classes/ClassC.php",
'silverstripe\test\classd' => "{$this->base}/module/classes/ClassD.php", 'silverstripe\\test\\classd' => "{$this->base}/module/classes/ClassD.php",
'silverstripe\test\classe' => "{$this->base}/module/classes/ClassE.php", 'silverstripe\\test\\classe' => "{$this->base}/module/classes/ClassE.php",
'silverstripe\test\classf' => "{$this->base}/module/classes/ClassF.php", 'silverstripe\\test\\classf' => "{$this->base}/module/classes/ClassF.php",
'silverstripe\test\classg' => "{$this->base}/module/classes/ClassG.php", 'silverstripe\\test\\classg' => "{$this->base}/module/classes/ClassG.php",
'silverstripe\test\classh' => "{$this->base}/module/classes/ClassH.php", 'silverstripe\\test\\classh' => "{$this->base}/module/classes/ClassH.php",
'silverstripe\framework\tests\classi' => "{$this->base}/module/classes/ClassI.php", 'silverstripe\\framework\\tests\\classi' => "{$this->base}/module/classes/ClassI.php",
); );
$this->assertEquals($expect, $this->manifest->getClasses()); $this->assertEquals($expect, $this->manifest->getClasses());
@ -89,29 +93,45 @@ class NamespacedClassManifestTest extends SapphireTest
public function testGetClassNames() public function testGetClassNames()
{ {
$this->assertEquals( $this->assertEquals(
array('silverstripe\test\classa', [
'silverstripe\test\classb', 'silverstripe\test\classc', 'silverstripe\test\classd', 'silverstripe\\test\\classa' => 'silverstripe\\test\\ClassA',
'silverstripe\test\classe', 'silverstripe\test\classf', 'silverstripe\test\classg', 'silverstripe\\test\\classb' => 'silverstripe\\test\\ClassB',
'silverstripe\test\classh', 'silverstripe\framework\tests\classi'), 'silverstripe\\test\\classc' => 'silverstripe\\test\\ClassC',
'silverstripe\\test\\classd' => 'silverstripe\\test\\ClassD',
'silverstripe\\test\\classe' => 'silverstripe\\test\\ClassE',
'silverstripe\\test\\classf' => 'silverstripe\\test\\ClassF',
'silverstripe\\test\\classg' => 'silverstripe\\test\\ClassG',
'silverstripe\\test\\classh' => 'silverstripe\\test\\ClassH',
'silverstripe\\framework\\tests\\classi' => 'SilverStripe\\Framework\\Tests\\ClassI',
],
$this->manifest->getClassNames() $this->manifest->getClassNames()
); );
} }
public function testGetDescendants() public function testGetDescendants()
{ {
$expect = array( $expect = [
'silverstripe\test\classa' => array('silverstripe\test\ClassB', 'silverstripe\test\ClassH'), 'silverstripe\\test\\classa' => [
); 'silverstripe\\test\\classb' => 'silverstripe\test\ClassB',
'silverstripe\\test\\classh' => 'silverstripe\test\ClassH',
],
];
$this->assertEquals($expect, $this->manifest->getDescendants()); $this->assertEquals($expect, $this->manifest->getDescendants());
} }
public function testGetDescendantsOf() public function testGetDescendantsOf()
{ {
$expect = array( $expect = [
'SILVERSTRIPE\TEST\CLASSA' => array('silverstripe\test\ClassB', 'silverstripe\test\ClassH'), 'SILVERSTRIPE\\TEST\\CLASSA' => [
'silverstripe\test\classa' => array('silverstripe\test\ClassB', 'silverstripe\test\ClassH'), 'silverstripe\\test\\classb' => 'silverstripe\test\ClassB',
); 'silverstripe\\test\\classh' => 'silverstripe\test\ClassH',
],
'silverstripe\\test\\classa' => [
'silverstripe\\test\\classb' => 'silverstripe\test\ClassB',
'silverstripe\\test\\classh' => 'silverstripe\test\ClassH',
],
];
foreach ($expect as $class => $desc) { foreach ($expect as $class => $desc) {
$this->assertEquals($desc, $this->manifest->getDescendantsOf($class)); $this->assertEquals($desc, $this->manifest->getDescendantsOf($class));
@ -121,32 +141,52 @@ class NamespacedClassManifestTest extends SapphireTest
public function testGetInterfaces() public function testGetInterfaces()
{ {
$expect = array( $expect = array(
'silverstripe\test\interfacea' => "{$this->base}/module/interfaces/InterfaceA.php", 'silverstripe\\test\\interfacea' => "{$this->base}/module/interfaces/InterfaceA.php",
); );
$this->assertEquals($expect, $this->manifest->getInterfaces()); $this->assertEquals($expect, $this->manifest->getInterfaces());
} }
public function testGetImplementors() public function testGetImplementors()
{ {
$expect = array( $expect = [
'silverstripe\test\interfacea' => array('silverstripe\test\ClassE'), 'silverstripe\\test\\interfacea' => [
'interfacea' => array('silverstripe\test\ClassF'), 'silverstripe\\test\\classe' => 'silverstripe\\test\\ClassE',
'silverstripe\test\subtest\interfacea' => array('silverstripe\test\ClassG'), ],
'silverstripe\security\permissionprovider' => array('SilverStripe\Framework\Tests\ClassI'), 'interfacea' => [
); 'silverstripe\\test\\classf' => 'silverstripe\\test\\ClassF',
],
'silverstripe\\test\\subtest\\interfacea' => [
'silverstripe\\test\\classg' => 'silverstripe\\test\\ClassG',
],
'silverstripe\\security\\permissionprovider' => [
'silverstripe\\framework\\tests\\classi' => 'SilverStripe\\Framework\\Tests\\ClassI',
],
];
$this->assertEquals($expect, $this->manifest->getImplementors()); $this->assertEquals($expect, $this->manifest->getImplementors());
} }
public function testGetImplementorsOf() public function testGetImplementorsOf()
{ {
$expect = array( $expect = [
'SILVERSTRIPE\TEST\INTERFACEA' => array('silverstripe\test\ClassE'), 'SILVERSTRIPE\\TEST\\INTERFACEA' => [
'silverstripe\test\interfacea' => array('silverstripe\test\ClassE'), 'silverstripe\\test\\classe' => 'silverstripe\\test\\ClassE',
'INTERFACEA' => array('silverstripe\test\ClassF'), ],
'interfacea' => array('silverstripe\test\ClassF'), 'silverstripe\\test\\interfacea' => [
'SILVERSTRIPE\TEST\SUBTEST\INTERFACEA' => array('silverstripe\test\ClassG'), 'silverstripe\\test\\classe' => 'silverstripe\\test\\ClassE',
'silverstripe\test\subtest\interfacea' => array('silverstripe\test\ClassG'), ],
); 'INTERFACEA' => [
'silverstripe\\test\\classf' => 'silverstripe\\test\\ClassF',
],
'interfacea' => [
'silverstripe\\test\\classf' => 'silverstripe\\test\\ClassF',
],
'SILVERSTRIPE\\TEST\\SUBTEST\\INTERFACEA' => [
'silverstripe\\test\\classg' => 'silverstripe\\test\\ClassG',
],
'silverstripe\\test\\subtest\\interfacea' => [
'silverstripe\\test\\classg' => 'silverstripe\\test\\ClassG',
],
];
foreach ($expect as $interface => $impl) { foreach ($expect as $interface => $impl) {
$this->assertEquals($impl, $this->manifest->getImplementorsOf($interface)); $this->assertEquals($impl, $this->manifest->getImplementorsOf($interface));

View File

@ -28,21 +28,27 @@ class DBClassNameTest extends SapphireTest
{ {
// Object 1 fields // Object 1 fields
$object = new DBClassNameTest\TestObject(); $object = new DBClassNameTest\TestObject();
/** @var DBClassName $defaultClass */
$defaultClass = $object->dbObject('DefaultClass'); $defaultClass = $object->dbObject('DefaultClass');
/** @var DBClassName $anyClass */
$anyClass = $object->dbObject('AnyClass'); $anyClass = $object->dbObject('AnyClass');
/** @var DBClassName $childClass */
$childClass = $object->dbObject('ChildClass'); $childClass = $object->dbObject('ChildClass');
/** @var DBClassName $leafClass */
$leafClass = $object->dbObject('LeafClass'); $leafClass = $object->dbObject('LeafClass');
// Object 2 fields // Object 2 fields
$object2 = new DBClassNameTest\ObjectSubClass(); $object2 = new DBClassNameTest\ObjectSubClass();
/** @var DBClassName $midDefault */
$midDefault = $object2->dbObject('MidClassDefault'); $midDefault = $object2->dbObject('MidClassDefault');
/** @var DBClassName $midClass */
$midClass = $object2->dbObject('MidClass'); $midClass = $object2->dbObject('MidClass');
// Default fields always default to children of base class (even if put in a subclass) // Default fields always default to children of base class (even if put in a subclass)
$mainSubclasses = array ( $mainSubclasses = array (
DBClassNameTest\TestObject::class => DBClassNameTest\TestObject::class, DBClassNameTest\TestObject::class,
DBClassNameTest\ObjectSubClass::class => DBClassNameTest\ObjectSubClass::class, DBClassNameTest\ObjectSubClass::class,
DBClassNameTest\ObjectSubSubClass::class => DBClassNameTest\ObjectSubSubClass::class, DBClassNameTest\ObjectSubSubClass::class,
); );
$this->assertEquals($mainSubclasses, $defaultClass->getEnumObsolete()); $this->assertEquals($mainSubclasses, $defaultClass->getEnumObsolete());
$this->assertEquals($mainSubclasses, $midDefault->getEnumObsolete()); $this->assertEquals($mainSubclasses, $midDefault->getEnumObsolete());
@ -56,15 +62,15 @@ class DBClassNameTest extends SapphireTest
// Classes bound to the middle of a tree // Classes bound to the middle of a tree
$midSubClasses = $mainSubclasses = array ( $midSubClasses = $mainSubclasses = array (
DBClassNameTest\ObjectSubClass::class => DBClassNameTest\ObjectSubClass::class, DBClassNameTest\ObjectSubClass::class,
DBClassNameTest\ObjectSubSubClass::class => DBClassNameTest\ObjectSubSubClass::class, DBClassNameTest\ObjectSubSubClass::class,
); );
$this->assertEquals($midSubClasses, $childClass->getEnumObsolete()); $this->assertEquals($midSubClasses, $childClass->getEnumObsolete());
$this->assertEquals($midSubClasses, $midClass->getEnumObsolete()); $this->assertEquals($midSubClasses, $midClass->getEnumObsolete());
// Leaf clasess contain only exactly one node // Leaf clasess contain only exactly one node
$this->assertEquals( $this->assertEquals(
array(DBClassNameTest\ObjectSubSubClass::class => DBClassNameTest\ObjectSubSubClass::class,), [ DBClassNameTest\ObjectSubSubClass::class ],
$leafClass->getEnumObsolete() $leafClass->getEnumObsolete()
); );
} }

View File

@ -2,13 +2,9 @@
namespace SilverStripe\ORM\Tests; namespace SilverStripe\ORM\Tests;
use SilverStripe\Assets\Tests\FileMigrationHelperTest\Extension;
use SilverStripe\Core\Config\Middleware\ExtensionMiddleware;
use SilverStripe\Dev\Debug;
use SilverStripe\ORM\DataObject;
use SilverStripe\Core\Config\Config; use SilverStripe\Core\Config\Config;
use SilverStripe\Dev\SapphireTest; use SilverStripe\Dev\SapphireTest;
use SilverStripe\ORM\DB; use SilverStripe\ORM\DataObject;
use SilverStripe\Security\Member; use SilverStripe\Security\Member;
class DataExtensionTest extends SapphireTest class DataExtensionTest extends SapphireTest
@ -207,9 +203,7 @@ class DataExtensionTest extends SapphireTest
public function testExtensionAllMethodNamesHasOwner() public function testExtensionAllMethodNamesHasOwner()
{ {
/** /** @var DataExtensionTest\MyObject $do */
* @var DataExtensionTest\MyObject $do
*/
$do = DataExtensionTest\MyObject::create(); $do = DataExtensionTest\MyObject::create();
$this->assertTrue($do->hasMethod('getTestValueWith_MyObject')); $this->assertTrue($do->hasMethod('getTestValueWith_MyObject'));

View File

@ -12,7 +12,6 @@ use SilverStripe\ORM\Tests\DataObjectSchemaGenerationTest\TestObject;
class DataObjectSchemaGenerationTest extends SapphireTest class DataObjectSchemaGenerationTest extends SapphireTest
{ {
protected static $extra_dataobjects = array( protected static $extra_dataobjects = array(
TestObject::class, TestObject::class,
TestIndexObject::class TestIndexObject::class
@ -20,6 +19,8 @@ class DataObjectSchemaGenerationTest extends SapphireTest
public static function setUpBeforeClass() public static function setUpBeforeClass()
{ {
// Start tests
static::start();
// enable fulltext option on this table // enable fulltext option on this table
TestIndexObject::config()->update( TestIndexObject::config()->update(
@ -197,6 +198,7 @@ class DataObjectSchemaGenerationTest extends SapphireTest
/** /**
* Tests the generation of the ClassName spec and ensure it's not unnecessarily influenced * Tests the generation of the ClassName spec and ensure it's not unnecessarily influenced
* by the order of classnames of existing records * by the order of classnames of existing records
* @skipUpgrade
*/ */
public function testClassNameSpecGeneration() public function testClassNameSpecGeneration()
{ {
@ -206,15 +208,12 @@ class DataObjectSchemaGenerationTest extends SapphireTest
DBClassName::clear_classname_cache(); DBClassName::clear_classname_cache();
$do1 = new TestObject(); $do1 = new TestObject();
$fields = $schema->databaseFields(TestObject::class, false); $fields = $schema->databaseFields(TestObject::class, false);
/**
* @skipUpgrade
*/
$this->assertEquals("DBClassName", $fields['ClassName']); $this->assertEquals("DBClassName", $fields['ClassName']);
$this->assertEquals( $this->assertEquals(
array( [
TestObject::class => TestObject::class, TestObject::class,
TestIndexObject::class => TestIndexObject::class TestIndexObject::class,
), ],
$do1->dbObject('ClassName')->getEnum() $do1->dbObject('ClassName')->getEnum()
); );
@ -224,10 +223,10 @@ class DataObjectSchemaGenerationTest extends SapphireTest
$item1->write(); $item1->write();
DBClassName::clear_classname_cache(); DBClassName::clear_classname_cache();
$this->assertEquals( $this->assertEquals(
array( [
TestObject::class => TestObject::class, TestObject::class,
TestIndexObject::class => TestIndexObject::class TestIndexObject::class,
), ],
$item1->dbObject('ClassName')->getEnum() $item1->dbObject('ClassName')->getEnum()
); );
$item1->delete(); $item1->delete();
@ -237,10 +236,10 @@ class DataObjectSchemaGenerationTest extends SapphireTest
$item2->write(); $item2->write();
DBClassName::clear_classname_cache(); DBClassName::clear_classname_cache();
$this->assertEquals( $this->assertEquals(
array( [
TestObject::class => TestObject::class, TestObject::class,
TestIndexObject::class => TestIndexObject::class TestIndexObject::class,
), ],
$item2->dbObject('ClassName')->getEnum() $item2->dbObject('ClassName')->getEnum()
); );
$item2->delete(); $item2->delete();
@ -252,10 +251,10 @@ class DataObjectSchemaGenerationTest extends SapphireTest
$item2->write(); $item2->write();
DBClassName::clear_classname_cache(); DBClassName::clear_classname_cache();
$this->assertEquals( $this->assertEquals(
array( [
TestObject::class => TestObject::class, TestObject::class,
TestIndexObject::class => TestIndexObject::class TestIndexObject::class,
), ],
$item1->dbObject('ClassName')->getEnum() $item1->dbObject('ClassName')->getEnum()
); );
$item1->delete(); $item1->delete();