Max F 5 年 前
コミット
424a95cbec

+ 21 - 0
bin/console.old.php

@@ -0,0 +1,21 @@
+#!/usr/bin/php
+<?php
+
+// CONFIG
+define('APP_DIR', realpath(__DIR__ . '/..'));
+define('VENDOR_DIR', APP_DIR . '/vendor');
+
+
+// AUTOLOAD
+$loader = require VENDOR_DIR . '/autoload.php';
+$loader->setPsr4('App\\', APP_DIR . '/src');
+
+
+\KarmaFW\App::boot();
+
+
+//\KarmaFW\App::getDb()->execute("set names utf8");
+
+
+// APP ROUTE & GO
+\KarmaFW\App::routeCommand($argv);

+ 34 - 6
bin/console.php

@@ -3,19 +3,47 @@
 
 // CONFIG
 define('APP_DIR', realpath(__DIR__ . '/..'));
-define('VENDOR_DIR', realpath(APP_DIR . '/vendor'));
+define('VENDOR_DIR', APP_DIR . '/vendor');
 
 
 // AUTOLOAD
 $loader = require VENDOR_DIR . '/autoload.php';
-$loader->setPsr4('App\\', __DIR__ . '/../src');
+$loader->setPsr4('App\\', APP_DIR . '/src');
 
 
-\KarmaFW\App::boot();
+use \KarmaFW\App;
+use \KarmaFW\App\Request;
+use \KarmaFW\App\Response;
+use \KarmaFW\App\Middlewares as KarmaMiddlewares;
 
 
-//\KarmaFW\App::getDb()->execute("set names utf8");
+ini_set('display_errors', 1);
 
 
-// APP ROUTE & GO
-\KarmaFW\App::routeCommand($argv);
+// Build request
+$request = Request::createFromGlobals();
+
+// Init App and Define workflow
+$app = new App([
+    //new KarmaMiddlewares\TrafficLogger,
+    new KarmaMiddlewares\ErrorHandler,
+    //new KarmaMiddlewares\ResponseTime,
+    //new KarmaMiddlewares\ForceHttps,
+    //new KarmaMiddlewares\GzipEncoding,
+    //new KarmaMiddlewares\MaintenanceMode,
+    new KarmaMiddlewares\LoadHelpers,
+    new KarmaMiddlewares\SessionHandler,
+    //'handle404',
+    //'Authentification',
+    //'CacheHtml',
+    //new KarmaMiddlewares\UrlPrefixRouter,
+    new KarmaMiddlewares\CommandRouter($argv),
+    //new KarmaMiddlewares\UrlRouter,
+]);
+
+// Process App workflow/pipe and return a $response
+$response = $app->handle($request);
+
+// Send $response->content to the client (browser or stdout)
+$response->send();
+

+ 54 - 4
src/App.php

@@ -2,11 +2,15 @@
 
 namespace KarmaFW;
 
-use KarmaFW\Routing\Router;
-use KarmaFW\Lib\Hooks\HooksManager;
-use KarmaFW\Database\Sql\SqlDb;
+use \KarmaFW\Routing\Router;
+use \KarmaFW\Lib\Hooks\HooksManager;
+use \KarmaFW\Database\Sql\SqlDb;
 //use \KarmaFW\Database\Sql\SqlOrmModel;
 
+use \KarmaFW\App\Request;
+use \KarmaFW\App\Response;
+use \KarmaFW\App\Pipe;
+
 
 define('FW_SRC_DIR', __DIR__);
 define('FW_DIR', __DIR__ . "/..");
@@ -25,6 +29,28 @@ class App
 	public static $db = null;
 	public static $data = [];
 
+	protected static $instance = null;
+	protected $middlewares;
+
+
+	public function __construct($middlewares=[])
+	{
+		$this->middlewares = $middlewares;
+		self::$instance = $this;
+	}
+
+
+	public function handle($request)
+	{
+		$response = new Response;
+		$pipe = new Pipe($this->middlewares);
+
+		$response = $pipe->next($request, $response);
+		return $response;
+	}
+
+
+	/* #### */
 
 	public static function boot()
 	{
@@ -33,7 +59,29 @@ class App
 		}
 
 		// TODO: config à migrer dans un fichier .env et .env.prod et .env.dev et .env.local (à charger dans cet ordre, avec overwrite)
