Skip to content
Snippets Groups Projects
Commit daa3ae53 authored by Demian Katz's avatar Demian Katz Committed by GitHub
Browse files

New code generator for creating plugins (#1247)

- Automatically creates and configures standard VuFind plugins
- Either uses an existing factory or creates a stub for building one
parent f583c6b3
Branches
Tags
No related merge requests found
......@@ -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',
......
......@@ -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>]',
......
......@@ -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
*
......
......@@ -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);
}
......
0% or .
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment