Skip to content

Commit

Permalink
[Framework + Test] implemented exercise hhamon#1.
Browse files Browse the repository at this point in the history
  • Loading branch information
RomainOliva committed Feb 22, 2016
1 parent 3a130df commit e68cb03
Show file tree
Hide file tree
Showing 10 changed files with 265 additions and 3 deletions.
11 changes: 11 additions & 0 deletions app/config/routes.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,11 @@
{
"routes": {
"hello": {
"path": "/hello",
"defaults": {
"_controller": "Application\\Controller\\Blog\\HelloWorldAction"
},
"methods": [ "GET", "POST" ]
}
}
}
5 changes: 4 additions & 1 deletion bootstrap.php
Original file line number Diff line number Diff line change
Expand Up @@ -15,6 +15,7 @@
use Framework\Routing\Loader\CompositeFileLoader;
use Framework\Routing\Loader\PhpFileLoader;
use Framework\Routing\Loader\XmlFileLoader;
use Framework\Routing\Loader\JsonFileLoader;
use Framework\ServiceLocator\ServiceLocator;
use Framework\Session\Driver\NativeDriver;
use Framework\Session\Session;
Expand All @@ -32,7 +33,8 @@
\PDO::ATTR_ERRMODE => \PDO::ERRMODE_EXCEPTION,
\PDO::MYSQL_ATTR_INIT_COMMAND => "SET NAMES 'UTF8'",
]);
$dic->setParameter('router.file', __DIR__.'/app/config/routes.xml');
//$dic->setParameter('router.file', __DIR__.'/app/config/routes.xml');
$dic->setParameter('router.file', __DIR__.'/app/config/routes.json');
$dic->setParameter('app.views_dir', __DIR__.'/app/views');
$dic->setParameter('twig.options', [
'cache' => __DIR__.'/../app/cache/twig',
Expand Down Expand Up @@ -70,6 +72,7 @@

$dic->register('router', function (ServiceLocator $dic) {
$loader = new CompositeFileLoader();
$loader->add(new JsonFileLoader());
$loader->add(new PhpFileLoader());
$loader->add(new XmlFileLoader());

Expand Down
4 changes: 2 additions & 2 deletions composer.json
Original file line number Diff line number Diff line change
Expand Up @@ -5,8 +5,8 @@
"license": "proprietary",
"authors": [
{
"name": "Hugo Hamon",
"email": "hugo.hamon@sensiolabs.com"
"name": "Romain Oliva",
"email": "olivar.info@gmail.com"
}
],
"autoload": {
Expand Down
116 changes: 116 additions & 0 deletions src/Framework/Routing/Loader/JsonFileLoader.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,116 @@
<?php

namespace Framework\Routing\Loader;

use Framework\Routing\Route;
use Framework\Routing\RouteCollection;

class JsonFileLoader implements FileLoaderInterface
{
public function load($path)
{
if ('json' !== pathinfo($path, PATHINFO_EXTENSION)) {
throw new UnsupportedFileTypeException(sprintf(
'File %s must be a valid JSON file.',
$path
));
}

if (!is_readable($path)) {
throw new \InvalidArgumentException(sprintf(
'File %s is not readable or does not exist.',
$path
));
}

return $this->parseRoutes($path);
}

private function parseRoutes($path)
{
$routes = new RouteCollection();
$json = json_decode(file_get_contents($path));

foreach ($json->route as $route) {
$this->parseRoute($routes, $route);
}

return $routes;
}

private function parseRoute(RouteCollection $routes, \stdClass $route)
{
if (empty($route->name)) {
throw new \RuntimeException('Each route must have a unique name.');
}

$name = (string) $route->name;
if (empty($route->path)) {
throw new \RuntimeException(sprintf('Route %s must have a path.', $name));
}

$methods = [];
if (!empty($route->methods)) {
$methods = explode('|', $route->methods);
}

$params = $this->parseRouteParams($route, $name);
$requirements = $this->parseRouteRequirements($route, $name);

$routes->add($name, new Route((string) $route->path, $params, $methods, $requirements));
}

private function parseRouteParams(\stdClass $route, $name)
{
$params = [];
if (!isset($route->default) && !count($route->defaults)) {
return $params;
}

foreach ($route->defaults as $key => $param) {
$params = array_merge($params, $this->parseRouteParam($name, $param, $key));
}

return $params;
}

private function parseRouteParam($name, $param, $key)
{
if (empty($param)) {
throw new \RuntimeException(sprintf(
'Parameter #%u for route %s can\'t be empty.',
$key,
$name
));
}

return [ (string) $key => (string) $param ];
}

private function parseRouteRequirements(\stdClass $route, $name)
{
$requirements = [];
if (!isset($route->requirements) || !count($route->requirements)) {
return $requirements;
}

foreach ($route->requirements as $key => $requirement) {
$requirements = array_merge($requirements, $this->parseRouteRequirement($name, $requirement, $key));
}

return $requirements;
}

private function parseRouteRequirement($name, $requirement, $key)
{
if (empty($requirement)) {
throw new \RuntimeException(sprintf(
'Requirement #%u for route %s can\'t be empty.',
$key,
$name
));
}

return [ (string) $key => (string) $requirement ];
}
}
11 changes: 11 additions & 0 deletions tests/Framework/Routing/Fixtures/invalid1.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,11 @@
{
"routes": {
"": {
"path": "/",
"defaults": {
"_controller": "Application\\Controller\\Blog\\ListPostsAction"
},
"methods": [ "GET" ]
}
}
}
10 changes: 10 additions & 0 deletions tests/Framework/Routing/Fixtures/invalid2.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,10 @@
{
"routes": {
"homepage": {
"defaults": {
"_controller": "Application\\Controller\\Blog\\ListPostsAction"
},
"methods": [ "GET" ]
}
}
}
9 changes: 9 additions & 0 deletions tests/Framework/Routing/Fixtures/invalid3.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,9 @@
{
"routes": {
"homepage": {
"path": "/",
"defaults": { "Application\\Controller\\Blog\\ListPostsAction" : "" },
"methods": [ "GET" ]
}
}
}
12 changes: 12 additions & 0 deletions tests/Framework/Routing/Fixtures/invalid4.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,12 @@
{
"routes": {
"homepage": {
"path": "/{lang}",
"defaults": {
"_controller": "Application\\Controller\\Blog\\ListPostsAction"
},
"requirements": { "[a-z]{2}":"" },
"methods": [ "GET" ]
}
}
}
33 changes: 33 additions & 0 deletions tests/Framework/Routing/Fixtures/routes.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,33 @@
{
"routes": {
"homepage": {
"path": "/",
"defaults": {
"_controller": "Application\\Controller\\Blog\\ListPostsAction"
},
"methods": [ "GET" ]
},
"blog": {
"path": "/blog",
"defaults": {
"_controller": "Application\\Controller\\Blog\\ListPostsAction"
},
"methods": [ "GET" ]
},
"blog_post": {
"path": "/blog/article-{id}.html",
"defaults": {
"_controller": "Application\\Controller\\Blog\\GetPostAction"
},
"requirements": { "id": "\\d+" },
"methods": [ "GET" ]
},
"hello": {
"path": "/hello",
"defaults": {
"_controller": "Application\\Controller\\Blog\\HelloWorldAction"
},
"methods": [ "GET" ]
}
}
}
57 changes: 57 additions & 0 deletions tests/Framework/Routing/Loader/JsonFileLoaderTest.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,57 @@
<?php

namespace Tests\Framework\Routing\Loader;

use Framework\Routing\Loader\JsonFileLoader;
use Framework\Routing\RouteCollection;

class JsonFileLoaderTest extends \PHPUnit_Framework_TestCase
{
/**
* @expectedException \Framework\Routing\Loader\UnsupportedFileTypeException
*/
public function testLoadUnsupportedFileType()
{
$loader = new JsonFileLoader();
$loader->load('foo.txt');
}

/**
* @expectedException \InvalidArgumentException
*/
public function testLoadNonExistentFile()
{
$loader = new JsonFileLoader();
$loader->load('foo.xml');
}

/**
* @expectedException \RuntimeException
* @dataProvider provideInvalidFile
*/
public function testLoadInvalidFile($file)
{
$loader = new JsonFileLoader();
$loader->load(__DIR__.'/../Fixtures/'.$file);
}

public function provideInvalidFile()
{
return [
[ 'invalid1.json' ],
[ 'invalid2.json' ],
[ 'invalid3.json' ],
[ 'invalid4.json' ],
];
}

public function testLoadFile()
{
$loader = new JsonFileLoader();

$this->assertInstanceOf(
RouteCollection::class,
$loader->load(__DIR__.'/../Fixtures/routes.json')
);
}
}

0 comments on commit e68cb03

Please sign in to comment.