-		require APP_DIR . '/config/config.php';
+		if (is_file(APP_DIR . '/config/config.php')) {
+			require APP_DIR . '/config/config.php';
+		}
+
+		if (! defined('APP_NAME')) {
+			define('APP_NAME', "PHP Application");
+		}
+
+		if (! defined('TPL_DIR')) {
+			//define('TPL_DIR', APP_DIR . '/templates');
+		}
+
+		if (! defined('ENV')) {
+			define('ENV', 'prod');
+		}
+
+		if (! defined('DB_DSN')) {
+			//define('DB_DSN', 'mysql://root@localhost/my_app');
+		}
+
+		if (! defined('ERROR_TEMPLATE')) {
+			//define('ERROR_TEMPLATE', "page_error.tpl.php");
+		}
 
 
 		// move fw_helpers at the end of the list (to be loaded the last one)
@@ -169,6 +217,7 @@ class App
 	*/
 
 
+	/*
 	public static function routeCommand($argv)
 	{
 		if (! self::$booted) {
@@ -208,5 +257,6 @@ class App
 
 		exit(1);
 	}
+	*/
 
 }

+ 66 - 0
src/App/Middlewares/CommandRouter.php

@@ -0,0 +1,66 @@
+<?php
+
+namespace KarmaFW\App\Middlewares;
+
+use \KarmaFW\App\Request;
+use \KarmaFW\App\Response;
+use \KarmaFW\App\ResponseError404;
+use \KarmaFW\App\ResponseFile;
+
+
+class CommandRouter
+{
+	protected $argv;
+
+
+	public function __construct($argv=[])
+	{
+		$this->argv = $argv;
+	}
+	
+
+	public function __invoke(Request $request, Response $response, callable $next)
+	{
+		$arguments = array_slice($this->argv, 0);
+		$script_name = array_shift($arguments);
+		$command_name = array_shift($arguments);
+		$class_name = implode('', array_map('ucfirst', explode("_", $command_name)));
+
+
+		if (! empty($class_name)) {
+			$class_user = '\\App\\Commands\\' . $class_name;
+			$class_fw = '\\KarmaFW\\Commands\\' . $class_name;
+
+			if (class_exists($class_user)) {
+				// User command
+				$command = new $class_user($request, $response);
+				$command->execute($arguments);
+
+			} else if (class_exists($class_fw)) {
+				// Framework command
+				$command = new $class_fw($request, $response);
+				$command->execute($arguments);
+
+			} else {
+				$this->usage("invalid command : " . $command_name);
+			}
+
+		} else {
+			$this->usage("missing command");
+		}
+
+		return $response;
+	}
+	
+
+	protected function usage($error)
+	{
+		echo "PHP Console script" . PHP_EOL . PHP_EOL; 
+		echo "Usage: php console.php <command> [arguments]" . PHP_EOL . PHP_EOL;
+
+		if ($error) {
+			echo "Warning: " . $error . PHP_EOL;
+		}
+	}
+
+}

+ 28 - 0
src/App/Middlewares/ErrorHandler.php

@@ -0,0 +1,28 @@
+<?php
+
+namespace KarmaFW\App\Middlewares;
+
+use \KarmaFW\App\Request;
+use \KarmaFW\App\Response;
+
+
+class ErrorHandler
+{
+    
+    public function __invoke(Request $request, Response $response, callable $next)
+    {
+        //set_error_handler(['ErrorHandler', 'display']);
+        //set_exception_handler(['ExceptionHandler', 'display']);
+
+        try {
+            $response = $next($request, $response);
+
+        } catch (\Throwable $e) {
+            echo "ErrorHandler CATCHED EXCEPTION" . PHP_EOL; // TODO
+            print_r($e);
+        }
+
+        return $response;
+    }
+
+}

+ 29 - 0
src/App/Middlewares/ForceHttps.php

@@ -0,0 +1,29 @@
+<?php
+
+namespace KarmaFW\App\Middlewares;
+
+use \KarmaFW\App\Request;
+use \KarmaFW\App\Response;
+
+
+class ForceHttps
+{
+	
+	public function __invoke(Request $request, Response $response, callable $next)
+	{
+		/*
+		print_r($request); throw new Exception("DEBUG ME", 1);
+		
+
+		$is_ssl = false; // TODO
+		if (! $is_ssl) {
+			$redirect_url = 'https://' . $request->SERVER['SERVER_NAME'] . $request->SERVER['REQUEST_URI'] . (empty($request->SERVER['QUERY_STRING']) ? '' : ('?' . $request->SERVER['QUERY_STRING']));
+			$status = 302;
+			return new ResponseRedirect($redirect_url, $status);
+		}
+		*/
+
+		return $next($request, $response);
+	}
+
+}

