diff --git a/_config/i18n.yml b/_config/i18n.yml index 2fd78c599..1c561b455 100644 --- a/_config/i18n.yml +++ b/_config/i18n.yml @@ -4,9 +4,8 @@ Before: '/i18n' --- SilverStripe\i18n\Data\Sources: module_priority: - - admin - - framework - - sapphire + - silverstripe\admin + - silverstripe\framework --- Name: defaulti18n --- diff --git a/docs/en/00_Getting_Started/03_Environment_Management.md b/docs/en/00_Getting_Started/03_Environment_Management.md index 289e3e20b..de94b2d36 100644 --- a/docs/en/00_Getting_Started/03_Environment_Management.md +++ b/docs/en/00_Getting_Started/03_Environment_Management.md @@ -85,6 +85,6 @@ SilverStripe core environment variables are listed here, though you're free to d | `SS_TRUSTED_PROXY_HOST_HEADER` | Used to define the proxy header to be used to determine the requested host name | | `SS_TRUSTED_PROXY_IPS` | IP address or CIDR range to trust proxy headers from | | `SS_ALLOWED_HOSTS` | A comma deliminated list of hostnames the site is allowed to respond to | -| `SS_MANIFESTCACHE` | The manifest cache to use (defaults to file based caching) | +| `SS_MANIFESTCACHE` | The manifest cache to use (defaults to file based caching). Must be a CacheInterface or CacheFactory class name | | `SS_IGNORE_DOT_ENV` | If set the .env file will be ignored. This is good for live to mitigate any performance implications of loading the .env file | | `SS_HOST` | The hostname to use when it isn't determinable by other means (eg: for CLI commands) | diff --git a/docs/en/02_Developer_Guides/16_Execution_Pipeline/02_Manifests.md b/docs/en/02_Developer_Guides/16_Execution_Pipeline/02_Manifests.md index c1c430da3..0ddc8c801 100644 --- a/docs/en/02_Developer_Guides/16_Execution_Pipeline/02_Manifests.md +++ b/docs/en/02_Developer_Guides/16_Execution_Pipeline/02_Manifests.md @@ -11,17 +11,10 @@ Others store aggregate information like nested configuration graphs. ## Storage -By default, manifests are stored on the local filesystem through PHP's `serialize()` method. -Combined with PHP opcode caching this provides fast access. -In order to share manifests between servers, or centralise cache management, -other storage adapters are available. These can be configured by a `SS_MANIFESTCACHE` constant, -placed in your `.env`. - - * `ManifestCache_File`: The default adapter using PHP's `serialize()` - * `ManifestCache_File_PHP`: Using `var_export()`, which is faster when a PHP opcode cache is installed - * `ManifestCache_APC`: Use PHP's [APC object cache](http://php.net/manual/en/book.apc.php) - -You can write your own adapters by implementing the `ManifestCache` interface. +By default, manifests are serialised and cached via a cache generated by the [api:ManifestCacheFactory]. +This can be customised via `SS_MANIFESTCACHE` environment variable to point to either another +[api:CacheFactory] or [CacheInterface](https://github.com/php-fig/cache/blob/master/src/CacheItemInterface.php) +implementor. ## Traversing the Filesystem diff --git a/docs/en/04_Changelogs/4.0.0.md b/docs/en/04_Changelogs/4.0.0.md index baf22e5e0..7fcb5a5d6 100644 --- a/docs/en/04_Changelogs/4.0.0.md +++ b/docs/en/04_Changelogs/4.0.0.md @@ -1067,6 +1067,7 @@ now generally safer to use the default inherited config, where in the past you w * Falsey config values (null, 0, false, etc) can now replace non-falsey values. * Introduced new ModuleLoader manifest, which allows modules to be found via composer name. E.g. `$cms = ModuleLoader::instance()->getManifest()->getModule('silverstripe/cms')` +* `ClassManifest::getOwnerModule()` now returns a `Module` object instance. * Certain methods have been moved from `Controller` to `RequestHandler`: * `Link` * `redirect` @@ -1081,7 +1082,7 @@ now generally safer to use the default inherited config, where in the past you w #### General and Core Removed API * `CMSMain::buildbrokenlinks()` action is removed. -* `SS_Log::add_writer()` method is removed. +* `SS_Log` class has been removed. Use `Injector::inst()->get(LoggerInterface::class)` instead. * Removed `CMSBatchAction_Delete` * Removed `CMSBatchAction_DeleteFromLive` * Removed `CMSMain.enabled_legacy_actions` config. @@ -1101,6 +1102,8 @@ now generally safer to use the default inherited config, where in the past you w * Removed `SilverStripeInjectionCreator` * Removed `i18n::get_translatable_modules` method. * Removed `i18nTextCollector_Writer_Php` +* `i18nTextCollector` no longer collects from `themes/` root dir. + Modules which provide themes via `/themes/` are now preferred. * Removed `i18nSSLegacyAdapter` * Removed `FunctionalTest::stat` * Removed `LeftAndMainMarkingFilter` @@ -1116,6 +1119,11 @@ now generally safer to use the default inherited config, where in the past you w * Removed `Config::FIRST_SET` and `Config::INHERITED` * Removed `RequestHandler.require_allowed_actions`. This is now fixed to on and cannot be disabled. +* Config or module searching methods have been removed from `ClassManifest`. Use `ModuleLoader` + to get this information instead: + - `getModules` + - `getConfigDirs` + - `getConfigs` #### General and Core Deprecated API @@ -1567,6 +1575,7 @@ New `TimeField` methods replace `getConfig()` / `setConfig()` * `i18n::get_language_name()` moved to `SilverStripe\i18n\Data\Locales::languageName()` * `i18n.module_priority` config moved to `SilverStripe\i18n\Data\Sources.module_priority` * `i18n::get_owner_module()` moved to `SilverStripe\Core\Manifest\ClassManifest::getOwnerModule()` + This now returns a `Module` object instance instead of a string. * `i18n::get_existing_translations()` moved to `SilverStripe\i18n\Data\Sources::getKnownLocales()` #### i18n API Removed API @@ -1592,6 +1601,7 @@ and have a slightly different API (e.g. `set()` instead of `save()`). Before: + :::php $cache = Cache::factory('myCache'); @@ -1612,6 +1622,7 @@ Before: After: + :::php use Psr\SimpleCache\CacheInterface; $cache = Injector::inst()->get(CacheInterface::class . '.myCache'); @@ -1629,6 +1640,21 @@ After: $cache->delete('myCacheKey'); + +With the necessary minimal config in `_config/mycache.yml` + + + :::yml + --- + Name: mycache + --- + SilverStripe\Core\Injector\Injector: + Psr\SimpleCache\CacheInterface.myCache: + factory: SilverStripe\Core\Cache\CacheFactory + constructor: + namespace: 'mycache' + + #### Configuration Changes Caches are now configured through dependency injection services instead of PHP. diff --git a/src/Core/Cache/ManifestCacheFactory.php b/src/Core/Cache/ManifestCacheFactory.php new file mode 100644 index 000000000..53298e0ce --- /dev/null +++ b/src/Core/Cache/ManifestCacheFactory.php @@ -0,0 +1,91 @@ +pushHandler(new StreamHandler('php://output')); + } else { + $logger->pushHandler(new ErrorLogHandler()); + } + } + + parent::__construct($args, $logger); + } + + /** + * Note: While the returned object is used as a singleton (by the originating Injector->get() call), + * this cache object shouldn't be a singleton itself - it has varying constructor args for the same service name. + * + * @param string $service The class name of the service. + * @param array $params The constructor parameters. + * @return CacheInterface + */ + public function create($service, array $params = array()) + { + // Override default cache generation with SS_MANIFESTCACHE + $cacheClass = getenv('SS_MANIFESTCACHE'); + if (!$cacheClass) { + return parent::create($service, $params); + } + + // Check if SS_MANIFESTCACHE is a factory + if (is_a($cacheClass, CacheFactory::class, true)) { + /** @var CacheFactory $factory */ + $factory = new $cacheClass; + return $factory->create($service, $params); + } + + // Check if SS_MANIFESTCACHE is a cache subclass + if (is_a($cacheClass, CacheInterface::class, true)) { + $args = array_merge($this->args, $params); + $namespace = isset($args['namespace']) ? $args['namespace'] : ''; + return $this->createCache($cacheClass, [$namespace]); + } + + // Validate type + throw new BadMethodCallException( + 'SS_MANIFESTCACHE is not a valid CacheInterface or CacheFactory class name' + ); + } + + /** + * Create cache directly without config / injector + * + * @param string $class + * @param array $args + * @return CacheInterface + */ + public function createCache($class, $args) + { + /** @var CacheInterface $cache */ + $reflection = new ReflectionClass($class); + $cache = $reflection->newInstanceArgs($args); + + // Assign cache logger + if ($this->logger && $cache instanceof LoggerAwareInterface) { + $cache->setLogger($this->logger); + } + + return $cache; + } +} diff --git a/src/Core/Config/CoreConfigFactory.php b/src/Core/Config/CoreConfigFactory.php index 53bf55dd9..5907cb4fe 100644 --- a/src/Core/Config/CoreConfigFactory.php +++ b/src/Core/Config/CoreConfigFactory.php @@ -6,11 +6,13 @@ use Monolog\Handler\ErrorLogHandler; use Monolog\Handler\StreamHandler; use Monolog\Logger; use Psr\Log\LoggerInterface; +use Psr\SimpleCache\CacheInterface; use SilverStripe\Config\Collections\CachedConfigCollection; use SilverStripe\Config\Collections\MemoryConfigCollection; use SilverStripe\Config\Transformer\PrivateStaticTransformer; use SilverStripe\Config\Transformer\YamlTransformer; use SilverStripe\Control\Director; +use SilverStripe\Core\Cache\CacheFactory; use SilverStripe\Core\Config\Middleware\ExtensionMiddleware; use SilverStripe\Core\Config\Middleware\InheritanceMiddleware; use SilverStripe\Core\Manifest\ClassLoader; @@ -45,14 +47,18 @@ class CoreConfigFactory * which conditionally generates a nested "core" config. * * @param bool $flush + * @param CacheFactory $cacheFactory * @return CachedConfigCollection */ - public function createRoot($flush) + public function createRoot($flush, CacheFactory $cacheFactory) { $instance = new CachedConfigCollection(); - // Set root cache - $instance->setPool($this->createPool()); + // Create config cache + $cache = $cacheFactory->create(CacheInterface::class.'.configcache', [ + 'namespace' => 'configcache' + ]); + $instance->setCache($cache); $instance->setFlush($flush); // Set collection creator @@ -172,32 +178,4 @@ class CoreConfigFactory return ModuleLoader::instance()->getManifest()->moduleExists($module); }); } - - /** - * @todo Refactor bootstrapping of manifest caching into app object - * @return FilesystemAdapter - */ - protected function createPool() - { - $cache = new FilesystemAdapter('configcache', 0, getTempFolder()); - $cache->setLogger($this->createLogger()); - return $cache; - } - - /** - * Create default error logger - * - * @todo Refactor bootstrapping of manifest logging into app object - * @return LoggerInterface - */ - protected function createLogger() - { - $logger = new Logger("configcache-log"); - if (Director::isDev()) { - $logger->pushHandler(new StreamHandler('php://output')); - } else { - $logger->pushHandler(new ErrorLogHandler()); - } - return $logger; - } } diff --git a/src/Core/Core.php b/src/Core/Core.php index e6eea0c82..8cfe5a3ce 100644 --- a/src/Core/Core.php +++ b/src/Core/Core.php @@ -1,5 +1,6 @@ 'manifestcache', + 'directory' => getTempFolder(), +]); + +// Build class manifest +$manifest = new ClassManifest(BASE_PATH, false, $flush, $manifestCacheFactory); // Register SilverStripe's class map autoload $loader = ClassLoader::instance(); @@ -79,11 +86,11 @@ $loader->registerAutoloader(); $loader->pushManifest($manifest); // Init module manifest -$moduleManifest = new ModuleManifest(BASE_PATH, false, $flush); +$moduleManifest = new ModuleManifest(BASE_PATH, false, $flush, $manifestCacheFactory); ModuleLoader::instance()->pushManifest($moduleManifest); // Build config manifest -$configManifest = CoreConfigFactory::inst()->createRoot($flush); +$configManifest = CoreConfigFactory::inst()->createRoot($flush, $manifestCacheFactory); ConfigLoader::instance()->pushManifest($configManifest); // After loading config, boot _config.php files @@ -94,7 +101,8 @@ SilverStripe\View\ThemeResourceLoader::instance()->addSet('$default', new Silver BASE_PATH, project(), false, - $flush + $flush, + $manifestCacheFactory )); // If in live mode, ensure deprecation, strict and notices are not reported diff --git a/src/Core/Manifest/ClassManifest.php b/src/Core/Manifest/ClassManifest.php index cad12e5ce..c84eca4ca 100644 --- a/src/Core/Manifest/ClassManifest.php +++ b/src/Core/Manifest/ClassManifest.php @@ -6,56 +6,112 @@ use Exception; use PhpParser\Error; use PhpParser\NodeTraverser; use PhpParser\NodeVisitor\NameResolver; +use PhpParser\Parser; use PhpParser\ParserFactory; -use SilverStripe\Control\Director; +use Psr\SimpleCache\CacheInterface; +use SilverStripe\Core\Cache\CacheFactory; +use SilverStripe\Dev\TestOnly; /** - * A utility class which builds a manifest of all classes, interfaces and some - * additional items present in a directory, and caches it. + * A utility class which builds a manifest of all classes, interfaces and caches it. * * It finds the following information: * - Class and interface names and paths. * - All direct and indirect descendants of a class. * - All implementors of an interface. - * - All module configuration files. */ class ClassManifest { - - const CONF_FILE = '_config.php'; - const CONF_DIR = '_config'; - + /** + * base manifest directory + * @var string + */ protected $base; + + /** + * Set if including test classes + * + * @see TestOnly + * @var bool + */ protected $tests; /** - * @var ManifestCache + * Cache to use, if caching. + * Set to null if uncached. + * + * @var CacheInterface|null */ protected $cache; /** + * Key to use for the top level cache of all items + * * @var string */ protected $cacheKey; + /** + * Map of classes to paths + * + * @var array + */ protected $classes = array(); - protected $roots = array(); - protected $children = array(); - protected $descendants = array(); - protected $interfaces = array(); - protected $implementors = array(); - protected $configs = array(); - protected $configDirs = array(); - protected $traits = array(); /** - * @var \PhpParser\Parser + * List of root classes with no parent class + * + * @var array + */ + protected $roots = array(); + + /** + * List of direct children for any class + * + * @var array + */ + protected $children = array(); + + /** + * List of descendents for any class (direct + indirect children) + * + * @var array + */ + protected $descendants = array(); + + /** + * List of interfaces and paths to those files + * + * @var array + */ + protected $interfaces = array(); + + /** + * List of direct implementors of any interface + * + * @var array + */ + protected $implementors = array(); + + /** + * Map of traits to paths + * + * @var array + */ + protected $traits = array(); + + /** + * PHP Parser for parsing found files + * + * @var Parser */ private $parser; + /** * @var NodeTraverser */ private $traverser; + /** * @var ClassManifestVisitor */ @@ -66,33 +122,44 @@ class ClassManifest * from the cache or re-scanning for classes. * * @param string $base The manifest base path. - * @param bool $includeTests Include the contents of "tests" directories. - * @param bool $forceRegen Force the manifest to be regenerated. - * @param bool $cache If the manifest is regenerated, cache it. + * @param bool $includeTests Include the contents of "tests" directories. + * @param bool $forceRegen Force the manifest to be regenerated. + * @param CacheFactory $cacheFactory Optional cache to use. Set to null to not cache. */ - public function __construct($base, $includeTests = false, $forceRegen = false, $cache = true) - { - $this->base = $base; + public function __construct( + $base, + $includeTests = false, + $forceRegen = false, + CacheFactory $cacheFactory = null + ) { + $this->base = $base; $this->tests = $includeTests; - $cacheClass = getenv('SS_MANIFESTCACHE') ?: 'SilverStripe\\Core\\Manifest\\ManifestCache_File'; - - $this->cache = new $cacheClass('classmanifest'.($includeTests ? '_tests' : '')); + // build cache from factory + if ($cacheFactory) { + $this->cache = $cacheFactory->create( + CacheInterface::class.'.classmanifest', + [ 'namespace' => 'classmanifest' . ($includeTests ? '_tests' : '') ] + ); + } $this->cacheKey = 'manifest'; - if (!$forceRegen && $data = $this->cache->load($this->cacheKey)) { - $this->classes = $data['classes']; - $this->descendants = $data['descendants']; - $this->interfaces = $data['interfaces']; + if (!$forceRegen && $this->cache && ($data = $this->cache->get($this->cacheKey))) { + $this->classes = $data['classes']; + $this->descendants = $data['descendants']; + $this->interfaces = $data['interfaces']; $this->implementors = $data['implementors']; - $this->configs = $data['configs']; - $this->configDirs = $data['configDirs']; - $this->traits = $data['traits']; + $this->traits = $data['traits']; } else { - $this->regenerate($cache); + $this->regenerate(); } } + /** + * Get or create active parser + * + * @return Parser + */ public function getParser() { if (!$this->parser) { @@ -246,49 +313,11 @@ class ClassManifest } } - /** - * Returns an array of paths to module config files. - * - * @return array - */ - public function getConfigs() - { - return $this->configs; - } - - /** - * Returns an array of module names mapped to their paths. - * - * "Modules" in SilverStripe are simply directories with a _config.php - * file. - * - * @return array - */ - public function getModules() - { - $modules = array(); - - if ($this->configs) { - foreach ($this->configs as $configPath) { - $modules[basename(dirname($configPath))] = dirname($configPath); - } - } - - if ($this->configDirs) { - foreach ($this->configDirs as $configDir) { - $path = preg_replace('/\/_config$/', '', dirname($configDir)); - $modules[basename($path)] = $path; - } - } - - return $modules; - } - /** * Get module that owns this class * * @param string $class Class name - * @return string + * @return Module */ public function getOwnerModule($class) { @@ -297,29 +326,32 @@ class ClassManifest return null; } + /** @var Module $rootModule */ + $rootModule = null; + // Find based on loaded modules - foreach ($this->getModules() as $parent => $module) { - if (stripos($path, realpath($parent)) === 0) { + $modules = ModuleLoader::instance()->getManifest()->getModules(); + foreach ($modules as $module) { + // Leave root module as fallback + if (empty($module->getRelativePath())) { + $rootModule = $module; + } elseif (stripos($path, realpath($module->getPath())) === 0) { return $module; } } - // Assume top level folder is the module name - $relativePath = substr($path, strlen(realpath(Director::baseFolder()))); - $parts = explode('/', trim($relativePath, '/')); - return array_shift($parts); + // Fall back to top level module + return $rootModule; } /** * Completely regenerates the manifest file. - * - * @param bool $cache Cache the result. */ - public function regenerate($cache = true) + public function regenerate() { $resets = array( 'classes', 'roots', 'children', 'descendants', 'interfaces', - 'implementors', 'configs', 'configDirs', 'traits' + 'implementors', 'traits' ); // Reset the manifest so stale info doesn't cause errors. @@ -329,11 +361,10 @@ class ClassManifest $finder = new ManifestFileFinder(); $finder->setOptions(array( - 'name_regex' => '/^((_config)|([^_].*))\\.php$/', + 'name_regex' => '/^[^_].*\\.php$/', 'ignore_files' => array('index.php', 'main.php', 'cli-script.php'), 'ignore_tests' => !$this->tests, 'file_callback' => array($this, 'handleFile'), - 'dir_callback' => array($this, 'handleDir') )); $finder->find($this->base); @@ -341,34 +372,20 @@ class ClassManifest $this->coalesceDescendants($root); } - if ($cache) { + if ($this->cache) { $data = array( 'classes' => $this->classes, 'descendants' => $this->descendants, 'interfaces' => $this->interfaces, 'implementors' => $this->implementors, - 'configs' => $this->configs, - 'configDirs' => $this->configDirs, 'traits' => $this->traits, ); - $this->cache->save($data, $this->cacheKey); + $this->cache->set($this->cacheKey, $data); } } - public function handleDir($basename, $pathname, $depth) + public function handleFile($basename, $pathname) { - if ($basename == self::CONF_DIR) { - $this->configDirs[] = $pathname; - } - } - - public function handleFile($basename, $pathname, $depth) - { - if ($basename == self::CONF_FILE) { - $this->configs[] = $pathname; - return; - } - $classes = null; $interfaces = null; $traits = null; @@ -379,24 +396,16 @@ class ClassManifest // since just using the datetime lead to problems with upgrading. $key = preg_replace('/[^a-zA-Z0-9_]/', '_', $basename) . '_' . md5_file($pathname); - $valid = false; - if ($data = $this->cache->load($key)) { - $valid = ( - isset($data['classes']) && is_array($data['classes']) - && isset($data['interfaces']) - && is_array($data['interfaces']) - && isset($data['traits']) - && is_array($data['traits']) - ); - - if ($valid) { - $classes = $data['classes']; - $interfaces = $data['interfaces']; - $traits = $data['traits']; - } - } - - if (!$valid) { + // Attempt to load from cache + if ($this->cache + && ($data = $this->cache->get($key)) + && $this->validateItemCache($data) + ) { + $classes = $data['classes']; + $interfaces = $data['interfaces']; + $traits = $data['traits']; + } else { + // Build from php file parser $fileContents = ClassContentRemover::remove_class_content($pathname); try { $stmts = $this->getParser()->parse($fileContents); @@ -410,14 +419,18 @@ class ClassManifest $interfaces = $this->getVisitor()->getInterfaces(); $traits = $this->getVisitor()->getTraits(); - $cache = array( - 'classes' => $classes, - 'interfaces' => $interfaces, - 'traits' => $traits, - ); - $this->cache->save($cache, $key); + // Save back to cache if configured + if ($this->cache) { + $cache = array( + 'classes' => $classes, + 'interfaces' => $interfaces, + 'traits' => $traits, + ); + $this->cache->set($key, $cache); + } } + // Merge this data into the global list foreach ($classes as $className => $classInfo) { $extends = isset($classInfo['extends']) ? $classInfo['extends'] : null; $implements = isset($classInfo['interfaces']) ? $classInfo['interfaces'] : null; @@ -496,4 +509,30 @@ class ClassManifest return array(); } } + + /** + * Verify that cached data is valid for a single item + * + * @param array $data + * @return bool + */ + protected function validateItemCache($data) + { + foreach (['classes', 'interfaces', 'traits'] as $key) { + // Must be set + if (!isset($data[$key])) { + return false; + } + // and an array + if (!is_array($data[$key])) { + return false; + } + // Detect legacy cache keys (non-associative) + $array = $data[$key]; + if (!empty($array) && is_numeric(key($array))) { + return false; + } + } + return true; + } } diff --git a/src/Core/Manifest/ManifestCache.php b/src/Core/Manifest/ManifestCache.php deleted file mode 100644 index b8cdab5ac..000000000 --- a/src/Core/Manifest/ManifestCache.php +++ /dev/null @@ -1,14 +0,0 @@ -pre = $name; - } - - function load($key) - { - return apc_fetch($this->pre . $key); - } - - function save($data, $key) - { - apc_store($this->pre . $key, $data); - } - - function clear() - { - } -} diff --git a/src/Core/Manifest/ManifestCache_File.php b/src/Core/Manifest/ManifestCache_File.php deleted file mode 100644 index ffd282d3a..000000000 --- a/src/Core/Manifest/ManifestCache_File.php +++ /dev/null @@ -1,37 +0,0 @@ -folder = TEMP_FOLDER . DIRECTORY_SEPARATOR . $name; - if (!is_dir($this->folder)) { - mkdir($this->folder); - } - } - - function load($key) - { - $file = $this->folder . DIRECTORY_SEPARATOR . 'cache_' . $key; - return file_exists($file) ? unserialize(file_get_contents($file)) : null; - } - - function save($data, $key) - { - $file = $this->folder . DIRECTORY_SEPARATOR . 'cache_' . $key; - file_put_contents($file, serialize($data)); - } - - function clear() - { - array_map('unlink', glob($this->folder . DIRECTORY_SEPARATOR . 'cache_*')); - } -} diff --git a/src/Core/Manifest/ManifestCache_File_PHP.php b/src/Core/Manifest/ManifestCache_File_PHP.php deleted file mode 100644 index 3b2acbe66..000000000 --- a/src/Core/Manifest/ManifestCache_File_PHP.php +++ /dev/null @@ -1,29 +0,0 @@ -folder . DIRECTORY_SEPARATOR . 'cache_' . $key; - if (file_exists($file)) { - include $file; - } - - return $loaded_manifest; - } - - function save($data, $key) - { - $file = $this->folder . DIRECTORY_SEPARATOR. 'cache_' . $key; - file_put_contents($file, 'base = $base; $this->cacheKey = sha1($base).'_modules'; $this->includeTests = $includeTests; - $this->cache = $this->getCache($includeTests); + // build cache from factory + if ($cacheFactory) { + $this->cache = $cacheFactory->create( + CacheInterface::class.'.modulemanifest', + [ 'namespace' => 'modulemanifest' . ($includeTests ? '_tests' : '') ] + ); + } // Unless we're forcing regen, try loading from cache - if (!$forceRegen) { - $this->modules = $this->cache->load($this->cacheKey) ?: []; + if (!$forceRegen && $this->cache) { + $this->modules = $this->cache->get($this->cacheKey) ?: []; } if (empty($this->modules)) { $this->regenerate($includeTests); } } - /** - * Provides a hook for mock unit tests despite no DI - * - * @param bool $includeTests - * @return ManifestCache - */ - protected function getCache($includeTests = false) - { - // Cache - $cacheClass = getenv('SS_MANIFESTCACHE') ?: ManifestCache_File::class; - return new $cacheClass('classmanifest'.($includeTests ? '_tests' : '')); - } - /** * Includes all of the php _config.php files found by this manifest. */ @@ -136,9 +132,8 @@ class ModuleManifest * Does _not_ build the actual variant * * @param bool $includeTests - * @param bool $cache Cache the result. */ - public function regenerate($includeTests = false, $cache = true) + public function regenerate($includeTests = false) { $this->modules = []; @@ -162,8 +157,8 @@ class ModuleManifest )); $finder->find($this->base); - if ($cache) { - $this->cache->save($this->modules, $this->cacheKey); + if ($this->cache) { + $this->cache->set($this->cacheKey, $this->modules); } } @@ -172,9 +167,8 @@ class ModuleManifest * * @param string $basename * @param string $pathname - * @param int $depth */ - public function addSourceConfigFile($basename, $pathname, $depth) + public function addSourceConfigFile($basename, $pathname) { $this->addModule(dirname($pathname)); } @@ -184,9 +178,8 @@ class ModuleManifest * * @param string $basename * @param string $pathname - * @param int $depth */ - public function addYAMLConfigFile($basename, $pathname, $depth) + public function addYAMLConfigFile($basename, $pathname) { if (preg_match('{/([^/]+)/_config/}', $pathname, $match)) { $this->addModule(dirname(dirname($pathname))); diff --git a/src/Dev/Deprecation.php b/src/Dev/Deprecation.php index 6af497ced..310d4805d 100644 --- a/src/Dev/Deprecation.php +++ b/src/Dev/Deprecation.php @@ -4,6 +4,8 @@ namespace SilverStripe\Dev; use SilverStripe\Control\Director; use SilverStripe\Core\Manifest\ClassLoader; +use SilverStripe\Core\Manifest\Module; +use SilverStripe\Core\Manifest\ModuleLoader; /** * Handles raising an notice when accessing a deprecated method @@ -96,7 +98,7 @@ class Deprecation * #notice) * * @param array $backtrace A backtrace as returned from debug_backtrace - * @return string The name of the module the call came from, or null if we can't determine + * @return Module The module being called */ protected static function get_calling_module_from_trace($backtrace) { @@ -106,10 +108,10 @@ class Deprecation $callingfile = realpath($backtrace[1]['file']); - $manifest = ClassLoader::instance()->getManifest(); - foreach ($manifest->getModules() as $name => $path) { - if (strpos($callingfile, realpath($path)) === 0) { - return $name; + $modules = ModuleLoader::instance()->getManifest()->getModules(); + foreach ($modules as $module) { + if (strpos($callingfile, realpath($module->getPath())) === 0) { + return $module; } } return null; @@ -193,8 +195,16 @@ class Deprecation if (self::$module_version_overrides) { $module = self::get_calling_module_from_trace($backtrace = debug_backtrace(0)); - if (isset(self::$module_version_overrides[$module])) { - $checkVersion = self::$module_version_overrides[$module]; + if ($module) { + if (($name = $module->getComposerName()) + && isset(self::$module_version_overrides[$name]) + ) { + $checkVersion = self::$module_version_overrides[$name]; + } elseif (($name = $module->getShortName()) + && isset(self::$module_version_overrides[$name]) + ) { + $checkVersion = self::$module_version_overrides[$name]; + } } } diff --git a/src/Logging/Log.php b/src/Logging/Log.php deleted file mode 100644 index a1a399d64..000000000 --- a/src/Logging/Log.php +++ /dev/null @@ -1,61 +0,0 @@ -get(LoggerInterface::class) instead'); - return Injector::inst()->get(LoggerInterface::class); - } - - /** - * Dispatch a message by priority level. - * - * The message parameter can be either a string (a simple error - * message), or an array of variables. The latter is useful for passing - * along a list of debug information for the writer to handle, such as - * error code, error line, error context (backtrace). - * - * @param mixed $message Exception object or array of error context variables - * @param string $priority Priority. Possible values: Log::ERR, Log::WARN, Log::NOTICE, Log::INFO or Log::DEBUG - * - * @deprecated 4.0.0:5.0.0 Use Injector::inst()->get('Logger')->log($priority, $message) instead - */ - public static function log($message, $priority) - { - Deprecation::notice('5.0', 'Use Injector::inst()->get(LoggerInterface::class)->log($priority, $message) instead'); - Injector::inst()->get(LoggerInterface::class)->log($priority, $message); - } -} diff --git a/src/View/ThemeManifest.php b/src/View/ThemeManifest.php index f577154ab..597f6604d 100644 --- a/src/View/ThemeManifest.php +++ b/src/View/ThemeManifest.php @@ -2,7 +2,8 @@ namespace SilverStripe\View; -use SilverStripe\Core\Manifest\ManifestCache; +use Psr\SimpleCache\CacheInterface; +use SilverStripe\Core\Cache\CacheFactory; use SilverStripe\Core\Manifest\ManifestFileFinder; /** @@ -37,7 +38,7 @@ class ThemeManifest implements ThemeList /** * Cache * - * @var ManifestCache + * @var CacheInterface */ protected $cache; @@ -51,7 +52,7 @@ class ThemeManifest implements ThemeList /** * List of theme root directories * - * @var string + * @var string[] */ protected $themes = null; @@ -64,18 +65,26 @@ class ThemeManifest implements ThemeList * * @param bool $includeTests Include tests in the manifest. * @param bool $forceRegen Force the manifest to be regenerated. + * @param CacheFactory $cacheFactory Cache factory to generate backend cache with */ - public function __construct($base, $project, $includeTests = false, $forceRegen = false) - { + public function __construct( + $base, + $project, + $includeTests = false, + $forceRegen = false, + CacheFactory $cacheFactory = null + ) { $this->base = $base; $this->tests = $includeTests; - $this->project = $project; - $cacheClass = getenv('SS_MANIFESTCACHE') - ?: 'SilverStripe\\Core\\Manifest\\ManifestCache_File'; - - $this->cache = new $cacheClass('thememanifest'.($includeTests ? '_tests' : '')); + // build cache from factory + if ($cacheFactory) { + $this->cache = $cacheFactory->create( + CacheInterface::class.'.thememanifest', + [ 'namespace' => 'thememanifest' . ($includeTests ? '_tests' : '') ] + ); + } $this->cacheKey = $this->getCacheKey(); if ($forceRegen) { @@ -117,10 +126,8 @@ class ThemeManifest implements ThemeList /** * Regenerates the manifest by scanning the base path. - * - * @param bool $cache */ - public function regenerate($cache = true) + public function regenerate() { $finder = new ManifestFileFinder(); $finder->setOptions(array( @@ -133,8 +140,8 @@ class ThemeManifest implements ThemeList $this->themes = []; $finder->find($this->base); - if ($cache) { - $this->cache->save($this->themes, $this->cacheKey); + if ($this->cache) { + $this->cache->set($this->cacheKey, $this->themes); } } @@ -171,7 +178,7 @@ class ThemeManifest implements ThemeList */ protected function init() { - if ($data = $this->cache->load($this->cacheKey)) { + if ($this->cache && ($data = $this->cache->get($this->cacheKey))) { $this->themes = $data; } else { $this->regenerate(); diff --git a/src/i18n/Data/Sources.php b/src/i18n/Data/Sources.php index 6f6aac613..429e7c034 100644 --- a/src/i18n/Data/Sources.php +++ b/src/i18n/Data/Sources.php @@ -4,7 +4,7 @@ namespace SilverStripe\i18n\Data; use SilverStripe\Core\Config\Configurable; use SilverStripe\Core\Injector\Injectable; -use SilverStripe\Core\Manifest\ClassLoader; +use SilverStripe\Core\Manifest\ModuleLoader; use SilverStripe\Core\Resettable; use SilverStripe\i18n\i18n; use SilverStripe\View\SSViewer; @@ -35,7 +35,7 @@ class Sources implements Resettable public function getSortedModules() { // Get list of module => path pairs, and then just the names - $modules = ClassLoader::instance()->getManifest()->getModules(); + $modules = ModuleLoader::instance()->getManifest()->getModules(); $moduleNames = array_keys($modules); // Remove the "project" module from the list - we'll add it back specially later if needed @@ -63,14 +63,14 @@ class Sources implements Resettable $order[] = $project; } - $sortedModules = array(); + $sortedModulePaths = array(); foreach ($order as $module) { if (isset($modules[$module])) { - $sortedModules[$module] = $modules[$module]; + $sortedModulePaths[$module] = $modules[$module]->getPath(); } } - $sortedModules = array_reverse($sortedModules, true); - return $sortedModules; + $sortedModulePaths = array_reverse($sortedModulePaths, true); + return $sortedModulePaths; } /** diff --git a/src/i18n/TextCollection/i18nTextCollector.php b/src/i18n/TextCollection/i18nTextCollector.php index 8dcaa6f4f..e44aedc27 100644 --- a/src/i18n/TextCollection/i18nTextCollector.php +++ b/src/i18n/TextCollection/i18nTextCollector.php @@ -5,6 +5,8 @@ namespace SilverStripe\i18n\TextCollection; use SilverStripe\Core\ClassInfo; use SilverStripe\Core\Injector\Injectable; use SilverStripe\Core\Manifest\ClassLoader; +use SilverStripe\Core\Manifest\Module; +use SilverStripe\Core\Manifest\ModuleLoader; use SilverStripe\Dev\Debug; use SilverStripe\Control\Director; use ReflectionClass; @@ -172,7 +174,7 @@ class i18nTextCollector } // Write each module language file - foreach ($entitiesByModule as $module => $entities) { + foreach ($entitiesByModule as $moduleName => $entities) { // Skip empty translations if (empty($entities)) { continue; @@ -180,42 +182,11 @@ class i18nTextCollector // Clean sorting prior to writing ksort($entities); - $path = $this->baseSavePath . '/' . $module; - $this->getWriter()->write($entities, $this->defaultLocale, $path); + $module = ModuleLoader::instance()->getManifest()->getModule($moduleName); + $this->write($module, $entities); } } - /** - * Gets the list of modules in this installer - * - * @param string $directory Path to look in - * @return array List of modules as paths relative to base - */ - protected function getModules($directory) - { - // Include self as head module - $modules = array(); - - // Get all standard modules - foreach (glob($directory."/*", GLOB_ONLYDIR) as $path) { - // Check for _config - if (!is_file("$path/_config.php") && !is_dir("$path/_config")) { - continue; - } - $modules[] = basename($path); - } - - // Get all themes - foreach (glob($directory."/themes/*", GLOB_ONLYDIR) as $path) { - // Check for templates - if (is_dir("$path/templates")) { - $modules[] = 'themes/'.basename($path); - } - } - - return $modules; - } - /** * Extract all strings from modules and return these grouped by module name * @@ -237,7 +208,13 @@ class i18nTextCollector // Restrict modules we update to just the specified ones (if any passed) if (!empty($restrictToModules)) { - foreach (array_diff(array_keys($entitiesByModule), $restrictToModules) as $module) { + // Normalise module names + $modules = array_filter(array_map(function ($name) { + $module = ModuleLoader::instance()->getManifest()->getModule($name); + return $module ? $module->getName() : null; + }, $restrictToModules)); + // Remove modules + foreach (array_diff(array_keys($entitiesByModule), $modules) as $module) { unset($entitiesByModule[$module]); } } @@ -350,9 +327,12 @@ class i18nTextCollector protected function findModuleForClass($class) { if (ClassInfo::exists($class)) { - return ClassLoader::instance() + $module = ClassLoader::instance() ->getManifest() ->getOwnerModule($class); + if ($module) { + return $module->getName(); + } } // If we can't find a class, see if it needs to be fully qualified @@ -368,7 +348,8 @@ class i18nTextCollector // Find all modules for candidate classes $modules = array_unique(array_map(function ($class) { - return ClassLoader::instance()->getManifest()->getOwnerModule($class); + $module = ClassLoader::instance()->getManifest()->getOwnerModule($class); + return $module ? $module->getName() : null; }, $classes)); if (count($modules) === 1) { @@ -413,24 +394,32 @@ class i18nTextCollector { // A master string tables array (one mst per module) $entitiesByModule = array(); - $modules = $this->getModules($this->basePath); + $modules = ModuleLoader::instance()->getManifest()->getModules(); foreach ($modules as $module) { // we store the master string tables $processedEntities = $this->processModule($module); - if (isset($entitiesByModule[$module])) { - $entitiesByModule[$module] = array_merge_recursive($entitiesByModule[$module], $processedEntities); + $moduleName = $module->getName(); + if (isset($entitiesByModule[$moduleName])) { + $entitiesByModule[$moduleName] = array_merge_recursive( + $entitiesByModule[$moduleName], + $processedEntities + ); } else { - $entitiesByModule[$module] = $processedEntities; + $entitiesByModule[$moduleName] = $processedEntities; } // Extract all entities for "foreign" modules ('module' key in array form) // @see CMSMenu::provideI18nEntities for an example usage - foreach ($entitiesByModule[$module] as $fullName => $spec) { - $specModule = $module; + foreach ($entitiesByModule[$moduleName] as $fullName => $spec) { + $specModuleName = $moduleName; // Rewrite spec if module is specified if (is_array($spec) && isset($spec['module'])) { - $specModule = $spec['module']; + // Normalise name (in case non-composer name is specified) + $specModule = ModuleLoader::instance()->getManifest()->getModule($spec['module']); + if ($specModule) { + $specModuleName = $specModule->getName(); + } unset($spec['module']); // If only element is defalt, simplify @@ -440,24 +429,34 @@ class i18nTextCollector } // Remove from source module - if ($specModule !== $module) { - unset($entitiesByModule[$module][$fullName]); + if ($specModuleName !== $moduleName) { + unset($entitiesByModule[$moduleName][$fullName]); } // Write to target module - if (!isset($entitiesByModule[$specModule])) { - $entitiesByModule[$specModule] = []; + if (!isset($entitiesByModule[$specModuleName])) { + $entitiesByModule[$specModuleName] = []; } - $entitiesByModule[$specModule][$fullName] = $spec; + $entitiesByModule[$specModuleName][$fullName] = $spec; } } return $entitiesByModule; } - - public function write($module, $entities) + /** + * Write entities to a module + * + * @param Module $module + * @param array $entities + * @return $this + */ + public function write(Module $module, $entities) { - $this->getWriter()->write($entities, $this->defaultLocale, $this->baseSavePath . '/' . $module); + $this->getWriter()->write( + $entities, + $this->defaultLocale, + $this->baseSavePath . '/' . $module->getRelativePath() + ); return $this; } @@ -465,10 +464,10 @@ class i18nTextCollector * Builds a master string table from php and .ss template files for the module passed as the $module param * @see collectFromCode() and collectFromTemplate() * - * @param string $module A module's name or just 'themes/' + * @param Module $module Module instance * @return array An array of entities found in the files that comprise the module */ - protected function processModule($module) + protected function processModule(Module $module) { $entities = array(); @@ -481,15 +480,14 @@ class i18nTextCollector if ($extension === 'php') { $entities = array_merge( $entities, - $this->collectFromCode($content, $module), + $this->collectFromCode($content, $filePath, $module), $this->collectFromEntityProviders($filePath, $module) ); } elseif ($extension === 'ss') { // templates use their filename as a namespace - $namespace = basename($filePath); $entities = array_merge( $entities, - $this->collectFromTemplate($content, $module, $namespace) + $this->collectFromTemplate($content, $filePath, $module) ); } } @@ -503,25 +501,29 @@ class i18nTextCollector /** * Retrieves the list of files for this module * - * @param string $module + * @param Module $module Module instance * @return array List of files to parse */ - protected function getFileListForModule($module) + protected function getFileListForModule(Module $module) { - $modulePath = "{$this->basePath}/{$module}"; + $modulePath = $module->getPath(); // Search all .ss files in themes - if (stripos($module, 'themes/') === 0) { + if (stripos($module->getRelativePath(), 'themes/') === 0) { return $this->getFilesRecursive($modulePath, null, 'ss'); } - // If Framework or non-standard module structure, so we'll scan all subfolders - if ($module === FRAMEWORK_DIR || !is_dir("{$modulePath}/code")) { + // If non-standard module structure, search all root files + if (!is_dir("{$modulePath}/code") && !is_dir("{$modulePath}/src")) { return $this->getFilesRecursive($modulePath); } // Get code files - $files = $this->getFilesRecursive("{$modulePath}/code", null, 'php'); + if (is_dir("{$modulePath}/src")) { + $files = $this->getFilesRecursive("{$modulePath}/src", null, 'php'); + } else { + $files = $this->getFilesRecursive("{$modulePath}/code", null, 'php'); + } // Search for templates in this module if (is_dir("{$modulePath}/templates")) { @@ -538,12 +540,15 @@ class i18nTextCollector * Note: Translations without default values are omitted. * * @param string $content The text content of a parsed template-file - * @param string $module Module's name or 'themes'. Could also be a namespace - * Generated by templates includes. E.g. 'UploadField.ss' + * @param string $fileName Filename Optional filename + * @param Module $module Module being collected * @return array Map of localised keys to default values provided for this code */ - public function collectFromCode($content, $module) + public function collectFromCode($content, $fileName, Module $module) { + // Get namespace either from $fileName or $module fallback + $namespace = $fileName ? basename($fileName) : $module->getName(); + $entities = array(); $tokens = token_get_all(" $entity) { unset($entities[$key]); - $entities[$this->normalizeEntity($key, $module)] = $entity; + $entities[$this->normalizeEntity($key, $namespace)] = $entity; } ksort($entities); @@ -725,12 +730,15 @@ class i18nTextCollector * * @param string $content The text content of a parsed template-file * @param string $fileName The name of a template file when method is used in self-referencing mode - * @param string $module Module's name or 'themes' + * @param Module $module Module being collected * @param array $parsedFiles * @return array $entities An array of entities representing the extracted template function calls */ - public function collectFromTemplate($content, $fileName, $module, &$parsedFiles = array()) + public function collectFromTemplate($content, $fileName, Module $module, &$parsedFiles = array()) { + // Get namespace either from $fileName or $module fallback + $namespace = $fileName ? basename($fileName) : $module->getName(); + // use parser to extract <%t style translatable entities $entities = Parser::getTranslatables($content, $this->getWarnOnEmptyDefault()); @@ -738,13 +746,13 @@ class i18nTextCollector // Collect in actual template if (preg_match_all('/(_t\([^\)]*?\))/ms', $content, $matches)) { foreach ($matches[1] as $match) { - $entities = array_merge($entities, $this->collectFromCode($match, $module)); + $entities = array_merge($entities, $this->collectFromCode($match, $fileName, $module)); } } foreach ($entities as $entity => $spec) { unset($entities[$entity]); - $entities[$this->normalizeEntity($entity, $module)] = $spec; + $entities[$this->normalizeEntity($entity, $namespace)] = $spec; } ksort($entities); @@ -760,10 +768,10 @@ class i18nTextCollector * * @uses i18nEntityProvider * @param string $filePath - * @param string $module + * @param Module $module * @return array */ - public function collectFromEntityProviders($filePath, $module = null) + public function collectFromEntityProviders($filePath, Module $module = null) { $entities = array(); $classes = ClassInfo::classes_for_file($filePath); diff --git a/tests/php/Core/Manifest/ClassLoaderTest.php b/tests/php/Core/Manifest/ClassLoaderTest.php index 47fc326c2..7a2306f5c 100644 --- a/tests/php/Core/Manifest/ClassLoaderTest.php +++ b/tests/php/Core/Manifest/ClassLoaderTest.php @@ -12,8 +12,25 @@ use SilverStripe\Dev\SapphireTest; class ClassLoaderTest extends SapphireTest { - protected $base; - protected $manifest; + /** + * @var string + */ + protected $baseManifest1; + + /** + * @var string + */ + protected $baseManifest2; + + /** + * @var ClassManifest + */ + protected $testManifest1; + + /** + * @var ClassManifest + */ + protected $testManifest2; public function setUp() { @@ -21,8 +38,8 @@ class ClassLoaderTest extends SapphireTest $this->baseManifest1 = dirname(__FILE__) . '/fixtures/classmanifest'; $this->baseManifest2 = dirname(__FILE__) . '/fixtures/classmanifest_other'; - $this->testManifest1 = new ClassManifest($this->baseManifest1, false, true, false); - $this->testManifest2 = new ClassManifest($this->baseManifest2, false, true, false); + $this->testManifest1 = new ClassManifest($this->baseManifest1, false); + $this->testManifest2 = new ClassManifest($this->baseManifest2, false); } public function testExclusive() diff --git a/tests/php/Core/Manifest/ClassManifestTest.php b/tests/php/Core/Manifest/ClassManifestTest.php index 9c32844d0..58918f164 100644 --- a/tests/php/Core/Manifest/ClassManifestTest.php +++ b/tests/php/Core/Manifest/ClassManifestTest.php @@ -2,6 +2,7 @@ namespace SilverStripe\Core\Tests\Manifest; +use Exception; use SilverStripe\Core\Manifest\ClassManifest; use SilverStripe\Dev\SapphireTest; @@ -11,8 +12,19 @@ use SilverStripe\Dev\SapphireTest; class ClassManifestTest extends SapphireTest { + /** + * @var string + */ protected $base; + + /** + * @var ClassManifest + */ protected $manifest; + + /** + * @var ClassManifest + */ protected $manifestTests; public function setUp() @@ -20,8 +32,8 @@ class ClassManifestTest extends SapphireTest parent::setUp(); $this->base = dirname(__FILE__) . '/fixtures/classmanifest'; - $this->manifest = new ClassManifest($this->base, false, true, false); - $this->manifestTests = new ClassManifest($this->base, true, true, false); + $this->manifest = new ClassManifest($this->base, false); + $this->manifestTests = new ClassManifest($this->base, true); } public function testGetItemPath() @@ -125,23 +137,6 @@ class ClassManifestTest extends SapphireTest } } - public function testGetConfigs() - { - $expect = array("{$this->base}/module/_config.php"); - $this->assertEquals($expect, $this->manifest->getConfigs()); - $this->assertEquals($expect, $this->manifestTests->getConfigs()); - } - - public function testGetModules() - { - $expect = array( - "module" => "{$this->base}/module", - "moduleb" => "{$this->base}/moduleb" - ); - $this->assertEquals($expect, $this->manifest->getModules()); - $this->assertEquals($expect, $this->manifestTests->getModules()); - } - public function testTestManifestIncludesTestClasses() { $this->assertNotContains('testclassa', array_keys($this->manifest->getClasses())); @@ -156,11 +151,10 @@ class ClassManifestTest extends SapphireTest /** * Assert that ClassManifest throws an exception when it encounters two files * which contain classes with the same name - * - * @expectedException Exception */ public function testManifestWarnsAboutDuplicateClasses() { - $dummy = new ClassManifest(dirname(__FILE__) . '/fixtures/classmanifest_duplicates', false, true, false); + $this->setExpectedException(Exception::class); + new ClassManifest(dirname(__FILE__) . '/fixtures/classmanifest_duplicates', false); } } diff --git a/tests/php/Core/Manifest/ModuleManifestTest.php b/tests/php/Core/Manifest/ModuleManifestTest.php new file mode 100644 index 000000000..cf58c2686 --- /dev/null +++ b/tests/php/Core/Manifest/ModuleManifestTest.php @@ -0,0 +1,72 @@ +base = dirname(__FILE__) . '/fixtures/classmanifest'; + $this->manifest = new ModuleManifest($this->base, false); + } + + public function testGetModules() + { + $modules = $this->manifest->getModules(); + $this->assertEquals( + [ + 'module', + 'silverstripe/awesome-module', + ], + array_keys($modules) + ); + } + + public function testGetLegacyModule() + { + $module = $this->manifest->getModule('module'); + $this->assertNotEmpty($module); + $this->assertEquals('module', $module->getName()); + $this->assertEquals('module', $module->getShortName()); + $this->assertEquals('module', $module->getRelativePath()); + $this->assertEmpty($module->getComposerName()); + } + + public function testGetComposerModule() + { + // Get by installer-name (folder) + $moduleByShortName = $this->manifest->getModule('moduleb'); + $this->assertNotEmpty($moduleByShortName); + + // Can also get this by full composer name + $module = $this->manifest->getModule('silverstripe/awesome-module'); + $this->assertNotEmpty($module); + $this->assertEquals($moduleByShortName->getPath(), $module->getPath()); + + // correctly respects vendor + $this->assertEmpty($this->manifest->getModule('wrongvendor/awesome-module')); + $this->assertEmpty($this->manifest->getModule('wrongvendor/moduleb')); + + // Properties of module + $this->assertEquals('silverstripe/awesome-module', $module->getName()); + $this->assertEquals('silverstripe/awesome-module', $module->getComposerName()); + $this->assertEquals('moduleb', $module->getShortName()); + $this->assertEquals('moduleb', $module->getRelativePath()); + } +} diff --git a/tests/php/Core/Manifest/NamespacedClassManifestTest.php b/tests/php/Core/Manifest/NamespacedClassManifestTest.php index 613893066..f7dcff190 100644 --- a/tests/php/Core/Manifest/NamespacedClassManifestTest.php +++ b/tests/php/Core/Manifest/NamespacedClassManifestTest.php @@ -13,7 +13,9 @@ use ReflectionMethod; */ class NamespacedClassManifestTest extends SapphireTest { - + /** + * @var string + */ protected $base; /** @@ -26,7 +28,7 @@ class NamespacedClassManifestTest extends SapphireTest parent::setUp(); $this->base = dirname(__FILE__) . '/fixtures/namespaced_classmanifest'; - $this->manifest = new ClassManifest($this->base, false, true, false); + $this->manifest = new ClassManifest($this->base, false); ClassLoader::instance()->pushManifest($this->manifest, false); } @@ -40,7 +42,7 @@ class NamespacedClassManifestTest extends SapphireTest { $this->assertContains('SilverStripe\Framework\Tests\ClassI', ClassInfo::implementorsOf('SilverStripe\\Security\\PermissionProvider')); - //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 // including all core classes $method = new ReflectionMethod($this->manifest, 'coalesceDescendants'); @@ -149,19 +151,4 @@ class NamespacedClassManifestTest extends SapphireTest $this->assertEquals($impl, $this->manifest->getImplementorsOf($interface)); } } - - public function testGetConfigs() - { - $expect = array("{$this->base}/module/_config.php"); - $this->assertEquals($expect, $this->manifest->getConfigs()); - } - - public function testGetModules() - { - $expect = array( - "module" => "{$this->base}/module", - "moduleb" => "{$this->base}/moduleb" - ); - $this->assertEquals($expect, $this->manifest->getModules()); - } } diff --git a/tests/php/Core/Manifest/ThemeResourceLoaderTest.php b/tests/php/Core/Manifest/ThemeResourceLoaderTest.php index 3d604d535..d89102d42 100644 --- a/tests/php/Core/Manifest/ThemeResourceLoaderTest.php +++ b/tests/php/Core/Manifest/ThemeResourceLoaderTest.php @@ -11,7 +11,9 @@ use SilverStripe\Dev\SapphireTest; */ class ThemeResourceLoaderTest extends SapphireTest { - + /** + * @var string + */ private $base; /** @@ -34,7 +36,7 @@ class ThemeResourceLoaderTest extends SapphireTest // Fake project root $this->base = dirname(__FILE__) . '/fixtures/templatemanifest'; // New ThemeManifest for that root - $this->manifest = new ThemeManifest($this->base, 'myproject', false, true); + $this->manifest = new ThemeManifest($this->base, 'myproject', false); // New Loader for that root $this->loader = new ThemeResourceLoader($this->base); $this->loader->addSet('$default', $this->manifest); diff --git a/tests/php/Core/Manifest/fixtures/classmanifest/moduleb/_config/moduleb.txt b/tests/php/Core/Manifest/fixtures/classmanifest/moduleb/_config/moduleb.txt deleted file mode 100644 index e69de29bb..000000000 diff --git a/tests/php/Core/Manifest/fixtures/classmanifest/moduleb/_config/moduleb.yml b/tests/php/Core/Manifest/fixtures/classmanifest/moduleb/_config/moduleb.yml new file mode 100644 index 000000000..feca68d2d --- /dev/null +++ b/tests/php/Core/Manifest/fixtures/classmanifest/moduleb/_config/moduleb.yml @@ -0,0 +1 @@ +module: {} diff --git a/tests/php/Core/Manifest/fixtures/classmanifest/moduleb/composer.json b/tests/php/Core/Manifest/fixtures/classmanifest/moduleb/composer.json new file mode 100644 index 000000000..3555c4d20 --- /dev/null +++ b/tests/php/Core/Manifest/fixtures/classmanifest/moduleb/composer.json @@ -0,0 +1,8 @@ +{ + "name": "silverstripe/awesome-module", + "description": "dummy test module", + "require": {}, + "extra": { + "installer-name": "moduleb" + } +} diff --git a/tests/php/Dev/DeprecationTest.php b/tests/php/Dev/DeprecationTest.php index a0f1614fc..ab9b4396c 100644 --- a/tests/php/Dev/DeprecationTest.php +++ b/tests/php/Dev/DeprecationTest.php @@ -116,7 +116,7 @@ class DeprecationTest extends SapphireTest protected function callThatOriginatesFromFramework() { - $this->assertEquals(TestDeprecation::get_module(), basename(FRAMEWORK_PATH)); + $this->assertEquals('silverstripe/framework', TestDeprecation::get_module()->getName()); $this->assertNull(Deprecation::notice('2.0', 'Deprecation test passed')); } } diff --git a/tests/php/i18n/i18nTest/_fakewebroot/i18ntestmodule/_config.php b/tests/php/i18n/i18nTest/_fakewebroot/i18ntestmodule/_config.php index e69de29bb..b3d9bbc7f 100644 --- a/tests/php/i18n/i18nTest/_fakewebroot/i18ntestmodule/_config.php +++ b/tests/php/i18n/i18nTest/_fakewebroot/i18ntestmodule/_config.php @@ -0,0 +1 @@ +alternateBasePath = __DIR__ . $s . 'i18nTest' . $s . "_fakewebroot"; Director::config()->update('alternate_base_folder', $this->alternateBasePath); + // New module manifest + $moduleManifest = new ModuleManifest($this->alternateBasePath, false); + $this->pushModuleManifest($moduleManifest); + // Replace old template loader with new one with alternate base path $this->oldThemeResourceLoader = ThemeResourceLoader::instance(); ThemeResourceLoader::set_instance($loader = new ThemeResourceLoader($this->alternateBasePath)); $loader->addSet( '$default', - new ThemeManifest( - $this->alternateBasePath, - project(), - false, - true - ) + new ThemeManifest($this->alternateBasePath, project(), false) ); SSViewer::set_themes([ @@ -94,7 +102,7 @@ trait i18nTestManifest i18n::set_locale('en_US'); // Set new manifest against the root - $classManifest = new ClassManifest($this->alternateBasePath, true, true, false); + $classManifest = new ClassManifest($this->alternateBasePath, true); $this->pushManifest($classManifest); // Setup uncached translator @@ -131,6 +139,12 @@ trait i18nTestManifest ClassLoader::instance()->pushManifest($manifest); } + protected function pushModuleManifest(ModuleManifest $manifest) + { + $this->moduleManifests++; + ModuleLoader::instance()->pushManifest($manifest); + } + /** * Pop off all extra manifests */ @@ -141,5 +155,9 @@ trait i18nTestManifest ClassLoader::instance()->popManifest(); $this->manifests--; } + while ($this->moduleManifests > 0) { + ModuleLoader::instance()->popManifest(); + $this->moduleManifests--; + } } } diff --git a/tests/php/i18n/i18nTextCollectorTest.php b/tests/php/i18n/i18nTextCollectorTest.php index fb7aca39b..d2a2ba3a3 100644 --- a/tests/php/i18n/i18nTextCollectorTest.php +++ b/tests/php/i18n/i18nTextCollectorTest.php @@ -4,12 +4,12 @@ namespace SilverStripe\i18n\Tests; use PHPUnit_Framework_Error_Notice; use SilverStripe\Assets\Filesystem; +use SilverStripe\Core\Manifest\ModuleLoader; use SilverStripe\Dev\SapphireTest; use SilverStripe\i18n\i18n; use SilverStripe\i18n\TextCollection\i18nTextCollector; use SilverStripe\i18n\Messages\YamlWriter; use SilverStripe\i18n\Tests\i18nTextCollectorTest\Collector; -use SilverStripe\View\SSViewer; class i18nTextCollectorTest extends SapphireTest { @@ -42,6 +42,7 @@ class i18nTextCollectorTest extends SapphireTest public function testConcatenationInEntityValues() { $c = i18nTextCollector::create(); + $module = ModuleLoader::instance()->getManifest()->getModule('i18ntestmodule'); $php = << "Line \"4\" and Line 5" ), - $c->collectFromCode($php, 'mymodule') + $c->collectFromCode($php, null, $module) ); } @@ -74,6 +75,7 @@ PHP; { $c = i18nTextCollector::create(); $c->setWarnOnEmptyDefault(false); + $mymodule = ModuleLoader::instance()->getManifest()->getModule('i18ntestmodule'); $html = << @@ -85,7 +87,7 @@ PHP; <%t i18nTestModule.INJECTIONS_4 name=\$absoluteBaseURL greeting=\$get_locale goodbye="global calls" %> <%t i18nTestModule.INJECTIONS_9 "An item|{count} items" is "Test Pluralisation" count=4 %> SS; - $c->collectFromTemplate($html, 'mymodule', 'Test'); + $c->collectFromTemplate($html, null, $mymodule); $this->assertEquals( [ @@ -103,7 +105,7 @@ SS; 'comment' => 'Test Pluralisation' ], ], - $c->collectFromTemplate($html, 'mymodule', 'Test') + $c->collectFromTemplate($html, null, $mymodule) ); // Test warning is raised on empty default @@ -112,19 +114,20 @@ SS; PHPUnit_Framework_Error_Notice::class, 'Missing localisation default for key i18nTestModule.INJECTIONS_3' ); - $c->collectFromTemplate($html, 'mymodule', 'Test'); + $c->collectFromTemplate($html, null, $mymodule); } public function testCollectFromTemplateSimple() { $c = i18nTextCollector::create(); + $mymodule = ModuleLoader::instance()->getManifest()->getModule('i18ntestmodule'); $html = << SS; $this->assertEquals( [ 'Test.SINGLEQUOTE' => 'Single Quote' ], - $c->collectFromTemplate($html, 'mymodule', 'Test') + $c->collectFromTemplate($html, null, $mymodule) ); $html = <<assertEquals( [ 'Test.DOUBLEQUOTE' => "Double Quote and Spaces" ], - $c->collectFromTemplate($html, 'mymodule', 'Test') + $c->collectFromTemplate($html, null, $mymodule) ); $html = <<assertEquals( [ 'Test.NOSEMICOLON' => "No Semicolon" ], - $c->collectFromTemplate($html, 'mymodule', 'Test') + $c->collectFromTemplate($html, null, $mymodule) ); } @@ -148,6 +151,7 @@ SS; { $c = i18nTextCollector::create(); $c->setWarnOnEmptyDefault(false); + $mymodule = ModuleLoader::instance()->getManifest()->getModule('i18ntestmodule'); $html = <<assertEquals( [ 'Test.NEWLINES' => "New Lines" ], - $c->collectFromTemplate($html, 'mymodule', 'Test') + $c->collectFromTemplate($html, 'Test', $mymodule) ); $html = << ' Prio and Value with "Double Quotes"', 'comment' => 'Comment with "Double Quotes"', ]], - $c->collectFromTemplate($html, 'mymodule', 'Test') + $c->collectFromTemplate($html, 'Test', $mymodule) ); $html = << " Prio and Value with 'Single Quotes'", 'comment' => "Comment with 'Single Quotes'", ]], - $c->collectFromTemplate($html, 'mymodule', 'Test') + $c->collectFromTemplate($html, 'Test', $mymodule) ); // Test empty @@ -197,7 +201,7 @@ SS; SS; $this->assertEquals( [], - $c->collectFromTemplate($html, 'mymodule', 'Test') + $c->collectFromTemplate($html, null, $mymodule) ); // Test warning is raised on empty default @@ -206,20 +210,21 @@ SS; PHPUnit_Framework_Error_Notice::class, 'Missing localisation default for key Test.PRIOANDCOMMENT' ); - $c->collectFromTemplate($html, 'mymodule', 'Test'); + $c->collectFromTemplate($html, 'Test', $mymodule); } public function testCollectFromCodeSimple() { $c = i18nTextCollector::create(); + $mymodule = ModuleLoader::instance()->getManifest()->getModule('i18ntestmodule'); $php = <<assertEquals( [ 'Test.SINGLEQUOTE' => 'Single Quote' ], - $c->collectFromCode($php, 'mymodule') + $c->collectFromCode($php, null, $mymodule) ); $php = <<assertEquals( [ 'Test.DOUBLEQUOTE' => "Double Quote and Spaces" ], - $c->collectFromCode($php, 'mymodule') + $c->collectFromCode($php, null, $mymodule) ); } public function testCollectFromCodeAdvanced() { $c = i18nTextCollector::create(); + $mymodule = ModuleLoader::instance()->getManifest()->getModule('i18ntestmodule'); $php = <<assertEquals( [ 'Test.NEWLINES' => "New Lines" ], - $c->collectFromCode($php, 'mymodule') + $c->collectFromCode($php, null, $mymodule) ); $php = << 'Comment with "Double Quotes"', ] ], - $c->collectFromCode($php, 'mymodule') + $c->collectFromCode($php, null, $mymodule) ); $php = << " Value with 'Single Quotes'", 'comment' => "Comment with 'Single Quotes'" ] ], - $c->collectFromCode($php, 'mymodule') + $c->collectFromCode($php, null, $mymodule) ); $php = <<assertEquals( [ 'Test.PRIOANDCOMMENT' => "Value with 'Escaped Single Quotes'" ], - $c->collectFromCode($php, 'mymodule') + $c->collectFromCode($php, null, $mymodule) ); $php = <<assertEquals( [ 'Test.PRIOANDCOMMENT' => "Doublequoted Value with 'Unescaped Single Quotes'"], - $c->collectFromCode($php, 'mymodule') + $c->collectFromCode($php, null, $mymodule) ); } public function testCollectFromCodeNamespace() { $c = i18nTextCollector::create(); - + $mymodule = ModuleLoader::instance()->getManifest()->getModule('i18ntestmodule'); $php = <<assertEquals( [ 'SilverStripe\\Framework\\Core\\MyClass.NEWLINES' => "New Lines" ], - $c->collectFromCode($php, 'mymodule') + $c->collectFromCode($php, null, $mymodule) ); } @@ -332,6 +338,7 @@ PHP; public function testNewlinesInEntityValues() { $c = i18nTextCollector::create(); + $mymodule = ModuleLoader::instance()->getManifest()->getModule('i18ntestmodule'); $php = <<assertEquals( [ 'Test.NEWLINESINGLEQUOTE' => "Line 1{$eol}Line 2" ], - $c->collectFromCode($php, 'mymodule') + $c->collectFromCode($php, null, $mymodule) ); $php = <<assertEquals( [ 'Test.NEWLINEDOUBLEQUOTE' => "Line 1{$eol}Line 2" ], - $c->collectFromCode($php, 'mymodule') + $c->collectFromCode($php, null, $mymodule) ); } @@ -367,6 +374,7 @@ PHP; { $c = i18nTextCollector::create(); $c->setWarnOnEmptyDefault(false); // Disable warnings for tests + $mymodule = ModuleLoader::instance()->getManifest()->getModule('i18ntestmodule'); $php = <<"Cat", "greeting"=>"meow", "goodbye"=> _t('i18nTestModule.INJECTIONS9', "An item|{count} items", ['count' => 4], "Test Pluralisation"); PHP; - $collectedTranslatables = $c->collectFromCode($php, 'mymodule'); + $collectedTranslatables = $c->collectFromCode($php, null, $mymodule); $expectedArray = [ 'i18nTestModule.INJECTIONS2' => "Hello {name} {greeting}. But it is late, {goodbye}", @@ -416,12 +424,13 @@ PHP; _t('i18nTestModule.INJECTIONS4', array("name"=>"Cat", "greeting"=>"meow", "goodbye"=>"meow")); PHP; $c->setWarnOnEmptyDefault(true); - $c->collectFromCode($php, 'mymodule'); + $c->collectFromCode($php, null, $mymodule); } public function testUncollectableCode() { $c = i18nTextCollector::create(); + $mymodule = ModuleLoader::instance()->getManifest()->getModule('i18ntestmodule'); $php = <<collectFromCode($php, 'mymodule'); + $collectedTranslatables = $c->collectFromCode($php, null, $mymodule); // Only one item is collectable $expectedArray = [ 'Collectable.KEY4' => 'Default' ]; @@ -441,20 +450,21 @@ PHP; { $c = i18nTextCollector::create(); $c->setWarnOnEmptyDefault(false); // Disable warnings for tests + $mymodule = ModuleLoader::instance()->getManifest()->getModule('i18ntestmodule'); $templateFilePath = $this->alternateBasePath . '/i18ntestmodule/templates/Layout/i18nTestModule.ss'; $html = file_get_contents($templateFilePath); - $matches = $c->collectFromTemplate($html, 'mymodule', 'RandomNamespace'); + $matches = $c->collectFromTemplate($html, $templateFilePath, $mymodule); - $this->assertArrayHasKey('RandomNamespace.LAYOUTTEMPLATENONAMESPACE', $matches); + $this->assertArrayHasKey('i18nTestModule.ss.LAYOUTTEMPLATENONAMESPACE', $matches); $this->assertEquals( 'Layout Template no namespace', - $matches['RandomNamespace.LAYOUTTEMPLATENONAMESPACE'] + $matches['i18nTestModule.ss.LAYOUTTEMPLATENONAMESPACE'] ); - $this->assertArrayHasKey('RandomNamespace.SPRINTFNONAMESPACE', $matches); + $this->assertArrayHasKey('i18nTestModule.ss.SPRINTFNONAMESPACE', $matches); $this->assertEquals( 'My replacement no namespace: %s', - $matches['RandomNamespace.SPRINTFNONAMESPACE'] + $matches['i18nTestModule.ss.SPRINTFNONAMESPACE'] ); $this->assertArrayHasKey('i18nTestModule.LAYOUTTEMPLATE', $matches); $this->assertEquals( @@ -474,44 +484,6 @@ PHP; $this->assertArrayNotHasKey('i18nTestModuleInclude.ss.SPRINTFINCLUDENONAMESPACE', $matches); } - public function testCollectFromThemesTemplates() - { - $c = i18nTextCollector::create(); - SSViewer::set_themes([ 'testtheme1' ]); - - // Collect from layout - $layoutFilePath = $this->alternateBasePath . '/themes/testtheme1/templates/Layout/i18nTestTheme1.ss'; - $layoutHTML = file_get_contents($layoutFilePath); - $layoutMatches = $c->collectFromTemplate($layoutHTML, 'themes/testtheme1', 'i18nTestTheme1.ss'); - - // all entities from i18nTestTheme1.ss - $this->assertEquals( - [ - 'i18nTestTheme1.LAYOUTTEMPLATE' => 'Theme1 Layout Template', - 'i18nTestTheme1.SPRINTFNAMESPACE' => 'Theme1 My replacement: %s', - 'i18nTestTheme1.ss.LAYOUTTEMPLATENONAMESPACE' => 'Theme1 Layout Template no namespace', - 'i18nTestTheme1.ss.SPRINTFNONAMESPACE' => 'Theme1 My replacement no namespace: %s', - ], - $layoutMatches - ); - - // Collect from include - $includeFilePath = $this->alternateBasePath . '/themes/testtheme1/templates/Includes/i18nTestTheme1Include.ss'; - $includeHTML = file_get_contents($includeFilePath); - $includeMatches = $c->collectFromTemplate($includeHTML, 'themes/testtheme1', 'i18nTestTheme1Include.ss'); - - // all entities from i18nTestTheme1Include.ss - $this->assertEquals( - [ - 'i18nTestTheme1Include.SPRINTFINCLUDENAMESPACE' => 'Theme1 My include replacement: %s', - 'i18nTestTheme1Include.WITHNAMESPACE' => 'Theme1 Include Entity with Namespace', - 'i18nTestTheme1Include.ss.NONAMESPACE' => 'Theme1 Include Entity without Namespace', - 'i18nTestTheme1Include.ss.SPRINTFINCLUDENONAMESPACE' => 'Theme1 My include replacement no namespace: %s' - ], - $includeMatches - ); - } - public function testCollectMergesWithExisting() { $c = i18nTextCollector::create(); @@ -608,63 +580,6 @@ PHP; " MAINTEMPLATE: 'Main Template Other Module'\n", $otherModuleLangFileContent ); - - // testtheme1 - $theme1LangFile = "{$this->alternateBaseSavePath}/themes/testtheme1/lang/" . $c->getDefaultLocale() . '.yml'; - $this->assertTrue( - file_exists($theme1LangFile), - 'Master theme language file can be written to themes/testtheme1 /lang folder' - ); - $theme1LangFileContent = file_get_contents($theme1LangFile); - $this->assertContains( - " MAINTEMPLATE: 'Theme1 Main Template'\n", - $theme1LangFileContent - ); - $this->assertContains( - " LAYOUTTEMPLATE: 'Theme1 Layout Template'\n", - $theme1LangFileContent - ); - $this->assertContains( - " SPRINTFNAMESPACE: 'Theme1 My replacement: %s'\n", - $theme1LangFileContent - ); - $this->assertContains( - " LAYOUTTEMPLATENONAMESPACE: 'Theme1 Layout Template no namespace'\n", - $theme1LangFileContent - ); - $this->assertContains( - " SPRINTFNONAMESPACE: 'Theme1 My replacement no namespace: %s'\n", - $theme1LangFileContent - ); - - $this->assertContains( - " SPRINTFINCLUDENAMESPACE: 'Theme1 My include replacement: %s'\n", - $theme1LangFileContent - ); - $this->assertContains( - " WITHNAMESPACE: 'Theme1 Include Entity with Namespace'\n", - $theme1LangFileContent - ); - $this->assertContains( - " NONAMESPACE: 'Theme1 Include Entity without Namespace'\n", - $theme1LangFileContent - ); - $this->assertContains( - " SPRINTFINCLUDENONAMESPACE: 'Theme1 My include replacement no namespace: %s'\n", - $theme1LangFileContent - ); - - // testtheme2 - $theme2LangFile = "{$this->alternateBaseSavePath}/themes/testtheme2/lang/" . $c->getDefaultLocale() . '.yml'; - $this->assertTrue( - file_exists($theme2LangFile), - 'Master theme language file can be written to themes/testtheme2 /lang folder' - ); - $theme2LangFileContent = file_get_contents($theme2LangFile); - $this->assertContains( - " MAINTEMPLATE: 'Theme2 Main Template'\n", - $theme2LangFileContent - ); } public function testCollectFromEntityProvidersInCustomObject() @@ -793,16 +708,14 @@ PHP; public function testModuleDetection() { $collector = new Collector(); - $modules = $collector->getModules_Test($this->alternateBasePath); + $modules = ModuleLoader::instance()->getManifest()->getModules(); $this->assertEquals( array( 'i18nnonstandardmodule', - 'i18nothermodule', 'i18ntestmodule', - 'themes/testtheme1', - 'themes/testtheme2' + 'i18nothermodule' ), - $modules + array_keys($modules) ); $this->assertEquals('i18ntestmodule', $collector->findModuleForClass_Test('i18nTestNamespacedClass')); @@ -853,22 +766,5 @@ PHP; $this->assertArrayHasKey("{$otherRoot}/code/i18nProviderClass.php", $otherFiles); $this->assertArrayHasKey("{$otherRoot}/code/i18nTestModuleDecorator.php", $otherFiles); $this->assertArrayHasKey("{$otherRoot}/templates/i18nOtherModule.ss", $otherFiles); - - // Themes should detect all ss files only - $theme1Files = $collector->getFileListForModule_Test('themes/testtheme1'); - $theme1Root = $this->alternateBasePath . '/themes/testtheme1/templates'; - $this->assertEquals(3, count($theme1Files)); - // Find only ss files - $this->assertArrayHasKey("{$theme1Root}/Includes/i18nTestTheme1Include.ss", $theme1Files); - $this->assertArrayHasKey("{$theme1Root}/Layout/i18nTestTheme1.ss", $theme1Files); - $this->assertArrayHasKey("{$theme1Root}/i18nTestTheme1Main.ss", $theme1Files); - - // Only 1 file here - $theme2Files = $collector->getFileListForModule_Test('themes/testtheme2'); - $this->assertEquals(1, count($theme2Files)); - $this->assertArrayHasKey( - $this->alternateBasePath . '/themes/testtheme2/templates/i18nTestTheme2.ss', - $theme2Files - ); } } diff --git a/tests/php/i18n/i18nTextCollectorTest/Collector.php b/tests/php/i18n/i18nTextCollectorTest/Collector.php index cd33be414..acb721cba 100644 --- a/tests/php/i18n/i18nTextCollectorTest/Collector.php +++ b/tests/php/i18n/i18nTextCollectorTest/Collector.php @@ -2,6 +2,8 @@ namespace SilverStripe\i18n\Tests\i18nTextCollectorTest; +use SilverStripe\Core\Manifest\Module; +use SilverStripe\Core\Manifest\ModuleLoader; use SilverStripe\Dev\TestOnly; use SilverStripe\i18n\TextCollection\i18nTextCollector; @@ -10,18 +12,17 @@ use SilverStripe\i18n\TextCollection\i18nTextCollector; */ class Collector extends i18nTextCollector implements TestOnly { - public function getModules_Test($directory) - { - return $this->getModules($directory); - } - public function resolveDuplicateConflicts_Test($entitiesByModule) { return $this->resolveDuplicateConflicts($entitiesByModule); } - public function getFileListForModule_Test($module) + public function getFileListForModule_Test($modulename) { + $module = ModuleLoader::instance()->getManifest()->getModule($modulename); + if (!$module) { + throw new \BadMethodCallException("No module named {$modulename}"); + } return $this->getFileListForModule($module); }