From 577adab042a20b91bd5ba543c614a81402e6d9da Mon Sep 17 00:00:00 2001 From: Rob Allen Date: Tue, 16 Dec 2014 19:29:43 +0000 Subject: [PATCH] Add our own InjectTemplateListener This depends on https://github.com/zendframework/zf2/pull/7046 --- .../RkaZf2Action/InjectTemplateListener.php | 227 ++++++++++++++++++ module/RkaZf2Action/Module.php | 14 ++ 2 files changed, 241 insertions(+) create mode 100644 module/RkaZf2Action/InjectTemplateListener.php diff --git a/module/RkaZf2Action/InjectTemplateListener.php b/module/RkaZf2Action/InjectTemplateListener.php new file mode 100644 index 0000000..fa00bc7 --- /dev/null +++ b/module/RkaZf2Action/InjectTemplateListener.php @@ -0,0 +1,227 @@ + template mappings + * + * @var array + */ + protected $controllerMap = array(); + + /** + * {@inheritDoc} + */ + public function attach(Events $events) + { + $this->listeners[] = $events->attach(MvcEvent::EVENT_DISPATCH, array($this, 'injectTemplate'), -90); + } + + /** + * Inject a template into the view model, if none present + * + * Template is derived from the controller found in the route match, and, + * optionally, the action, if present. + * + * @param MvcEvent $e + * @return void + */ + public function injectTemplate(MvcEvent $e) + { + $model = $e->getResult(); + if (!$model instanceof ViewModel) { + return; + } + + $template = $model->getTemplate(); + if (!empty($template)) { + return; + } + + $routeMatch = $e->getRouteMatch(); + $controller = $e->getTarget(); + if (is_object($controller)) { + $controller = get_class($controller); + } + if (!$controller) { + $controller = $routeMatch->getParam('controller', ''); + } + + $template = $this->mapController($controller); + if (!$template) { + $module = $this->deriveModuleNamespace($controller); + + if ($namespace = $routeMatch->getParam(ModuleRouteListener::MODULE_NAMESPACE)) { + $controllerSubNs = $this->deriveControllerSubNamespace($namespace); + if (!empty($controllerSubNs)) { + if (!empty($module)) { + $module .= '/' . $controllerSubNs; + } else { + $module = $controllerSubNs; + } + } + } + + $controller = $this->deriveControllerClass($controller); + $template = $this->inflectName($module); + + if (!empty($template)) { + $template .= '/'; + } + $template .= $this->inflectName($controller); + } + + $action = $routeMatch->getParam('action'); + if (null !== $action) { + $template .= '/' . $this->inflectName($action); + } + $model->setTemplate($template); + } + + /** + * Set map of controller namespace -> template pairs + * + * @param array $map + * @return self + */ + public function setControllerMap(array $map) + { + krsort($map); + $this->controllerMap = $map; + return $this; + } + + /** + * Maps controller to template if controller namespace is whitelisted or mapped + * + * @param string $controller controller FQCN + * @return string|false template name or false if controller was not matched + */ + public function mapController($controller) + { + foreach ($this->controllerMap as $namespace => $replacement) { + if ( + // Allow disabling rule by setting value to false since config + // merging have no feature to remove entries + false == $replacement + // Match full class or full namespace + || !($controller === $namespace || strpos($controller, $namespace . '\\') === 0) + ) { + continue; + } + + $map = ''; + // Map namespace to $replacement if its value is string + if (is_string($replacement)) { + $map = rtrim($replacement, '/') . '/'; + $controller = substr($controller, strlen($namespace) + 1); + } + + //strip Controller namespace(s) (but not classname) + $parts = explode('\\', $controller); + array_pop($parts); + $parts = array_diff($parts, array('Controller')); + //strip trailing Controller in class name + $parts[] = $this->deriveControllerClass($controller); + $controller = implode('/', $parts); + + $template = trim($map . $controller, '/'); + + //inflect CamelCase to dash + return $this->inflectName($template); + } + return false; + } + + /** + * Inflect a name to a normalized value + * + * @param string $name + * @return string + */ + protected function inflectName($name) + { + if (!$this->inflector) { + $this->inflector = new CamelCaseToDashFilter(); + } + $name = $this->inflector->filter($name); + return strtolower($name); + } + + /** + * Determine the top-level namespace of the controller + * + * @param string $controller + * @return string + */ + protected function deriveModuleNamespace($controller) + { + if (!strstr($controller, '\\')) { + return ''; + } + $module = substr($controller, 0, strpos($controller, '\\')); + return $module; + } + + /** + * @param $namespace + * @return string + */ + protected function deriveControllerSubNamespace($namespace) + { + if (!strstr($namespace, '\\')) { + return ''; + } + $nsArray = explode('\\', $namespace); + + // Remove the first two elements representing the module and controller directory. + $subNsArray = array_slice($nsArray, 2); + if (empty($subNsArray)) { + return ''; + } + return implode('/', $subNsArray); + } + + /** + * Determine the name of the controller + * + * Strip the namespace, and the suffix "Controller" if present. + * + * @param string $controller + * @return string + */ + protected function deriveControllerClass($controller) + { + if (strstr($controller, '\\')) { + $controller = substr($controller, strrpos($controller, '\\') + 1); + } + + if ((10 < strlen($controller)) + && ('Controller' == substr($controller, -10)) + ) { + $controller = substr($controller, 0, -10); + } elseif ((6 < strlen($controller)) + && ('Action' == substr($controller, -6)) + ) { + $controller = substr($controller, 0, -6); + } + + return $controller; + } +} diff --git a/module/RkaZf2Action/Module.php b/module/RkaZf2Action/Module.php index d45cda2..99b7f1f 100644 --- a/module/RkaZf2Action/Module.php +++ b/module/RkaZf2Action/Module.php @@ -8,8 +8,20 @@ class Module { + public function getConfig() + { + // This works if https://github.com/zendframework/zf2/pull/7046 is merged + return [ + 'service_manager' => [ + 'invokables' => [ + 'InjectTemplateListener' => 'RkaZf2Action\InjectTemplateListener', + ], + ], + ]; + } public function onBootstrap(MvcEvent $e) { + // This is needed if https://github.com/zendframework/zf2/pull/7046 is not merged $sharedEvents = $e->getApplication()->getEventManager()->getSharedManager(); $sharedEvents->attach('Zend\Stdlib\DispatchableInterface', 'dispatch', array($this, 'renameTemplate'), -91); } @@ -22,6 +34,8 @@ public function onBootstrap(MvcEvent $e) * end of the template name. This listener removes the "-action" as it's from * the Action class' name which we do not want to be in the template name. * + * This is needed for Zend Framework < 2.4.0 + * * @param MvcEvent $e * @return void */