+ 18 - 0
src/App/Middlewares/GzipEncoding.php

@@ -0,0 +1,18 @@
+<?php
+
+namespace KarmaFW\App\Middlewares;
+
+use \KarmaFW\App\Request;
+use \KarmaFW\App\Response;
+
+
+class GzipEncoding
+{
+	
+	public function __invoke(Request $request, Response $response, callable $next)
+	{
+
+		return $next($request, $response);
+	}
+
+}

+ 36 - 0
src/App/Middlewares/LoadHelpers.php

@@ -0,0 +1,36 @@
+<?php
+
+namespace KarmaFW\App\Middlewares;
+
+use \KarmaFW\App\Request;
+use \KarmaFW\App\Response;
+
+
+class LoadHelpers
+{
+	protected $helpers_dirs = [
+		FW_SRC_DIR . "/helpers",
+		APP_DIR . "/src/helpers",
+	];
+	
+	
+	public function __invoke(Request $request, Response $response, callable $next)
+	{
+		foreach ($this->helpers_dirs as $helpers_dir) {
+			$this->loadHelpers($helpers_dir);
+		}
+
+		return $next($request, $response);
+	}
+
+
+	protected function loadHelpers($dir)
+	{
+		$helpers = glob($dir . '/helpers_*.php');
+
+		foreach ($helpers as $helper) {
+			require $helper;
+		}
+	}
+
+}

+ 18 - 0
src/App/Middlewares/MaintenanceMode.php

@@ -0,0 +1,18 @@
+<?php
+
+namespace KarmaFW\App\Middlewares;
+
+use \KarmaFW\App\Request;
+use \KarmaFW\App\Response;
+
+
+class MaintenanceMode
+{
+	
+	public function __invoke(Request $request, Response $response, callable $next)
+	{
+
+		return $next($request, $response);
+	}
+
+}

+ 28 - 0
src/App/Middlewares/ResponseTime.php

@@ -0,0 +1,28 @@
+<?php
+
+namespace KarmaFW\App\Middlewares;
+
+use \KarmaFW\App\Request;
+use \KarmaFW\App\Response;
+
+
+class ResponseTime
+{
+    
+    public function __invoke(Request $request, Response $response, callable $next)
+    {
+        if (! isset($request->SERVER['REQUEST_TIME_FLOAT'])) {
+            $request->SERVER['REQUEST_TIME_FLOAT'] = microtime(true);
+        }
+
+        $response = $next($request, $response);
+
+        $ts_end = microtime(true);
+        $duration = $ts_end - $request->SERVER['REQUEST_TIME_FLOAT'];
+
+        $response->addHeader('X-Response-Time', round($duration, 4));
+
+        return $response;
+    }
+
+}

+ 23 - 0
src/App/Middlewares/SessionHandler.php

@@ -0,0 +1,23 @@
+<?php
+
+namespace KarmaFW\App\Middlewares;
+
+use \KarmaFW\App\Request;
+use \KarmaFW\App\Response;
+
+
+class SessionHandler
+{
+	
+	public function __invoke(Request $request, Response $response, callable $next)
+	{
+		//$savePath = ini_get('session.save_path');
+        //ini_set('session.save_path', $savePath);
+        //ini_set('session.save_handler', 'files');
+
+		session_start();
+		
+		return $next($request, $response);
+	}
+
+}

+ 18 - 0
src/App/Middlewares/TrafficLogger.php

@@ -0,0 +1,18 @@
+<?php
+
+namespace KarmaFW\App\Middlewares;
+
+use \KarmaFW\App\Request;
+use \KarmaFW\App\Response;
+
+
+class TrafficLogger
+{
+	
+	public function __invoke(Request $request, Response $response, callable $next)
+	{
+
+		return $next($request, $response);
+	}
+
+}

+ 18 - 0
src/App/Middlewares/UrlPrefixRouter.php

@@ -0,0 +1,18 @@
+<?php
+
+namespace KarmaFW\App\Middlewares;
+
+use \KarmaFW\App\Request;
+use \KarmaFW\App\Response;
+
+
+class UrlPrefixRouter
+{
+	
+	public function __invoke(Request $request, Response $response, callable $next)
+	{
+
+		return $next($request, $response);
+	}
+
+}

+ 48 - 0
src/App/Middlewares/UrlRouter.php

