Page MenuHomePhabricator

No OneTemporary

diff --git a/core/router/parameters.php b/core/router/Parameters.php
similarity index 97%
rename from core/router/parameters.php
rename to core/router/Parameters.php
index 8aa493f..24a9f28 100644
--- a/core/router/parameters.php
+++ b/core/router/Parameters.php
@@ -1,143 +1,146 @@
<?php namespace spitfire\core\router;
/**
* The parameters a route reads out of a server or URI. This allows the application
* to gracefully manage the parameters and give them over to the callback function
* that should replace it.
*/
class Parameters
{
/**
* This parameters are used to handle named parameters that a URL has handled
* from a Server or URI.
*
* @var string[]
*/
private $parameters = Array();
/**
* Some components are greedy (this means, they accept incomplete routes or
* patterns as target for a string), the leftovers are stored in this array
* for the application to use at it's own discretion.
*
* For example, Spitfire's default rules make heavy use of this tool as a way
* to retrieve the controller, action and object from a request.
*
* @var string[]
*/
private $unparsed = Array();
/**
* The extension that was parsed from a URL. This is considered a parameter in
* the context of the URL - since the route can provide it as additional information.
+ *
+ * This shouldn't be a part of the parameters
*
+ * @deprecated since version 0.1-dev 20170622
* @var string
*/
private $extension = 'php';
/**
* Returns the extension that the URL originally had in the system.
*
* @return string
*/
public function getExtension() {
return $this->extension;
}
public function setExtension($extension) {
$this->extension = $extension;
return $this;
}
/**
* Imports a set of parameters parsed by the router. Usually, this will be a
* single element provided by the route.
*
* @param string[] $params
*/
public function addParameters($params) {
$this->parameters = array_merge($this->parameters, $params);
return $this;
}
/**
* Returns the parameter for the given parameter name. Please note that this
* function may return boolean false and empty strings alike. You can use the
* === operator to compare the values and check if the returned one was
* because the data was not set or empty.
*
* @param string $name
* @return string
*/
public function getParameter($name) {
return (isset($this->parameters[$name]))? $this->parameters[$name] : false;
}
/**
* Returns the list of parameters parsed from the URL path. Please note that
* every parameter is sent as a URL portion and therefore a string.
*
* @return string[]
*/
public function getParameters() {
return $this->parameters;
}
/**
* Return the list of URL components that were unaffected by a 'greedy' route.
* That means that the parsed route was longer than the parameters parsed the
* route parsed.
*
* @return string[]
*/
public function getUnparsed() {
return $this->unparsed;
}
/**
* Sets the list of parameters this element holds. This is usually used
* internally to indicate what parameters where passed with the route.
*
* @param string[] $parameters
*/
public function setParameters($parameters) {
$this->parameters = $parameters;
}
/**
* Allows to set the list of URL fragments that were sent with the request but
* were not parsed by the route. This function is usually called from within
* Spitfire's router to indicate the lack of a need for this elements.
*
* The content of this URL will be useful to you when defining greedy URLs.
*
* @param string[] $unparsed
*/
public function setUnparsed($unparsed) {
$this->unparsed = $unparsed;
return $this;
}
/**
* Replaces the parameters contained in this object in a string. This will
* look for prefixed versions of a kez and replace them with the key's value
* and then return the string.
*
* @param string $string The string to search for matches and replace
*/
public function replaceInString($string) {
foreach ($this->parameters as $key => $val) {
$string = str_replace(':'.$key, $val, $string);
}
return $string;
}
public function merge($with) {
$_return = new Parameters();
$_return->setParameters(array_merge($this->getParameters(), $with->getParameters()));
return $_return;
}
}
\ No newline at end of file
diff --git a/core/router/Router.php b/core/router/Router.php
index fb71203..d8be182 100644
--- a/core/router/Router.php
+++ b/core/router/Router.php
@@ -1,84 +1,89 @@
<?php namespace spitfire\core\router;
/**
* Routers are tools that allow your application to listen on alternative urls and
* attach controllers to different URLs than they would normally do. Please note
* that enabling a route to a certain controller does not disable it's canonical
* URL.
*
* @author César de la Cal <cesar@magic3w.com>
*/
class Router extends Routable
{
private $servers = Array();
/**
* This rewrites a request into a Path (or in given cases, a Response). This
* allows Spitfire to use the data from the Router to accordingly find a
* controller to handle the request being thrown at it.
*
* Please note that Spitfire is 'lazy' about it's routes. Once it found a valid
* one that can be used to respond to the request it will stop looking for
* another possible rewrite.
*
* @param string $server
* @param string $route
* @param string $method
* @param string $protocol
* @return Path|Response
*/
public function rewrite ($server, $route, $method, $protocol) {
#Ensures the base server is created
$this->server();
#Loop through the servers to find valid routes
$servers = $this->servers;
+ #Split up the URL, get the extension
+ $url = pathinfo($route, PATHINFO_DIRNAME) . '/' . pathinfo($route, PATHINFO_BASENAME);
+ $ext = pathinfo($route, PATHINFO_EXTENSION);
+
+ #Loop over the servers
foreach ($servers as $box) { /* @var $box Server */
- if (false !== $t = $box->rewrite($server, $route, $method, $protocol)) {
- return $t;
+ if (false !== $t = $box->rewrite($server, $url, $method, $protocol)) {
+ return $t instanceof \spitfire\core\Path? $t->setFormat($ext) : $t;
}
}
#Implicit else.
throw new \publicException('No such route', 404);
}
/**
*
* @param string $address
* @param int $protocol
* @return Server
*/
public function server($address = null) {
if ($address === null) {
$address = isset($_SERVER['HTTP_HOST'])? $_SERVER['HTTP_HOST'] : 'localhost';
}
if (isset($this->servers[$address])) { return $this->servers[$address]; }
return $this->servers[$address] = new Server($address, $this);
}
/**
* Returns the list of routes. This is usually called by the "Server" to
* retrieve generic routes
*
* @return Server[]
*/
public function getServers() {
return $this->servers;
}
/**
* Allows the router to act with a singleton pattern. This allows your app to
* share routes across several points of it.
*
* @staticvar Router $instance
* @return Router
*/
public static function getInstance() {
static $instance = null;
if ($instance) { return $instance; }
else { return $instance = new Router(); }
}
}
diff --git a/core/router/URIPattern.php b/core/router/URIPattern.php
index 787c812..2c69444 100644
--- a/core/router/URIPattern.php
+++ b/core/router/URIPattern.php
@@ -1,186 +1,186 @@
<?php namespace spitfire\core\router;
use spitfire\exceptions\PrivateException;
use Strings;
/*
* The MIT License
*
* Copyright 2017 César de la Cal Bretschneider <cesar@magic3w.com>.
*
* Permission is hereby granted, free of charge, to any person obtaining a copy
* of this software and associated documentation files (the "Software"), to deal
* in the Software without restriction, including without limitation the rights
* to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
* copies of the Software, and to permit persons to whom the Software is
* furnished to do so, subject to the following conditions:
*
* The above copyright notice and this permission notice shall be included in
* all copies or substantial portions of the Software.
*
* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
* IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
* FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
* AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
* LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
* OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
* THE SOFTWARE.
*/
/**
* The URIPattern class is a wrapper to gather several patterns and test them
* simultaneously.
*/
class URIPattern
{
/**
* The patterns used by this class to test the URL it receives.
*
* @var Pattern[]
*/
private $patterns;
/**
* Indicates whether this is an open ended pattern. This implies that it'll
* accept URL that are longer than the patterns it can test with (these
* additional parameters will get appended to the object).
*
* @var boolean
*/
private $open;
/**
* Instances a new URI Pattern. This class allows your application to test
* whether a URL matches the pattern you gave to the constructor.
*
* @param string $pattern
*/
public function __construct($pattern) {
/*
* If the pattern ends in a slash it's not considered open ended, this is
* important for how we parse the pattern.
*/
$this->open = !Strings::endsWith($pattern, '/');
/*
* The patterns allow the system to test the URL piece by piece, making it
* more granular.
*/
$this->patterns = array_values(array_map(function ($e) { return new Pattern($e); }, array_filter(explode('/', $pattern))));
}
/**
* Tests whether a given $uri matches the patterns that this object holds.
* Please note that if the URI is too long and the pattern is not open
* ended it will throw a missmatch exception.
*
* @param string $uri
* @return \spitfire\core\router\Parameters
* @throws RouteMismatchException
*/
public function test($uri) {
- $pieces = array_filter(explode('/', $uri));
+ $pieces = is_array($uri)? $uri : array_filter(explode('/', $uri));
$params = new Parameters();
/*
* Extract the extension. To do so, we check whether the last element on
* the URI has a dot in it, extract the extension and push the element
* back onto the array.
*/
$last = explode('.', array_pop($pieces));
$params->setExtension(isset($last[1])? array_pop($last) : 'php');
array_push($pieces, implode('.', $last));
/*
* Walk the patterns and test whether they're all satisfied. Remember that
* test raises an Exception when unsatisfied, so there's no need to check -
* if the code runs the route was satisfied
*/
for ($i = 0; $i < count($this->patterns); $i++) {
$params->addParameters($this->patterns[$i]->test(array_shift($pieces)));
}
if (count($pieces)) {
if ($this->open) { $params->setUnparsed($pieces); }
else { throw new RouteMismatchException('Too many parameters', 1705201331); }
}
return $params;
}
/**
* Takes a parameter list and constructs a string URI from the combination
* of patterns and parameters.
*
* @param type $parameters
* @return type
* @throws PrivateException
* @throws RouteMismatchException
*/
public function reverse($parameters) {
/*
* If the data we're receiving is a parameters object, then we'll extract
* the raw data from it in order to work with it.
*/
$params = $parameters instanceof Parameters? $parameters->getParameters() : $parameters;
$add = $parameters instanceof Parameters? $parameters->getUnparsed() : [];
/*
* If there is parameters that exceed the predefined length then we drop
* them if the route does not support them.
*/
if (!empty($add) && !$this->open) {
throw new PrivateException('This route rejects additional params', 1705221031);
}
/*
* Prepare a pair of variables we need for the loop. First of all we need
* to ensure that we have an array to write the replacements to, and then
* we need a counter to make sure that all the parameters given were also
* used.
*/
$replaced = [];
$left = count($params);
/*
* Loop over the patterns and test the parameters. There's one quirk - the
* system assumes that a parameter is never used twice but won't fail
* gracefully if the user ignores that restriction.
*/
foreach($this->patterns as $p) {
#Static URL elements
if(!$p->getName()) {
$replaced[] = $p->getPattern()[0];
continue;
}
/*
* For non static URL elements we check whether the appropriate parameter
* is defined. Then we test it and if the parameter was defined we will
* accept it.
*/
$defined = isset($params[$p->getName()])? $params[$p->getName()] : null;
$replaced = array_merge($replaced, $p->test($defined));
$defined? $left-- : null;
}
/*
* Leftover parameters indicate that the system was unable to reverse the
* route properly
*/
if ($left) {
throw new PrivateException('Parameter count exceeded pattern count', 1705221044);
}
return '/' . implode('/', array_merge($replaced, $add)) . '/';
}
public static function make($str) {
if ($str instanceof URIPattern) { return $str; }
elseif (is_string($str)) { return new URIPattern($str); }
else { throw new \InvalidArgumentException('Invalid pattern for URIPattern::make', 1706091621); }
}
}
diff --git a/core/router/reverser/ServerReverserInterface.php b/core/router/reverser/ServerReverserInterface.php
index 22771f4..74e999d 100644
--- a/core/router/reverser/ServerReverserInterface.php
+++ b/core/router/reverser/ServerReverserInterface.php
@@ -1,17 +1,28 @@
<?php namespace spitfire\core\router\reverser;
/**
- * {Needs doc}
+ * This interface allows a developer to create server reversers for servers.
+ * Usually the servers are less complicated than routes and therefore won't
+ * require any dedicated reverser, but any class implementing this can suit
+ * that role.
*/
interface ServerReverserInterface
{
/**
+ * Reverses the Server hostname from a set of parameters, allowing to reassemble
+ * a full absolute URL from the given parameters.
+ *
* @param string[] $parameters
- * @return bool|string The rewriten server URI or
+ * @return bool|string The rewriten server URI or false if there isn't
*/
function reverse($parameters);
- /** @return \spitfire\core\router\Server */
+ /**
+ * Gets the Server for the reverser. This allows the app to extract the server's
+ * routes to merge them with the global routes.
+ *
+ * @return \spitfire\core\router\Server
+ */
function getServer();
}
diff --git a/tests/router/RouterTest.php b/tests/router/RouterTest.php
index 157754e..092052b 100644
--- a/tests/router/RouterTest.php
+++ b/tests/router/RouterTest.php
@@ -1,139 +1,150 @@
<?php namespace tests\spitfire\core\router\Route;
/*
* This file helps testing the basic functionality of Spitfire's router. It will
* check that rewriting basic strings and Objects will work properly.
*/
use PHPUnit\Framework\TestCase;
use spitfire\core\router\Route;
use spitfire\core\router\Router;
class RouterTest extends TestCase
{
private $router;
public function setUp() {
$this->router = new Router();
}
/**
* Tests the creation of routes. This will just request the router to create
* a route and verify that the returned value is a Route and not something
* else.
*/
public function testCreateRoute() {
$route = $this->router->get('/test', 'test2');
$this->assertInstanceOf('\spitfire\core\router\Redirection', $route);
}
/**
* This method tests the different string rewriting options that Spitfire
* will provide you with when creating routes.
*/
public function testStringRoute() {
$router = $this->router;
#Prepare a route that redirects with no parameters
$route = $router->get('/test', 'test2');
$this->assertEquals(true, $route->test('/test', 'GET', Route::PROTO_HTTP, $router->server()));
$this->assertEquals('/test2/', $route->rewrite('/test', 'GET', Route::PROTO_HTTP, $router->server()));
$this->assertEquals(false, $route->test('/test', 'POST', Route::PROTO_HTTP, $router->server()));
//> This last test should fail because we're sending a POST request to a GET route
#Prepare a route that redirects with parameters
$route2 = $router->get('/another/:param', '/:param/another');
$this->assertEquals('/test/another/', $route2->rewrite('/another/test', 'GET', Route::PROTO_HTTP, $router->server()));
$this->assertEquals('/test/another/', $route2->rewrite('/another/test/', 'GET', Route::PROTO_HTTP, $router->server()));
$this->assertEquals(false, $route2->test('/another/test', 'POST', Route::PROTO_HTTP, $router->server()));
}
public function testTrailingSlashStringRoute() {
$router = $this->router;
#Create a route with a trailing slash
$route1 = $router->get('/this/is/a/test/', '/output/');
$this->assertEquals(true, $route1->test('/this/is/a/test', 'GET', Route::PROTO_HTTP, $router->server()), 'The route should match a route without trailing slash');
$this->assertEquals(true, $route1->test('/this/is/a/test/', 'GET', Route::PROTO_HTTP, $router->server()), 'The route should match a route with a trailing slash');
$this->assertEquals( '/output/', $route1->rewrite('/this/is/a/test/', 'GET', Route::PROTO_HTTP, $router->server()), 'The route should match a route with a trailing slash');
$this->assertNotEquals('/output/', $route1->rewrite('/this/is/a/test/with/extra', 'GET', Route::PROTO_HTTP, $router->server()), 'The route should not match additional pieces');
#Create a route without a trailing slash
$route2 = $router->get('/this/is/a/test', '/output/');
$this->assertEquals(true, $route2->test('/this/is/a/test/with/more/fragments', 'GET', Route::PROTO_HTTP, $router->server()), 'The route shoud match a route with additional fragments');
$this->assertEquals(true, $route2->test('/this/is/a/test/', 'GET', Route::PROTO_HTTP, $router->server()), 'The route shoud match a route with a trailing slash');
$this->assertEquals('/output/', $route2->rewrite('/this/is/a/test/', 'GET', Route::PROTO_HTTP, $router->server()), 'The route should rewrite a string without additional frgaments fine');
$this->assertEquals(false, $route2->rewrite('/this/is/a/test/with/strings', 'GET', Route::PROTO_HTTP, $router->server()), 'The route should remove additional fragments.');
#Create a route without a trailing slash with a target without trailing slash
#This addresses a bug found on 16/11/2014
$route3 = $router->get('/this/is/a/test', '/output');
$this->assertEquals(true, $route3->test('/this/is/a/test/with/more/fragments', 'GET', Route::PROTO_HTTP, $router->server()), 'The route shoud match a route with additional fragments');
$this->assertEquals(true, $route3->test('/this/is/a/test/', 'GET', Route::PROTO_HTTP, $router->server()), 'The route shoud match a route with a trailing slash');
$this->assertEquals('/output/', $route3->rewrite('/this/is/a/test/', 'GET', Route::PROTO_HTTP, $router->server()), 'The route should rewrite a string without additional frgaments fine');
$this->assertEquals('/output/with/strings/', $route3->rewrite('/this/is/a/test/with/strings', 'GET', Route::PROTO_HTTP, $router->server()), 'The route should rewrite a string with additional fragments fine.');
}
public function testArrayRoute() {
$router = $this->router;
#Rewrite a parameter based URL into an array
$route = $router->get('/:param1/:param2', Array('controller' => ':param1', 'action' => ':param2'));
#Test whether matching works for the array string
$this->assertEquals(true, $route->test('/another/test', 'GET', Route::PROTO_HTTP, $router->server()));
#Test if the route returns a Path object
$this->assertInstanceOf('\spitfire\core\Path', $route->rewrite('/another/test', 'GET', Route::PROTO_HTTP, $router->server()));
#Test if the server returns a Patch object
$this->assertInstanceOf('\spitfire\core\Path', $router->server()->rewrite('localhost', '/another/test', 'GET', Route::PROTO_HTTP));
#Test if the rewriting succeeded and the data was written in the right spot
$path = $router->rewrite('localhost', '/another/test', 'GET', Route::PROTO_HTTP);
$this->assertEquals('another', current($path->getController()));
$this->assertEquals('test', $path->getAction());
}
public function testArrayRouteWithStaticFragments() {
$router = $this->router;
#Rewrite a parameter based URL into an array
$router->get('/:param1/:param2', Array('controller' => ':param1', 'action' => 'something', 'object' => ':param2'));
#Test if the rewriting succeeded and the data was written in the right spot
$path = $router->rewrite('localhost', '/another/test', 'GET', Route::PROTO_HTTP);
$this->assertEquals('another', current($path->getController()));
$this->assertEquals('something', $path->getAction());
$this->assertEquals(Array('test'), $path->getObject());
}
public function testOptionalParameters() {
$router = $this->router;
$router->get('/test/:param1?optional', Array('controller' => ':param1'));
$p1 = $router->rewrite('localhost', '/test/provided', 'GET', Route::PROTO_HTTP);
$p2 = $router->rewrite('localhost', '/test/', 'GET', Route::PROTO_HTTP);
$p3 = $router->rewrite('localhost', '/some/', 'GET', Route::PROTO_HTTP);
$this->assertEquals('provided', current($p1->getController()));
$this->assertEquals('optional', current($p2->getController()));
$this->assertEquals(false, $p3);
}
+ public function testExtension() {
+ $router = $this->router;
+ $router->get('/test/:param1', Array('controller' => ':param1'));
+
+ $p1 = $router->rewrite('localhost', '/test/provided.xml', 'GET', Route::PROTO_HTTP);
+ $p2 = $router->rewrite('localhost', '/test/provided.json', 'GET', Route::PROTO_HTTP);
+
+ $this->assertEquals('xml', $p1->getFormat());
+ $this->assertEquals('json', $p2->getFormat());
+ }
+
public function testExtraction() {
$router = $this->router;
$reverse = \spitfire\core\router\ParametrizedPath::fromArray(['controller' => ':p']);
$router->get('/test/:param1', Array('controller' => ':param1'));
$rewrite = $router->rewrite('localhost', '/test/provided', 'GET', Route::PROTO_HTTP);
$data = $reverse->extract($rewrite);
$this->assertEquals('provided', $data->getParameter('p'));
}
}
\ No newline at end of file

File Metadata

Mime Type
text/x-diff
Expires
Apr 13 2021, 9:24 PM (9 w, 6 s ago)
Storage Engine
blob
Storage Format
Raw Data
Storage Handle
1461
Default Alt Text
(22 KB)

Event Timeline