From fc2c7731620399864043f282226dd98fa6d7ce77 Mon Sep 17 00:00:00 2001 From: Demian Katz <demian.katz@villanova.edu> Date: Wed, 7 Feb 2018 09:30:40 -0500 Subject: [PATCH] Add extendclass generator to replace extendservice. --- module/VuFindConsole/Module.php | 1 + module/VuFindConsole/config/module.config.php | 1 + .../Controller/GenerateController.php | 36 ++++++ .../Generator/GeneratorTools.php | 108 +++++++++++++++++- 4 files changed, 144 insertions(+), 2 deletions(-) diff --git a/module/VuFindConsole/Module.php b/module/VuFindConsole/Module.php index 09d67f3c707..6c4b4c7fa03 100644 --- a/module/VuFindConsole/Module.php +++ b/module/VuFindConsole/Module.php @@ -99,6 +99,7 @@ class Module implements \Zend\ModuleManager\Feature\ConsoleUsageProviderInterfac return [ 'compile theme' => 'Flatten a theme hierarchy for improved performance', 'generate dynamicroute' => 'Add a dynamic route', + 'generate extendclass' => 'Subclass a service, w/ lookup by class name', 'generate extendservice' => 'Override a service with a new child class', 'generate nontabrecordaction' => 'Add routes for non-tab record action', 'generate recordroute' => 'Add a record route', diff --git a/module/VuFindConsole/config/module.config.php b/module/VuFindConsole/config/module.config.php index 2a8482527b4..6debc0e4420 100644 --- a/module/VuFindConsole/config/module.config.php +++ b/module/VuFindConsole/config/module.config.php @@ -52,6 +52,7 @@ $config = [ $routes = [ 'compile/theme' => 'compile theme [--force] [<source>] [<target>]', 'generate/dynamicroute' => 'generate dynamicroute [<name>] [<newController>] [<newAction>] [<module>]', + 'generate/extendclass' => 'generate extendclass [<class>] [<target>]', 'generate/extendservice' => 'generate extendservice [<source>] [<target>]', 'generate/nontabrecordaction' => 'generate nontabrecordaction [<newAction>] [<module>]', 'generate/recordroute' => 'generate recordroute [<base>] [<newController>] [<module>]', diff --git a/module/VuFindConsole/src/VuFindConsole/Controller/GenerateController.php b/module/VuFindConsole/src/VuFindConsole/Controller/GenerateController.php index d4945d27f97..aff4b6f8979 100644 --- a/module/VuFindConsole/src/VuFindConsole/Controller/GenerateController.php +++ b/module/VuFindConsole/src/VuFindConsole/Controller/GenerateController.php @@ -87,6 +87,42 @@ class GenerateController extends AbstractBase return $this->getSuccessResponse(); } + /** + * Extend an existing class + * + * @return \Zend\Console\Response + */ + public function extendclassAction() + { + // Display help message if parameters missing: + $request = $this->getRequest(); + $class = $request->getParam('class'); + $target = $request->getParam('target'); + if (empty($class) || empty($target)) { + Console::writeLine( + 'Usage: ' . $request->getScriptName() . ' generate extendclass' + . ' [class_name] [target_module]' + ); + Console::writeLine( + "\tclass_name - the name of the class you wish to extend" + ); + Console::writeLine( + "\ttarget_module - the module where the new class will be generated" + ); + return $this->getFailureResponse(); + } + + try { + $this->getGeneratorTools() + ->extendClass($this->serviceLocator, $class, $target); + } catch (\Exception $e) { + Console::writeLine($e->getMessage()); + return $this->getFailureResponse(); + } + + return $this->getSuccessResponse(); + } + /** * Extend an existing service * diff --git a/module/VuFindConsole/src/VuFindConsole/Generator/GeneratorTools.php b/module/VuFindConsole/src/VuFindConsole/Generator/GeneratorTools.php index 35bc069300a..9c22031c563 100644 --- a/module/VuFindConsole/src/VuFindConsole/Generator/GeneratorTools.php +++ b/module/VuFindConsole/src/VuFindConsole/Generator/GeneratorTools.php @@ -27,6 +27,7 @@ */ namespace VuFindConsole\Generator; +use Interop\Container\ContainerInterface; use Zend\Code\Generator\ClassGenerator; use Zend\Code\Generator\FileGenerator; use Zend\Code\Generator\MethodGenerator; @@ -61,6 +62,106 @@ class GeneratorTools $this->config = $config; } + /** + * Extend a class defined somewhere in the service manager or its child + * plugin managers. + * + * @param ContainerInterface $container Service manager + * @param string $class Class name to extend + * @param string $target Target module in which to create new + * service + * + * @return bool + * @throws \Exception + */ + public function extendClass(ContainerInterface $container, $class, $target) + { + // Set things up differently depending on whether this is a top-level + // service or a class in a plugin manager. + if ($container->has($class)) { + $factory = $this->getFactoryFromContainer($container, $class); + $configPath = ['service_manager']; + } else { + $pm = $this->getPluginManagerContainingClass($container, $class); + $apmFactory = new \VuFind\ServiceManager\AbstractPluginManagerFactory(); + $pmKey = $apmFactory->getConfigKey(get_class($pm)); + $factory = $this->getFactoryFromContainer($pm, $class); + $configPath = ['vufind', 'plugin_managers', $pmKey]; + } + + // No factory found? Throw an error! + if (!$factory) { + throw new \Exception('Could not find factory for ' . $class); + } + + // Create the custom subclass. + $newClass = $this->createSubclassInModule($class, $target); + + // Finalize the local module configuration -- create a factory for the + // new class, and set up the new class as an alias for the old class. + $factoryPath = array_merge($configPath, ['factories', $newClass]); + $this->writeNewConfig($factoryPath, $factory, $target); + $aliasPath = array_merge($configPath, ['aliases', $class]); + // Don't back up the config twice -- the first backup from the previous + // write operation is sufficient. + $this->writeNewConfig($aliasPath, $newClass, $target, false); + + return true; + } + + /** + * Get a list of factories in the provided container. + * + * @param ContainerInterface $container Container to inspect + * + * @return array + */ + protected function getAllFactoriesFromContainer(ContainerInterface $container) + { + // There is no "getFactories" method, so we need to use reflection: + $reflectionProperty = new \ReflectionProperty($container, 'factories'); + $reflectionProperty->setAccessible(true); + return $reflectionProperty->getValue($container); + } + + /** + * Get a factory from the provided container (or null if undefined). + * + * @param ContainerInterface $container Container to inspect + * @param string $class Class whose factory we want + * + * @return string + */ + protected function getFactoryFromContainer(ContainerInterface $container, $class) + { + $factories = $this->getAllFactoriesFromContainer($container); + return isset($factories[$class]) ? $factories[$class] : null; + } + + /** + * Search all plugin managers for one containing the requested class (or return + * null if none found). + * + * @param ContainerInterface $container Service manager + * @param string $class Class to search for + * + * @return ContainerInterface + */ + protected function getPluginManagerContainingClass(ContainerInterface $container, + $class + ) { + $factories = $this->getAllFactoriesFromContainer($container); + foreach (array_keys($factories) as $service) { + if (substr($service, -13) == 'PluginManager') { + $pm = $container->get($service); + if (null !== $this->getFactoryFromContainer($pm, $class)) { + return $pm; + } + } + } + return null; + } + /** * Extend a service defined in module.config.php. * @@ -376,15 +477,18 @@ class GeneratorTools * @param array $path Representation of path in config array * @param string $setting New setting to write into config * @param string $module Module in which to write the configuration + * @param bool $backup Should we back up the existing config? * * @return void * @throws \Exception */ - protected function writeNewConfig($path, $setting, $module) + protected function writeNewConfig($path, $setting, $module, $backup = true) { // Create backup of configuration $configPath = $this->getModuleConfigPath($module); - $this->backUpFile($configPath); + if ($backup) { + $this->backUpFile($configPath); + } $config = include $configPath; $current = & $config; -- GitLab