@@ -0,0 +1,48 @@
+<?php
+
+namespace KarmaFW\App\Middlewares;
+
+use \KarmaFW\App\Request;
+use \KarmaFW\App\Response;
+use \KarmaFW\App\ResponseError404;
+use \KarmaFW\App\ResponseRedirect;
+use \KarmaFW\App\ResponseFile;
+use \KarmaFW\Routing\Router;
+
+
+class UrlRouter
+{
+	
+	public function __invoke(Request $request, Response $response, callable $next)
+	{
+		// LOAD ROUTES
+		if (is_file(APP_DIR . '/config/routes.php')) {
+			require APP_DIR . '/config/routes.php';
+		}
+
+
+		try {
+			$router = new Router;
+
+			ob_start();
+			
+			$response = Router::routeRequest($request, $response);
+
+			// en principe le contenu de la reponse est dans $response->content
+			// mais si il y a eu des "echo", ils sont capturés par le ob_start puis insérés au début de $response->content
+
+			$content = ob_get_contents();
+			ob_end_clean();
+			$response->prepend($content);
+
+			$response = $next($request, $response);
+
+		} catch (\Throwable $e) {
+			echo "UrlRouter CATCHED EXCEPTION" . PHP_EOL; // TODO
+			print_r($e);
+		}
+
+		return $response;
+	}
+
+}

+ 32 - 0
src/App/Pipe.php

@@ -0,0 +1,32 @@
+<?php
+
+namespace KarmaFW\App;
+
+
+class Pipe
+{
+    protected $services = [];
+
+
+    public function __construct($services=[])
+    {
+        $this->services = $services;
+    }
+
+    public function run()
+    {
+        
+    }
+
+    public function next(Request $request, Response $response)
+    {
+        if (! $this->services) {
+        	//throw new \Exception("no more service", 1);
+            return $response;
+        }
+
+        $service = array_shift($this->services);
+        $response = call_user_func($service, $request, $response, [$this, 'next']);
+        return $response;
+    }
+}

+ 157 - 0
src/App/Request.php

@@ -0,0 +1,157 @@
+<?php
+
+namespace KarmaFW\App;
+
+
+class Request
+{
+	protected $url = null;
+	protected $method = null;
+	public $GET = null;
+	public $POST = null;
+	public $COOKIE = null;
+	public $SESSION = null;
+	public $ENV = null;
+	public $FILES = null;
+	public $SERVER = null;
+
+
+	public function __construct($url, $method)
+	{
+		$this->url = $url;
+		$this->method = $method;
+	}
+
+
+	public static function createFromArgv()
+	{
+		// TODO
+		//$request = new self($url, $method);
+		//return $request;
+	}
+
+
+	public static function createFromGlobals()
+	{
+		$url = isset($_SERVER['REQUEST_URI']) ? explode("?", $_SERVER['REQUEST_URI'])[0] : null;
+		$method = isset($_SERVER['REQUEST_METHOD']) ? $_SERVER['REQUEST_METHOD'] : null;
+
+		$request = new self($url, $method);
+		/*
+		$request->setGet(isset($_GET) ? $_GET : []);
+		$request->setPost(isset($_POST) ? $_POST : []);
+		$request->setCookie(isset($_COOKIE) ? $_COOKIE : []);
+		$request->setSession(isset($_SESSION) ? $_SESSION : []);
+		$request->setEnv(isset($_ENV) ? $_ENV : []);
+		$request->setFiles(isset($_FILES) ? $_FILES : []);
+		$request->setServer(isset($_SERVER) ? $_SERVER : []);
+		*/
+
+		$request->GET = isset($_GET) ? $_GET : [];
+		$request->POST = isset($_POST) ? $_POST : [];
+		$request->COOKIE = isset($_COOKIE) ? $_COOKIE : [];
+		$request->SESSION = isset($_SESSION) ? $_SESSION : [];
+		$request->ENV = isset($_ENV) ? $_ENV : [];
+		$request->FILES = isset($_FILES) ? $_FILES : [];
+		$request->SERVER = isset($_SERVER) ? $_SERVER : [];
+
+		return $request;
+	}
+
+
+	public function getUrl()
+	{
+		return $this->url;
+	}
+
+	public function getMethod()
+	{
+		return $this->method;
+	}
+
+	/*
+
+	public function setUrl($url)
+	{
+		$this->url = $url;
+	}
+
+
+	public function setMethod($method)
+	{
+		$this->method = $method;
+	}
+
+	public function getGet()
+	{
+		return is_null($key) ? $this->GET : (isset($this->GET[$key]) ? $this->GET[$key] : null);
+	}
+
+	public function setGet($GET)
+	{
+		$this->GET = $GET;
+	}
+
+	public function getPost()
+	{
+		return is_null($key) ? $this->POST : (isset($this->POST[$key]) ? $this->POST[$key] : null);
+	}
+
+	public function setPost($POST)
+	{
+		$this->POST = $POST;
+	}
+
+	public function getCookie()
+	{
+		return is_null($key) ? $this->COOKIE : (isset($this->COOKIE[$key]) ? $this->COOKIE[$key] : null);
+	}
+
+	public function setCookie($COOKIE)
+	{
+		$this->COOKIE = $COOKIE;
+	}
+
+	public function getSession()
+	{
+		return is_null($key) ? $this->SESSION : (isset($this->SESSION[$key]) ? $this->SESSION[$key] : null);
+	}
+
+	public function setSession($SESSION)
+	{
+		$this->SESSION = $SESSION;
+	}
+
+	public function getEnv()
+	{
+		return is_null($key) ? $this->ENV : (isset($this->ENV[$key]) ? $this->ENV[$key] : null);
+	}
+
+	public function setEnv($ENV)
+	{
+		$this->ENV = $ENV;
+	}
+
+	public function getFiles()
+	{
+		return is_null($key) ? $this->FILES : (isset($this->FILES[$key]) ? $this->FILES[$key] : null);
+	}
+
+	public function setFiles($FILES)
+	{
+		$this->FILES = $FILES;
+	}
+
+	public function getServer($key=null)
+	{
+		return is_null($key) ? $this->SERVER : (isset($this->SERVER[$key]) ? $this->SERVER[$key] : null);
+	}
+
+	public function setServer($SERVER)
+	{
+		$this->SERVER = $SERVER;
+	}
+	*/
+
+
+}

