base); $name = $module->getName(); // Save if not already added if (empty($this->modules[$name])) { $this->modules[$name] = $module; return; } // Validate duplicate module $path = $module->getPath(); $otherPath = $this->modules[$name]->getPath(); if ($otherPath !== $path) { throw new LogicException( "Module {$name} is in two places - {$path} and {$otherPath}" ); } } /** * Returns true if the passed module exists * * @param string $name Either full composer name or short name * @return bool */ public function moduleExists($name) { $module = $this->getModule($name); return !empty($module); } /** * Constructs and initialises a new configuration object, either loading * from the cache or re-scanning for classes. * * @param string $base The project base path. * @param bool $includeTests * @param bool $forceRegen Force the manifest to be regenerated. */ public function __construct($base, $includeTests = false, $forceRegen = false) { $this->base = $base; $this->cacheKey = sha1($base).'_modules'; $this->includeTests = $includeTests; $this->cache = $this->getCache($includeTests); // Unless we're forcing regen, try loading from cache if (!$forceRegen) { $this->modules = $this->cache->load($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. */ public function activateConfig() { foreach ($this->getModules() as $module) { $module->activate(); } } /** * Completely regenerates the manifest file. Scans through finding all php _config.php and yaml _config/*.ya?ml * files,parses the yaml files into fragments, sorts them and figures out what values need to be checked to pick * the correct variant. * * Does _not_ build the actual variant * * @param bool $includeTests * @param bool $cache Cache the result. */ public function regenerate($includeTests = false, $cache = true) { $this->modules = []; $finder = new ManifestFileFinder(); $finder->setOptions(array( 'min_depth' => 0, 'name_regex' => '/(^|[\/\\\\])_config.php$/', 'ignore_tests' => !$includeTests, 'file_callback' => array($this, 'addSourceConfigFile'), // Cannot be max_depth: 1 due to "/framework/admin/_config.php" 'max_depth' => 2 )); $finder->find($this->base); $finder = new ManifestFileFinder(); $finder->setOptions(array( 'name_regex' => '/\.ya?ml$/', 'ignore_tests' => !$includeTests, 'file_callback' => array($this, 'addYAMLConfigFile'), 'max_depth' => 2 )); $finder->find($this->base); if ($cache) { $this->cache->save($this->modules, $this->cacheKey); } } /** * Record finding of _config.php file * * @param string $basename * @param string $pathname * @param int $depth */ public function addSourceConfigFile($basename, $pathname, $depth) { $this->addModule(dirname($pathname)); } /** * Handle lookup of _config/*.yml file * * @param string $basename * @param string $pathname * @param int $depth */ public function addYAMLConfigFile($basename, $pathname, $depth) { if (preg_match('{/([^/]+)/_config/}', $pathname, $match)) { $this->addModule(dirname(dirname($pathname))); } } /** * Get module by name * * @param string $name * @return Module */ public function getModule($name) { // Optimised find if (isset($this->modules[$name])) { return $this->modules[$name]; } // Fall back to lookup by shortname if (!strstr($name, '/')) { foreach ($this->modules as $module) { if (strcasecmp($module->getShortName(), $name) === 0) { return $module; } } } return null; } /** * Get modules found * * @return Module[] */ public function getModules() { return $this->modules; } }