diff --git a/module/VuFindConsole/Module.php b/module/VuFindConsole/Module.php index bbcbf4f931733ae4d14e43a50783c623719c3bcd..b0462b1990aad2d3610c1658f30929a5d936cd2e 100644 --- a/module/VuFindConsole/Module.php +++ b/module/VuFindConsole/Module.php @@ -104,6 +104,7 @@ class Module implements \Zend\ModuleManager\Feature\ConsoleUsageProviderInterfac '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 plugin' => 'Create a new plugin class', 'generate recordroute' => 'Add a record route', 'generate staticroute' => 'Add a static route', 'generate theme' => 'Create and configure a new theme', diff --git a/module/VuFindConsole/config/module.config.php b/module/VuFindConsole/config/module.config.php index bdadb5389c7d8d86c9fe590e9cffd7c5d693e39e..8289573f769a73ae58b772e816f306f99289fa60 100644 --- a/module/VuFindConsole/config/module.config.php +++ b/module/VuFindConsole/config/module.config.php @@ -55,6 +55,7 @@ $routes = [ 'generate/extendclass' => 'generate extendclass [--extendfactory] [<class>] [<target>]', 'generate/extendservice' => 'generate extendservice [<source>] [<target>]', 'generate/nontabrecordaction' => 'generate nontabrecordaction [<newAction>] [<module>]', + 'generate/plugin' => 'generate plugin [<class>] [<factory>]', 'generate/recordroute' => 'generate recordroute [<base>] [<newController>] [<module>]', 'generate/staticroute' => 'generate staticroute [<name>] [<module>]', 'generate/theme' => 'generate theme [<themename>]', diff --git a/module/VuFindConsole/src/VuFindConsole/Controller/GenerateController.php b/module/VuFindConsole/src/VuFindConsole/Controller/GenerateController.php index 01252e446f5e7e0dd1a2122af42365468bb82b2f..aa190de726d9ddc2b7ec136a8d31f7df1215b6bf 100644 --- a/module/VuFindConsole/src/VuFindConsole/Controller/GenerateController.php +++ b/module/VuFindConsole/src/VuFindConsole/Controller/GenerateController.php @@ -222,6 +222,43 @@ class GenerateController extends AbstractBase return $this->getSuccessResponse(); } + /** + * Create a new plugin class + * + * @return \Zend\Console\Response + */ + public function pluginAction() + { + // Display help message if parameters missing: + $request = $this->getRequest(); + $class = $request->getParam('class'); + $factory = $request->getParam('factory'); + + if (empty($class)) { + Console::writeLine( + 'Usage: ' . $request->getScriptName() . ' generate plugin' + . ' [class_name] [factory]' + ); + Console::writeLine( + "\tclass_name - the name of the class you wish to create" + ); + Console::writeLine( + "\tfactory - an existing factory to use (omit to generate a new one)" + ); + return $this->getFailureResponse(); + } + + try { + $this->getGeneratorTools() + ->createPlugin($this->serviceLocator, $class, $factory); + } catch (\Exception $e) { + Console::writeLine($e->getMessage()); + return $this->getFailureResponse(); + } + + return $this->getSuccessResponse(); + } + /** * Add a new record route definition * diff --git a/module/VuFindConsole/src/VuFindConsole/Generator/GeneratorTools.php b/module/VuFindConsole/src/VuFindConsole/Generator/GeneratorTools.php index 82732035c29dadd84ec704ecc7e43c342fca06e4..615112fb4e4268bdd5292b7afad994724c63715d 100644 --- a/module/VuFindConsole/src/VuFindConsole/Generator/GeneratorTools.php +++ b/module/VuFindConsole/src/VuFindConsole/Generator/GeneratorTools.php @@ -62,6 +62,124 @@ class GeneratorTools $this->config = $config; } + /** + * Create a plugin class. + * + * @param ContainerInterface $container Service manager + * @param string $class Class name to create + * @param string $factory Existing factory to use (null to + * generate a new one) + * + * @return bool + * @throws \Exception + */ + public function createPlugin(ContainerInterface $container, $class, + $factory = null + ) { + // Derive some key bits of information from the new class name: + $classParts = explode('\\', $class); + $module = $classParts[0]; + $shortName = strtolower(array_pop($classParts)); + $classParts[0] = 'VuFind'; + $classParts[] = 'PluginManager'; + $pmClass = implode('\\', $classParts); + // Set a flag for whether to generate a factory, and create class name + // if necessary. If existing factory specified, ensure it really exists. + if ($generateFactory = empty($factory)) { + $factory = $class . 'Factory'; + } elseif (!class_exists($factory)) { + throw new \Exception("Undefined factory: $factory"); + } + + // Figure out further information based on the plugin manager: + if (!$container->has($pmClass)) { + throw new \Exception('Cannot find expected plugin manager: ' . $pmClass); + } + $pm = $container->get($pmClass); + if (!method_exists($pm, 'getExpectedInterface')) { + throw new \Exception( + $pmClass . ' does not implement getExpectedInterface!' + ); + } + + // Force getExpectedInterface() to be public so we can read it: + $reflectionMethod = new \ReflectionMethod($pm, 'getExpectedInterface'); + $reflectionMethod->setAccessible(true); + $interface = $reflectionMethod->invoke($pm); + + // Figure out whether the plugin requirement is an interface or a + // parent class so we can create the right thing.... + if (interface_exists($interface)) { + $parent = null; + $interfaces = [$interface]; + } else { + $parent = $interface; + $interfaces = []; + } + $apmFactory = new \VuFind\ServiceManager\AbstractPluginManagerFactory(); + $pmKey = $apmFactory->getConfigKey(get_class($pm)); + $configPath = ['vufind', 'plugin_managers', $pmKey]; + + // Generate the classes and configuration: + $this->createClassInModule($class, $module, $parent, $interfaces); + if ($generateFactory) { + $this->generateFactory($class, $factory, $module); + } + $factoryPath = array_merge($configPath, ['factories', $class]); + $this->writeNewConfig($factoryPath, $factory, $module); + $aliasPath = array_merge($configPath, ['aliases', $shortName]); + // Don't back up the config twice -- the first backup from the previous + // write operation is sufficient. + $this->writeNewConfig($aliasPath, $class, $module, false); + + return true; + } + + /** + * Generate a factory class. + * + * @param string $class Name of class being built by factory + * @param string $factory Name of factory to generate + * @param string $module Name of module to generate factory within + * + * @return void + */ + protected function generateFactory($class, $factory, $module) + { + $this->createClassInModule( + $factory, $module, null, + ['Zend\ServiceManager\Factory\FactoryInterface'], + function ($generator) use ($class) { + $method = MethodGenerator::fromArray( + [ + 'name' => '__invoke', + 'body' => 'return new \\' . $class . '();', + ] + ); + $param1 = [ + 'name' => 'container', + 'type' => 'Interop\Container\ContainerInterface' + ]; + $param2 = [ + 'name' => 'requestedName', + ]; + $param3 = [ + 'name' => 'options', + 'type' => 'array', + 'defaultValue' => null, + ]; + $method->setParameters([$param1, $param2, $param3]); + // Copy doc block from this class' factory: + $reflection = new \Zend\Code\Reflection\MethodReflection( + GeneratorToolsFactory::class, '__invoke' + ); + $example = MethodGenerator::fromReflection($reflection); + $method->setDocBlock($example->getDocBlock()); + $generator->addMethods([$method]); + } + ); + } + /** * Extend a class defined somewhere in the service manager or its child * plugin managers. @@ -396,16 +514,22 @@ class GeneratorTools * Extend a specified class within a specified module. Return the name of * the new subclass. * - * @param string $class Name of class to create - * @param string $module Module in which to create the new class - * @param string $parent Parent class (null for no parent) + * @param string $class Name of class to create + * @param string $module Module in which to create the new class + * @param string $parent Parent class (null for no parent) + * @param string[] $interfaces Interfaces for class to implement + * @param \Callable $callback Callback to set up class generator * * @return void * @throws \Exception */ - protected function createClassInModule($class, $module, $parent = null) - { - $generator = new ClassGenerator($class, null, null, $parent); + protected function createClassInModule($class, $module, $parent = null, + array $interfaces = [], $callback = null + ) { + $generator = new ClassGenerator($class, null, null, $parent, $interfaces); + if (is_callable($callback)) { + $callback($generator); + } return $this->writeClass($generator, $module); }