+ 181 - 0
src/App/Response.php

@@ -0,0 +1,181 @@
+<?php
+
+namespace KarmaFW\App;
+
+
+class Response
+{
+	protected $headers = [];
+	protected $content = '';
+	protected $status = 200;
+	protected $status_name = 'OK';
+	protected $content_type = '';
+	protected $headers_sent = false;
+
+	public const http_status_codes = [
+		100 => 'Continue',
+		101 => 'Switching Protocols',
+		102 => 'Processing',
+		103 => 'Early Hints',
+
+		200 => 'OK',
+		201 => 'Created',
+		202 => 'Accepted',
+		203 => 'Non-Authoritative Information',
+		204 => 'No Content',
+		205 => 'Reset Content',
+		206 => 'Partial Content',
+
+		300 => 'Multiple Choices',
+		301 => 'Moved Permanently',
+		302 => 'Found',
+		303 => 'See Other',
+		304 => 'Not Modified',
+
+		400 => 'Bad Request',
+		401 => 'Unauthorized',
+		402 => 'Payment Required',
+		403 => 'Forbidden',
+		404 => 'Not Found',
+		405 => 'Method Not Allowed',
+		406 => 'Not Acceptable',
+		407 => 'Proxy Authentication Required',
+		408 => 'Request Time-out',
+		409 => 'Conflict',
+		410 => 'Gone',
+		411 => 'Length Required',
+
+		500 => 'Internal Server Error',
+		501 => 'Not Implemented',
+		502 => 'Bad Gateway ou Proxy Error',
+		503 => 'Service Unavailable',
+		504 => 'Gateway Time-out',
+		505 => 'HTTP Version not supported',
+	];
+
+
+	public function __construct($content='', $content_type='text/html')
+	{
+		$this->content = $content;
+		$this->content_type = $content_type;
+	}
+
+	public function getStatus()
+	{
+		return $this->status;
+	}
+
+	public function getStatusName()
+	{
+		return $this->status_name;
+	}
+
+	public function setStatus($status=200)
+	{
+		$this->status = $status;
+
+		$status_name = isset(self::http_status_codes[$status]) ? self::http_status_codes[$status] : "Unknown status";
+		$this->status_name = $status_name;
+	}
+
+	public function getContentType()
+	{
+		return $this->content_type;
+	}
+
+	public function setContentType($content_type)
+	{
+		$this->content_type = $content_type;
+	}
+
+	public function getContent()
+	{
+		return $this->content;
+	}
+
+	public function getContentLength()
+	{
+		return strlen($this->content);
+	}
+
+	public function setContent($content)
+	{
+		$this->content = $content;
+	}
+
+	public function append($content)
+	{
+		$this->content .= $content;
+	}
+
+	public function prepend($content)
+	{
+		$this->content = $content . $this->content;
+	}
+
+	public function getHeaders()
+	{
+		return $this->headers;
+	}
+
+	public function setheaders($headers)
+	{
+		//return $this->headers = $headers;
+		$this->headers = [];
+		foreach ($headers as $k => $v) {
+			$this->addHeader($k, $v);
+		}
+	}
+
+	public function addHeader($key, $value)
+	{
+		$key = ucwords(strtolower($key), " -\t\r\n\f\v");
+		$this->headers[$key] = $value;
+	}
+
+
+	public function sendHeaders()
+	{
+		if ($this->headers_sent) {
+			error_log("Warning: headers already sent");
+			return;
+		}
+
+		if (! empty($this->status)) {
+			// TODO
+
+			$status_name = empty($this->status_name) ? "Unknown http status" : $this->status_name;
+
+			header('HTTP/1.0 ' . $this->status . ' ' . $status_name);
+			$this->headers['Status'] = 'HTTP/1.0 ' . $this->status . ' ' . $status_name;
+
+			$this->headers['X-Status'] = $this->status;
+		}
+
+		if (empty($this->headers['Content-Type']) && ! empty($this->content_type)) {
+			$this->headers['Content-Type'] = $this->content_type;
+		}
+		if (empty($this->headers['Content-Length'])) {
+			$this->headers['Content-Length'] = $this->getContentLength();
+		}
+		foreach ($this->headers as $k => $v) {
+			header($k . ": " . $v);
+		}
+		$this->headers_sent = true;
+	}
+
+
+	public function send()
+	{
+		$this->headers_sent = ($this->headers_sent || headers_sent());
+
+		if (! $this->headers_sent) {
+			$this->sendHeaders();
+		}
+
+		if (strlen($this->content) > 0) {
+			echo $this->content;
+		}
+	}
+
+}

