Compare commits
6 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
07de5df32f | ||
|
|
de03a1f215 | ||
|
|
01e9fd2de3 | ||
|
|
6363ad1b4d | ||
|
|
ae19bba912 | ||
|
|
2ae0f14b99 |
@@ -1,7 +1,6 @@
|
|||||||
<?xml version="1.0"?>
|
<?xml version="1.0"?>
|
||||||
<ruleset>
|
<ruleset>
|
||||||
<rule ref="PSR12"/>
|
<rule ref="PSR12"/>
|
||||||
<file>src/</file>
|
<file>src/</file>
|
||||||
<file>console</file>
|
|
||||||
<arg name="colors"/>
|
<arg name="colors"/>
|
||||||
</ruleset>
|
</ruleset>
|
||||||
@@ -1,13 +1,12 @@
|
|||||||
{
|
{
|
||||||
"name": "greg/framework",
|
"name": "kletellier/framework",
|
||||||
"description": "Mini MVC component",
|
"description": "Mini MVC component",
|
||||||
"type": "project",
|
"type": "project",
|
||||||
"license": "MIT",
|
"license": "MIT",
|
||||||
"require": {
|
"require": {
|
||||||
"php": "~8.1",
|
"php": "~8.1",
|
||||||
"kletellier/pdowrapper": "dev-master",
|
"kletellier/pdowrapper": "^1.0",
|
||||||
"symfony/http-kernel": "^6.3",
|
"symfony/http-kernel": "^6.3",
|
||||||
"bramus/router": "~1.6",
|
|
||||||
"filp/whoops": "^2.15",
|
"filp/whoops": "^2.15",
|
||||||
"symfony/var-dumper": "^6.3",
|
"symfony/var-dumper": "^6.3",
|
||||||
"eftec/bladeone": "^4.9",
|
"eftec/bladeone": "^4.9",
|
||||||
@@ -15,8 +14,8 @@
|
|||||||
},
|
},
|
||||||
"repositories": [
|
"repositories": [
|
||||||
{
|
{
|
||||||
"type": "git",
|
"type": "composer",
|
||||||
"url": "https://git.kletellier.xyz/greg/PdoWrapper.git"
|
"url": "https://git.kletellier.xyz/api/packages/greg/composer"
|
||||||
}
|
}
|
||||||
],
|
],
|
||||||
"require-dev": {
|
"require-dev": {
|
||||||
|
|||||||
24
src/Commands/TestCommand.php
Normal file
24
src/Commands/TestCommand.php
Normal file
@@ -0,0 +1,24 @@
|
|||||||
|
<?php
|
||||||
|
|
||||||
|
namespace Kletellier\MiniWeb\Commands;
|
||||||
|
|
||||||
|
use Symfony\Component\Console\Command\Command;
|
||||||
|
use Symfony\Component\Console\Input\InputInterface;
|
||||||
|
use Symfony\Component\Console\Output\OutputInterface;
|
||||||
|
|
||||||
|
class TestCommand extends Command
|
||||||
|
{
|
||||||
|
protected static $defaultName = 'app:test';
|
||||||
|
protected static $defaultDescription = 'Test.';
|
||||||
|
|
||||||
|
protected function configure(): void
|
||||||
|
{
|
||||||
|
$this->setHelp('Test');
|
||||||
|
}
|
||||||
|
|
||||||
|
protected function execute(InputInterface $input, OutputInterface $output): int
|
||||||
|
{
|
||||||
|
$output->writeln("test");
|
||||||
|
return Command::SUCCESS;
|
||||||
|
}
|
||||||
|
}
|
||||||
16
src/Controllers/Home.php
Normal file
16
src/Controllers/Home.php
Normal file
@@ -0,0 +1,16 @@
|
|||||||
|
<?php
|
||||||
|
|
||||||
|
namespace Kletellier\MiniWeb\Controllers;
|
||||||
|
|
||||||
|
use Kletellier\MiniWeb\Core\Controllers\Controller;
|
||||||
|
use DateTimeZone;
|
||||||
|
|
||||||
|
class Home extends Controller
|
||||||
|
{
|
||||||
|
public function index()
|
||||||
|
{
|
||||||
|
$date = new \DateTime('now', new DateTimeZone("Europe/Paris"));
|
||||||
|
$html = $this->renderView("index", ["date" => $date->format("d/m/Y H:i:s")]);
|
||||||
|
$this->render($html);
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -2,31 +2,29 @@
|
|||||||
|
|
||||||
namespace Kletellier\Framework\Core\Kernel;
|
namespace Kletellier\Framework\Core\Kernel;
|
||||||
|
|
||||||
use Kletellier\Framework\Core\Routing\Router;
|
use Kletellier\Framework\Core\Routing\Routing;
|
||||||
use Kletellier\Framework\Core\Kernel\Container;
|
use Kletellier\Framework\Core\Kernel\Container;
|
||||||
use Dotenv\Dotenv;
|
use Dotenv\Dotenv;
|
||||||
use Kletellier\PdoWrapper\Connection;
|
use Kletellier\PdoWrapper\Connection;
|
||||||
|
|
||||||
class Application
|
class Application
|
||||||
{
|
{
|
||||||
protected Router $router;
|
protected Routing $router;
|
||||||
protected $_whoops;
|
protected $_whoops;
|
||||||
private $_root;
|
private $_root;
|
||||||
|
|
||||||
public function __construct(string $root)
|
public function __construct(string $root)
|
||||||
{
|
{
|
||||||
$this->_root = $root;
|
$this->_root = $root;
|
||||||
|
|
||||||
// enable error reporting
|
// enable error reporting
|
||||||
$this->setReporting();
|
$this->setReporting();
|
||||||
|
|
||||||
try {
|
// create container
|
||||||
$dotenv = Dotenv::createImmutable($this->_root);
|
Container::set("ROOT", $this->_root);
|
||||||
$dotenv->load();
|
|
||||||
} catch (\Throwable $th) {
|
// init config
|
||||||
e($th->getMessage());
|
Config::getInstance();
|
||||||
die();
|
|
||||||
}
|
|
||||||
|
|
||||||
if ($this->isDebug()) {
|
if ($this->isDebug()) {
|
||||||
$this->_whoops = new \Whoops\Run();
|
$this->_whoops = new \Whoops\Run();
|
||||||
@@ -35,9 +33,6 @@ class Application
|
|||||||
$this->_whoops->register();
|
$this->_whoops->register();
|
||||||
}
|
}
|
||||||
|
|
||||||
// create container
|
|
||||||
Container::set("ROOT",$this->_root);
|
|
||||||
|
|
||||||
$db_config = $this->getDatabaseConfig();
|
$db_config = $this->getDatabaseConfig();
|
||||||
|
|
||||||
if (!empty($db_config)) {
|
if (!empty($db_config)) {
|
||||||
@@ -46,37 +41,40 @@ class Application
|
|||||||
}
|
}
|
||||||
|
|
||||||
// Init router
|
// Init router
|
||||||
$this->router = new Router();
|
$this->router = new Routing();
|
||||||
}
|
}
|
||||||
|
|
||||||
private function isDebug(): bool
|
private function isDebug(): bool
|
||||||
{
|
{
|
||||||
$ret = true;
|
$ret = (Config::get("DEBUG") === "true");
|
||||||
if (isset($_ENV["DEBUG"])) {
|
|
||||||
$ret = ($_ENV["DEBUG"] == "true");
|
|
||||||
}
|
|
||||||
return $ret;
|
return $ret;
|
||||||
}
|
}
|
||||||
|
|
||||||
private function getDatabaseConfig(): array
|
private function getDatabaseConfig(): array
|
||||||
{
|
{
|
||||||
$ret = [];
|
$ret = [];
|
||||||
$connections = isset($_ENV["CONNECTIONS"]) ? explode(",", $_ENV["CONNECTIONS"]) : [];
|
$connections = explode(",", Config::get("CONNECTIONS") ?? "") ;
|
||||||
foreach ($connections as $connection) {
|
foreach ($connections as $connection) {
|
||||||
if ($connection !== "") {
|
if ($connection !== "") {
|
||||||
$conn = [];
|
$conn = [];
|
||||||
$key = (strtoupper(trim($connection)));
|
$key = (strtoupper(trim($connection)));
|
||||||
$conn["TYPE"] = isset($_ENV[$this->getKey("TYPE", $key)]) ? $_ENV[$this->getKey("TYPE", $key)] : null;
|
$conn["TYPE"] = Config::get($this->getKey("TYPE", $key));
|
||||||
$conn["USER"] = isset($_ENV[$this->getKey("USER", $key)]) ? $_ENV[$this->getKey("USER", $key)] : null;
|
$conn["USER"] = Config::get($this->getKey("USER", $key));
|
||||||
$conn["HOST"] = isset($_ENV[$this->getKey("HOST", $key)]) ? $_ENV[$this->getKey("HOST", $key)] : null;
|
$conn["HOST"] = Config::get($this->getKey("HOST", $key));
|
||||||
$conn["PASSWORD"] = isset($_ENV[$this->getKey("PASSWORD", $key)]) ? $_ENV[$this->getKey("PASSWORD", $key)] : null;
|
$conn["PASSWORD"] = Config::get($this->getKey("PASSWORD", $key));
|
||||||
$conn["DATABASE"] = isset($_ENV[$this->getKey("DATABASE", $key)]) ? $_ENV[$this->getKey("DATABASE", $key)] : null;
|
$conn["DATABASE"] = Config::get($this->getKey("DATABASE", $key));
|
||||||
$conn["PORT"] = isset($_ENV[$this->getKey("PORT", $key)]) ? $_ENV[$this->getKey("PORT", $key)] : null;
|
$conn["PORT"] = Config::get($this->getKey("PORT", $key));
|
||||||
if ($conn["TYPE"] == "sqlite") {
|
if ($conn["TYPE"] == "sqlite") {
|
||||||
if (!is_file($conn["DATABASE"])) {
|
if (!is_file($conn["DATABASE"])) {
|
||||||
$conn["DATABASE"] = $this->_root . DIRECTORY_SEPARATOR . "databases" . DIRECTORY_SEPARATOR . $conn["DATABASE"];
|
$conn["DATABASE"] = $this->_root . DIRECTORY_SEPARATOR . "databases" . DIRECTORY_SEPARATOR . $conn["DATABASE"];
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
if ($conn["TYPE"] == "mysql") {
|
||||||
|
$charset = Config::get($this->getKey("CHARSET", $key));
|
||||||
|
if ($charset !== null) {
|
||||||
|
$conn["CHARSET"] = $charset;
|
||||||
|
}
|
||||||
|
}
|
||||||
$ret[$connection] = $conn;
|
$ret[$connection] = $conn;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
73
src/Core/Kernel/Config.php
Normal file
73
src/Core/Kernel/Config.php
Normal file
@@ -0,0 +1,73 @@
|
|||||||
|
<?php
|
||||||
|
|
||||||
|
namespace Kletellier\Framework\Core\Kernel;
|
||||||
|
|
||||||
|
class Config
|
||||||
|
{
|
||||||
|
private static $instance;
|
||||||
|
private array $container;
|
||||||
|
private string $cachepath;
|
||||||
|
|
||||||
|
private function __construct()
|
||||||
|
{
|
||||||
|
$this->cachepath = Container::get("ROOT") . DIRECTORY_SEPARATOR . "cache" . DIRECTORY_SEPARATOR . "config.php";
|
||||||
|
$this->container = [];
|
||||||
|
$this->fillValues();
|
||||||
|
}
|
||||||
|
|
||||||
|
private function createCache()
|
||||||
|
{
|
||||||
|
if (isset($this->container["DEBUG"])) {
|
||||||
|
if (($this->container["DEBUG"] == "false") && !is_file($this->cachepath)) {
|
||||||
|
// production app cache not created
|
||||||
|
$str = "<?php return " . var_export($this->container, true) . "; ?>";
|
||||||
|
file_put_contents($this->cachepath, $str);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private function fillValues()
|
||||||
|
{
|
||||||
|
$config = [];
|
||||||
|
if (is_file($this->cachepath)) {
|
||||||
|
$config = require $this->cachepath;
|
||||||
|
} else {
|
||||||
|
$config = \Dotenv\Dotenv::createArrayBacked(Container::get("ROOT"))->load();
|
||||||
|
}
|
||||||
|
foreach ($config as $key => $value) {
|
||||||
|
$this->container[$key] = $value;
|
||||||
|
}
|
||||||
|
$this->createCache();
|
||||||
|
}
|
||||||
|
|
||||||
|
public static function getInstance()
|
||||||
|
{
|
||||||
|
if (self::$instance === null) {
|
||||||
|
self::$instance = new self();
|
||||||
|
}
|
||||||
|
|
||||||
|
return self::$instance;
|
||||||
|
}
|
||||||
|
|
||||||
|
public function setValue($key, $value)
|
||||||
|
{
|
||||||
|
$this->container[$key] = $value;
|
||||||
|
}
|
||||||
|
|
||||||
|
public function getValue($key)
|
||||||
|
{
|
||||||
|
return isset($this->container[$key]) ? $this->container[$key] : null;
|
||||||
|
}
|
||||||
|
|
||||||
|
public static function get(string $key)
|
||||||
|
{
|
||||||
|
$inst = self::getInstance();
|
||||||
|
return $inst->getValue($key);
|
||||||
|
}
|
||||||
|
|
||||||
|
public static function set(string $key, mixed $value)
|
||||||
|
{
|
||||||
|
$inst = self::getInstance();
|
||||||
|
return $inst->setValue($key, $value);
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -2,16 +2,18 @@
|
|||||||
|
|
||||||
namespace Kletellier\Framework\Core\Kernel;
|
namespace Kletellier\Framework\Core\Kernel;
|
||||||
|
|
||||||
class Container
|
class Container
|
||||||
{
|
{
|
||||||
private static $instance;
|
private static $instance;
|
||||||
private array $container;
|
private array $container;
|
||||||
|
|
||||||
private function __construct() {
|
private function __construct()
|
||||||
|
{
|
||||||
$this->container = [];
|
$this->container = [];
|
||||||
}
|
}
|
||||||
|
|
||||||
public static function getInstance() {
|
public static function getInstance()
|
||||||
|
{
|
||||||
if (self::$instance === null) {
|
if (self::$instance === null) {
|
||||||
self::$instance = new self();
|
self::$instance = new self();
|
||||||
}
|
}
|
||||||
@@ -19,23 +21,25 @@ class Container
|
|||||||
return self::$instance;
|
return self::$instance;
|
||||||
}
|
}
|
||||||
|
|
||||||
public function setValue($key, $value) {
|
public function setValue($key, $value)
|
||||||
|
{
|
||||||
$this->container[$key] = $value;
|
$this->container[$key] = $value;
|
||||||
}
|
}
|
||||||
|
|
||||||
public function getValue($key) {
|
public function getValue($key)
|
||||||
|
{
|
||||||
return isset($this->container[$key]) ? $this->container[$key] : null;
|
return isset($this->container[$key]) ? $this->container[$key] : null;
|
||||||
}
|
}
|
||||||
|
|
||||||
public static function get(string $key)
|
public static function get(string $key)
|
||||||
{
|
{
|
||||||
$inst = self::getInstance();
|
$inst = self::getInstance();
|
||||||
return $inst->getValue($key);
|
return $inst->getValue($key);
|
||||||
}
|
}
|
||||||
|
|
||||||
public static function set(string $key, mixed $value)
|
public static function set(string $key, mixed $value)
|
||||||
{
|
{
|
||||||
$inst = self::getInstance();
|
$inst = self::getInstance();
|
||||||
return $inst->setValue($key, $value);
|
return $inst->setValue($key, $value);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -31,10 +31,7 @@ class Templating
|
|||||||
|
|
||||||
private function isDebug(): bool
|
private function isDebug(): bool
|
||||||
{
|
{
|
||||||
$ret = true;
|
$ret = (Config::get("DEBUG") == "true");
|
||||||
if (isset($_ENV["DEBUG"])) {
|
|
||||||
$ret = ($_ENV["DEBUG"] == "true");
|
|
||||||
}
|
|
||||||
return $ret;
|
return $ret;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -2,37 +2,527 @@
|
|||||||
|
|
||||||
namespace Kletellier\Framework\Core\Routing;
|
namespace Kletellier\Framework\Core\Routing;
|
||||||
|
|
||||||
use Bramus\Router\Router as RouterRouter;
|
/**
|
||||||
use Kletellier\Framework\Core\Kernel\Container;
|
* @author Bram(us) Van Damme <bramus@bram.us>
|
||||||
|
* @copyright Copyright (c), 2013 Bram(us) Van Damme
|
||||||
|
* @license MIT public license
|
||||||
|
*/
|
||||||
class Router
|
class Router
|
||||||
{
|
{
|
||||||
private $_router;
|
/**
|
||||||
private $_root;
|
* @var array The route patterns and their handling functions
|
||||||
|
*/
|
||||||
|
private $afterRoutes = array();
|
||||||
|
|
||||||
public function __construct()
|
/**
|
||||||
{
|
* @var array The before middleware route patterns and their handling functions
|
||||||
$this->_root = Container::get("ROOT");
|
*/
|
||||||
$this->init();
|
private $beforeRoutes = array();
|
||||||
}
|
|
||||||
|
|
||||||
public function run()
|
/**
|
||||||
{
|
* @var array [object|callable] The function to be executed when no route has been matched
|
||||||
$this->_router->run();
|
*/
|
||||||
}
|
protected $notFoundCallback = [];
|
||||||
|
|
||||||
private function init()
|
/**
|
||||||
{
|
* @var string Current base route, used for (sub)route mounting
|
||||||
$this->_router = new RouterRouter();
|
*/
|
||||||
$this->loadFromFile($this->_root . DIRECTORY_SEPARATOR . "routes");
|
private $baseRoute = '';
|
||||||
$this->_router->set404("404 - Not found");
|
|
||||||
}
|
|
||||||
|
|
||||||
private function loadFromFile($path)
|
/**
|
||||||
|
* @var string The Request Method that needs to be handled
|
||||||
|
*/
|
||||||
|
private $requestedMethod = '';
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @var string The Server Base Path for Router Execution
|
||||||
|
*/
|
||||||
|
private $serverBasePath;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @var string Default Controllers Namespace
|
||||||
|
*/
|
||||||
|
private $namespace = '';
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Store a before middleware route and a handling function to be executed when accessed using one of the specified methods.
|
||||||
|
*
|
||||||
|
* @param string $methods Allowed methods, | delimited
|
||||||
|
* @param string $pattern A route pattern such as /about/system
|
||||||
|
* @param object|callable $fn The handling function to be executed
|
||||||
|
*/
|
||||||
|
public function before($methods, $pattern, $fn)
|
||||||
{
|
{
|
||||||
if (is_dir($path)) {
|
$pattern = $this->baseRoute . '/' . trim($pattern, '/');
|
||||||
$router = $this->_router;
|
$pattern = $this->baseRoute ? rtrim($pattern, '/') : $pattern;
|
||||||
require $path . DIRECTORY_SEPARATOR . "routes.php";
|
|
||||||
|
foreach (explode('|', $methods) as $method) {
|
||||||
|
$this->beforeRoutes[$method][] = array(
|
||||||
|
'pattern' => $pattern,
|
||||||
|
'fn' => $fn,
|
||||||
|
);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Store a route and a handling function to be executed when accessed using one of the specified methods.
|
||||||
|
*
|
||||||
|
* @param string $methods Allowed methods, | delimited
|
||||||
|
* @param string $pattern A route pattern such as /about/system
|
||||||
|
* @param object|callable $fn The handling function to be executed
|
||||||
|
*/
|
||||||
|
public function match($methods, $pattern, $fn)
|
||||||
|
{
|
||||||
|
$pattern = $this->baseRoute . '/' . trim($pattern, '/');
|
||||||
|
$pattern = $this->baseRoute ? rtrim($pattern, '/') : $pattern;
|
||||||
|
|
||||||
|
foreach (explode('|', $methods) as $method) {
|
||||||
|
$this->afterRoutes[$method][] = array(
|
||||||
|
'pattern' => $pattern,
|
||||||
|
'fn' => $fn,
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Shorthand for a route accessed using any method.
|
||||||
|
*
|
||||||
|
* @param string $pattern A route pattern such as /about/system
|
||||||
|
* @param object|callable $fn The handling function to be executed
|
||||||
|
*/
|
||||||
|
public function all($pattern, $fn)
|
||||||
|
{
|
||||||
|
$this->match('GET|POST|PUT|DELETE|OPTIONS|PATCH|HEAD', $pattern, $fn);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Shorthand for a route accessed using GET.
|
||||||
|
*
|
||||||
|
* @param string $pattern A route pattern such as /about/system
|
||||||
|
* @param object|callable $fn The handling function to be executed
|
||||||
|
*/
|
||||||
|
public function get($pattern, $fn)
|
||||||
|
{
|
||||||
|
$this->match('GET', $pattern, $fn);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Shorthand for a route accessed using POST.
|
||||||
|
*
|
||||||
|
* @param string $pattern A route pattern such as /about/system
|
||||||
|
* @param object|callable $fn The handling function to be executed
|
||||||
|
*/
|
||||||
|
public function post($pattern, $fn)
|
||||||
|
{
|
||||||
|
$this->match('POST', $pattern, $fn);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Shorthand for a route accessed using PATCH.
|
||||||
|
*
|
||||||
|
* @param string $pattern A route pattern such as /about/system
|
||||||
|
* @param object|callable $fn The handling function to be executed
|
||||||
|
*/
|
||||||
|
public function patch($pattern, $fn)
|
||||||
|
{
|
||||||
|
$this->match('PATCH', $pattern, $fn);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Shorthand for a route accessed using DELETE.
|
||||||
|
*
|
||||||
|
* @param string $pattern A route pattern such as /about/system
|
||||||
|
* @param object|callable $fn The handling function to be executed
|
||||||
|
*/
|
||||||
|
public function delete($pattern, $fn)
|
||||||
|
{
|
||||||
|
$this->match('DELETE', $pattern, $fn);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Shorthand for a route accessed using PUT.
|
||||||
|
*
|
||||||
|
* @param string $pattern A route pattern such as /about/system
|
||||||
|
* @param object|callable $fn The handling function to be executed
|
||||||
|
*/
|
||||||
|
public function put($pattern, $fn)
|
||||||
|
{
|
||||||
|
$this->match('PUT', $pattern, $fn);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Shorthand for a route accessed using OPTIONS.
|
||||||
|
*
|
||||||
|
* @param string $pattern A route pattern such as /about/system
|
||||||
|
* @param object|callable $fn The handling function to be executed
|
||||||
|
*/
|
||||||
|
public function options($pattern, $fn)
|
||||||
|
{
|
||||||
|
$this->match('OPTIONS', $pattern, $fn);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Mounts a collection of callbacks onto a base route.
|
||||||
|
*
|
||||||
|
* @param string $baseRoute The route sub pattern to mount the callbacks on
|
||||||
|
* @param callable $fn The callback method
|
||||||
|
*/
|
||||||
|
public function mount($baseRoute, $fn)
|
||||||
|
{
|
||||||
|
// Track current base route
|
||||||
|
$curBaseRoute = $this->baseRoute;
|
||||||
|
|
||||||
|
// Build new base route string
|
||||||
|
$this->baseRoute .= $baseRoute;
|
||||||
|
|
||||||
|
// Call the callable
|
||||||
|
call_user_func($fn);
|
||||||
|
|
||||||
|
// Restore original base route
|
||||||
|
$this->baseRoute = $curBaseRoute;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Get all request headers.
|
||||||
|
*
|
||||||
|
* @return array The request headers
|
||||||
|
*/
|
||||||
|
public function getRequestHeaders()
|
||||||
|
{
|
||||||
|
$headers = array();
|
||||||
|
|
||||||
|
// If getallheaders() is available, use that
|
||||||
|
if (function_exists('getallheaders')) {
|
||||||
|
$headers = getallheaders();
|
||||||
|
|
||||||
|
// getallheaders() can return false if something went wrong
|
||||||
|
if ($headers !== false) {
|
||||||
|
return $headers;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Method getallheaders() not available or went wrong: manually extract 'm
|
||||||
|
foreach ($_SERVER as $name => $value) {
|
||||||
|
if ((substr($name, 0, 5) == 'HTTP_') || ($name == 'CONTENT_TYPE') || ($name == 'CONTENT_LENGTH')) {
|
||||||
|
$headers[str_replace(array(' ', 'Http'), array('-', 'HTTP'), ucwords(strtolower(str_replace('_', ' ', substr($name, 5)))))] = $value;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return $headers;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Get the request method used, taking overrides into account.
|
||||||
|
*
|
||||||
|
* @return string The Request method to handle
|
||||||
|
*/
|
||||||
|
public function getRequestMethod()
|
||||||
|
{
|
||||||
|
// Take the method as found in $_SERVER
|
||||||
|
$method = $_SERVER['REQUEST_METHOD'];
|
||||||
|
|
||||||
|
// If it's a HEAD request override it to being GET and prevent any output, as per HTTP Specification
|
||||||
|
// @url http://www.w3.org/Protocols/rfc2616/rfc2616-sec9.html#sec9.4
|
||||||
|
if ($_SERVER['REQUEST_METHOD'] == 'HEAD') {
|
||||||
|
ob_start();
|
||||||
|
$method = 'GET';
|
||||||
|
}
|
||||||
|
|
||||||
|
// If it's a POST request, check for a method override header
|
||||||
|
elseif ($_SERVER['REQUEST_METHOD'] == 'POST') {
|
||||||
|
$headers = $this->getRequestHeaders();
|
||||||
|
if (isset($headers['X-HTTP-Method-Override']) && in_array($headers['X-HTTP-Method-Override'], array('PUT', 'DELETE', 'PATCH'))) {
|
||||||
|
$method = $headers['X-HTTP-Method-Override'];
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return $method;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Set a Default Lookup Namespace for Callable methods.
|
||||||
|
*
|
||||||
|
* @param string $namespace A given namespace
|
||||||
|
*/
|
||||||
|
public function setNamespace($namespace)
|
||||||
|
{
|
||||||
|
if (is_string($namespace)) {
|
||||||
|
$this->namespace = $namespace;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Get the given Namespace before.
|
||||||
|
*
|
||||||
|
* @return string The given Namespace if exists
|
||||||
|
*/
|
||||||
|
public function getNamespace()
|
||||||
|
{
|
||||||
|
return $this->namespace;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Execute the router: Loop all defined before middleware's and routes, and execute the handling function if a match was found.
|
||||||
|
*
|
||||||
|
* @param object|callable $callback Function to be executed after a matching route was handled (= after router middleware)
|
||||||
|
*
|
||||||
|
* @return bool
|
||||||
|
*/
|
||||||
|
public function run($callback = null)
|
||||||
|
{
|
||||||
|
// Define which method we need to handle
|
||||||
|
$this->requestedMethod = $this->getRequestMethod();
|
||||||
|
|
||||||
|
// Handle all before middlewares
|
||||||
|
if (isset($this->beforeRoutes[$this->requestedMethod])) {
|
||||||
|
$this->handle($this->beforeRoutes[$this->requestedMethod]);
|
||||||
|
}
|
||||||
|
|
||||||
|
// Handle all routes
|
||||||
|
$numHandled = 0;
|
||||||
|
if (isset($this->afterRoutes[$this->requestedMethod])) {
|
||||||
|
$numHandled = $this->handle($this->afterRoutes[$this->requestedMethod], true);
|
||||||
|
}
|
||||||
|
|
||||||
|
// If no route was handled, trigger the 404 (if any)
|
||||||
|
if ($numHandled === 0) {
|
||||||
|
$this->trigger404($this->afterRoutes[$this->requestedMethod]);
|
||||||
|
} // If a route was handled, perform the finish callback (if any)
|
||||||
|
else {
|
||||||
|
if ($callback && is_callable($callback)) {
|
||||||
|
$callback();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// If it originally was a HEAD request, clean up after ourselves by emptying the output buffer
|
||||||
|
if ($_SERVER['REQUEST_METHOD'] == 'HEAD') {
|
||||||
|
ob_end_clean();
|
||||||
|
}
|
||||||
|
|
||||||
|
// Return true if a route was handled, false otherwise
|
||||||
|
return $numHandled !== 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Set the 404 handling function.
|
||||||
|
*
|
||||||
|
* @param object|callable|string $match_fn The function to be executed
|
||||||
|
* @param object|callable $fn The function to be executed
|
||||||
|
*/
|
||||||
|
public function set404($match_fn, $fn = null)
|
||||||
|
{
|
||||||
|
if (!is_null($fn)) {
|
||||||
|
$this->notFoundCallback[$match_fn] = $fn;
|
||||||
|
} else {
|
||||||
|
$this->notFoundCallback['/'] = $match_fn;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Triggers 404 response
|
||||||
|
*
|
||||||
|
* @param string $pattern A route pattern such as /about/system
|
||||||
|
*/
|
||||||
|
public function trigger404($match = null)
|
||||||
|
{
|
||||||
|
|
||||||
|
// Counter to keep track of the number of routes we've handled
|
||||||
|
$numHandled = 0;
|
||||||
|
|
||||||
|
// handle 404 pattern
|
||||||
|
if (count($this->notFoundCallback) > 0) {
|
||||||
|
// loop fallback-routes
|
||||||
|
foreach ($this->notFoundCallback as $route_pattern => $route_callable) {
|
||||||
|
// matches result
|
||||||
|
$matches = [];
|
||||||
|
|
||||||
|
// check if there is a match and get matches as $matches (pointer)
|
||||||
|
$is_match = $this->patternMatches($route_pattern, $this->getCurrentUri(), $matches, PREG_OFFSET_CAPTURE);
|
||||||
|
|
||||||
|
// is fallback route match?
|
||||||
|
if ($is_match) {
|
||||||
|
// Rework matches to only contain the matches, not the orig string
|
||||||
|
$matches = array_slice($matches, 1);
|
||||||
|
|
||||||
|
// Extract the matched URL parameters (and only the parameters)
|
||||||
|
$params = array_map(function ($match, $index) use ($matches) {
|
||||||
|
|
||||||
|
// We have a following parameter: take the substring from the current param position until the next one's position (thank you PREG_OFFSET_CAPTURE)
|
||||||
|
if (isset($matches[$index + 1]) && isset($matches[$index + 1][0]) && is_array($matches[$index + 1][0])) {
|
||||||
|
if ($matches[$index + 1][0][1] > -1) {
|
||||||
|
return trim(substr($match[0][0], 0, $matches[$index + 1][0][1] - $match[0][1]), '/');
|
||||||
|
}
|
||||||
|
} // We have no following parameters: return the whole lot
|
||||||
|
|
||||||
|
return isset($match[0][0]) && $match[0][1] != -1 ? trim($match[0][0], '/') : null;
|
||||||
|
}, $matches, array_keys($matches));
|
||||||
|
|
||||||
|
$this->invoke($route_callable);
|
||||||
|
|
||||||
|
++$numHandled;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if (($numHandled == 0) && (isset($this->notFoundCallback['/']))) {
|
||||||
|
$this->invoke($this->notFoundCallback['/']);
|
||||||
|
} elseif ($numHandled == 0) {
|
||||||
|
header($_SERVER['SERVER_PROTOCOL'] . ' 404 Not Found');
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Replace all curly braces matches {} into word patterns (like Laravel)
|
||||||
|
* Checks if there is a routing match
|
||||||
|
*
|
||||||
|
* @param $pattern
|
||||||
|
* @param $uri
|
||||||
|
* @param $matches
|
||||||
|
* @param $flags
|
||||||
|
*
|
||||||
|
* @return bool -> is match yes/no
|
||||||
|
*/
|
||||||
|
private function patternMatches($pattern, $uri, &$matches, $flags)
|
||||||
|
{
|
||||||
|
// Replace all curly braces matches {} into word patterns (like Laravel)
|
||||||
|
$pattern = preg_replace('/\/{(.*?)}/', '/(.*?)', $pattern);
|
||||||
|
|
||||||
|
// we may have a match!
|
||||||
|
return boolval(preg_match_all('#^' . $pattern . '$#', $uri, $matches, PREG_OFFSET_CAPTURE));
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Handle a a set of routes: if a match is found, execute the relating handling function.
|
||||||
|
*
|
||||||
|
* @param array $routes Collection of route patterns and their handling functions
|
||||||
|
* @param bool $quitAfterRun Does the handle function need to quit after one route was matched?
|
||||||
|
*
|
||||||
|
* @return int The number of routes handled
|
||||||
|
*/
|
||||||
|
private function handle($routes, $quitAfterRun = false)
|
||||||
|
{
|
||||||
|
// Counter to keep track of the number of routes we've handled
|
||||||
|
$numHandled = 0;
|
||||||
|
|
||||||
|
// The current page URL
|
||||||
|
$uri = $this->getCurrentUri();
|
||||||
|
|
||||||
|
// Loop all routes
|
||||||
|
foreach ($routes as $route) {
|
||||||
|
// get routing matches
|
||||||
|
$is_match = $this->patternMatches($route['pattern'], $uri, $matches, PREG_OFFSET_CAPTURE);
|
||||||
|
|
||||||
|
// is there a valid match?
|
||||||
|
if ($is_match) {
|
||||||
|
// Rework matches to only contain the matches, not the orig string
|
||||||
|
$matches = array_slice($matches, 1);
|
||||||
|
|
||||||
|
// Extract the matched URL parameters (and only the parameters)
|
||||||
|
$params = array_map(function ($match, $index) use ($matches) {
|
||||||
|
|
||||||
|
// We have a following parameter: take the substring from the current param position until the next one's position (thank you PREG_OFFSET_CAPTURE)
|
||||||
|
if (isset($matches[$index + 1]) && isset($matches[$index + 1][0]) && is_array($matches[$index + 1][0])) {
|
||||||
|
if ($matches[$index + 1][0][1] > -1) {
|
||||||
|
return trim(substr($match[0][0], 0, $matches[$index + 1][0][1] - $match[0][1]), '/');
|
||||||
|
}
|
||||||
|
} // We have no following parameters: return the whole lot
|
||||||
|
|
||||||
|
return isset($match[0][0]) && $match[0][1] != -1 ? trim($match[0][0], '/') : null;
|
||||||
|
}, $matches, array_keys($matches));
|
||||||
|
|
||||||
|
// Call the handling function with the URL parameters if the desired input is callable
|
||||||
|
$this->invoke($route['fn'], $params);
|
||||||
|
|
||||||
|
++$numHandled;
|
||||||
|
|
||||||
|
// If we need to quit, then quit
|
||||||
|
if ($quitAfterRun) {
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Return the number of routes handled
|
||||||
|
return $numHandled;
|
||||||
|
}
|
||||||
|
|
||||||
|
private function invoke($fn, $params = array())
|
||||||
|
{
|
||||||
|
if (is_callable($fn)) {
|
||||||
|
call_user_func_array($fn, $params);
|
||||||
|
}
|
||||||
|
|
||||||
|
// If not, check the existence of special parameters
|
||||||
|
elseif (stripos($fn, '@') !== false) {
|
||||||
|
// Explode segments of given route
|
||||||
|
list($controller, $method) = explode('@', $fn);
|
||||||
|
|
||||||
|
// Adjust controller class if namespace has been set
|
||||||
|
if ($this->getNamespace() !== '') {
|
||||||
|
$controller = $this->getNamespace() . '\\' . $controller;
|
||||||
|
}
|
||||||
|
|
||||||
|
try {
|
||||||
|
$reflectedMethod = new \ReflectionMethod($controller, $method);
|
||||||
|
// Make sure it's callable
|
||||||
|
if ($reflectedMethod->isPublic() && (!$reflectedMethod->isAbstract())) {
|
||||||
|
if ($reflectedMethod->isStatic()) {
|
||||||
|
forward_static_call_array(array($controller, $method), $params);
|
||||||
|
} else {
|
||||||
|
// Make sure we have an instance, because a non-static method must not be called statically
|
||||||
|
if (\is_string($controller)) {
|
||||||
|
$controller = new $controller();
|
||||||
|
}
|
||||||
|
call_user_func_array(array($controller, $method), $params);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
} catch (\ReflectionException $reflectionException) {
|
||||||
|
// The controller class is not available or the class does not have the method $method
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Define the current relative URI.
|
||||||
|
*
|
||||||
|
* @return string
|
||||||
|
*/
|
||||||
|
public function getCurrentUri()
|
||||||
|
{
|
||||||
|
// Get the current Request URI and remove rewrite base path from it (= allows one to run the router in a sub folder)
|
||||||
|
$uri = substr(rawurldecode($_SERVER['REQUEST_URI']), strlen($this->getBasePath()));
|
||||||
|
|
||||||
|
// Don't take query params into account on the URL
|
||||||
|
if (strstr($uri, '?')) {
|
||||||
|
$uri = substr($uri, 0, strpos($uri, '?'));
|
||||||
|
}
|
||||||
|
|
||||||
|
// Remove trailing slash + enforce a slash at the start
|
||||||
|
return '/' . trim($uri, '/');
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Return server base Path, and define it if isn't defined.
|
||||||
|
*
|
||||||
|
* @return string
|
||||||
|
*/
|
||||||
|
public function getBasePath()
|
||||||
|
{
|
||||||
|
// Check if server base path is defined, if not define it.
|
||||||
|
if ($this->serverBasePath === null) {
|
||||||
|
$this->serverBasePath = implode('/', array_slice(explode('/', $_SERVER['SCRIPT_NAME']), 0, -1)) . '/';
|
||||||
|
}
|
||||||
|
|
||||||
|
return $this->serverBasePath;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Explicilty sets the server base path. To be used when your entry script path differs from your entry URLs.
|
||||||
|
* @see https://github.com/bramus/router/issues/82#issuecomment-466956078
|
||||||
|
*
|
||||||
|
* @param string
|
||||||
|
*/
|
||||||
|
public function setBasePath($serverBasePath)
|
||||||
|
{
|
||||||
|
$this->serverBasePath = $serverBasePath;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
38
src/Core/Routing/Routing.php
Normal file
38
src/Core/Routing/Routing.php
Normal file
@@ -0,0 +1,38 @@
|
|||||||
|
<?php
|
||||||
|
|
||||||
|
namespace Kletellier\Framework\Core\Routing;
|
||||||
|
|
||||||
|
use Kletellier\Framework\Core\Routing\Router;
|
||||||
|
use Kletellier\Framework\Core\Kernel\Container;
|
||||||
|
|
||||||
|
class Routing
|
||||||
|
{
|
||||||
|
private $_router;
|
||||||
|
private $_root;
|
||||||
|
|
||||||
|
public function __construct()
|
||||||
|
{
|
||||||
|
$this->_root = Container::get("ROOT");
|
||||||
|
$this->init();
|
||||||
|
}
|
||||||
|
|
||||||
|
public function run()
|
||||||
|
{
|
||||||
|
$this->_router->run();
|
||||||
|
}
|
||||||
|
|
||||||
|
private function init()
|
||||||
|
{
|
||||||
|
$this->_router = new Router();
|
||||||
|
$this->loadFromFile($this->_root . DIRECTORY_SEPARATOR . "routes");
|
||||||
|
$this->_router->set404("404 - Not found");
|
||||||
|
}
|
||||||
|
|
||||||
|
private function loadFromFile($path)
|
||||||
|
{
|
||||||
|
if (is_dir($path)) {
|
||||||
|
$router = $this->_router;
|
||||||
|
require $path . DIRECTORY_SEPARATOR . "routes.php";
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
Reference in New Issue
Block a user