Page MenuHomePhabricator

No OneTemporary

diff --git a/App.php b/App.php
index 199bd0b..0a0d4bd 100644
--- a/App.php
+++ b/App.php
@@ -1,210 +1,210 @@
<?php namespace spitfire;
use Controller;
use spitfire\exceptions\PublicException;
use ReflectionClass;
use spitfire\ClassInfo;
use spitfire\core\Context;
use spitfire\core\Path;
use spitfire\core\router\Parameters;
use spitfire\core\router\Router;
use spitfire\exceptions\PrivateException;
use URL;
/**
* Spitfire Application Class. This class is the base of every other 'app', an
* app is a wrapper of controllers (this allows to plug them into other SF sites)
* that defines a set of rules to avoid collissions with the rest of the apps.
*
* Every app resides inside of a namespace, this externally defined variable
* defines what calls Spitfire redirects to the app.
*
* @author César de la Cal<cesar@magic3w.com>
* @last-revision 2013-10-11
*/
abstract class App
{
/**
* The basedir is the root directory of an application. For spitfire this is
* usually the /bin directory. This directory contains all the app specific
* data. Including controllers, views and models.
*
* In the specific case of Spitfire this folder also contains the 'child apps'
* that can be added to it.
*
* @var string
*/
private $basedir;
private $URISpace;
/**
* Creates a new App. Receives the directory where this app resides in
* and the URI namespace it uses.
*
* @param string $basedir The root directory of this app
* @param string $URISpace The URI namespace it 'owns'
*/
public function __construct($basedir, $URISpace) {
$this->basedir = $basedir;
$this->URISpace = $URISpace;
}
/**
* Generates a URL. This method will generate a string / URL depending on the
* string being inputted.
*
* @param string $url Generates a URL for the current app
* @return URL|string
*/
public function url($url) {
if (0 === strpos($url, 'http://')) { return $url; }
if (0 === strpos($url, 'https://')) { return $url; }
if (0 === strpos($url, 'www.')) { return 'http://' . $url; }
$arguments = func_get_args();
array_unshift($arguments, $this);
//Once PHP7 is stable we can use the splat (...) operator to do this
$reflection = new ReflectionClass('\URL');
return $reflection->newInstanceArgs($arguments);
}
public function getBaseDir() {
return $this->basedir;
}
public function getURISpace() {
return $this->URISpace;
}
public function getDirectory($contenttype) {
switch($contenttype) {
case ClassInfo::TYPE_CONTROLLER:
return $this->getBaseDir() . 'controllers/';
case ClassInfo::TYPE_MODEL:
return $this->getBaseDir() . 'models/';
case ClassInfo::TYPE_VIEW:
return $this->getBaseDir() . 'views/';
case ClassInfo::TYPE_LOCALE:
return $this->getBaseDir() . 'locales/';
case ClassInfo::TYPE_BEAN:
return $this->getBaseDir() . 'beans/';
case ClassInfo::TYPE_COMPONENT:
return $this->getBaseDir() . 'components/';
case ClassInfo::TYPE_STDCLASS:
return $this->getBaseDir() . 'classes/';
case ClassInfo::TYPE_APP:
return $this->getBaseDir() . 'apps/';
}
}
/**
* Checks if the current application has a controller with the name specified
* by the single argument this receives. In case a controller is found and
* it is not abstract the app will return the fully qualified class name of
* the Controller.
*
* It should not be necessary to check the return value with the === operator
* as the return value on success should never be matched otherwise.
*
* @param string $name The name of the controller being searched
* @return string|boolean The name of the class that has the controller
*/
public function hasController($name) {
$name = (array)$name;
$c = $this->getNameSpace() . implode('\\', $name) . 'Controller';
if (!class_exists($c)) { return false; }
$reflection = new ReflectionClass($c);
if ($reflection->isAbstract()) { return false; }
return $c;
}
/**
* Creates a new Controller inside the context of the request. Please note
* that this may throw an Exception due to the controller not being found.
*
* @param string $controller
* @param Context $intent
* @return Controller
* @throws PublicException
*/
public function getController($controller, Context$intent) {
#Get the controllers class name. If it doesn't exist it'll be false
$c = $this->hasController($controller);
#If no controller was found, we can throw an exception letting the user know
if ($c === false) { throw new PublicException("Page not found", 404, new PrivateException("Controller {$controller[0]} not found", 0) ); }
#Otherwise we will instantiate the class and return it
return new $c($intent);
}
public function getControllerURI($controller) {
return explode('\\', substr(get_class($controller), strlen($this->getNameSpace()), 0-strlen('Controller')));
}
public function getView(Controller$controller) {
$name = implode('\\', $this->getControllerURI($controller));
$c = $this->getNameSpace() . $name . 'View';
if (!class_exists($c)) { $c = \spitfire\mvc\View::class; }
return new $c($controller->context);
}
public function getControllerDirectory() {
return $this->getBaseDir() . 'controllers/';
}
/**
* Creates the default routes for this application. Spitfire will assume that
* a /app/controller/action/object type of path is what you wish to use for
* your app. If you'd rather have custom rules - feel free to override these.
*/
public function createRoutes() {
$ns = $this->URISpace? '/' . $this->URISpace : '';
$uriSpace = $this->URISpace;
#The default route just returns a path based on app/controller/action/object
#If your application does not wish this to happen, please override createRoutes
#with your custome code.
$default = Router::getInstance()->request($ns, function (Parameters$params) use ($uriSpace) {
$args = $params->getUnparsed();
- return new Path($uriSpace, array_shift($args), array_shift($args), $args, $params->getExtension());
+ return new Path($uriSpace, array_shift($args), array_shift($args), $args);
});
#The reverser for the default route is rather simple again.
#It will concatenate app, controller and action
$default->setReverser(new core\router\reverser\ClosureReverser(function (Path$path) {
$app = $path->getApp();
$controller = $path->getController();
$action = $path->getAction();
$object = $path->getObject();
if ($controller === (array)core\Environment::get('default_controller')) { $controller = Array(); }
if ($action === core\Environment::get('default_action')) { $action = ''; }
return '/' . trim(implode('/', array_filter(array_merge([$app], (array)$controller, [$action], $object))), '/');
}));
}
abstract public function enable();
abstract public function getNameSpace();
abstract public function getAssetsDirectory();
/**
* Returns the directory the templates are located in. This function should be
* avoided in favor of the getDirectory function.
*
* @deprecated since version 0.1-dev 20150423
*/
public function getTemplateDirectory() {
return $this->getBaseDir() . 'templates/';
}
}
diff --git a/core/router/Parameters.php b/core/router/Parameters.php
index 24a9f28..13ff908 100644
--- a/core/router/Parameters.php
+++ b/core/router/Parameters.php
@@ -1,146 +1,121 @@
<?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/RewriteRule.php b/core/router/RewriteRule.php
index e80172a..577633c 100644
--- a/core/router/RewriteRule.php
+++ b/core/router/RewriteRule.php
@@ -1,181 +1,171 @@
<?php namespace spitfire\core\router;
use Closure;
/*
* 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.
*/
/**
* A route is a class that rewrites a URL path (route) that matches a
* route or pattern (old_route) into a new route that the system can
* use (new_route) to handle the current request.
*
* A Route will only accept Closures, Responses or Paths (including arrays that
* can be interpreted as Paths by the translation class) as the target.
*
* @todo Define translate class for array to Path translation
* @todo Define parameter class to replace inside Paths
* @author César de la Cal <cesar@magic3w.com>
*/
abstract class RewriteRule
{
/* These constants are meant for evaluating if a request should be answered
* depending on if the request is done via HTTP(S). This is especially useful
* when your application wants to enforce HTTPS for certain requests.
*/
const PROTO_HTTP = 0x01;
const PROTO_HTTPS = 0x02;
const PROTO_ANY = 0x03;
/* These constants are intended to allow routes to react differently depending
* on the METHOD used to issue the request the server is receiving. Spitfire
* accepts any of the standard GET, POST, PUT or DELETE methods.
*/
const METHOD_GET = 0x01;
const METHOD_POST = 0x02;
const METHOD_PUT = 0x04;
const METHOD_DELETE = 0x08;
const METHOD_HEAD = 0x10;
/**
* This var holds a reference to a route server (an object containing a pattern
* to match virtualhosts) that isolates this route from the others.
*
* @var Routable
*/
private $server;
private $pattern;
private $patternStr;
private $newRoute;
- private $parameters;
private $method;
private $protocol;
/**
* A route is a pattern Spitfire uses to redirect an URL to something else.
* It can 'redirect' (without it being a 302) a request to a new URL, it can
* directly send back a response or assign a custom controller, action and
* object to the request.
*
* @param Routable $server The server this route belongs to
* @param string $pattern
* @param Closure|ParametrizedPath|array $new_route
* @param string $method
* @param int $proto
*/
public function __construct(Routable$server, $pattern, $new_route, $method, $proto = Route::PROTO_ANY) {
$this->server = $server;
$this->newRoute = $new_route;
$this->method = $method;
$this->protocol = $proto;
$this->patternStr = $pattern;
$this->pattern = URIPattern::make($pattern);
}
/**
* Checks whether a certain method applies to this route. The route can accept
* as many protocols as it wants. The protocols are converted to hex integers
* and are AND'd to check whether the selected protocol is included in the
* list of admitted ones.
*
* @param string|int $method
* @return boolean
*/
public function testMethod($method) {
if (!is_numeric($method)) {
switch ($method){
case 'GET' : $method = self::METHOD_GET; break;
case 'POST': $method = self::METHOD_POST; break;
case 'HEAD': $method = self::METHOD_HEAD; break;
case 'PUT' : $method = self::METHOD_PUT; break;
case 'DELETE': $method = self::METHOD_DELETE; break;
}
}
return $this->method & $method;
}
/**
* Tests whether the requested protocol (HTTPS or not) is accepted by this
* route. We use once again binary masks to test the protocol. This means that
* we can either use HTTP (01), HTTPS (10) or both (11) which will translate
* into an integer 1, 2 and 3.
*
* This way the user can quickly decide whether he wants to use any or both
* of them to match a route.
*
* @param boolean $protocol
* @return boolean
*/
public function testProto($protocol) {
if (!is_int($protocol)) {
$protocol = ($protocol && $protocol != 'off')? Route::PROTO_HTTPS : Route::PROTO_HTTP;
}
return $this->protocol & $protocol;
}
public function test($URI, $method, $protocol) {
try {
#First we test the URI. Since the URI will throw an exception, we need
#to catch it and return false.
$this->pattern->test($URI);
return $this->testMethod($method) && $this->testProto($protocol);
}
catch (RouteMismatchException$e) {
return false;
}
}
-
- public function getParameters($keys = false) {
- if (!$keys) { return $this->parameters; }
-
- $array = array_keys($this->parameters);
- array_walk($array, function(&$e) {$e = ':' . $e;});
- return $array;
- }
-
/**
*
* @return URIPattern
*/
public function getTarget() {
return $this->newRoute;
}
/**
*
* @return URIPattern
*/
public function getSource() {
return $this->pattern;
}
/**
*
* @todo Server parameter should be a Parameter set provided by the server IMO
* Need to check whether the server itself could have any impact on it
*/
abstract public function rewrite($URI, $method, $protocol, $server);
}
diff --git a/core/router/Route.php b/core/router/Route.php
index ef72044..fb74154 100644
--- a/core/router/Route.php
+++ b/core/router/Route.php
@@ -1,125 +1,80 @@
<?php namespace spitfire\core\router;
use Closure;
use Exception;
use spitfire\core\router\reverser\ClosureReverser;
use spitfire\core\router\reverser\RouteReverserInterface;
/**
* A route is a class that rewrites a URL path (route) that matches a
* route or pattern (old_route) into a new route that the system can
* use (new_route) to handle the current request.
*
* A Route will only accept Closures, Responses or Paths (including arrays that
* can be interpreted as Paths by the translation class) as the target.
*
- * @todo Define translate class for array to Path translation
- * @todo Define parameter class to replace inside Paths
* @author César de la Cal <cesar@magic3w.com>
*/
class Route extends RewriteRule
{
/* These constants are meant for evaluating if a request should be answered
* depending on if the request is done via HTTP(S). This is especially useful
* when your application wants to enforce HTTPS for certain requests.
*/
const PROTO_HTTP = 0x01;
const PROTO_HTTPS = 0x02;
const PROTO_ANY = 0x03;
/* These constants are intended to allow routes to react differently depending
* on the METHOD used to issue the request the server is receiving. Spitfire
* accepts any of the standard GET, POST, PUT or DELETE methods.
*/
const METHOD_GET = 0x01;
const METHOD_POST = 0x02;
const METHOD_PUT = 0x04;
const METHOD_DELETE = 0x08;
const METHOD_HEAD = 0x10;
- private $parameters;
-
private $reverser = null;
/**
- * Tests all the elements of a pattern to see whether the tested route is
- * valid or not and to fetch the parameters for it. In case the route and the
- * URL match we will have an array of parameters in the route that allow us
- * to customize a request.
- *
- * @throws RouteMismatchException In case the route was not valid.
- * @param Pattern[] $pattern
- * @param string[] $array
- */
- protected function patternWalk($pattern, $array) {
- foreach ($pattern as $p) {
- $this->parameters->addParameters($p->test(array_shift($array)));
- }
- $this->parameters->setUnparsed($array);
- }
-
- /**
- * Tests if a URL matches the current Route. If so it will return true and you
- * can use the parameters in it.
*
* @param string $URI
- * @return boolean
+ * @param string $method
+ * @param string $protocol
+ * @param Parameters $server
+ * @param string $extension
+ * @return \spitfire\core\Path|\spitfire\core\Response
*/
- public function testURI($URI) {
- $array = array_filter(explode('/', $URI));
+ public function rewrite($URI, $method, $protocol, $server, $extension = 'php') {
+ $params = $this->getSource()->test($URI);
- $this->parameters = new Parameters();
-
- #Check the extension
- $last = explode('.', array_pop($array));
- $this->parameters->setExtension(isset($last[1])? array_pop($last) : 'php');
- array_push($array, implode('.', $last));
-
- try {
- $this->patternWalk($this->getSource(), $array);
- return true;
- } catch(RouteMismatchException $e) {
- return false;
- }
- }
-
- public function rewrite($URI, $method, $protocol, $server) {
- if ($this->test($URI, $method, $protocol)) {
- if ($this->getTarget() instanceof Closure) {return call_user_func_array($this->getTarget(), Array($this->parameters, $server->getParameters()));}
- if ($this->getTarget() instanceof ParametrizedPath) { return $this->getTarget()->replace($server->getParameters()->merge($this->getSource()->test($URI))); }
- }
- return false;
- }
-
- public function getParameters($keys = false) {
- if (!$keys) { return $this->parameters; }
+ if ($this->getTarget() instanceof Closure) {return call_user_func_array($this->getTarget(), Array($params, $server->getParameters()));}
+ if ($this->getTarget() instanceof ParametrizedPath) { return $this->getTarget()->replace($server->getParameters()->merge($this->getSource()->test($URI))); }
- $array = array_keys($this->parameters);
- array_walk($array, function(&$e) {$e = ':' . $e;});
- return $array;
}
/**
*
* @return RouteReverserInterface
*/
public function getReverser() {
if ($this->reverser || !$this->getTarget() instanceof ParametrizedPath) { return $this->reverser; }
return $this->reverser = new ClosureReverser(function ($path) {
try { return $this->getSource()->reverse($this->getTarget()->extract($path)); }
catch (Exception$e) { return false; }
});
}
/**
*
* @param RouteReverserInterface $reverser
* @return Route
*/
public function setReverser($reverser) {
$this->reverser = $reverser;
return $this;
}
}
diff --git a/core/router/Router.php b/core/router/Router.php
index d8be182..e45d947 100644
--- a/core/router/Router.php
+++ b/core/router/Router.php
@@ -1,89 +1,95 @@
<?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.
*
+ * @todo The extension should be passed down to the servers (and therefore
+ * the routes) to allow the routes to respond to different requests properly.
+ *
* @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, $url, $method, $protocol)) {
+ if (false !== $t = $box->rewrite($server, $url, $method, $protocol, $ext)) {
+ /**
+ * @fixme In a future, the extension should be written to the path elsewhere
+ */
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 2c69444..f42ef38 100644
--- a/core/router/URIPattern.php
+++ b/core/router/URIPattern.php
@@ -1,186 +1,177 @@
<?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 = 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); }
}
}

File Metadata

Mime Type
text/x-diff
Expires
Apr 12 2021, 5:08 PM (9 w, 1 d ago)
Storage Engine
blob
Storage Format
Raw Data
Storage Handle
1447
Default Alt Text
(32 KB)

Event Timeline