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