+ 23 - 0
src/App/ResponseError.php

@@ -0,0 +1,23 @@
+<?php
+
+namespace KarmaFW\App;
+
+
+class ResponseError extends Response
+{
+	protected $status = 500;
+	protected $status_name = 'Server Error';
+
+
+	public function __construct($status=500, $content=null, $content_type='text/html')
+	{
+		parent::__construct($content, $content_type);
+
+		if (is_null($content)) {
+			$this->content = '<h1>' . $this->status_name . '</h1>';
+		}
+
+		$this->setStatus($status);
+	}
+
+}

+ 17 - 0
src/App/ResponseError404.php

@@ -0,0 +1,17 @@
+<?php
+
+namespace KarmaFW\App;
+
+
+class ResponseError404 extends ResponseError
+{
+	protected $status = 404;
+	protected $status_name = 'Not Found';
+
+
+	public function __construct($content='', $content_type='text/html')
+	{
+		parent::__construct($this->status, $content, $content_type);
+	}
+
+}

+ 64 - 0
src/App/ResponseFile.php

@@ -0,0 +1,64 @@
+<?php
+
+namespace KarmaFW\App;
+
+
+class ResponseFile extends Response
+{
+	protected $file_path;
+
+
+	public function __construct($file_path)
+	{
+		parent::__construct('', null);
+
+		$this->file_path = $file_path;
+	}
+
+
+	public function sendHeaders()
+	{
+		if ($this->headers_sent) {
+			// Warning: headers already sent
+			//error_log("Warning: headers already sent");
+			//return;
+		}
+
+		if (! is_file($this->file_path)) {
+			// File not found
+			$this->headers['Content-Length'] = 0;
+
+		} else {
+			$this->headers['Content-Length'] = filesize($this->file_path);
+			$this->headers['Content-Type'] = "application/octet-stream";
+			$this->headers['Content-Transfer-Encoding'] = "Binary";
+			$this->headers['Content-disposition'] = 'attachment; filename="' . basename($this->file_path) . '"';
+		}
+
+		parent::sendHeaders();
+	}
+
+
+	public function send()
+	{
+		if (! is_file($this->file_path)) {
+			// ERROR 404
+			$this->setStatus(404);
+		}
+
+		if ($this->headers_sent) {
+			// Warning: redirect may not work
+		}
+
+		$this->sendHeaders();
+
+		if (is_file($this->file_path)) {
+			readfile($this->file_path);
+
+		} else {
+			echo "File not found";
+		}
+	}
+
+
+}

+ 42 - 0
src/App/ResponseRedirect.php

