From 477807378b063908bbd29ba07c3397012cb48408 Mon Sep 17 00:00:00 2001 From: Demian Katz <demian.katz@villanova.edu> Date: Wed, 7 Feb 2018 14:40:19 -0500 Subject: [PATCH] Generator improvements. - Add controller and controller plugin support to extendclass - Make static factory support smarter --- .../Generator/GeneratorTools.php | 78 ++++++++++++++----- 1 file changed, 57 insertions(+), 21 deletions(-) diff --git a/module/VuFindConsole/src/VuFindConsole/Generator/GeneratorTools.php b/module/VuFindConsole/src/VuFindConsole/Generator/GeneratorTools.php index df6431668da..946bf9a2125 100644 --- a/module/VuFindConsole/src/VuFindConsole/Generator/GeneratorTools.php +++ b/module/VuFindConsole/src/VuFindConsole/Generator/GeneratorTools.php @@ -80,11 +80,16 @@ class GeneratorTools ) { // Set things up differently depending on whether this is a top-level // service or a class in a plugin manager. + $cm = $container->get('ControllerManager'); + $cpm = $container->get('ControllerPluginManager'); if ($container->has($class)) { $factory = $this->getFactoryFromContainer($container, $class); $configPath = ['service_manager']; - } else { - $pm = $this->getPluginManagerContainingClass($container, $class); + } elseif ($factory = $this->getFactoryFromContainer($cm, $class)) { + $configPath = ['controllers']; + } elseif ($factory = $this->getFactoryFromContainer($cpm, $class)) { + $configPath = ['controller_plugins']; + } elseif ($pm = $this->getPluginManagerContainingClass($container, $class)) { $apmFactory = new \VuFind\ServiceManager\AbstractPluginManagerFactory(); $pmKey = $apmFactory->getConfigKey(get_class($pm)); $factory = $this->getFactoryFromContainer($pm, $class); @@ -92,7 +97,7 @@ class GeneratorTools } // No factory found? Throw an error! - if (!$factory) { + if (empty($factory)) { throw new \Exception('Could not find factory for ' . $class); } @@ -101,7 +106,7 @@ class GeneratorTools // Create the custom factory only if requested. $newFactory = $extendFactory - ? $this->createSubclassInModule($factory, $target) + ? $this->cloneFactory($factory, $target) : $factory; // Finalize the local module configuration -- create a factory for the @@ -202,6 +207,7 @@ class GeneratorTools switch ($sourceType) { case 'factories': + $this->createSubclassInModule($parts[$partCount - 1], $target); $newConfig = $this->cloneFactory($config, $target); break; case 'invokables': @@ -226,39 +232,63 @@ class GeneratorTools */ protected function cloneFactory($factory, $module) { + // If the factory is a stand-alone class, it's simple to clone: + if (class_exists($factory)) { + return $this->createSubclassInModule($factory, $module); + } + // Make sure we can figure out how to handle the factory; it should // either be a [controller, method] array or a "controller::method" // string; anything else will cause a problem. $parts = is_string($factory) ? explode('::', $factory) : $factory; if (!is_array($parts) || count($parts) != 2 || !class_exists($parts[0]) - || !method_exists($parts[0], $parts[1]) + || !is_callable($parts) ) { throw new \Exception('Unexpected factory configuration format.'); } list($factoryClass, $factoryMethod) = $parts; $newFactoryClass = $this->generateLocalClassName($factoryClass, $module); if (!class_exists($newFactoryClass)) { - $this->createClassInModule($newFactoryClass, $module); + $this->createSubclassInModule($factoryClass, $module); $skipBackup = true; } else { $skipBackup = false; } - if (method_exists($newFactoryClass, $factoryMethod)) { - throw new \Exception("$newFactoryClass::$factoryMethod already exists."); - } $oldReflection = new ClassReflection($factoryClass); $newReflection = new ClassReflection($newFactoryClass); - $generator = ClassGenerator::fromReflection($newReflection); - $method = MethodGenerator::fromReflection( - $oldReflection->getMethod($factoryMethod) - ); - $this->createServiceClassAndUpdateFactory( - $method, $oldReflection->getNamespaceName(), $module - ); - $generator->addMethodFromGenerator($method); - $this->writeClass($generator, $module, true, $skipBackup); + try { + $newMethod = $newReflection->getMethod($factoryMethod); + if ($newMethod->getDeclaringClass()->getName() == $newFactoryClass) { + throw new \Exception( + "$newFactoryClass::$factoryMethod already exists." + ); + } + + $generator = ClassGenerator::fromReflection($newReflection); + $method = MethodGenerator::fromReflection( + $oldReflection->getMethod($factoryMethod) + ); + $this->updateFactory( + $method, $oldReflection->getNamespaceName(), $module + ); + $generator->addMethodFromGenerator($method); + $this->writeClass($generator, $module, true, $skipBackup); + } catch (\ReflectionException $e) { + // If a parent factory has a __callStatic method, the method we are + // trying to rewrite may not exist. In that case, we can just inherit + // __callStatic and ignore the error. Any other exception should be + // treated as a fatal error. + if (method_exists($factoryClass, '__callStatic')) { + Console::writeLine('Error: ' . $e->getMessage()); + Console::writeLine( + '__callStatic in parent factory; skipping method generation.' + ); + } else { + throw $e; + } + } return $newFactoryClass . '::' . $factoryMethod; } @@ -274,7 +304,7 @@ class GeneratorTools * @return void * @throws \Exception */ - protected function createServiceClassAndUpdateFactory(MethodGenerator $method, + protected function updateFactory(MethodGenerator $method, $ns, $module ) { $body = $method->getBody(); @@ -289,7 +319,7 @@ class GeneratorTools // Figure out fully qualified name for purposes of createSubclassInModule(): $fqClassName = (substr($className, 0, 1) != '\\') ? "$ns\\$className" : $className; - $newClass = $this->createSubclassInModule($fqClassName, $module); + $newClass = $this->generateLocalClassName($fqClassName, $module); $body = preg_replace( '/new\s+' . addslashes($className) . '\s*\(/m', 'new \\' . $newClass . '(', @@ -370,7 +400,13 @@ class GeneratorTools throw new \Exception("$fullPath already exists."); } } - if (!file_put_contents($fullPath, $generator->generate())) { + // TODO: this is a workaround for an apparent bug in Zend\Code which + // omits the leading backslash on "extends" statements when rewriting + // existing classes. Can we remove this after a future Zend\Code upgrade? + $code = str_replace( + 'extends VuFind\\', 'extends \\VuFind\\', $generator->generate() + ); + if (!file_put_contents($fullPath, $code)) { throw new \Exception("Problem writing to $fullPath."); } Console::writeLine("Saved file: $fullPath"); -- GitLab