Implement front controller

This commit is contained in:
Dmitriy Simushev 2014-05-06 14:22:31 +00:00
parent 813f27c4d2
commit a15b37d9ea
11 changed files with 714 additions and 1 deletions

View File

@ -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"

View File

@ -23,3 +23,19 @@ Options +FollowSymLinks
php_value mbstring.http_output pass
php_flag session.auto_start off
</IfModule>
# Redirect requests to the front controller
<IfModule mod_rewrite.c>
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]
</IfModule>
# Deny access to .yml files
<FilesMatch "\.yml$">
Deny from all
</FilesMatch>

31
src/mibew/app.php Normal file
View File

@ -0,0 +1,31 @@
<?php
/*
* Copyright 2005-2014 the original author or authors.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
// Initialize libraries
require_once(dirname(__FILE__) . '/libs/init.php');
use Symfony\Component\HttpFoundation\Request;
use Mibew\Application;
$application = new Application();
// Process request
$request = Request::createFromGlobals();
$response = $application->handleRequest($request);
// Send response to the user
$response->send();

View File

@ -0,0 +1,102 @@
<?php
/*
* Copyright 2005-2014 the original author or authors.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
namespace Mibew;
use Mibew\Routing\Router;
use Mibew\Routing\RouteCollectionLoader;
use Mibew\Routing\Exception\AccessDeniedException;
use Mibew\Controller\ControllerResolver;
use Symfony\Component\HttpFoundation\Request;
use Symfony\Component\HttpFoundation\Response;
use Symfony\Component\Routing\RequestContext;
use Symfony\Component\Routing\Exception\ResourceNotFoundException;
use Symfony\Component\Routing\Exception\MethodNotAllowedException;
use Symfony\Component\Config\FileLocator;
/**
* Incapsulates whole application
*/
class Application
{
/**
* @var Router|null
*/
protected $router = null;
/**
* @var FileLocator|null
*/
protected $fileLocator = null;
/**
* @var ControllerResolver|null
*/
protected $controllerResolver = null;
/**
* Class constructor.
*/
public function __construct()
{
$this->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);
}
}
}

View File

@ -0,0 +1,124 @@
<?php
/*
* Copyright 2005-2014 the original author or authors.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
namespace Mibew\Controller;
use Mibew\Routing\Router;
use Mibew\Routing\RouterAwareInterface;
use Mibew\Style\StyleInterface;
use Mibew\Style\PageStyle;
use Symfony\Component\Routing\Generator\UrlGeneratorInterface;
use Symfony\Component\HttpFoundation\RedirectResponse;
/**
* A base class for all controllers.
*/
abstract class AbstractController implements RouterAwareInterface
{
/**
* @var Router|null
*/
protected $router = null;
/**
* @var StyleInterface|null
*/
protected $style = null;
/**
* {@inheritdoc}
*/
public function setRouter(Router $router)
{
$this->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;
}
}

View File

@ -0,0 +1,100 @@
<?php
/*
* Copyright 2005-2014 the original author or authors.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
namespace Mibew\Controller;
use Symfony\Component\HttpFoundation\Request;
use Mibew\Routing\Router;
use Mibew\Routing\RouterAwareInterface;
class ControllerResolver
{
/**
* @var Router|null
*/
protected $router = null;
/**
* Class constructor.
*
* @param Router $router Router instance.
*/
public function __construct(Router $router)
{
$this->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 "<Class>::<method>"
* 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);
}
}

View File

@ -0,0 +1,27 @@
<?php
/*
* Copyright 2005-2014 the original author or authors.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
namespace Mibew\Routing\Exception;
/**
* Access to the resource is forbidden.
*
* This exception should trigger an HTTP 403 response.
*/
class AccessDeniedException extends \RuntimeException
{
}

View File

@ -0,0 +1,116 @@
<?php
/*
* Copyright 2005-2014 the original author or authors.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
namespace Mibew\Routing;
use Mibew\Plugin\Manager as PluginManager;
use Mibew\EventDispatcher;
use Symfony\Component\Routing\Loader\YamlFileLoader;
use Symfony\Component\Routing\RouteCollection;
use Symfony\Component\Config\FileLocatorInterface;
/**
* Encapsulates routes loading logic.
*/
class RouteCollectionLoader
{
/**
* Indicates that only routes of the core should be loaded.
*/
const ROUTES_CORE = 1;
/**
* Indicates that only plugins' routes should be loaded.
*/
const ROUTES_PLUGINS = 2;
/**
* Indicates that all available routes should be loaded.
*/
const ROUTES_ALL = 3;
/**
* @var YamlFileLoader|null
*/
protected $loader = null;
/**
* Class constructor.
*/
public function __construct(FileLocatorInterface $locator)
{
$this->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;
}
}

View File

@ -0,0 +1,155 @@
<?php
/*
* Copyright 2005-2014 the original author or authors.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
namespace Mibew\Routing;
use Symfony\Component\HttpFoundation\Request;
use Symfony\Component\Routing\RequestContext;
use Symfony\Component\Routing\RouterInterface;
use Symfony\Component\Routing\RouteCollection;
use Symfony\Component\Routing\Generator\UrlGenerator;
use Symfony\Component\Routing\Matcher\RequestMatcherInterface;
use Symfony\Component\Routing\Matcher\UrlMatcher;
use Mibew\Routing\RouteCollectionLoader;
class Router implements RouterInterface, RequestMatcherInterface
{
/**
* @var UrlMatcher|null
*/
protected $matcher = null;
/**
* @var UrlGenerator|null
*/
protected $generator = null;
/**
* @var RequestContext
*/
protected $context;
/**
* @var RouteCollection|null
*/
protected $collection = null;
/**
* @var RouteCollectionLoader|null
*/
protected $loader = null;
/**
* Class constructor.
*
* @ param RouteLoader $loader An instance of route loader.
* @param RequestContext $context The context of the request.
*/
public function __construct(RouteCollectionLoader $loader, RequestContext $context = null)
{
$this->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;
}
}

View File

@ -0,0 +1,38 @@
<?php
/*
* Copyright 2005-2014 the original author or authors.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
namespace Mibew\Routing;
/**
* An interface for all router aware objects.
*/
interface RouterAwareInterface
{
/**
* Sets associated router object.
*
* @param Router $router A router instance.
*/
public function setRouter(Router $router);
/**
* Gets associated router object.
*
* @return Router A router instance;
*/
public function getRouter();
}

View File