@@ -0,0 +1,42 @@
+<?php
+
+namespace KarmaFW\App;
+
+
+class ResponseRedirect extends Response
+{
+	protected $url = null;
+	protected $status = 302;
+
+
+	public function __construct($url, $status=302)
+	{
+		parent::__construct('', null); // $content, $content_type
+
+		$this->setStatus($status)
+		$this->url = $url;
+	}
+
+	public function sendHeaders()
+	{
+		if ($this->headers_sent) {
+			error_log("Warning: headers already sent");
+			return;
+		}
+
+		$this->headers['Location'] = $this->url;
+
+		parent::sendHeaders();
+	}
+
+	public function send()
+	{
+		if ($this->headers_sent) {
+			// Warning: redirect may not work
+		}
+
+		$this->sendHeaders();
+	}
+
+
+}

+ 86 - 6
src/Routing/Router.php

@@ -3,6 +3,10 @@
 namespace KarmaFW\Routing;
 
 use \KarmaFW\WebApp;
+use \KarmaFW\App\Request;
+use \KarmaFW\App\Response;
+use \KarmaFW\App\ResponseError;
+use \KarmaFW\App\ResponseError404;
 
 
 class Router
@@ -134,7 +138,7 @@ class Router
 
 
 	// Lookup the first matching route then execute it 
