From a15b37d9ea20fb2a8cbe266398584d5f21a46bbf Mon Sep 17 00:00:00 2001 From: Dmitriy Simushev Date: Tue, 6 May 2014 14:22:31 +0000 Subject: [PATCH] Implement front controller --- src/composer.json | 6 +- src/mibew/.htaccess | 16 ++ src/mibew/app.php | 31 ++++ src/mibew/libs/classes/Mibew/Application.php | 102 ++++++++++++ .../Mibew/Controller/AbstractController.php | 124 ++++++++++++++ .../Mibew/Controller/ControllerResolver.php | 100 +++++++++++ .../Exception/AccessDeniedException.php | 27 +++ .../Mibew/Routing/RouteCollectionLoader.php | 116 +++++++++++++ .../libs/classes/Mibew/Routing/Router.php | 155 ++++++++++++++++++ .../Mibew/Routing/RouterAwareInterface.php | 38 +++++ src/mibew/libs/routing.yml | 0 11 files changed, 714 insertions(+), 1 deletion(-) create mode 100644 src/mibew/app.php create mode 100644 src/mibew/libs/classes/Mibew/Application.php create mode 100644 src/mibew/libs/classes/Mibew/Controller/AbstractController.php create mode 100644 src/mibew/libs/classes/Mibew/Controller/ControllerResolver.php create mode 100644 src/mibew/libs/classes/Mibew/Routing/Exception/AccessDeniedException.php create mode 100644 src/mibew/libs/classes/Mibew/Routing/RouteCollectionLoader.php create mode 100644 src/mibew/libs/classes/Mibew/Routing/Router.php create mode 100644 src/mibew/libs/classes/Mibew/Routing/RouterAwareInterface.php create mode 100644 src/mibew/libs/routing.yml diff --git a/src/composer.json b/src/composer.json index 55d8a024..1b51ad31 100644 --- a/src/composer.json +++ b/src/composer.json @@ -1,6 +1,10 @@ { "require": { - "xamin/handlebars.php": "dev-master#81f3efbb84eadba729bb6cb37ee9fca6825b37d1" + "xamin/handlebars.php": "dev-master#81f3efbb84eadba729bb6cb37ee9fca6825b37d1", + "symfony/http-foundation": "2.4.*", + "symfony/routing": "2.4.*", + "symfony/config": "2.4.*", + "symfony/yaml": "2.4.*" }, "config": { "vendor-dir": "mibew/vendor" diff --git a/src/mibew/.htaccess b/src/mibew/.htaccess index f821987f..dc803fcb 100644 --- a/src/mibew/.htaccess +++ b/src/mibew/.htaccess @@ -23,3 +23,19 @@ Options +FollowSymLinks php_value mbstring.http_output pass php_flag session.auto_start off + +# Redirect requests to the front controller + + RewriteEngine On + + # Alter only requests for files and directories that do not exist + RewriteCond %{REQUEST_FILENAME} !-f + RewriteCond %{REQUEST_FILENAME} !-d + # Rewrite all requests to front controller + RewriteRule ^(.*)$ app.php [QSA,L] + + +# Deny access to .yml files + + Deny from all + diff --git a/src/mibew/app.php b/src/mibew/app.php new file mode 100644 index 00000000..febe070e --- /dev/null +++ b/src/mibew/app.php @@ -0,0 +1,31 @@ +handleRequest($request); + +// Send response to the user +$response->send(); diff --git a/src/mibew/libs/classes/Mibew/Application.php b/src/mibew/libs/classes/Mibew/Application.php new file mode 100644 index 00000000..546e5fd3 --- /dev/null +++ b/src/mibew/libs/classes/Mibew/Application.php @@ -0,0 +1,102 @@ +fileLocator = new FileLocator(array(MIBEW_FS_ROOT)); + $this->router = new Router(new RouteCollectionLoader($this->fileLocator)); + $this->controllerResolver = new ControllerResolver($this->router); + } + + /** + * Handles incomming request. + * + * @param Request $request Incoming request + * @return Response Resulting response + */ + public function handleRequest(Request $request) + { + // Actualize request context in the internal router instance + $context = new RequestContext(); + $context->fromRequest($request); + $this->router->setContext($context); + + try { + // Try to match route + $parameters = $this->router->matchRequest($request); + $request->attributes->add($parameters); + + // Get controller + $controller = $this->controllerResolver->getController($request); + + // Execute the controller's action and get response. + $response = call_user_func($controller, $request); + } catch(AccessDeniedException $e) { + return new Response('Forbidden', 403); + } catch (ResourceNotFoundException $e) { + return new Response('Not Found', 404); + } catch (MethodNotAllowedException $e) { + return new Response('Method Not Allowed', 405); + } catch (Exception $e) { + return new Response('Internal Server Error', 500); + } + + if ($response instanceof Response) { + return $response; + } else { + // Convert all content returned by a controller's action to Response + // instance. + return new Response((string)$response); + } + } +} diff --git a/src/mibew/libs/classes/Mibew/Controller/AbstractController.php b/src/mibew/libs/classes/Mibew/Controller/AbstractController.php new file mode 100644 index 00000000..471e39df --- /dev/null +++ b/src/mibew/libs/classes/Mibew/Controller/AbstractController.php @@ -0,0 +1,124 @@ +router = $router; + } + + /** + * {@inheritdoc} + */ + public function getRouter() + { + return $this->router; + } + + /** + * Generates a URL from the given parameters. + * + * @param string $route The name of the route. + * @param mixed $parameters An array of parameters. + * @param bool|string $referenceType The type of reference (one of the + * constants in UrlGeneratorInterface). + * + * @return string The generated URL. + * + * @see UrlGeneratorInterface + */ + public function generateUrl($route, $parameters = array(), $referenceType = UrlGeneratorInterface::ABSOLUTE_PATH) + { + return $this->getRouter()->generate($route, $parameters, $referenceType); + } + + /** + * Returns a RedirectResponse to the given URL. + * + * @param string $url The URL to redirect to. + * @param int $status The status code to use for the Response. + * + * @return RedirectResponse + */ + public function redirect($url, $status = 302) + { + return new RedirectResponse($url, $status); + } + + /** + * Returns a rendered template. + * + * @param string $template Name of the template. + * @param array $parameters An array of parameters to pass to the template. + * + * @return string The rendered template + */ + public function render($template, array $parameters = array()) + { + // TODO: Remove bufferization after all pages will be replaced with + // controllers and direct output will be removed from the *Style classes. + ob_start(); + $this->getStyle()->render($template, $parameters); + $content = ob_get_clean(); + + return $content; + } + + /** + * Returns style object related with the controller. + * + * The method can be overridden in child classes to implement style choosing + * logic. + * + * @return StyleInterface + * @todo Update the method after rewriting style choosing logic + */ + protected function getStyle() + { + if (is_null($this->style)) { + $this->style = new PageStyle(PageStyle::getDefaultStyle()); + } + + return $this->style; + } +} diff --git a/src/mibew/libs/classes/Mibew/Controller/ControllerResolver.php b/src/mibew/libs/classes/Mibew/Controller/ControllerResolver.php new file mode 100644 index 00000000..62432640 --- /dev/null +++ b/src/mibew/libs/classes/Mibew/Controller/ControllerResolver.php @@ -0,0 +1,100 @@ +router = $router; + } + + /** + * Resolves controller by request. + * + * @param Request $request Incoming request. + * @return callable + * @throws \InvalidArgumentException If the controller cannot be resolved. + */ + public function getController(Request $request) + { + // Get controller name from the request + $controller = $request->attributes->get('_controller'); + if (!$controller) { + throw new \InvalidArgumentException('The "_controller" parameter is missed.'); + } + + // Build callable for specified controller + $callable = $this->createController($controller); + + if (!is_callable($callable)) { + throw new \InvalidArgumentException(sprintf( + 'Controller "%s" for URI "%s" is not callable.', + $controller, + $request->getPathInfo() + )); + } + + return $callable; + } + + /** + * Builds controller callable by its full name. + * + * @param string $controller Full controller name in "::" + * format. + * @return callable Controller callable + * @throws \InvalidArgumentException + */ + protected function createController($controller) + { + if (strpos($controller, '::') === FALSE) { + throw new \InvalidArgumentException(sprintf( + 'Unable to find controller "%s".', + $controller + )); + } + + list($class, $method) = explode('::', $controller, 2); + + if (!class_exists($class)) { + throw new \InvalidArgumentException(sprintf('Class "%s" does not exist.', $class)); + } + + $object = new $class(); + if ($object instanceof RouterAwareInterface) { + $object->setRouter($this->router); + } + + return array($object, $method); + } +} diff --git a/src/mibew/libs/classes/Mibew/Routing/Exception/AccessDeniedException.php b/src/mibew/libs/classes/Mibew/Routing/Exception/AccessDeniedException.php new file mode 100644 index 00000000..92be0025 --- /dev/null +++ b/src/mibew/libs/classes/Mibew/Routing/Exception/AccessDeniedException.php @@ -0,0 +1,27 @@ +loader = new YamlFileLoader($locator); + } + + /** + * Load routes of specified type. + * + * @param int $type Type of routes to load. Can be one of + * RouteCollectionLoader::ROUTES_* constants. + * @return RouteCollection + */ + public function load($type = self::ROUTES_ALL) + { + $collection = new RouteCollection(); + + // Load core routes if needed + if ($type & self::ROUTES_CORE) { + $collection->addCollection($this->loadCoreRoutes()); + } + + // Load plugins routes if needed + if ($type & self::ROUTES_PLUGINS) { + $collection->addCollection($this->loadPluginRoutes()); + } + + // Add an ability for plugins to alter routes list + $arguments = array('routes' => $collection); + EventDispatcher::getInstance()->triggerEvent('routesAlter', $arguments); + + return $arguments['routes']; + } + + /** + * Loads routes of the core. + * + * @return RouteCollection + * @throws \RuntimeException If core routing file is not found. + */ + protected function loadCoreRoutes() + { + return $this->loader->load('libs/routing.yml'); + } + + /** + * Loads plugins' routes. + * + * @return RouteCollection + */ + protected function loadPluginRoutes() + { + $collection = new RouteCollection(); + foreach (PluginManager::getAllPlugins() as $plugin) { + $file = $plugin->getFilesPath() . '/routing.yml'; + if (!file_exists($file)) { + continue; + } + $collection->addCollection($this->loader->load($file)); + } + + return $collection; + } +} diff --git a/src/mibew/libs/classes/Mibew/Routing/Router.php b/src/mibew/libs/classes/Mibew/Routing/Router.php new file mode 100644 index 00000000..e18cf6eb --- /dev/null +++ b/src/mibew/libs/classes/Mibew/Routing/Router.php @@ -0,0 +1,155 @@ +context = $context ? $context : new RequestContext(); + $this->loader = $loader; + } + + /** + * {@inheritdoc} + */ + public function getRouteCollection() + { + if (is_null($this->collection)) { + $this->collection = $this->loader->load(); + } + + return $this->collection; + } + + /** + * {@inheritdoc} + */ + public function generate($name, $parameters = array(), $referenceType = self::ABSOLUTE_PATH) + { + return $this->getGenerator()->generate($name, $parameters, $referenceType); + } + + /** + * {@inheritdoc} + */ + public function getContext() + { + return $this->context; + } + + /** + * {@inheritdoc} + */ + public function setContext(RequestContext $context) + { + $this->context = $context; + + // Update request context in URL matcher instance + if (!is_null($this->matcher)) { + $this->matcher->setContext($context); + } + + // Update request context in URL generator instance + if (!is_null($this->generator)) { + $this->generator->setContext($context); + } + } + + /** + * {@inheritdoc} + */ + public function matchRequest(Request $request) + { + return $this->getMatcher()->matchRequest($request); + } + + /** + * {@inheritdoc} + */ + public function match($pathinfo) + { + return $this->getMatcher()->match($pathinfo); + } + + /** + * Gets the UrlMatcher instance associated with this Router. + * + * @return UrlMatcher + */ + public function getMatcher() { + if (is_null($this->matcher)) { + $this->matcher = new UrlMatcher($this->getRouteCollection(), $this->getContext()); + } + + return $this->matcher; + } + + /** + * Gets the UrlGenerator instance associated with this Router. + * + * @return UrlGenerator + */ + public function getGenerator() { + if (is_null($this->generator)) { + $this->generator = new UrlGenerator($this->getRouteCollection(), $this->getContext()); + } + + return $this->generator; + } +} diff --git a/src/mibew/libs/classes/Mibew/Routing/RouterAwareInterface.php b/src/mibew/libs/classes/Mibew/Routing/RouterAwareInterface.php new file mode 100644 index 00000000..1fed2da4 --- /dev/null +++ b/src/mibew/libs/classes/Mibew/Routing/RouterAwareInterface.php @@ -0,0 +1,38 @@ +