Page MenuHomePhabricator

No OneTemporary

diff --git a/App.php b/App.php
index 847a6cb..26210d4 100644
--- a/App.php
+++ b/App.php
@@ -1,197 +1,202 @@
<?php namespace spitfire;
use Controller;
use ReflectionClass;
use spitfire\ClassInfo;
use spitfire\core\Context;
use spitfire\core\Environment;
use spitfire\core\Path;
use spitfire\core\router\Parameters;
use spitfire\core\router\reverser\ClosureReverser;
use spitfire\core\router\Router;
use spitfire\exceptions\PrivateException;
use spitfire\exceptions\PublicException;
use spitfire\mvc\View;
/**
* 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;
}
public function getBaseDir() {
return $this->basedir;
}
public function getURISpace() {
return $this->URISpace;
}
/**
*
* @deprecated since version 0.1-dev 20171129
* @param type $contenttype
* @return type
*/
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 = View::class; }
return new $c($controller->context);
}
+ /**
+ *
+ * @deprecated since version 0.1-dev 20180524
+ * @return type
+ */
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, Parameters$server, $extension) use ($uriSpace) {
$args = $params->getUnparsed();
return new Path($uriSpace, array_shift($args), array_shift($args), $args, $extension);
});
#The reverser for the default route is rather simple again.
#It will concatenate app, controller and action
$default->setReverser(new ClosureReverser(function (Path$path, $explicit = false) {
$app = $path->getApp();
$controller = $path->getController();
$action = $path->getAction();
$object = $path->getObject();
if ($controller === (array)Environment::get('default_controller') && empty($object) && !$explicit) { $controller = Array(); }
if ($action === Environment::get('default_action') && empty($object) && !$explicit) { $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/SpitFire.php b/SpitFire.php
index c5eafa0..9502f70 100644
--- a/SpitFire.php
+++ b/SpitFire.php
@@ -1,197 +1,198 @@
<?php namespace spitfire;
use spitfire\App;
use spitfire\core\Context;
use spitfire\core\Environment;
use spitfire\core\Request;
use spitfire\core\Response;
use spitfire\exceptions\ExceptionHandler;
use spitfire\exceptions\PrivateException;
use Strings;
if (!defined('APP_DIRECTORY')){
define ('APP_DIRECTORY', 'bin/apps/', true);
define ('CONFIG_DIRECTORY', 'bin/settings/', true);
define ('ASSET_DIRECTORY', 'assets/', true);
define ('CONTROLLERS_DIRECTORY', 'bin/controllers/', true);
define ('TEMPLATES_DIRECTORY', 'bin/templates/', true);
}
/**
* Dispatcher class of Spitfire. Calls all the required classes for Spitfire to run.
*
* @author César de la Cal <cesar@magic3w.com>
* @package spitfire
*/
class SpitFire extends App
{
static $started = false;
private $cwd;
private $request;
private $debug;
private $apps = Array();
public function __construct() {
#Check if SF is running
if (self::$started) { throw new PrivateException('Spitfire is already running'); }
#Set the current working directory
$this->cwd = BASEDIR;
#Import the exception handler for logging
$this->debug = ExceptionHandler::getInstance();
#Call parent
parent::__construct('bin/', null);
self::$started = true;
}
public function prepare() {
#Try to include the user's evironment & routes
ClassInfo::includeIfPossible(CONFIG_DIRECTORY . 'environments.php');
ClassInfo::includeIfPossible(CONFIG_DIRECTORY . 'routes.php');
#Define the current timezone
date_default_timezone_set(Environment::get('timezone'));
#Set the display errors directive to the value of debug
ini_set("display_errors" , Environment::get('debug_mode')? '1' : '0');
}
public function fire() {
#Import the apps
ClassInfo::includeIfPossible(CONFIG_DIRECTORY . 'apps.php');
foreach ($this->apps as $app) { $app->createRoutes(); }
$this->createRoutes();
#Get the current path...
$request = $this->request = Request::fromServer();
#If the user responded to the current route with a response we do not need
#to handle the request
if (!$request instanceof Response) {
#Start debugging output
ob_start();
#If the request has no defined controller, action and object it will define
#those now.
$path = $request->getPath();
#Receive the initial context for the app. The controller can replace this later
/*@var $initContext Context*/
$initContext = ($path instanceof Context)? $path : $request->makeContext();
- #Define the context for the helper function lang()
+ #Define the context, include the application's middleware configuration.
current_context($initContext);
+ include CONFIG_DIRECTORY . 'middleware.php';
#Get the return context
/*@var $context Context*/
$context = $initContext->run();
#End debugging output
$context->view->set('_SF_DEBUG_OUTPUT', ob_get_clean());
#Send the response
$context->response->send();
} else {
$request->getPath()->send();
}
}
public function registerApp($app, $namespace) {
$this->apps[$namespace] = $app;
}
public function appExists($namespace) {
if (!is_string($namespace)) { return false; }
return isset($this->apps[$namespace]);
}
public function findAppForClass($name) {
if (empty($this->apps)) return $this;
foreach($this->apps as $app) {
if (Strings::startsWith($name, $app->getNameSpace())) {
return $app;
}
}
return $this;
}
/**
*
* @param string $namespace
* @return App
*/
public function getApp($namespace) {
return isset($this->apps[$namespace]) ? $this->apps[$namespace] : $this;
}
public static function baseUrl(){
if (Environment::get('base_url')) { return Environment::get('base_url'); }
list($base_url) = explode('/index.php', $_SERVER['PHP_SELF'], 2);
return $base_url;
}
public function log($msg) {
if ($this->debug) $this->debug->log ($msg);
return $msg;
}
public function getMessages() {
return $this->debug->getMessages();
}
public function getCWD() {
return $this->cwd;
}
/**
* Returns the directory the assets are located in. This function should be
* avoided in favor of the ASSET_DIRECTORY constant.
*
* Please note that the fact that this function provides a bit more flexibility
* than a constant would also allow users to create erratic patterns.
*
* @deprecated since version 0.1-dev 20150423
*/
public function getAssetsDirectory() {
return ASSET_DIRECTORY;
}
/**
* Returns the directory the templates are located in. This function should be
* avoided in favor of the TEMPLATE_DIRECTORY constant.
*
* @deprecated since version 0.1-dev 20150423
*/
public function getTemplateDirectory() {
return TEMPLATES_DIRECTORY;
}
public function enable() {
return null;
}
/**
* Returns the current request.
*
* @return Request The current request
*/
public function getRequest() {
return $this->request;
}
public function getNameSpace() {
return '';
}
}
diff --git a/core/Context.php b/core/Context.php
index 4a9212e..3e1bf7e 100644
--- a/core/Context.php
+++ b/core/Context.php
@@ -1,122 +1,135 @@
<?php namespace spitfire\core;
-use \spitfire\App;
use Controller;
use publicException;
+use spitfire\App;
use spitfire\cache\MemcachedAdapter;
use spitfire\core\annotations\ActionReflector;
+use spitfire\core\annotations\AnnotationParser;
use spitfire\core\Request;
use spitfire\core\Response;
use spitfire\exceptions\PrivateException;
use spitfire\InputSanitizer;
use spitfire\io\session\Session;
+use spitfire\mvc\middleware\MiddlewareStack;
use spitfire\mvc\View;
+use function spitfire;
/**
* The context is a wrapper for an Intent. Basically it describes a full request
* for a page inside Spitfire. Usually you would have a single Context in any
* execution.
*
* Several contexts will usually only be found in Unit Tests that mock the context,
* when using nested controllers or in a CLI application.
*
* @link http://www.spitfirephp.com/wiki/index.php/Class:Context
* @author César de la Cal <cesar@magic3w.com>
* @last-revision 2013-10-25
*/
class Context
{
/**
* This is a reference to the context itself. This is a little helper for the
* View and Controller objects that do expose the Context's elements via Magic
* methods, this way we do not need any extra cases for the context.
*
* @var Context
*/
public $context;
+ public $middleware;
+
/**
* The application running the current context. The app will provide the controller
* to handle the request / context provided.
*
* @var App
*/
public $app;
/**
* The controller is in charge of preparig a proper response to the request.
* This is the first logical level that is user-defined.
*
* @var Controller
*/
public $controller;
public $action;
public $object;
public $extension;
+ public $annotations;
/**
* Holds the view the app uses to handle the current request. This view is in
* charge of rendering the page once the controller has finished processing
* it.
*
* @var View
*/
public $view;
public $parameters;
public $get;
public $post;
public $cache;
/**
*
* @var Request
*/
public $request;
public $response;
public $session;
function __construct() {
$this->context = $this;
}
public static function create() {
$context = new Context;
$context->get = new InputSanitizer($_GET);
$context->post = new InputSanitizer($_POST);
$context->session = Session::getInstance();
$context->cache = MemcachedAdapter::getInstance();
$context->request = Request::get();
$context->parameters = new InputSanitizer($context->request->getPath()->getParameters());
$context->response = new Response($context);
+ $context->middleware = new MiddlewareStack($context);
$context->app = spitfire()->getApp($context->request->getPath()->getApp());
$context->controller = $context->app->getController($context->request->getPath()->getController(), $context);
$context->action = $context->request->getPath()->getAction();
$context->object = $context->request->getPath()->getObject();
$context->view = $context->app->getView($context->controller);
+
+ $reflector = new \ReflectionMethod($context->controller, $context->action);
+ $annotationParser = new AnnotationParser();
+ $context->annotations = $annotationParser->parse($reflector->getDocComment());
+
return $context;
}
public function run() {
#Run the onload
if (method_exists($this->controller, '_onload') ) {
call_user_func_array(Array($this->controller, '_onload'), Array($this->action));
}
- $reflector = new ActionReflector($this->controller, $this->action);
- $reflector->execute();
+ $this->middleware->before();
#Check if the controller can handle the request
$request = Array($this->controller, $this->action);
if (is_callable($request)) { $_return = call_user_func_array($request, $this->object); }
else { throw new publicException('Page not found', 404, new PrivateException('Action not found', 0)); }
+ $this->middleware->after();
+
if ($_return instanceof Context) { return $_return; }
else { return $this; }
}
public function __clone() {
$this->context = $this;
}
}
diff --git a/core/annotations/ActionReflector.php b/core/annotations/ActionReflector.php
deleted file mode 100644
index 4ead509..0000000
--- a/core/annotations/ActionReflector.php
+++ /dev/null
@@ -1,195 +0,0 @@
-<?php namespace spitfire\core\annotations;
-
-use Controller;
-use Exception;
-use ReflectionClass;
-use spitfire\exceptions\PublicException;
-use function current_context;
-
-/**
- * This tool allows to parse annotations for an action. An action is a method of
- * a controller, that can handle requests and therefore prepare a response.
- * Annotations are special comments with lines which start with an @, just like
- * the author annotation on this class.
- *
- * @author César de la Cal <cesar@magic3w.com>
- */
-class ActionReflector
-{
- /**
- * The controller which's action shall be tested for annotations. It can be
- * either an instance of Controller or a string, with the name of the
- * controller class.
- *
- * @var Controller|string
- */
- private $controller;
-
- /**
- * The name of the method that has annotations to be parsed. This allows to
- * modify the app's behavior based on annotations which are quick and easy
- * to write and understand.
- *
- * @var string
- */
- private $action;
-
- /**
- * Builds a new Reflector of an action. This reflectors allow to modify the
- * app's behavior based on annotations to allow developers using Spitfire
- * to modify it's behavior quickly and seamlessly.
- *
- * @param Controller|string $controller
- * @param string $method
- */
- public function __construct($controller, $method) {
- $this->controller = $controller;
- $this->action = $method;
- }
-
- /**
- * Parses the annotations for the action and modifies the behavior based on
- * their content. This method will collect the annotations and the loop through
- * them calling a method that executes the tasks dictated by the annotations.
- *
- * @todo Needs better name
- */
- public function execute() {
- #Collect the annotations
- $parser = new AnnotationParser();
- $docComment = $this->getDocBlock();
-
- #The docComment was empty or unavailable, so no need to do anything
- if ($docComment === false) { return; }
-
- $anotations = $parser->parse($this->getDocBlock());
-
- #Loop through the annotations and apply them
- foreach ($anotations as $annotation => $entries) {
- $this->applyAnotation($annotation, $entries[0]);
- }
- }
-
- /**
- * Executes the corresponding action to a certain annotation, this receives the
- * annotation as an array of the strings of the annotation split by spaces.
- *
- * This allows versatile and fast annotation parsing that does not slow down
- * the application.
- *
- * @todo Convert this whole ordeal into something more flexible and usable
- * @param string[] $annotation
- */
- public function applyAnotation($method, $annotation) {
-
- switch($method) {
- case 'request-method':
- return call_user_func_array(Array($this, 'method'), $annotation);
- case 'cache':
- return call_user_func_array(Array($this, 'cache' ), $annotation);
- case 'template':
- return call_user_func_array(Array($this, 'template' ), $annotation);
- case 'layout':
- return call_user_func_array(Array($this, 'layout' ), $annotation);
- /*
- * Stuff that still is in the works
- * * Route
- */
- }
- }
-
- /**
- * Fetches the docblock of the action by creating a reflection of the class,
- * the action method and then requesting it's docblock. Guess there is no more
- * straightforward way to do this currently.
- *
- * @return string
- */
- public function getDocBlock() {
- $reflection = new ReflectionClass($this->controller);
-
- try {
- $method = $reflection->getMethod($this->action);
- } catch (Exception $ex) {
- try {$method = $reflection->getMethod('__call');}
- catch (Exception $i) {throw $ex;}
- }
-
- return $method->getDocComment();
- }
-
- /**
- * Checks whether the request being sent by the user is acceptable for the
- * selected action. This allows your application to set a bunch of valid methods
- * that will avoid this one throwing an exception informing about the invalid
- * request.
- *
- * @return mixed
- * @throws PublicException If the user is throwing a request with one method
- * that is not accepted.
- */
- protected function method() {
- $accepted = func_get_args();
- foreach($accepted as $ok) {
- if (strtolower($ok) === strtolower($_SERVER['REQUEST_METHOD'])) {
- return;
- }
- }
-
- throw new PublicException("No valid request", 400);
- }
-
- /**
- * Sets the time the current Request is still valid. This is especially useful
- * to reduce server load and increase performance when applied to requests
- * that are not expected to change in foreseeable time, especially big requests
- * like images.
- */
- protected function cache() {
- current_context()
- ->response
- ->getHeaders()
- ->set('expires', gmdate('D, d M Y H:i:s \G\M\T', strtotime(implode(' ', func_get_args()))));
- }
-
- /**
- * Defines whether the current template is rendered or not and what file is
- * used for that purpose. This allows your application to quickly define
- * templates that are not located in normal locations.
- *
- * @return mixed
- */
- protected function template() {
-
- $file = implode(' ', func_get_args());
-
- if ($file == 'none') {
- return current_context()->view->setRenderTemplate(false);
- }
-
- current_context()->view->setFile($file);
- }
-
- /**
- * Defines whether the current template is rendered or not with a layout
- * and what file is used for that purpose. This allows your application to
- * quickly define templates that are not located in normal locations.
- *
- * @return mixed
- */
- protected function layout() {
-
- $file = implode(' ', func_get_args());
-
- if (current_context()->request->getPath()->getFormat() !== 'php') {
- return;
- }
-
- if ($file === 'none') {
- return current_context()->view->setRenderLayout(false);
- }
-
- current_context()->view->setLayoutFile($file);
- }
-
-}
\ No newline at end of file
diff --git a/core/annotations/AnnotationParser.php b/core/annotations/AnnotationParser.php
index 6b5e9aa..8288f5b 100644
--- a/core/annotations/AnnotationParser.php
+++ b/core/annotations/AnnotationParser.php
@@ -1,105 +1,105 @@
<?php namespace spitfire\core\annotations;
use BadMethodCallException;
use Reflector;
use Strings;
/**
* Reads a docblock and parses the information that it provides to extract the
* annotations that a programmer may use to modify an application's behavior.
*
* To keep these as flexible as possible, the parser will just return an array
* of information that it extracted from the docblock.
*
* @author César de la Cal Bretschneider <cesar@magic3w.com>
*/
class AnnotationParser
{
/**
* Filters the data from a docblock, extracting the lines that do contain
* annotations and the information these provide.
*
* The return for this will look like this:
* <code>Array('annotationA paramA paramB', 'annotationB paramA paramB')</code>
*
* With this information, the parse function will be able to organize the data
* so it's easily accessible to the programmer using it.
*
* Notice that this function will trim off asterisks and forward slashes of your
* annotation, so if you wish to use those you need to make sure to ask the user
* to quote them for the application to properly use them.
*
* Annotations for this function may as well be numeric or contain special
* characters. You're though encouraged to use simple alphanumeric characters,
* since we're not testing for the operation with Unicode.
*
* @param Reflector|string $doc
* @return string[]
*/
protected function filter($doc) {
#Raw contains the complete docblock comment, which will contain extra data
#that may be uninteresting, we will filter it and return.
#This is an interesting case of a _pain in the ass_, you would expect to be
#able to somehow decently test if the thing is a reflection. But you can't...
$raw = is_object($doc) && method_exists($doc, 'getDocComment') ? $doc->getDocComment() : $doc;
#Check if raw is a string or if whatever we got passed was bogus
if (!is_string($raw)) { throw new BadMethodCallException('Invalid argument', 1607131552); }
#Individual lines make it easier to parse the data
$pieces = explode("\n", $raw);
#Remove unrelated data
$clean = array_filter(array_map(function ($e) {
$trimmed = trim($e, "\r\t */");
return Strings::startsWith($trimmed, '@')? ltrim($trimmed, '@') : null;
}, $pieces));
return $clean;
}
/**
* The parser will retrieve a DocComment, or any similar structure and read
* the annotations, providing you with an array that is structured like this
*
* <code>Array('annotation' => Array(Array('paramA'))</code>
*
* Please note that there are three levels to the array:
* * Annotation type
* * Annotation disambiguation (you can have several &at;param for example)
* * Parameters
*
* With the data structured, although inside a multi-layer array, it should be
* fairly simple to access the data that you need to make use of the annotation.
*
* @todo The return of this parser is weird enough to justify a wiki page or
* a special return type
*
* @param Reflector|string $doc
* @return string[][][]
*/
public function parse($doc) {
#Prepare the variables we need.
$annotations = Array();
$clean = $this->filter($doc);
#Sort the data
foreach ($clean as $line) {
$segments = array_filter(explode(' ', $line));
$name = array_shift($segments);
#If uninitialized, initialize the array for the docblock
if (!isset($annotations[$name])) { $annotations[$name] = Array(); }
#Add the value we parsed
- $annotations[$name][] = $segments;
+ $annotations[$name][] = implode(' ', $segments);
}
return $annotations;
}
}
diff --git a/mvc/middleware/MiddlewareInterface.php b/mvc/middleware/MiddlewareInterface.php
new file mode 100644
index 0000000..589fed6
--- /dev/null
+++ b/mvc/middleware/MiddlewareInterface.php
@@ -0,0 +1,37 @@
+<?php namespace spitfire\mvc\middleware;
+
+use spitfire\core\Context;
+use spitfire\core\Response;
+
+/*
+ * The MIT License
+ *
+ * Copyright 2018 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.
+ */
+
+interface MiddlewareInterface
+{
+
+ public function before(Context$context);
+
+ public function after(Context$context, Response$response);
+
+}
diff --git a/mvc/middleware/MiddlewareStack.php b/mvc/middleware/MiddlewareStack.php
new file mode 100644
index 0000000..c5c5d51
--- /dev/null
+++ b/mvc/middleware/MiddlewareStack.php
@@ -0,0 +1,64 @@
+<?php namespace spitfire\mvc\middleware;
+
+use spitfire\core\Context;
+
+/*
+ * The MIT License
+ *
+ * Copyright 2018 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.
+ */
+
+class MiddlewareStack
+{
+
+ /**
+ *
+ * @var Context
+ */
+ private $ctx;
+
+ /**
+ *
+ * @var MiddlewareInterface[]
+ */
+ private $middleware = [];
+
+ public function __construct(Context$ctx) {
+ $this->ctx = $ctx;
+ }
+
+ public function register(MiddlewareInterface$mw) {
+ $this->middleware[] = $mw;
+ }
+
+ public function before() {
+ foreach ($this->middleware as $middleware) {
+ $middleware->before($this->ctx);
+ }
+ }
+
+ public function after() {
+ foreach ($this->middleware as $middleware) {
+ $middleware->after($this->ctx, $this->ctx->response);
+ }
+ }
+
+}
diff --git a/mvc/middleware/standard/CacheDurationMiddleware.php b/mvc/middleware/standard/CacheDurationMiddleware.php
new file mode 100644
index 0000000..f1cb0f8
--- /dev/null
+++ b/mvc/middleware/standard/CacheDurationMiddleware.php
@@ -0,0 +1,59 @@
+<?php namespace spitfire\mvc\middleware\standard;
+
+use spitfire\core\Context;
+use spitfire\core\Response;
+use spitfire\mvc\middleware\MiddlewareInterface;
+
+/*
+ * The MIT License
+ *
+ * Copyright 2018 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.
+ */
+
+class CacheDurationMiddleware implements MiddlewareInterface
+{
+
+ public function after(Context $context, Response $response) {
+
+ }
+
+ /**
+ * Sets the time the current Request is still valid. This is especially useful
+ * to reduce server load and increase performance when applied to requests
+ * that are not expected to change in foreseeable time, especially big requests
+ * like images.
+ */
+ public function before(Context $context) {
+ $duration = reset($context->annotations['cache']);
+
+ /**
+ * If duration was not provided, this middleware component does nothing at
+ * all. Just return.
+ */
+ if (!$duration) { return; }
+
+ $context
+ ->response
+ ->getHeaders()
+ ->set('expires', gmdate('D, d M Y H:i:s \G\M\T', strtotime($duration)));
+ }
+
+}
diff --git a/mvc/middleware/standard/LayoutMiddleware.php b/mvc/middleware/standard/LayoutMiddleware.php
new file mode 100644
index 0000000..1c71ba2
--- /dev/null
+++ b/mvc/middleware/standard/LayoutMiddleware.php
@@ -0,0 +1,64 @@
+<?php namespace spitfire\mvc\middleware\standard;
+
+use spitfire\core\Context;
+use spitfire\core\Response;
+use spitfire\mvc\middleware\MiddlewareInterface;
+
+/*
+ * The MIT License
+ *
+ * Copyright 2018 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.
+ */
+
+class LayoutMiddleware implements MiddlewareInterface
+{
+
+ public function after(Context $context, Response $response) {
+
+ }
+
+ /**
+ * Defines whether the current template is rendered or not with a layout
+ * and what file is used for that purpose. This allows your application to
+ * quickly define templates that are not located in normal locations.
+ *
+ * @return mixed
+ */
+ public function before(Context $context) {
+
+ $file = reset($context->annotations['layout']);
+
+ if (!$file) {
+ return;
+ }
+
+ if ($context->request->getPath()->getFormat() !== 'php') {
+ return;
+ }
+
+ if ($file === 'none') {
+ return current_context()->view->setRenderLayout(false);
+ }
+
+ current_context()->view->setLayoutFile($file);
+ }
+
+}
diff --git a/mvc/middleware/standard/ModelMiddleware.php b/mvc/middleware/standard/ModelMiddleware.php
new file mode 100644
index 0000000..72e6842
--- /dev/null
+++ b/mvc/middleware/standard/ModelMiddleware.php
@@ -0,0 +1,88 @@
+<?php namespace spitfire\mvc\middleware\standard;
+
+use spitfire\core\Context;
+use spitfire\core\Response;
+use spitfire\mvc\middleware\MiddlewareInterface;
+
+/*
+ * The MIT License
+ *
+ * Copyright 2018 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.
+ */
+
+/**
+ * This middleware component does fulfill the rather simple task of handling
+ * Arguments to Controller actions which require passing a Model as argument.
+ *
+ * This makes it less tedious to write code that allows developers to manipulate
+ * a single record from a database. Your code may look like this:
+ *
+ * <code>public function detail(Usermodel$user)</code>
+ *
+ * In this case, if the user does not provide a valid user-id as parameter, the
+ * application will fail with a server error.
+ *
+ * If you wish to make the parameter optional, just write your code like this:
+ *
+ * <code>public function detail(UserModel$user = null)</code>
+ *
+ * If the user didn't provide the value, it will be null. You then will have to
+ * test the value before using it.
+ */
+class ModelMiddleware implements MiddlewareInterface
+{
+
+ private $db;
+
+ public function __construct($db) {
+ $this->db = $db;
+ }
+
+ /**
+ *
+ * @param Context $context
+ */
+ public function before(Context $context) {
+ $controller = new \ReflectionClass($context->controller);
+ $action = $controller->getMethod($context->action);
+ $object = $context->object;
+
+ $params = $action->getParameters();
+
+ for ($i = 0; $i < count($params); $i++) {
+ /*@var $param \ParameterReflection*/
+ $param = $params[$i];
+
+ if (!$param->getClass()->isSubclassOf(\spitfire\Model::class)) { continue; }
+
+ $table = $this->db->table(substr($param->getClass()->getName(), 0, 0 - strlen('model')));
+ $object[$i] = $table->getById($object[$i]);
+
+ }
+
+ $context->object = $object;
+ }
+
+ public function after(Context $context, Response $response) {
+
+ }
+
+}
diff --git a/mvc/middleware/standard/RequestMethodMiddleware.php b/mvc/middleware/standard/RequestMethodMiddleware.php
new file mode 100644
index 0000000..6ce774e
--- /dev/null
+++ b/mvc/middleware/standard/RequestMethodMiddleware.php
@@ -0,0 +1,71 @@
+<?php namespace spitfire\mvc\middleware\standard;
+
+use spitfire\core\Context;
+use spitfire\core\Response;
+use spitfire\exceptions\PublicException;
+use spitfire\mvc\middleware\MiddlewareInterface;
+
+/*
+ * The MIT License
+ *
+ * Copyright 2018 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.
+ */
+
+/**
+ *
+ * @author César de la Cal Bretschneider <cesar@magic3w.com>
+ */
+class RequestMethodMiddleware implements MiddlewareInterface
+{
+
+ public function after(Context $context, Response $response) {
+
+ }
+
+ /**
+ * Checks whether the request being sent by the user is acceptable for the
+ * selected action. This allows your application to set a bunch of valid methods
+ * that will avoid this one throwing an exception informing about the invalid
+ * request.
+ *
+ * @return mixed
+ * @throws PublicException If the user is throwing a request with one method
+ * that is not accepted.
+ */
+ public function before(Context $context) {
+ $annotation = reset($context->annotations['request-method']);
+
+ if (!$annotation) {
+ return;
+ }
+
+ $accepted = explode(' ', $annotation);
+
+ foreach($accepted as $ok) {
+ if (strtolower($ok) === strtolower($_SERVER['REQUEST_METHOD'])) {
+ return;
+ }
+ }
+
+ throw new PublicException("No valid request", 400);
+ }
+
+}
diff --git a/mvc/middleware/standard/TemplateMiddleware.php b/mvc/middleware/standard/TemplateMiddleware.php
new file mode 100644
index 0000000..8a5b128
--- /dev/null
+++ b/mvc/middleware/standard/TemplateMiddleware.php
@@ -0,0 +1,60 @@
+<?php namespace spitfire\mvc\middleware\standard;
+
+use spitfire\core\Context;
+use spitfire\core\Response;
+use spitfire\mvc\middleware\MiddlewareInterface;
+
+/*
+ * The MIT License
+ *
+ * Copyright 2018 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.
+ */
+
+class TemplateMiddleware implements MiddlewareInterface
+{
+
+ public function after(Context $context, Response $response) {
+
+ }
+
+ /**
+ * Defines whether the current template is rendered or not and what file is
+ * used for that purpose. This allows your application to quickly define
+ * templates that are not located in normal locations.
+ *
+ * @return mixed
+ */
+ public function before(Context $context) {
+
+ $file = reset($context->annotations['template']);
+
+ if (!$file) {
+ return;
+ }
+
+ if ($file == 'none') {
+ return current_context()->view->setRenderTemplate(false);
+ }
+
+ current_context()->view->setFile($file);
+ }
+
+}
diff --git a/mvc/middleware/standard/ValidationMiddleware.php b/mvc/middleware/standard/ValidationMiddleware.php
new file mode 100644
index 0000000..a80e1d2
--- /dev/null
+++ b/mvc/middleware/standard/ValidationMiddleware.php
@@ -0,0 +1,62 @@
+<?php namespace spitfire\mvc\middleware\standard;
+
+use spitfire\core\Context;
+use spitfire\core\Response;
+use spitfire\mvc\middleware\MiddlewareInterface;
+use spitfire\validation\parser\Parser;
+
+/*
+ * The MIT License
+ *
+ * Copyright 2018 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.
+ */
+
+class ValidationMiddleware implements MiddlewareInterface
+{
+
+ public function before(Context $context) {
+ $expressions = $context->annotations['validate'];
+ $parser = new Parser();
+
+ $context->validation = collect();
+
+ foreach ($expressions as $expression) {
+ $throw = true;
+
+ if(substr($expression, 0, 2) === '>>') {
+ $expression = substr($expression, 2);
+ $throw = false;
+ }
+
+ $validator = $parser->parse($expression)->setValue(['GET' => $_GET->getRaw(), 'POST' => $_POST, 'OBJ' => $context->object]);
+
+ if (!$validator->isOk()) {
+ if ($throw) { throw new \spitfire\exceptions\PublicException('Validation failed', 400); }
+ $context->validation->add($validator->getMessages());
+ }
+ }
+ }
+
+ public function after(Context $context, Response $response) {
+
+ }
+
+}
diff --git a/tests/core/annotation/AnnotationParserTest.php b/tests/core/annotation/AnnotationParserTest.php
index c13ab91..da7d57d 100644
--- a/tests/core/annotation/AnnotationParserTest.php
+++ b/tests/core/annotation/AnnotationParserTest.php
@@ -1,56 +1,54 @@
<?php namespace tests\core\annotation;
use PHPUnit\Framework\TestCase;
use ReflectionClass;
use spitfire\core\annotations\AnnotationParser;
class AnnotationParserTest extends TestCase
{
public function testParser() {
$string = "/**\n * @param test A \n * @param test B \n */";
$parser = new AnnotationParser();
$annotations = $parser->parse($string);
#Test the element is actually there
$this->assertArrayHasKey('param', $annotations);
#Ensure it did parse the same annotation twice and properly structure the array
$this->assertCount(1, $annotations);
$this->assertCount(2, $annotations['param']);
#Test the value is what we expect
- $this->assertEquals('test', $annotations['param'][0][0]);
- $this->assertEquals('A', $annotations['param'][0][1]);
- $this->assertEquals('B', $annotations['param'][1][1]);
+ $this->assertEquals('test A', $annotations['param'][0]);
+ $this->assertEquals('test B', $annotations['param'][1]);
}
/**
*
* @sometest test A
* @sometest test B
*/
public function testParserReflection() {
$parser = new AnnotationParser();
$reflec = new ReflectionClass($this);
$annotations = $parser->parse($reflec->getMethod('testParserReflection'));
#Test the element is actually there
$this->assertArrayHasKey('sometest', $annotations);
#Ensure it did parse the same annotation twice and properly structure the array
$this->assertCount(1, $annotations);
$this->assertCount(2, $annotations['sometest']);
#Test the value is what we expect
- $this->assertEquals('test', $annotations['sometest'][0][0]);
- $this->assertEquals('A', $annotations['sometest'][0][1]);
- $this->assertEquals('B', $annotations['sometest'][1][1]);
+ $this->assertEquals('test A', $annotations['sometest'][0]);
+ $this->assertEquals('test B', $annotations['sometest'][1]);
}
}

File Metadata

Mime Type
text/x-diff
Expires
Apr 12 2021, 3:11 AM (9 w, 1 d ago)
Storage Engine
blob
Storage Format
Raw Data
Storage Handle
1852
Default Alt Text
(47 KB)

Event Timeline