-	public static function routeByUrl($request_method, $request_uri, $debug = false)
+	public static function routeByUrl($request_method, $request_uri, $debug = false, $response = null)
 	{
 		foreach (self::$routes as $route) {
 			if ($debug) {
@@ -164,7 +168,7 @@ class Router
 
 				} else if (is_callable($callback)) {
 					self::$routed_url = $route;
-					self::routeRun($route, $callback, $request_method, $request_uri);
+					self::routeRun($route, $callback, $request_method, $request_uri, $response);
 
 				} else {
 					// Error: callback not callable
@@ -179,22 +183,21 @@ class Router
 		return false;
 	}
 
-
-	public static function routeRun($route, $callback, $request_method, $request_uri)
+	public static function routeRun($route, $callback, $request_method, $request_uri, $response=null)
 	{
 		$matched_params = $route->getMatchedParams();
 
 		if (gettype($callback) == 'array') {
 			//echo " => ARRAY !<br />" . PHP_EOL;
 			//pre($callback, 1);
-			$controller = new $callback[0]($request_uri, $request_method, $route);
+			$controller = new $callback[0]($request_uri, $request_method, $route, $response);
 			WebApp::$controller = $controller;
 			call_user_func([$controller, $callback[1]], $matched_params);
 
 		} else {
 			//echo " => FUNCTION !<br />" . PHP_EOL;
 			//pre($callback, 1);
-			$callback($request_uri, $request_method, $route, $matched_params);
+			$callback($request_uri, $request_method, $route, $matched_params, $response);
 		}
 
 
@@ -202,6 +205,83 @@ class Router
 	}
 
 
+	public static function routeRequest(Request $request, Response $response)
+	{
+		$request_method = $request->getMethod();
+		$request_uri = $request->getUrl();
+
+		foreach (self::$routes as $route) {
+			$route->setCalledMethod($request_method);
+			$route->setCalledUrl($request_uri);
+
+			$match = $route->match($request_method, $request_uri);
+
+			if ($match) {
+				$before_callback = $route->getBeforeCallback();
+				if (! empty($before_callback)) {
+					$before_callback($route);
+				}
+
+				$callback = $route->getCallback();
+				if (empty($callback)) {
+					// route found but no callback defined
+					//return 0;
+					return new ResponseError404("<h1>Page not Found</h1><p>Warning: route found but no callback defined</p>");
+
+				} else if (is_callable($callback)) {
+					// OK !
+					self::$routed_url = $route;
+					$response = self::requestRouteRun($route, $callback, $request, $response);
+					return $response;
+
+				} else {
+					// route found but callback is not callable
+					//return null;
+					return new ResponseError404("<h1>Page not Found</h1><p>Warning: route callback is not callable</p>");
+				}
+
+			}
+
+		}
+
+		// no matching route
+		//return false;
+		return new ResponseError404("<h1>Page not Found</h1><p>Warning: no matching route</p>");
+	}
+
+
+	public static function requestRouteRun(Route $route, callable $callback, Request $request, Response $response)
+	{
+		$matched_params = $route->getMatchedParams();
+
+		if (gettype($callback) == 'array') {
+			//echo " => ARRAY !<br />" . PHP_EOL;
+			//pre($callback, 1);
+			$controller = new $callback[0]($request, $response);
+			WebApp::$controller = $controller;
+
+			$route_response = call_user_func([$controller, $callback[1]], $matched_params);
+
+		} else {
+			//echo " => FUNCTION !<br />" . PHP_EOL;
+			//pre($callback, 1);
+			$route_response = $callback($request, $response, $matched_params);
+		}
+
+		if ($route_response instanceof Response) {
+			$response = $route_response;
+
+		} else if ($route_response) {
+			return new ResponseError(500, "<h1>Server Error</h1><p>Error: \$response is not a Response</p>");
+
+		} else {
+			//return new ResponseError(500, "<h1>Server Error</h1><p>Error: \$response is empty</p>");
+		}
+
+		return $response;
+	}
+
+
 	// Search a route by its name
 	public static function findRouteByName($expected_route_name, $debug = false)
 	{

+ 3 - 1
src/WebApp.php

@@ -40,7 +40,9 @@ class WebApp extends App
 
 
 		// LOAD ROUTES
-		require APP_DIR . '/config/routes.php'; // NOTE => a déplacer dans \KarmaFW\WebApp::boot() ??
+		if (is_file(APP_DIR . '/config/routes.php')) {
+			require APP_DIR . '/config/routes.php';
+		}
 
 
 		if (defined('USE_HOOKS') && USE_HOOKS) {

+ 24 - 0
www/index.old.php

@@ -0,0 +1,24 @@
+<?php
+
+// CONFIG
+define('APP_DIR', realpath(__DIR__ . '/..'));
+define('VENDOR_DIR', APP_DIR . '/vendor');
+
+
+// AUTOLOAD
+$loader = require VENDOR_DIR . '/autoload.php';
+$loader->setPsr4('App\\', APP_DIR . '/src');
+
+
+use \KarmaFW\App;
+use \KarmaFW\App\Request;
+use \KarmaFW\App\Response;
+use \KarmaFW\App\Middlewares as KarmaMiddlewares;
+
+
+ini_set('display_errors', 1);
+
+
+\KarmaFW\WebApp::boot();
+\KarmaFW\WebApp::routeUrl();
+

+ 30 - 27
www/index.php

@@ -2,44 +2,47 @@
 
 // CONFIG
 define('APP_DIR', realpath(__DIR__ . '/..'));
-define('VENDOR_DIR', realpath(APP_DIR . '/vendor'));
+define('VENDOR_DIR', APP_DIR . '/vendor');
 
 
 // AUTOLOAD
 $loader = require VENDOR_DIR . '/autoload.php';
-$loader->setPsr4('App\\', __DIR__ . '/../src');
+$loader->setPsr4('App\\', APP_DIR . '/src');
 
 
-// ERRORS HANDLER
-//$whoops = new \Whoops\Run;
-//$whoops->prependHandler(new \Whoops\Handler\PrettyPageHandler);
-//$whoops->register();
+use \KarmaFW\App;
+use \KarmaFW\App\Request;
+use \KarmaFW\App\Response;
+use \KarmaFW\App\Middlewares as KarmaMiddlewares;
 
 
-// LOAD ROUTES
-require APP_DIR . '/config/routes.php';
+ini_set('display_errors', 1);
 
 
-// DEFINE HOOKS
-//\KarmaFW\Hooks\Lib\HooksManager::addHookAction('webcontroller__init', function ($controller) {
-//	echo "webcontroller hooked<hr />";
-//});
+// Build request
+$request = Request::createFromGlobals();
 
+// Init App and Define workflow
+$app = new App([
+    new KarmaMiddlewares\TrafficLogger,
+    new KarmaMiddlewares\ErrorHandler,
+    new KarmaMiddlewares\ResponseTime,
+    new KarmaMiddlewares\ForceHttps,
+    //new KarmaMiddlewares\GzipEncoding,
+    //new KarmaMiddlewares\MaintenanceMode,
+    new KarmaMiddlewares\LoadHelpers,
+    new KarmaMiddlewares\SessionHandler,
+    //'handle404',
+    //'Authentification',
+    //'CacheHtml',
+    //new KarmaMiddlewares\UrlPrefixRouter,
+    //new KarmaMiddlewares\CommandRouter($argv),
+    new KarmaMiddlewares\UrlRouter,
+]);
 
+// Process App workflow/pipe and return a $response
+$response = $app->handle($request);
 
-// YOUR INIT CODE HERE (before App::boot)
-
-
-// APP BOOT
-\KarmaFW\WebApp::boot();
-
-
-// YOUR INIT CODE HERE (after App::boot)
-
-//\KarmaFW\WebApp::getDb()->execute("set names utf8"); // set mysql UTF8
-
-
-
-// APP ROUTE & GO
-\KarmaFW\WebApp::routeUrl();
+// Send $response->content to the client (browser or stdout)
+$response->send();