From 24b711b80c86289215a4ca3804a885715717d762 Mon Sep 17 00:00:00 2001 From: MDevoldere Date: Sun, 25 Apr 2021 22:48:47 +0200 Subject: [PATCH 01/24] first commit --- .gitignore | 2 + AppExample/www/index.php | 15 +++ AppExample/www/router.php | 26 +++++ MD/App.php | 39 +++++++ MD/Controllers/ApiController.php | 107 +++++++++++++++++++ MD/Controllers/Controller.php | 67 ++++++++++++ MD/Controllers/IController.php | 12 +++ MD/Db/DbContext.php | 178 +++++++++++++++++++++++++++++++ MD/Db/IRepository.php | 25 +++++ MD/Db/IRepositoryReader.php | 15 +++ MD/Db/Model.php | 47 ++++++++ MD/Db/Repository.php | 69 ++++++++++++ MD/Db/RepositoryReader.php | 27 +++++ MD/Http/Http.php | 86 +++++++++++++++ MD/Http/IRequest.php | 51 +++++++++ MD/Http/IResponse.php | 65 +++++++++++ MD/Http/IRouter.php | 26 +++++ MD/Http/Request.php | 72 +++++++++++++ MD/Http/Response.php | 91 ++++++++++++++++ MD/Http/Router.php | 60 +++++++++++ MD/Loader.php | 69 ++++++++++++ MD/Views/IView.php | 12 +++ MD/Views/View.php | 65 +++++++++++ README.md | 3 +- composer.json | 26 +++++ 25 files changed, 1254 insertions(+), 1 deletion(-) create mode 100644 AppExample/www/index.php create mode 100644 AppExample/www/router.php create mode 100644 MD/App.php create mode 100644 MD/Controllers/ApiController.php create mode 100644 MD/Controllers/Controller.php create mode 100644 MD/Controllers/IController.php create mode 100644 MD/Db/DbContext.php create mode 100644 MD/Db/IRepository.php create mode 100644 MD/Db/IRepositoryReader.php create mode 100644 MD/Db/Model.php create mode 100644 MD/Db/Repository.php create mode 100644 MD/Db/RepositoryReader.php create mode 100644 MD/Http/Http.php create mode 100644 MD/Http/IRequest.php create mode 100644 MD/Http/IResponse.php create mode 100644 MD/Http/IRouter.php create mode 100644 MD/Http/Request.php create mode 100644 MD/Http/Response.php create mode 100644 MD/Http/Router.php create mode 100644 MD/Loader.php create mode 100644 MD/Views/IView.php create mode 100644 MD/Views/View.php create mode 100644 composer.json diff --git a/.gitignore b/.gitignore index dfcfd56..91b1491 100644 --- a/.gitignore +++ b/.gitignore @@ -348,3 +348,5 @@ MigrationBackup/ # Ionide (cross platform F# VS Code tools) working folder .ionide/ + +/vendor/ diff --git a/AppExample/www/index.php b/AppExample/www/index.php new file mode 100644 index 0000000..4ab8f1b --- /dev/null +++ b/AppExample/www/index.php @@ -0,0 +1,15 @@ +handleRequest()); + } + + Http::notFound('invalid controller'); + } + + /** + * Load Controller from given IRouter + * @param IRouter $_router the IRouter object to use + * @return IController|null the loaded IController or null if not found + */ + static protected function handleController(IRouter $_router): ?IController + { + return $_router->getController(); + } +} \ No newline at end of file diff --git a/MD/Controllers/ApiController.php b/MD/Controllers/ApiController.php new file mode 100644 index 0000000..bf4a78f --- /dev/null +++ b/MD/Controllers/ApiController.php @@ -0,0 +1,107 @@ +repo)) { + Http::notImplemented(); + } + + $m = $this->request->getMethod(); + $a = $this->request->getAction(); + $id = $this->request->getId(); + + switch($m) + { + case 'get': + $this->get(); + break; + case 'post': + //Auth::sessionRequired(); + if($id !== null) { + Http::notFound(); + } + $this->post(); + break; + case 'put': + //Auth::sessionRequired(); + if($id === null) { + Http::notFound(); + } + $this->put(); + break; + case 'delete': + //Auth::sessionRequired(); + if($id === null) { + Http::notFound(); + } + $this->delete(); + break; + default: + Http::notAllowed(); + break; + } + + return $this->response; + } + + protected function get() + { + if(empty($this->request->id)) { + $data = $this->repo->getById($this->request->getId()); + } + else { + $data = $this->repo->getAll(); + } + + $this->response->setData($data); + } + + protected function post() + { + $data = $this->request->getData(); + + if(!$this->repo->validate($data)) { + Http::badRequest(); + } + + $this->response->setCode(201); + $this->response->setData(['added' => $data]); + } + + protected function put() + { + $data = $this->request->getData(); + + if(!$this->repo->exists($this->request->getId())) { + Http::notFound(); + } + + if(!$this->repo->validate($data)) { + Http::badRequest(); + } + + $this->response->setCode(202); + $this->response->setData(['updated' => $data]); + } + + protected function delete() + { + if(!$this->repo->exists($this->request->getId())) { + Http::notFound(); + } + + $this->response->setCode(204); + $this->response->setData(['deleted' => $this->request->getId()]); + } +} \ No newline at end of file diff --git a/MD/Controllers/Controller.php b/MD/Controllers/Controller.php new file mode 100644 index 0000000..9a2adeb --- /dev/null +++ b/MD/Controllers/Controller.php @@ -0,0 +1,67 @@ +router = $_router; + $this->request = $_router->getRequest(); + $this->response = new Response(); + $this->repo = null; + $this->view = false; + $this->init(); + } + + protected function init() + { + + } + + public function handleRequest(): IResponse + { + $a = $this->request->getAction(); + + if(!method_exists($this, $a)) { + return $this->response->setCode(404)->addData('error', 'Invalid Action'); + } + + $this->{$a}(); + + if($this->view === false) { + return $this->response; + } + + $layout = new View($this->router->getPath().'Views/'); + $layout->setFile('_layout'); + $layout->setChild('page', $this->router->getViewPath()); + $this->response->setView($layout); + return $this->response; + } + + public function setRepository(string $_table, string $_pk) + { + $this->repo = new Repository($_table, $_pk); + } + + abstract public function indexAction(): void; +} \ No newline at end of file diff --git a/MD/Controllers/IController.php b/MD/Controllers/IController.php new file mode 100644 index 0000000..6a145fc --- /dev/null +++ b/MD/Controllers/IController.php @@ -0,0 +1,12 @@ + PDO::ERRMODE_EXCEPTION, + PDO::ATTR_DEFAULT_FETCH_MODE => PDO::FETCH_ASSOC, + PDO::ATTR_EMULATE_PREPARES => false + ] + ); + break; + case 'sqlite': + static::$pdo = new PDO($c['db_dsn'], 'charset=utf8'); + static::$pdo->setAttribute(PDO::ATTR_ERRMODE, PDO::ERRMODE_EXCEPTION); + static::$pdo->setAttribute(PDO::ATTR_DEFAULT_FETCH_MODE, PDO::FETCH_ASSOC); + static::$pdo->exec('pragma synchronous = off;'); + break; + default: + exit('Db Error 1'); + break; + } + } + + return static::$pdo; + } catch (Exception $e) { + exit('Db Error 10'); + } + } + + + /***** + * + * OPERATIONS GENERIQUES + * + * *****/ + + + /** + * Retourne le jeu de résultat d'une requête SELECT exécutée + * @param PDOStatement $stmt Le jeu de résultat de la requête exécutée + * @param bool $_all true = retourne toutes les lignes trouvées. false = retourne la 1ère ligne trouvée + */ + static protected function fetchStmt(PDOStatement $stmt, bool $_all = false) + { + $r = (($_all === false) ? $stmt->fetch() : $stmt->fetchAll()); + $stmt->closeCursor(); + return (!empty($r) ? $r : []); + } + + /** Exécute une requête de lecture simple + * @param string $_query La requête SQL à exécuter + * @param bool $_all true = retourne toutes les lignes trouvées. false = retourne la 1ère ligne trouvée + * @return mixed Le jeu de résultat ou empty si aucun résultat + */ + static public function query(string $_query, bool $_all = false) + { + try { + return static::fetchStmt(static::$pdo->query($_query), $_all); + } catch (Exception $e) { + exit('Db Error 11'); + } + } + + /** Exécute une requête de lecture paramétrée + * @param string $_query La requête à exécuter + * @param array $_values Les paramètres de la requête + * @param bool $_all true = retourne toutes les lignes trouvées. false = retourne la 1er ligne trouvée + * @param null|string $_model = Le nom de la classe Modèle à utiliser dans le jeu de résultat ou le mode défini à la connexion si null + * @return mixed Le jeu de résultat ou empty si aucun résultat + */ + static public function fetch(string $_query, array $_values = [], bool $_all = false) + { + try { + $stmt = static::$pdo->prepare($_query); + return ($stmt->execute($_values) ? static::fetchStmt($stmt, $_all) : null); + } catch (Exception $e) { + exit('Db Error 100' . $e->getMessage()); + } + } + + static public function fetchAll(string $_query, array $_values = []) + { + return static::fetch($_query, $_values, true); + } + + + /** Exécute une requête d'écriture paramétrée + * @param string $_query La requête à exécuter + * @param array $_values Les paramètres de la requête + * @return int Le nombre de lignes affectées + */ + static public function exec(string $_query, array $_values = []): int + { + try { + $stmt = static::$pdo->prepare($_query); + + if ($stmt->execute($_values)) { + $r = $stmt->rowCount(); + $stmt->closeCursor(); + return $r; + } + return 0; + } catch (Exception $e) { + exit('Db Error 101'); + } + } + + + /** Insère un nouvel élément + * @param array|Db $_values Le tableau de valeurs correspondant à la table courante + * @return int Le nombre de lignes affectées + */ + /*static public function insert(array $_values): int + { + $cols = \array_keys($_values); + $vals = (':' . \implode(', :', $cols)); + $cols = \implode(',', $cols); + + return static::exec("INSERT INTO " . static::$tableName . " (" . $cols . ") VALUES (" . $vals . ");", $_values); + }*/ + + /** Met à jour un élément + * @param array\Db $_values Le tableau de valeurs correspondant à la table courante. Doit contenir l'identifiant de la ligne à mettre à jour. + * @return int Le nombre de lignes affectées + */ + /*static public function update(array $_values): int + { + $cols = []; + + foreach ($_values as $k => $v) { + $cols[$k] = ($k . '=:' . $k); + } + + return static::exec("UPDATE " . static::$tableName . " SET " . \implode(', ', $cols) . " WHERE " . static::$pkName . "=:" . static::$pkName . ";", $_values); + }*/ + + /** Supprime un élément + * @param int $_id L'identifiant de la ligne à supprimer + * @return int Le nombre de lignes affectées + */ + /*static public function delete($_id): int + { + return static::exec("DELETE FROM " . static::$tableName . " WHERE " . static::$pkName . "=:id;", [':id' => $_id]); + }*/ +} diff --git a/MD/Db/IRepository.php b/MD/Db/IRepository.php new file mode 100644 index 0000000..e6a9870 --- /dev/null +++ b/MD/Db/IRepository.php @@ -0,0 +1,25 @@ +pk = $_pk; + $this->struct = $_struct; + } + + abstract public function getId(): int; + + + public function validate(array &$_input, $_id = null): bool + { + if($_id === null) { + foreach($this->struct as $k => $v) { + if(!array_key_exists($k, $_input)) { + return false; + } + } + } + + foreach($_input as $k => $v) { + if(!array_key_exists($k, $this->struct)) { + return false; + } + + /*switch($k) { + + }*/ + + } + return true; + } +} \ No newline at end of file diff --git a/MD/Db/Repository.php b/MD/Db/Repository.php new file mode 100644 index 0000000..f98fd20 --- /dev/null +++ b/MD/Db/Repository.php @@ -0,0 +1,69 @@ +table = $_table; + $this->pk = $_pk; + $this->dbContext = $_dbContext; + } + + public function exists($_id): bool + { + return $this->dbContext::fetch("SELECT COUNT(*) as nb FROM " . $this->table . " WHERE " . $this->pk . "=:cond;", [':cond' => $_id], false)['nb'] > 0; + } + + public function count(): int + { + return $this->dbContext::query(("SELECT COUNT(*) as nb FROM " . $this->table . ";"), false)['nb']; + } + + public function getAll(): array + { + return $this->dbContext::query(("SELECT * FROM " . $this->table . ";"), true); + } + + public function getBy(string $_col, string $_value, bool $_all = false) : array + { + return $this->dbContext::fetch("SELECT * FROM " . $this->table . " WHERE " . \basename($_col) . "=:cond;", [':cond' => $_value], $_all); + } + + public function getById($_id): array + { + return $this->getBy($this->pk, $_id, false); + } + + + public function validate(array &$_input): bool + { + return true; + /*$m = $this->getFirst(); + return empty(array_diff_key($m, $_input));*/ + } + + public function add(array $_input) : bool + { + return true; + } + + public function update($_id, array $_input): bool + { + return $this->exists($_id); + } + + public function delete($_id): bool + { + return $this->exists($_id); + } +} \ No newline at end of file diff --git a/MD/Db/RepositoryReader.php b/MD/Db/RepositoryReader.php new file mode 100644 index 0000000..298fbb0 --- /dev/null +++ b/MD/Db/RepositoryReader.php @@ -0,0 +1,27 @@ +dbContext::query(("SELECT MAX(" .$this->pk . ") as nb FROM " . $this->table . ";"), false)['nb']; + } + + public function getFirst(): array + { + return $this->dbContext::query("SELECT * FROM " . $this->table . " ORDER BY " . $this->pk . " ASC LIMIT 1;"); + } + + public function getLatest(): array + { + return $this->dbContext::query("SELECT * FROM " . $this->table . " ORDER BY " . $this->pk . " DESC LIMIT 1;"); + } + + public function getRandom(): array + { + return $this->dbContext::query("SELECT * FROM " . $this->table . " ORDER BY RAND() ASC LIMIT 1;"); + } +} \ No newline at end of file diff --git a/MD/Http/Http.php b/MD/Http/Http.php new file mode 100644 index 0000000..b8e1610 --- /dev/null +++ b/MD/Http/Http.php @@ -0,0 +1,86 @@ +getContentType()); + self::end($response->getCode(), $response->getBody()); + } + + static public function ok(string $data) + { + self::end(200, $data); + } + + static public function accepted(string $data) + { + self::end(202, $data); + } + + static public function badRequest(string $msg = 'Invalid message received') + { + self::end(400, $msg); + } + + static public function unauthorized(string $msg = 'Invalid Token') + { + self::end(401, $msg); + } + + static public function forbidden(string $msg = 'You are not allowed to access this resource') + { + self::end(403, $msg); + } + + static public function notFound(string $msg = 'Not found') + { + self::end(404, ('No route for '.$_SERVER['REQUEST_URI']. ' ('.$msg.')')); + } + + static public function notAllowed() + { + self::end(405, ('No route for '.$_SERVER['REQUEST_METHOD'].' '.$_SERVER['REQUEST_URI'])); + } + + static public function notImplemented() + { + self::end(501, 'Not implemented'); + } +} \ No newline at end of file diff --git a/MD/Http/IRequest.php b/MD/Http/IRequest.php new file mode 100644 index 0000000..59e7ada --- /dev/null +++ b/MD/Http/IRequest.php @@ -0,0 +1,51 @@ +method = mb_convert_case($_SERVER['REQUEST_METHOD'] ?? 'get', MB_CASE_LOWER); + $this->controller = mb_convert_case($_route[0] ?? 'home', MB_CASE_TITLE); + $this->action = mb_convert_case($_route[1] ?? 'index', MB_CASE_LOWER); + $this->id = ($_route[2] ?? null); + } + + public function getMethod(): string + { + return $this->method; + } + + public function getRoute(): string + { + return sprintf('%s/%s/%s', $this->controller, $this->action, $this->id); + } + + public function getController(): string + { + return ($this->controller . 'Controller'); + } + + public function getAction(): string + { + return ($this->action . 'Action'); + } + + public function getId(): ?string + { + return $this->id; + } + + public function getData(): array + { + switch($this->method) + { + case 'get': + case 'delete': + return Http::secure($_GET ?? []); + break; + case 'post': + $a = Http::secure($_POST ?? []); + if(!empty($_FILES)) { + $a['_files'] = &$_FILES; + } + return $a; + break; + case 'put': + $a = null; + parse_str(file_get_contents('php://input'), $a); + return Http::secure($a); + break; + default: + return []; + break; + } + } +} \ No newline at end of file diff --git a/MD/Http/Response.php b/MD/Http/Response.php new file mode 100644 index 0000000..02da6bf --- /dev/null +++ b/MD/Http/Response.php @@ -0,0 +1,91 @@ +code = 200; + $this->contentType = IResponse::HTTP_JSON; + $this->data = []; + $this->view = null; + } + + public function getCode(): int + { + return $this->code; + } + + public function setCode(int $_code): IResponse + { + $this->code = $_code; + return $this; + } + + public function getContentType(): string + { + return $this->contentType; + } + + public function getBody(): string + { + if($this->view === null) { + return json_encode($this->data, JSON_UNESCAPED_UNICODE | JSON_UNESCAPED_SLASHES | JSON_PRETTY_PRINT); + } + + return $this->view->fetch($this->data); + } + + public function getData(): array + { + return $this->data; + return $this; + } + + public function setData(array $_data = []): IResponse + { + $this->data[] = $_data; + return $this; + } + + public function appendData(array $_data = []): IResponse + { + foreach($_data as $k => $v) { + $this->addData($k, $v); + } + return $this; + } + + public function addData(string $_key, $_value = null): IResponse + { + $this->data[$_key] = $_value; + return $this; + } + + public function setLayout(?IView $_view): IResponse + { + $this->view = $_view; + return $this; + } + + public function setView(?IView $_view): IView + { + $this->view = $_view; + $this->contentType = ($_view !== null) ? IResponse::HTTP_HTML : IResponse::HTTP_JSON; + return $this->view; + } +} \ No newline at end of file diff --git a/MD/Http/Router.php b/MD/Http/Router.php new file mode 100644 index 0000000..8e0132c --- /dev/null +++ b/MD/Http/Router.php @@ -0,0 +1,60 @@ +path = (dirname(__DIR__, 2) . DIRECTORY_SEPARATOR . str_replace('\\', DIRECTORY_SEPARATOR, $_namespace) . DIRECTORY_SEPARATOR); + $this->namespace = $_namespace; + $this->url = $_baseUrl; + + $_route = (($this->url !== '/') ? str_replace($this->url, '/', $_SERVER['REQUEST_URI']) : $_SERVER['REQUEST_URI']); + $_route = str_replace('//', '/', $_route); + $_route = explode('?', $_route)[0] ?? '/'; + $_route = Http::secure(explode('/', trim($_route, '/'))); + + $this->request = new Request($_route); + + $this->controller = sprintf('\\%s\\Controllers\\%s', $this->namespace, $this->request->getController()); + + $this->view = sprintf('%s/%s', basename($this->request->getController(), 'Controller'), basename($this->request->getAction(), 'Action')); + } + + public function getPath(): string + { + return $this->path; + } + + public function getViewPath(): string + { + return $this->view; + } + + public function getRequest(): IRequest + { + return $this->request; + } + + public function getController(): ?IController + { + if(Loader::classPath($this->controller) === null) { + return null; + } + return (new $this->controller($this)); + } + +} \ No newline at end of file diff --git a/MD/Loader.php b/MD/Loader.php new file mode 100644 index 0000000..cd7328e --- /dev/null +++ b/MD/Loader.php @@ -0,0 +1,69 @@ + + * + * @method static string classPath(string $_classname) Get a class local path from its name + * @method static void autoload(string $_classname) Autoloader (triggered by PHP) + * @method static void register() Register this Loader in PHP autoloading stack + * + * @todo Attach/Detach Error Handler(s) + */ +abstract class Loader +{ + /** + * Get local path from class name + * ex: \MD\MyNamespace\MyClass --> RootPath/MD/MyNamespace/MyClass.php + * @param string $_classname the class full name + * @return string|null the class local path or null if file not exists + */ + static public function classPath(string $_classname): ?string + { + $_classname = (\str_replace('\\', DIRECTORY_SEPARATOR, $_classname) . '.php'); + $c = (MD . $_classname); + return \is_file($c) ? $c : null; + } + + /** + * Autoloader (triggered by PHP) + * @param string $_classname the class to autoload + */ + static public function autoload(string $_classname) + { + if (null !== ($classname = self::classpath($_classname))) { + //echo $classname.'
'; + require $classname; + return true; + } + + //echo $_classname.'
'; + exit('Invalid Component'); + //return false; + } + + /** + * Register this Loader in PHP autoloading stack + * Required before use \MD\* classes + */ + static public function register() + { + /** const MD: define RootPath for autoloading purposes */ + \define('MD', (dirname(__DIR__) . DIRECTORY_SEPARATOR)); + + /** register */ + \spl_autoload_register('\\MD\\Loader::autoload'); + + // \set_error_handler(''); + } + +} + +// register this loader when loading this file +\MD\Loader::register(); diff --git a/MD/Views/IView.php b/MD/Views/IView.php new file mode 100644 index 0000000..bb887d7 --- /dev/null +++ b/MD/Views/IView.php @@ -0,0 +1,12 @@ +path = $_path; + $this->file = 'index'; + $this->childs = []; + } + + public function setFile(string $_filename): IView + { + $this->file = ($this->path.$_filename.'.php'); + + if(!\is_file($this->file)) { + Http::notFound('invalid view ('.$_filename.')'); + } + + return $this; + } + + public function fetch(array $_vars = []) : string + { + foreach($this->childs as $k => $v) { + $_vars[$k] = $v->fetch($_vars); + } + + \ob_start(); + \extract($_vars); + \extract(self::$vars, EXTR_SKIP); + require ($this->file); + return \ob_get_clean(); + } + + public function setChild(string $_key, string $_filename): IView + { + $v = new self($this->path); + $v->setFile($_filename); + $this->childs[$_key] = $v; + return $v; + } +} diff --git a/README.md b/README.md index 01684ef..a90c15f 100644 --- a/README.md +++ b/README.md @@ -1 +1,2 @@ -# MD_PHP_MVC \ No newline at end of file +# MD_PHP_MVC + diff --git a/composer.json b/composer.json new file mode 100644 index 0000000..83c678c --- /dev/null +++ b/composer.json @@ -0,0 +1,26 @@ +{ + "name": "mdevoldere/edu-php-mvc", + "description": "A simple MVC for educationnal purposes", + "type": "library", + "version": "0.0.1", + "license": "GPL-3.0-or-later", + "authors": [ + { + "name": "MDevoldere", + "email": "dev@devoldere.net", + "homepage": "https://devoldere.net", + "role": "Owner" + } + ], + "repositories": [ + { + "type": "vcs", + "url": "https://github.com/mdevoldere/MD_PHP_MVC" + } + ], + "minimum-stability": "stable", + "require": { + "php" : ">=7.4", + "ext-mbstring": "*" + } +} From 83ac3b048d184d70ed3e3e9dbe5b9eff4e6899e6 Mon Sep 17 00:00:00 2001 From: MDevoldere Date: Tue, 27 Apr 2021 20:32:48 +0200 Subject: [PATCH 02/24] del AppExample --- AppExample/www/index.php | 15 --------------- AppExample/www/router.php | 26 -------------------------- composer.json | 9 +++++++-- 3 files changed, 7 insertions(+), 43 deletions(-) delete mode 100644 AppExample/www/index.php delete mode 100644 AppExample/www/router.php diff --git a/AppExample/www/index.php b/AppExample/www/index.php deleted file mode 100644 index 4ab8f1b..0000000 --- a/AppExample/www/index.php +++ /dev/null @@ -1,15 +0,0 @@ -=7.4", "ext-mbstring": "*" - } + }, + "autoload": { + "psr-4": { + "MD\\": "MD" + } + } } From 7e5d670a8375d288abbd2e83c858222e22427579 Mon Sep 17 00:00:00 2001 From: MDevoldere Date: Tue, 27 Apr 2021 20:41:41 +0200 Subject: [PATCH 03/24] namespace --- MD/App.php | 8 ++++---- MD/Controllers/ApiController.php | 4 ++-- MD/Controllers/Controller.php | 18 +++++++++--------- MD/Controllers/IController.php | 4 ++-- MD/Db/DbContext.php | 2 +- MD/Db/IRepository.php | 2 +- MD/Db/IRepositoryReader.php | 2 +- MD/Db/Model.php | 2 +- MD/Db/Repository.php | 4 ++-- MD/Db/RepositoryReader.php | 2 +- MD/Http/Http.php | 2 +- MD/Http/IRequest.php | 2 +- MD/Http/IResponse.php | 4 ++-- MD/Http/IRouter.php | 4 ++-- MD/Http/Request.php | 2 +- MD/Http/Response.php | 6 +++--- MD/Http/Router.php | 6 +++--- MD/Loader.php | 10 +++++----- MD/Views/IView.php | 2 +- MD/Views/View.php | 4 ++-- composer.json | 2 +- 21 files changed, 46 insertions(+), 46 deletions(-) diff --git a/MD/App.php b/MD/App.php index b05cba9..afa807c 100644 --- a/MD/App.php +++ b/MD/App.php @@ -1,10 +1,10 @@ table = $_table; $this->pk = $_pk; diff --git a/MD/Db/RepositoryReader.php b/MD/Db/RepositoryReader.php index 298fbb0..263ee78 100644 --- a/MD/Db/RepositoryReader.php +++ b/MD/Db/RepositoryReader.php @@ -1,6 +1,6 @@ RootPath/MD/MyNamespace/MyClass.php + * ex: \Md\MyNamespace\MyClass --> RootPath/MD/MyNamespace/MyClass.php * @param string $_classname the class full name * @return string|null the class local path or null if file not exists */ @@ -50,7 +50,7 @@ static public function autoload(string $_classname) /** * Register this Loader in PHP autoloading stack - * Required before use \MD\* classes + * Required before use \Md\* classes */ static public function register() { @@ -58,7 +58,7 @@ static public function register() \define('MD', (dirname(__DIR__) . DIRECTORY_SEPARATOR)); /** register */ - \spl_autoload_register('\\MD\\Loader::autoload'); + \spl_autoload_register('\\Md\\Loader::autoload'); // \set_error_handler(''); } @@ -66,4 +66,4 @@ static public function register() } // register this loader when loading this file -\MD\Loader::register(); +\Md\Loader::register(); diff --git a/MD/Views/IView.php b/MD/Views/IView.php index bb887d7..cc6e79e 100644 --- a/MD/Views/IView.php +++ b/MD/Views/IView.php @@ -1,6 +1,6 @@ Date: Tue, 27 Apr 2021 20:43:02 +0200 Subject: [PATCH 04/24] re --- {MD => Mdev}/App.php | 0 {MD => Mdev}/Controllers/ApiController.php | 0 {MD => Mdev}/Controllers/Controller.php | 0 {MD => Mdev}/Controllers/IController.php | 0 {MD => Mdev}/Db/DbContext.php | 0 {MD => Mdev}/Db/IRepository.php | 0 {MD => Mdev}/Db/IRepositoryReader.php | 0 {MD => Mdev}/Db/Model.php | 0 {MD => Mdev}/Db/Repository.php | 0 {MD => Mdev}/Db/RepositoryReader.php | 0 {MD => Mdev}/Http/Http.php | 0 {MD => Mdev}/Http/IRequest.php | 0 {MD => Mdev}/Http/IResponse.php | 0 {MD => Mdev}/Http/IRouter.php | 0 {MD => Mdev}/Http/Request.php | 0 {MD => Mdev}/Http/Response.php | 0 {MD => Mdev}/Http/Router.php | 0 {MD => Mdev}/Loader.php | 0 {MD => Mdev}/Views/IView.php | 0 {MD => Mdev}/Views/View.php | 0 20 files changed, 0 insertions(+), 0 deletions(-) rename {MD => Mdev}/App.php (100%) rename {MD => Mdev}/Controllers/ApiController.php (100%) rename {MD => Mdev}/Controllers/Controller.php (100%) rename {MD => Mdev}/Controllers/IController.php (100%) rename {MD => Mdev}/Db/DbContext.php (100%) rename {MD => Mdev}/Db/IRepository.php (100%) rename {MD => Mdev}/Db/IRepositoryReader.php (100%) rename {MD => Mdev}/Db/Model.php (100%) rename {MD => Mdev}/Db/Repository.php (100%) rename {MD => Mdev}/Db/RepositoryReader.php (100%) rename {MD => Mdev}/Http/Http.php (100%) rename {MD => Mdev}/Http/IRequest.php (100%) rename {MD => Mdev}/Http/IResponse.php (100%) rename {MD => Mdev}/Http/IRouter.php (100%) rename {MD => Mdev}/Http/Request.php (100%) rename {MD => Mdev}/Http/Response.php (100%) rename {MD => Mdev}/Http/Router.php (100%) rename {MD => Mdev}/Loader.php (100%) rename {MD => Mdev}/Views/IView.php (100%) rename {MD => Mdev}/Views/View.php (100%) diff --git a/MD/App.php b/Mdev/App.php similarity index 100% rename from MD/App.php rename to Mdev/App.php diff --git a/MD/Controllers/ApiController.php b/Mdev/Controllers/ApiController.php similarity index 100% rename from MD/Controllers/ApiController.php rename to Mdev/Controllers/ApiController.php diff --git a/MD/Controllers/Controller.php b/Mdev/Controllers/Controller.php similarity index 100% rename from MD/Controllers/Controller.php rename to Mdev/Controllers/Controller.php diff --git a/MD/Controllers/IController.php b/Mdev/Controllers/IController.php similarity index 100% rename from MD/Controllers/IController.php rename to Mdev/Controllers/IController.php diff --git a/MD/Db/DbContext.php b/Mdev/Db/DbContext.php similarity index 100% rename from MD/Db/DbContext.php rename to Mdev/Db/DbContext.php diff --git a/MD/Db/IRepository.php b/Mdev/Db/IRepository.php similarity index 100% rename from MD/Db/IRepository.php rename to Mdev/Db/IRepository.php diff --git a/MD/Db/IRepositoryReader.php b/Mdev/Db/IRepositoryReader.php similarity index 100% rename from MD/Db/IRepositoryReader.php rename to Mdev/Db/IRepositoryReader.php diff --git a/MD/Db/Model.php b/Mdev/Db/Model.php similarity index 100% rename from MD/Db/Model.php rename to Mdev/Db/Model.php diff --git a/MD/Db/Repository.php b/Mdev/Db/Repository.php similarity index 100% rename from MD/Db/Repository.php rename to Mdev/Db/Repository.php diff --git a/MD/Db/RepositoryReader.php b/Mdev/Db/RepositoryReader.php similarity index 100% rename from MD/Db/RepositoryReader.php rename to Mdev/Db/RepositoryReader.php diff --git a/MD/Http/Http.php b/Mdev/Http/Http.php similarity index 100% rename from MD/Http/Http.php rename to Mdev/Http/Http.php diff --git a/MD/Http/IRequest.php b/Mdev/Http/IRequest.php similarity index 100% rename from MD/Http/IRequest.php rename to Mdev/Http/IRequest.php diff --git a/MD/Http/IResponse.php b/Mdev/Http/IResponse.php similarity index 100% rename from MD/Http/IResponse.php rename to Mdev/Http/IResponse.php diff --git a/MD/Http/IRouter.php b/Mdev/Http/IRouter.php similarity index 100% rename from MD/Http/IRouter.php rename to Mdev/Http/IRouter.php diff --git a/MD/Http/Request.php b/Mdev/Http/Request.php similarity index 100% rename from MD/Http/Request.php rename to Mdev/Http/Request.php diff --git a/MD/Http/Response.php b/Mdev/Http/Response.php similarity index 100% rename from MD/Http/Response.php rename to Mdev/Http/Response.php diff --git a/MD/Http/Router.php b/Mdev/Http/Router.php similarity index 100% rename from MD/Http/Router.php rename to Mdev/Http/Router.php diff --git a/MD/Loader.php b/Mdev/Loader.php similarity index 100% rename from MD/Loader.php rename to Mdev/Loader.php diff --git a/MD/Views/IView.php b/Mdev/Views/IView.php similarity index 100% rename from MD/Views/IView.php rename to Mdev/Views/IView.php diff --git a/MD/Views/View.php b/Mdev/Views/View.php similarity index 100% rename from MD/Views/View.php rename to Mdev/Views/View.php From 056d47ddf047b91054562c73667a83ca11375f6e Mon Sep 17 00:00:00 2001 From: MDevoldere Date: Tue, 27 Apr 2021 20:43:23 +0200 Subject: [PATCH 05/24] re --- {Mdev => Md}/App.php | 0 {Mdev => Md}/Controllers/ApiController.php | 0 {Mdev => Md}/Controllers/Controller.php | 0 {Mdev => Md}/Controllers/IController.php | 0 {Mdev => Md}/Db/DbContext.php | 0 {Mdev => Md}/Db/IRepository.php | 0 {Mdev => Md}/Db/IRepositoryReader.php | 0 {Mdev => Md}/Db/Model.php | 0 {Mdev => Md}/Db/Repository.php | 0 {Mdev => Md}/Db/RepositoryReader.php | 0 {Mdev => Md}/Http/Http.php | 0 {Mdev => Md}/Http/IRequest.php | 0 {Mdev => Md}/Http/IResponse.php | 0 {Mdev => Md}/Http/IRouter.php | 0 {Mdev => Md}/Http/Request.php | 0 {Mdev => Md}/Http/Response.php | 0 {Mdev => Md}/Http/Router.php | 0 {Mdev => Md}/Loader.php | 0 {Mdev => Md}/Views/IView.php | 0 {Mdev => Md}/Views/View.php | 0 20 files changed, 0 insertions(+), 0 deletions(-) rename {Mdev => Md}/App.php (100%) rename {Mdev => Md}/Controllers/ApiController.php (100%) rename {Mdev => Md}/Controllers/Controller.php (100%) rename {Mdev => Md}/Controllers/IController.php (100%) rename {Mdev => Md}/Db/DbContext.php (100%) rename {Mdev => Md}/Db/IRepository.php (100%) rename {Mdev => Md}/Db/IRepositoryReader.php (100%) rename {Mdev => Md}/Db/Model.php (100%) rename {Mdev => Md}/Db/Repository.php (100%) rename {Mdev => Md}/Db/RepositoryReader.php (100%) rename {Mdev => Md}/Http/Http.php (100%) rename {Mdev => Md}/Http/IRequest.php (100%) rename {Mdev => Md}/Http/IResponse.php (100%) rename {Mdev => Md}/Http/IRouter.php (100%) rename {Mdev => Md}/Http/Request.php (100%) rename {Mdev => Md}/Http/Response.php (100%) rename {Mdev => Md}/Http/Router.php (100%) rename {Mdev => Md}/Loader.php (100%) rename {Mdev => Md}/Views/IView.php (100%) rename {Mdev => Md}/Views/View.php (100%) diff --git a/Mdev/App.php b/Md/App.php similarity index 100% rename from Mdev/App.php rename to Md/App.php diff --git a/Mdev/Controllers/ApiController.php b/Md/Controllers/ApiController.php similarity index 100% rename from Mdev/Controllers/ApiController.php rename to Md/Controllers/ApiController.php diff --git a/Mdev/Controllers/Controller.php b/Md/Controllers/Controller.php similarity index 100% rename from Mdev/Controllers/Controller.php rename to Md/Controllers/Controller.php diff --git a/Mdev/Controllers/IController.php b/Md/Controllers/IController.php similarity index 100% rename from Mdev/Controllers/IController.php rename to Md/Controllers/IController.php diff --git a/Mdev/Db/DbContext.php b/Md/Db/DbContext.php similarity index 100% rename from Mdev/Db/DbContext.php rename to Md/Db/DbContext.php diff --git a/Mdev/Db/IRepository.php b/Md/Db/IRepository.php similarity index 100% rename from Mdev/Db/IRepository.php rename to Md/Db/IRepository.php diff --git a/Mdev/Db/IRepositoryReader.php b/Md/Db/IRepositoryReader.php similarity index 100% rename from Mdev/Db/IRepositoryReader.php rename to Md/Db/IRepositoryReader.php diff --git a/Mdev/Db/Model.php b/Md/Db/Model.php similarity index 100% rename from Mdev/Db/Model.php rename to Md/Db/Model.php diff --git a/Mdev/Db/Repository.php b/Md/Db/Repository.php similarity index 100% rename from Mdev/Db/Repository.php rename to Md/Db/Repository.php diff --git a/Mdev/Db/RepositoryReader.php b/Md/Db/RepositoryReader.php similarity index 100% rename from Mdev/Db/RepositoryReader.php rename to Md/Db/RepositoryReader.php diff --git a/Mdev/Http/Http.php b/Md/Http/Http.php similarity index 100% rename from Mdev/Http/Http.php rename to Md/Http/Http.php diff --git a/Mdev/Http/IRequest.php b/Md/Http/IRequest.php similarity index 100% rename from Mdev/Http/IRequest.php rename to Md/Http/IRequest.php diff --git a/Mdev/Http/IResponse.php b/Md/Http/IResponse.php similarity index 100% rename from Mdev/Http/IResponse.php rename to Md/Http/IResponse.php diff --git a/Mdev/Http/IRouter.php b/Md/Http/IRouter.php similarity index 100% rename from Mdev/Http/IRouter.php rename to Md/Http/IRouter.php diff --git a/Mdev/Http/Request.php b/Md/Http/Request.php similarity index 100% rename from Mdev/Http/Request.php rename to Md/Http/Request.php diff --git a/Mdev/Http/Response.php b/Md/Http/Response.php similarity index 100% rename from Mdev/Http/Response.php rename to Md/Http/Response.php diff --git a/Mdev/Http/Router.php b/Md/Http/Router.php similarity index 100% rename from Mdev/Http/Router.php rename to Md/Http/Router.php diff --git a/Mdev/Loader.php b/Md/Loader.php similarity index 100% rename from Mdev/Loader.php rename to Md/Loader.php diff --git a/Mdev/Views/IView.php b/Md/Views/IView.php similarity index 100% rename from Mdev/Views/IView.php rename to Md/Views/IView.php diff --git a/Mdev/Views/View.php b/Md/Views/View.php similarity index 100% rename from Mdev/Views/View.php rename to Md/Views/View.php From c8b611825090e7da6ac2dc6946cc03e52970543c Mon Sep 17 00:00:00 2001 From: MDevoldere Date: Tue, 27 Apr 2021 21:51:13 +0200 Subject: [PATCH 06/24] upd --- Md/App.php | 2 +- Md/Http/IRequest.php | 11 ++--- Md/Http/IRouter.php | 5 +-- Md/Http/Request.php | 4 +- Md/Http/Router.php | 18 +++------ Md/Loader.php | 10 +++-- Md/Views/View.php | 2 +- README.md | 95 ++++++++++++++++++++++++++++++++++++++++++++ 8 files changed, 119 insertions(+), 28 deletions(-) diff --git a/Md/App.php b/Md/App.php index afa807c..ad92dbd 100644 --- a/Md/App.php +++ b/Md/App.php @@ -36,4 +36,4 @@ static protected function handleController(IRouter $_router): ?IController { return $_router->getController(); } -} \ No newline at end of file +} diff --git a/Md/Http/IRequest.php b/Md/Http/IRequest.php index 827a833..d6912f8 100644 --- a/Md/Http/IRequest.php +++ b/Md/Http/IRequest.php @@ -5,11 +5,12 @@ /** * Http/Request represents the current HTTP Query * - * @method getMethod() - * @method getRoute() - * @method getController() - * @method getAction() - * @method getId() + * @method string getMethod() + * @method string getRoute() + * @method array getData() + * @method string getController() + * @method string getAction() + * @method null|string getId() */ interface IRequest { diff --git a/Md/Http/IRouter.php b/Md/Http/IRouter.php index a60759d..ee9a58c 100644 --- a/Md/Http/IRouter.php +++ b/Md/Http/IRouter.php @@ -17,10 +17,7 @@ interface IRouter { public function getPath(): string; - public function getViewPath(): string; - public function getRequest(): IRequest; - public function getController(): ?IController; -} \ No newline at end of file +} diff --git a/Md/Http/Request.php b/Md/Http/Request.php index 01b8a78..2d651b8 100644 --- a/Md/Http/Request.php +++ b/Md/Http/Request.php @@ -6,6 +6,7 @@ class Request implements IRequest { + /** @var string $method HTTP Method */ private string $method; private string $controller; private string $action; @@ -13,6 +14,7 @@ class Request implements IRequest public function __construct(array $_route) { + $_route = Http::secure($_route); $this->method = mb_convert_case($_SERVER['REQUEST_METHOD'] ?? 'get', MB_CASE_LOWER); $this->controller = mb_convert_case($_route[0] ?? 'home', MB_CASE_TITLE); $this->action = mb_convert_case($_route[1] ?? 'index', MB_CASE_LOWER); @@ -69,4 +71,4 @@ public function getData(): array break; } } -} \ No newline at end of file +} diff --git a/Md/Http/Router.php b/Md/Http/Router.php index 1e7716f..17b375e 100644 --- a/Md/Http/Router.php +++ b/Md/Http/Router.php @@ -9,27 +9,22 @@ class Router implements IRouter { - protected string $namespace; protected string $path; - protected string $url; protected string $controller; protected string $view; protected IRequest $request; - public function __construct(string $_namespace, string $_baseUrl = '/') + public function __construct(string $_namespace, string $_path) { - $this->path = (dirname(__DIR__, 2) . DIRECTORY_SEPARATOR . str_replace('\\', DIRECTORY_SEPARATOR, $_namespace) . DIRECTORY_SEPARATOR); - $this->namespace = $_namespace; - $this->url = $_baseUrl; + $this->path = (dirname($_path) . DIRECTORY_SEPARATOR . str_replace('\\', DIRECTORY_SEPARATOR, $_namespace) . DIRECTORY_SEPARATOR); - $_route = (($this->url !== '/') ? str_replace($this->url, '/', $_SERVER['REQUEST_URI']) : $_SERVER['REQUEST_URI']); - $_route = str_replace('//', '/', $_route); + $_route = str_replace('//', '/', $_SERVER['REQUEST_URI']); $_route = explode('?', $_route)[0] ?? '/'; - $_route = Http::secure(explode('/', trim($_route, '/'))); + $_route = explode('/', trim($_route, '/')); $this->request = new Request($_route); - $this->controller = sprintf('\\%s\\Controllers\\%s', $this->namespace, $this->request->getController()); + $this->controller = sprintf('%s\\Controllers\\%s', $_namespace, $this->request->getController()); $this->view = sprintf('%s/%s', basename($this->request->getController(), 'Controller'), basename($this->request->getAction(), 'Action')); } @@ -51,9 +46,6 @@ public function getRequest(): IRequest public function getController(): ?IController { - if(Loader::classPath($this->controller) === null) { - return null; - } return (new $this->controller($this)); } diff --git a/Md/Loader.php b/Md/Loader.php index b97bc89..49249c1 100644 --- a/Md/Loader.php +++ b/Md/Loader.php @@ -5,7 +5,7 @@ /** * MDClass AutoLoader * - * + * @deprecated * * @version 1.0.0 * @author mdevoldere @@ -19,6 +19,7 @@ abstract class Loader { /** + * @deprecated * Get local path from class name * ex: \Md\MyNamespace\MyClass --> RootPath/MD/MyNamespace/MyClass.php * @param string $_classname the class full name @@ -28,10 +29,12 @@ static public function classPath(string $_classname): ?string { $_classname = (\str_replace('\\', DIRECTORY_SEPARATOR, $_classname) . '.php'); $c = (MD . $_classname); + echo ('
'.var_export($c, true));
         return \is_file($c) ? $c : null;
     }
 
     /**
+     * @deprecated
      * Autoloader (triggered by PHP)
      * @param string $_classname the class to autoload
      */
@@ -49,6 +52,7 @@ static public function autoload(string $_classname)
     }
 
     /**
+     * @deprecated
      * Register this Loader in PHP autoloading stack
      * Required before use \Md\* classes
      */
@@ -58,7 +62,7 @@ static public function register()
         \define('MD', (dirname(__DIR__) . DIRECTORY_SEPARATOR));
 
         /** register */
-        \spl_autoload_register('\\Md\\Loader::autoload');
+        //\spl_autoload_register('\\Md\\Loader::autoload');
 
         // \set_error_handler('');
     }
@@ -66,4 +70,4 @@ static public function register()
 }
 
 // register this loader when loading this file
-\Md\Loader::register();
+// \Md\Loader::register();
diff --git a/Md/Views/View.php b/Md/Views/View.php
index 07461c2..8b6ebc5 100644
--- a/Md/Views/View.php
+++ b/Md/Views/View.php
@@ -34,7 +34,7 @@ public function __construct(string $_path)
     public function setFile(string $_filename): IView
     {
         $this->file = ($this->path.$_filename.'.php');
-
+        echo ('
'.var_export($this, true));
         if(!\is_file($this->file)) {
             Http::notFound('invalid view ('.$_filename.')');
         }
diff --git a/README.md b/README.md
index a90c15f..746b1ad 100644
--- a/README.md
+++ b/README.md
@@ -1,2 +1,97 @@
 # MD_PHP_MVC
 
+For educational purposes. Please do not use this on production environment.
+
+
+## Usage
+
+Create Directory for your project.
+
+Type theses cmds inside your project directory.
+
+> composer init
+>
+> composer require mdevoldere/edu-php-mvc
+
+Create project structure like this:
+
+- YourApp
+    - Controllers
+        - HomeController.php
+    - Models
+    - Views
+        - Home
+            - toto.php
+        - _layout.php
+- vendor
+- www
+    - index.php
+
+Where `YourApp` is the directory name AND namespace of your app.
+
+Add needed PSR4 in your composer.json
+
+Edit files :
+
+```php
+/* www/index.php */
+
+// Autoloader
+require dirname(__DIR__).'/vendor/autoload.php';
+
+// Set IRouter 
+$router = new \Md\Http\Router('AppExample', __DIR__);
+
+// Run App 
+\Md\App::run($router);
+```
+
+```php
+/* YourApp/Controllers/HomeController.php */
+
+namespace YourApp\Controllers;
+
+use Md\Controllers\Controller;
+
+class HomeController extends Controller
+{
+    public function init()
+    {
+        // load your components
+    }
+
+    public function indexAction(): void
+    {
+        // add some data to response object
+        $this->response->addData('Hello', 'World');
+    }
+
+    public function totoAction(): void
+    {
+        // set response as HTML response 
+        // view filename is "{request->controller}/{request->action}.php"
+        // ex (this action): 
+        // url is "/home/toto"
+        // file is "YourApp/Views/Home/toto.php"
+        $this->view = true; 
+
+        $this->response->addData('Action', 'You selected add action');
+    }
+}
+```
+
+
+```php
+/* YourApp/Views/_layout.php */
+
+

Layout

+ + +``` + +```php +/* YourApp/Views/Home/toto.php */ +/* $Action ref response->data['Action'] */ +

Toto Page !

+

+``` From 9af63580979cfff57c05e6c396a2501e416e7910 Mon Sep 17 00:00:00 2001 From: MDevoldere Date: Wed, 28 Apr 2021 21:36:23 +0200 Subject: [PATCH 07/24] Fix setData Response --- .gitignore | 1 + Md/Http/Response.php | 2 +- Md/Views/View.php | 2 +- README.md | 93 +------------------------------------------- 4 files changed, 5 insertions(+), 93 deletions(-) diff --git a/.gitignore b/.gitignore index 91b1491..8adfde4 100644 --- a/.gitignore +++ b/.gitignore @@ -9,6 +9,7 @@ *.user *.userosscache *.sln.docstates +/vendor/ # User-specific files (MonoDevelop/Xamarin Studio) *.userprefs diff --git a/Md/Http/Response.php b/Md/Http/Response.php index a147755..a6861a6 100644 --- a/Md/Http/Response.php +++ b/Md/Http/Response.php @@ -58,7 +58,7 @@ public function getData(): array public function setData(array $_data = []): IResponse { - $this->data[] = $_data; + $this->data = $_data; return $this; } diff --git a/Md/Views/View.php b/Md/Views/View.php index 8b6ebc5..63a0006 100644 --- a/Md/Views/View.php +++ b/Md/Views/View.php @@ -34,7 +34,7 @@ public function __construct(string $_path) public function setFile(string $_filename): IView { $this->file = ($this->path.$_filename.'.php'); - echo ('
'.var_export($this, true));
+        // echo ('
'.var_export($this, true));
         if(!\is_file($this->file)) {
             Http::notFound('invalid view ('.$_filename.')');
         }
diff --git a/README.md b/README.md
index 746b1ad..a58139e 100644
--- a/README.md
+++ b/README.md
@@ -1,97 +1,8 @@
-# MD_PHP_MVC
+# PHP MVC BASIC EXAMPLE
 
 For educational purposes. Please do not use this on production environment.
 
 
 ## Usage
 
-Create Directory for your project.
-
-Type theses cmds inside your project directory.
-
-> composer init
->
-> composer require mdevoldere/edu-php-mvc
-
-Create project structure like this:
-
-- YourApp
-    - Controllers
-        - HomeController.php
-    - Models
-    - Views
-        - Home
-            - toto.php
-        - _layout.php
-- vendor
-- www
-    - index.php
-
-Where `YourApp` is the directory name AND namespace of your app.
-
-Add needed PSR4 in your composer.json
-
-Edit files :
-
-```php
-/* www/index.php */
-
-// Autoloader
-require dirname(__DIR__).'/vendor/autoload.php';
-
-// Set IRouter 
-$router = new \Md\Http\Router('AppExample', __DIR__);
-
-// Run App 
-\Md\App::run($router);
-```
-
-```php
-/* YourApp/Controllers/HomeController.php */
-
-namespace YourApp\Controllers;
-
-use Md\Controllers\Controller;
-
-class HomeController extends Controller
-{
-    public function init()
-    {
-        // load your components
-    }
-
-    public function indexAction(): void
-    {
-        // add some data to response object
-        $this->response->addData('Hello', 'World');
-    }
-
-    public function totoAction(): void
-    {
-        // set response as HTML response 
-        // view filename is "{request->controller}/{request->action}.php"
-        // ex (this action): 
-        // url is "/home/toto"
-        // file is "YourApp/Views/Home/toto.php"
-        $this->view = true; 
-
-        $this->response->addData('Action', 'You selected add action');
-    }
-}
-```
-
-
-```php
-/* YourApp/Views/_layout.php */
-
-

Layout

- - -``` - -```php -/* YourApp/Views/Home/toto.php */ -/* $Action ref response->data['Action'] */ -

Toto Page !

-

-``` +https://github.com/mdevoldere/edu-php-mvc-skeleton \ No newline at end of file From d5dc9710f6297a1b87032a4af6e83b63ba797184 Mon Sep 17 00:00:00 2001 From: MDevoldere Date: Wed, 28 Apr 2021 21:55:11 +0200 Subject: [PATCH 08/24] repo namespace --- Md/Controllers/ApiController.php | 107 ------------------------------- Md/Loader.php | 73 --------------------- composer.json | 2 +- 3 files changed, 1 insertion(+), 181 deletions(-) delete mode 100644 Md/Controllers/ApiController.php delete mode 100644 Md/Loader.php diff --git a/Md/Controllers/ApiController.php b/Md/Controllers/ApiController.php deleted file mode 100644 index 7919604..0000000 --- a/Md/Controllers/ApiController.php +++ /dev/null @@ -1,107 +0,0 @@ -repo)) { - Http::notImplemented(); - } - - $m = $this->request->getMethod(); - $a = $this->request->getAction(); - $id = $this->request->getId(); - - switch($m) - { - case 'get': - $this->get(); - break; - case 'post': - //Auth::sessionRequired(); - if($id !== null) { - Http::notFound(); - } - $this->post(); - break; - case 'put': - //Auth::sessionRequired(); - if($id === null) { - Http::notFound(); - } - $this->put(); - break; - case 'delete': - //Auth::sessionRequired(); - if($id === null) { - Http::notFound(); - } - $this->delete(); - break; - default: - Http::notAllowed(); - break; - } - - return $this->response; - } - - protected function get() - { - if(empty($this->request->id)) { - $data = $this->repo->getById($this->request->getId()); - } - else { - $data = $this->repo->getAll(); - } - - $this->response->setData($data); - } - - protected function post() - { - $data = $this->request->getData(); - - if(!$this->repo->validate($data)) { - Http::badRequest(); - } - - $this->response->setCode(201); - $this->response->setData(['added' => $data]); - } - - protected function put() - { - $data = $this->request->getData(); - - if(!$this->repo->exists($this->request->getId())) { - Http::notFound(); - } - - if(!$this->repo->validate($data)) { - Http::badRequest(); - } - - $this->response->setCode(202); - $this->response->setData(['updated' => $data]); - } - - protected function delete() - { - if(!$this->repo->exists($this->request->getId())) { - Http::notFound(); - } - - $this->response->setCode(204); - $this->response->setData(['deleted' => $this->request->getId()]); - } -} \ No newline at end of file diff --git a/Md/Loader.php b/Md/Loader.php deleted file mode 100644 index 49249c1..0000000 --- a/Md/Loader.php +++ /dev/null @@ -1,73 +0,0 @@ - - * - * @method static string classPath(string $_classname) Get a class local path from its name - * @method static void autoload(string $_classname) Autoloader (triggered by PHP) - * @method static void register() Register this Loader in PHP autoloading stack - * - * @todo Attach/Detach Error Handler(s) - */ -abstract class Loader -{ - /** - * @deprecated - * Get local path from class name - * ex: \Md\MyNamespace\MyClass --> RootPath/MD/MyNamespace/MyClass.php - * @param string $_classname the class full name - * @return string|null the class local path or null if file not exists - */ - static public function classPath(string $_classname): ?string - { - $_classname = (\str_replace('\\', DIRECTORY_SEPARATOR, $_classname) . '.php'); - $c = (MD . $_classname); - echo ('
'.var_export($c, true));
-        return \is_file($c) ? $c : null;
-    }
-
-    /**
-     * @deprecated
-     * Autoloader (triggered by PHP)
-     * @param string $_classname the class to autoload
-     */
-    static public function autoload(string $_classname)
-    {
-        if (null !== ($classname = self::classpath($_classname))) {
-            //echo $classname.'
'; - require $classname; - return true; - } - - //echo $_classname.'
'; - exit('Invalid Component'); - //return false; - } - - /** - * @deprecated - * Register this Loader in PHP autoloading stack - * Required before use \Md\* classes - */ - static public function register() - { - /** const MD: define RootPath for autoloading purposes */ - \define('MD', (dirname(__DIR__) . DIRECTORY_SEPARATOR)); - - /** register */ - //\spl_autoload_register('\\Md\\Loader::autoload'); - - // \set_error_handler(''); - } - -} - -// register this loader when loading this file -// \Md\Loader::register(); diff --git a/composer.json b/composer.json index d09709f..edb7174 100644 --- a/composer.json +++ b/composer.json @@ -16,7 +16,7 @@ "repositories": [ { "type": "vcs", - "url": "https://github.com/mdevoldere/MD_PHP_MVC" + "url": "https://github.com/mdevoldere/edu-php-mvc.git" } ], "require": { From 49b5ee606347b28aa1397e663ab013da2c2c3d35 Mon Sep 17 00:00:00 2001 From: MDevoldere Date: Thu, 13 May 2021 13:05:54 +0200 Subject: [PATCH 09/24] DbContext Update --- Md/Controllers/Controller.php | 11 +++ Md/Db/DbContext.php | 127 ++++++++++++++++++---------------- Md/Db/IDbContext.php | 41 +++++++++++ Md/Db/Repository.php | 19 +++-- 4 files changed, 133 insertions(+), 65 deletions(-) create mode 100644 Md/Db/IDbContext.php diff --git a/Md/Controllers/Controller.php b/Md/Controllers/Controller.php index 40b4132..543797d 100644 --- a/Md/Controllers/Controller.php +++ b/Md/Controllers/Controller.php @@ -10,14 +10,21 @@ use Md\Http\Response; use Md\Views\View; +/** + * Abstract Class Controller + */ abstract class Controller implements IController { + /** @var IRouter $router The router used by current App */ protected IRouter $router; + /** @var IRequest $request The HTTP request extracted from $router */ protected IRequest $request; + /** @var IResponse $response The HTTP response object */ protected IResponse $response; + /** @var null|IRepository $repo The Repository to use or null if no repository */ protected ?IRepository $repo; protected bool $view; @@ -58,6 +65,10 @@ public function handleRequest(): IResponse return $this->response; } + /** + * Set Generic Repository from table name and primary key name + * Remember to open the connection with DbContext before use any repo + */ public function setRepository(string $_table, string $_pk) { $this->repo = new Repository($_table, $_pk); diff --git a/Md/Db/DbContext.php b/Md/Db/DbContext.php index 1a35617..4073f45 100644 --- a/Md/Db/DbContext.php +++ b/Md/Db/DbContext.php @@ -6,89 +6,96 @@ use PdoStatement; use Exception; -/** Classe Db +/** Class Db * * @author MDevoldere - * @version 1.0 + * @version 1.0.1 * @access public */ -abstract class DbContext +class DbContext implements IDbContext { - /** @var PDO $db Représente une connexion vers une base de données */ - static protected ?PDO $pdo = null; - /** - * PDO Factory - */ - static public function getPdo(array $c = []): ?PDO + /** @var IDbContext[] $context */ + static protected array $context = []; + + static public function getContext(string $_context = 'default') : ?IDbContext + { + return self::$context[$_context] ?? null; + } + + static public function setContext(array $c = [], string $_context = 'default'): ?IDbContext { try { - if (static::$pdo === null) { - if (empty($c['db_type']) || empty($c['db_dsn'])) { - exit('Db Error 0'); - return null; - } - - switch ($c['db_type']) { - case 'mysql': - static::$pdo = new PDO( - $c['db_dsn'], - ($c['db_user'] ?? 'root'), - ($c['db_pass'] ?? ''), - [ - PDO::ATTR_ERRMODE => PDO::ERRMODE_EXCEPTION, - PDO::ATTR_DEFAULT_FETCH_MODE => PDO::FETCH_ASSOC, - PDO::ATTR_EMULATE_PREPARES => false - ] - ); - break; - case 'sqlite': - static::$pdo = new PDO($c['db_dsn'], 'charset=utf8'); - static::$pdo->setAttribute(PDO::ATTR_ERRMODE, PDO::ERRMODE_EXCEPTION); - static::$pdo->setAttribute(PDO::ATTR_DEFAULT_FETCH_MODE, PDO::FETCH_ASSOC); - static::$pdo->exec('pragma synchronous = off;'); - break; - default: - exit('Db Error 1'); - break; - } + + if (empty($c['db_type']) || empty($c['db_dsn'])) { + exit('Db Error 0'); + return null; + } + + switch ($c['db_type']) { + case 'mysql': + self::$context[$_context] = new DbContext(new PDO( + $c['db_dsn'], + ($c['db_user'] ?? 'root'), + ($c['db_pass'] ?? ''), + [ + PDO::ATTR_ERRMODE => PDO::ERRMODE_EXCEPTION, + PDO::ATTR_DEFAULT_FETCH_MODE => PDO::FETCH_ASSOC, + PDO::ATTR_EMULATE_PREPARES => false + ] + )); + break; + case 'sqlite': + $pdo = new PDO($c['db_dsn'], 'charset=utf8'); + $pdo->setAttribute(PDO::ATTR_ERRMODE, PDO::ERRMODE_EXCEPTION); + $pdo->setAttribute(PDO::ATTR_DEFAULT_FETCH_MODE, PDO::FETCH_ASSOC); + $pdo->exec('pragma synchronous = off;'); + self::$context[$_context] = new DbContext($pdo); + break; + default: + exit('Db Error 1'); + break; } - return static::$pdo; + return self::getContext($_context); + } catch (Exception $e) { exit('Db Error 10'); } } - /***** - * - * OPERATIONS GENERIQUES - * - * *****/ - - /** * Retourne le jeu de résultat d'une requête SELECT exécutée * @param PDOStatement $stmt Le jeu de résultat de la requête exécutée * @param bool $_all true = retourne toutes les lignes trouvées. false = retourne la 1ère ligne trouvée + * @return array le jeu de résultat ou un tableau vide */ - static protected function fetchStmt(PDOStatement $stmt, bool $_all = false) + static protected function fetchStmt(PDOStatement $stmt, bool $_all = false): array { $r = (($_all === false) ? $stmt->fetch() : $stmt->fetchAll()); $stmt->closeCursor(); return (!empty($r) ? $r : []); } + + /** @var PDO $db Représente une connexion vers une base de données */ + protected ?PDO $pdo = null; + + protected function __construct(PDO $_pdo) + { + $this->pdo = $_pdo; + } + /** Exécute une requête de lecture simple * @param string $_query La requête SQL à exécuter * @param bool $_all true = retourne toutes les lignes trouvées. false = retourne la 1ère ligne trouvée * @return mixed Le jeu de résultat ou empty si aucun résultat */ - static public function query(string $_query, bool $_all = false) + public function query(string $_query, bool $_all = false): array { try { - return static::fetchStmt(static::$pdo->query($_query), $_all); + return self::fetchStmt($this->pdo->query($_query), $_all); } catch (Exception $e) { exit('Db Error 11'); } @@ -98,34 +105,38 @@ static public function query(string $_query, bool $_all = false) * @param string $_query La requête à exécuter * @param array $_values Les paramètres de la requête * @param bool $_all true = retourne toutes les lignes trouvées. false = retourne la 1er ligne trouvée - * @param null|string $_model = Le nom de la classe Modèle à utiliser dans le jeu de résultat ou le mode défini à la connexion si null * @return mixed Le jeu de résultat ou empty si aucun résultat */ - static public function fetch(string $_query, array $_values = [], bool $_all = false) + public function fetch(string $_query, array $_values = [], bool $_all = false): array { try { - $stmt = static::$pdo->prepare($_query); - return ($stmt->execute($_values) ? static::fetchStmt($stmt, $_all) : null); + $stmt = $this->pdo->prepare($_query); + return ($stmt->execute($_values) ? static::fetchStmt($stmt, $_all) : []); } catch (Exception $e) { exit('Db Error 100' . $e->getMessage()); } } - static public function fetchAll(string $_query, array $_values = []) + /** Exécute une requête de lecture paramétrée et retourne toutes les lignes trouvées + * @param string $_query La requête à exécuter + * @param array $_values Les paramètres de la requête + * @return mixed Le jeu de résultat ou empty si aucun résultat + */ + public function fetchAll(string $_query, array $_values = []): array { - return static::fetch($_query, $_values, true); + return $this->fetch($_query, $_values, true); } - /** Exécute une requête d'écriture paramétrée + /** Exécute une requête d'écriture paramétrée et retourne le nombre de lignes affectées * @param string $_query La requête à exécuter * @param array $_values Les paramètres de la requête * @return int Le nombre de lignes affectées */ - static public function exec(string $_query, array $_values = []): int + public function exec(string $_query, array $_values = []): int { try { - $stmt = static::$pdo->prepare($_query); + $stmt = $this->pdo->prepare($_query); if ($stmt->execute($_values)) { $r = $stmt->rowCount(); diff --git a/Md/Db/IDbContext.php b/Md/Db/IDbContext.php new file mode 100644 index 0000000..c648664 --- /dev/null +++ b/Md/Db/IDbContext.php @@ -0,0 +1,41 @@ +table = $_table; $this->pk = $_pk; - $this->dbContext = $_dbContext; + $this->db = DbContext::getContext($_dbContext); + + if(empty($this->db)) { + exit('Repository Error 1 ('.$_dbContext.')'); + } } public function exists($_id): bool { - return $this->dbContext::fetch("SELECT COUNT(*) as nb FROM " . $this->table . " WHERE " . $this->pk . "=:cond;", [':cond' => $_id], false)['nb'] > 0; + return $this->db->fetch("SELECT COUNT(*) as nb FROM " . $this->table . " WHERE " . $this->pk . "=:cond;", [':cond' => $_id], false)['nb'] > 0; } public function count(): int { - return $this->dbContext::query(("SELECT COUNT(*) as nb FROM " . $this->table . ";"), false)['nb']; + return $this->db->query(("SELECT COUNT(*) as nb FROM " . $this->table . ";"), false)['nb']; } public function getAll(): array { - return $this->dbContext::query(("SELECT * FROM " . $this->table . ";"), true); + return $this->db->query(("SELECT * FROM " . $this->table . ";"), true); } public function getBy(string $_col, string $_value, bool $_all = false) : array { - return $this->dbContext::fetch("SELECT * FROM " . $this->table . " WHERE " . \basename($_col) . "=:cond;", [':cond' => $_value], $_all); + return $this->db->fetch("SELECT * FROM " . $this->table . " WHERE " . basename($_col) . "=:cond;", [':cond' => $_value], $_all); } public function getById($_id): array From f000ec3a6eb00c251811718437583a97b6696797 Mon Sep 17 00:00:00 2001 From: MDevoldere Date: Sun, 16 May 2021 23:54:20 +0200 Subject: [PATCH 10/24] update from diag --- Md/App.php | 17 +++++++++- Md/Controllers/Controller.php | 5 +-- Md/Controllers/IController.php | 4 +++ Md/Db/DbContext.php | 27 ++++++++++------ Md/Exceptions/ExceptionHandler.php | 51 ++++++++++++++++++++++++++++++ Md/Http/Http.php | 29 +++++++---------- Md/Http/IRequest.php | 9 ++++++ Md/Http/IResponse.php | 4 +-- Md/Http/IRouter.php | 4 +-- Md/Http/Request.php | 39 +++++++++++++---------- Md/Http/Response.php | 11 ++----- Md/Http/Router.php | 9 ++---- 12 files changed, 145 insertions(+), 64 deletions(-) create mode 100644 Md/Exceptions/ExceptionHandler.php diff --git a/Md/App.php b/Md/App.php index ad92dbd..f18043d 100644 --- a/Md/App.php +++ b/Md/App.php @@ -3,6 +3,7 @@ namespace Md; use Md\Controllers\IController; +use Md\Db\DbContext; use Md\Http\Http; use Md\Http\IRouter; @@ -19,7 +20,9 @@ class App * @param IRouter $_router the IRouter object to use */ final static public function run(IRouter $_router): void - { + { + self::handleDatabases($_router); + if(null !== ($c = static::handleController($_router))) { Http::response($c->handleRequest()); } @@ -36,4 +39,16 @@ static protected function handleController(IRouter $_router): ?IController { return $_router->getController(); } + + static protected function handleDatabases(IRouter $_router) : void + { + $f = ($_router->getPath().'var/db.conf.php'); + + if(is_file($f)) { + $a = require $f; + foreach($a as $context => $params) { + DbContext::setContext($context, $params); + } + } + } } diff --git a/Md/Controllers/Controller.php b/Md/Controllers/Controller.php index 543797d..0a1a00f 100644 --- a/Md/Controllers/Controller.php +++ b/Md/Controllers/Controller.php @@ -27,6 +27,7 @@ abstract class Controller implements IController /** @var null|IRepository $repo The Repository to use or null if no repository */ protected ?IRepository $repo; + /** @var bool $view defines if a view should be loaded (true) */ protected bool $view; public function __construct(IRouter $_router) @@ -58,9 +59,9 @@ public function handleRequest(): IResponse return $this->response; } - $layout = new View($this->router->getPath().'Views/'); + $layout = new View($this->router->getViewsPath()); $layout->setFile('_layout'); - $layout->setChild('page', $this->router->getViewPath()); + $layout->setChild('page', $this->request->getView()); $this->response->setView($layout); return $this->response; } diff --git a/Md/Controllers/IController.php b/Md/Controllers/IController.php index 76eb55b..688882e 100644 --- a/Md/Controllers/IController.php +++ b/Md/Controllers/IController.php @@ -8,5 +8,9 @@ interface IController { + /** + * Handle current request and returns response object + * @return IResponse + */ public function handleRequest(): IResponse; } \ No newline at end of file diff --git a/Md/Db/DbContext.php b/Md/Db/DbContext.php index 4073f45..97f814d 100644 --- a/Md/Db/DbContext.php +++ b/Md/Db/DbContext.php @@ -23,7 +23,7 @@ static public function getContext(string $_context = 'default') : ?IDbContext return self::$context[$_context] ?? null; } - static public function setContext(array $c = [], string $_context = 'default'): ?IDbContext + static public function setContext(string $_context, array $c): ?IDbContext { try { @@ -82,7 +82,8 @@ static protected function fetchStmt(PDOStatement $stmt, bool $_all = false): arr /** @var PDO $db Représente une connexion vers une base de données */ protected ?PDO $pdo = null; - protected function __construct(PDO $_pdo) + + public function __construct(PDO $_pdo) { $this->pdo = $_pdo; } @@ -154,29 +155,37 @@ public function exec(string $_query, array $_values = []): int * @param array|Db $_values Le tableau de valeurs correspondant à la table courante * @return int Le nombre de lignes affectées */ - /*static public function insert(array $_values): int + public function insert(string $_table, array $_values): int { $cols = \array_keys($_values); $vals = (':' . \implode(', :', $cols)); $cols = \implode(',', $cols); - return static::exec("INSERT INTO " . static::$tableName . " (" . $cols . ") VALUES (" . $vals . ");", $_values); - }*/ + return $this->exec("INSERT INTO " . $_table . " (" . $cols . ") VALUES (" . $vals . ");", $_values); + } /** Met à jour un élément * @param array\Db $_values Le tableau de valeurs correspondant à la table courante. Doit contenir l'identifiant de la ligne à mettre à jour. * @return int Le nombre de lignes affectées */ - /*static public function update(array $_values): int + public function update(string $_table, string $_pk, array $_values): int { + $id = null; $cols = []; foreach ($_values as $k => $v) { - $cols[$k] = ($k . '=:' . $k); + if($k !== $_pk) { + $cols[$k] = ($k . '=:' . $k); + } + else { + $id = $v; + } } - return static::exec("UPDATE " . static::$tableName . " SET " . \implode(', ', $cols) . " WHERE " . static::$pkName . "=:" . static::$pkName . ";", $_values); - }*/ + if($id !== null) { + return $this->exec("UPDATE " . $_table . " SET " . \implode(', ', $cols) . " WHERE " . $_pk . "=:" . $id . ";", $_values); + } + } /** Supprime un élément * @param int $_id L'identifiant de la ligne à supprimer diff --git a/Md/Exceptions/ExceptionHandler.php b/Md/Exceptions/ExceptionHandler.php new file mode 100644 index 0000000..68ced4e --- /dev/null +++ b/Md/Exceptions/ExceptionHandler.php @@ -0,0 +1,51 @@ +getCode()) + { + case 200: + case 201: + case 202: + case 204: + case 400: + case 401: + case 403: + case 404: + case 405: + case 406: + case 500: + case 501: + Http::end($exception->getCode(), $exception->getMessage()); + break; + default: + Http::end(500, $exception->getCode() . ' ' . $exception->getMessage()); + break; + } + } + + public function errorHandler($error_level, $error_message, $error_file, $error_line) + { + $error_message = sprintf('%s %s %d %s', $error_level, $error_message, $error_line, $error_file); + + Http::end(500, $error_message); + } +} \ No newline at end of file diff --git a/Md/Http/Http.php b/Md/Http/Http.php index 362f121..0782c49 100644 --- a/Md/Http/Http.php +++ b/Md/Http/Http.php @@ -3,35 +3,30 @@ namespace Md\Http; -use function array_map, explode, preg_match, http_response_code; +use function array_map, header, http_response_code, preg_match; +/** + * HTTP Operations + */ class Http { - - static private IRequest $request; - static private IResponse $response; - + /** + * Secure data in array + * Accept alphanumerics characters only + * @return array the input array whitout + * @todo Move method outside this class + */ static public function secure(array $_data = []): array { return array_filter(array_map(function ($v) { if(!preg_match("/^[A-Za-z0-9]*$/", $v)) { - self::badRequest('Invalid request'); + self::badRequest('Invalid data'); } return $v; // return \preg_replace("/[^a-zA-Z0-9]/", "", \basename(\strip_tags($v), '.php')); }, $_data)); } - static public function getRequest(): IRequest - { - return self::$request; - } - - static public function getResponse(): IResponse - { - return self::$response; - } - static public function end(int $_code = 500, string $data = 'Internal Error') { http_response_code($_code); @@ -83,4 +78,4 @@ static public function notImplemented() { self::end(501, 'Not implemented'); } -} \ No newline at end of file +} diff --git a/Md/Http/IRequest.php b/Md/Http/IRequest.php index d6912f8..46c92d2 100644 --- a/Md/Http/IRequest.php +++ b/Md/Http/IRequest.php @@ -49,4 +49,13 @@ public function getAction(): string; * @return string the value provided to the method invoked in Controller */ public function getId(): ?string; + + /** + * Get View relative path + * ex: + * - request is /users/profile/23 + * - view = users/profile + * @return string the view relative path + */ + public function getView(): string; } \ No newline at end of file diff --git a/Md/Http/IResponse.php b/Md/Http/IResponse.php index 1b330d0..86226ec 100644 --- a/Md/Http/IResponse.php +++ b/Md/Http/IResponse.php @@ -59,7 +59,7 @@ public function addData(string $_key, $_value): IResponse; /** * Replace and set View for rendering * @param IView|null $_data the view to use - * @return IView|null + * @return $this */ - public function setView(?IView $_view): ?IView; + public function setView(?IView $_view): IResponse; } \ No newline at end of file diff --git a/Md/Http/IRouter.php b/Md/Http/IRouter.php index ee9a58c..b441ea4 100644 --- a/Md/Http/IRouter.php +++ b/Md/Http/IRouter.php @@ -10,14 +10,14 @@ * Map Http Request with Local System * * @method string getPath() get current App local path - * @method string getViewPath() get current App Views path (relative to getPath()) + * @method string getViewsPath() get current App Views path (relative to getPath()) * @method IRequest getRequest() get current Http Request * @method null|IController getController() get controller using current Http Request */ interface IRouter { public function getPath(): string; - public function getViewPath(): string; + public function getViewsPath(): string; public function getRequest(): IRequest; public function getController(): ?IController; } diff --git a/Md/Http/Request.php b/Md/Http/Request.php index 2d651b8..1fbae51 100644 --- a/Md/Http/Request.php +++ b/Md/Http/Request.php @@ -11,14 +11,16 @@ class Request implements IRequest private string $controller; private string $action; private ?string $id; + private string $view; public function __construct(array $_route) { $_route = Http::secure($_route); $this->method = mb_convert_case($_SERVER['REQUEST_METHOD'] ?? 'get', MB_CASE_LOWER); - $this->controller = mb_convert_case($_route[0] ?? 'home', MB_CASE_TITLE); + $this->controller = mb_convert_case($_route[0] ?? 'Home', MB_CASE_TITLE); $this->action = mb_convert_case($_route[1] ?? 'index', MB_CASE_LOWER); $this->id = ($_route[2] ?? null); + $this->view = ($this->controller.'/'.$this->action); } public function getMethod(): string @@ -31,21 +33,6 @@ public function getRoute(): string return sprintf('%s/%s/%s', $this->controller, $this->action, $this->id); } - public function getController(): string - { - return ($this->controller . 'Controller'); - } - - public function getAction(): string - { - return ($this->action . 'Action'); - } - - public function getId(): ?string - { - return $this->id; - } - public function getData(): array { switch($this->method) @@ -71,4 +58,24 @@ public function getData(): array break; } } + + public function getController(): string + { + return ($this->controller . 'Controller'); + } + + public function getAction(): string + { + return ($this->action . 'Action'); + } + + public function getId(): ?string + { + return $this->id; + } + + public function getView(): string + { + return $this->view; + } } diff --git a/Md/Http/Response.php b/Md/Http/Response.php index a6861a6..04b5174 100644 --- a/Md/Http/Response.php +++ b/Md/Http/Response.php @@ -53,7 +53,6 @@ public function getBody(): string public function getData(): array { return $this->data; - return $this; } public function setData(array $_data = []): IResponse @@ -76,16 +75,10 @@ public function addData(string $_key, $_value = null): IResponse return $this; } - public function setLayout(?IView $_view): IResponse - { - $this->view = $_view; - return $this; - } - - public function setView(?IView $_view): IView + public function setView(?IView $_view): IResponse { $this->view = $_view; $this->contentType = ($_view !== null) ? IResponse::HTTP_HTML : IResponse::HTTP_JSON; - return $this->view; + return $this; } } \ No newline at end of file diff --git a/Md/Http/Router.php b/Md/Http/Router.php index 17b375e..905d41e 100644 --- a/Md/Http/Router.php +++ b/Md/Http/Router.php @@ -11,7 +11,6 @@ class Router implements IRouter { protected string $path; protected string $controller; - protected string $view; protected IRequest $request; public function __construct(string $_namespace, string $_path) @@ -24,9 +23,7 @@ public function __construct(string $_namespace, string $_path) $this->request = new Request($_route); - $this->controller = sprintf('%s\\Controllers\\%s', $_namespace, $this->request->getController()); - - $this->view = sprintf('%s/%s', basename($this->request->getController(), 'Controller'), basename($this->request->getAction(), 'Action')); + $this->controller = ('\\' . $_namespace . '\\Controllers\\' . $this->request->getController()); } public function getPath(): string @@ -34,9 +31,9 @@ public function getPath(): string return $this->path; } - public function getViewPath(): string + public function getViewsPath(): string { - return $this->view; + return ($this->path . '/Views/'); } public function getRequest(): IRequest From 092c730f157ec2cacf82484ce09a0bc8fc343ba1 Mon Sep 17 00:00:00 2001 From: MDevoldere Date: Sun, 27 Jun 2021 13:16:53 +0200 Subject: [PATCH 11/24] comments and some fixes --- Md/Controllers/Controller.php | 15 ++---- Md/Db/DbContext.php | 93 ++++++++++++++++++----------------- Md/Db/IDbContext.php | 34 ++++++------- Md/Db/IRepository.php | 56 +++++++++++++++++++-- Md/Http/IResponse.php | 4 +- Md/Http/Request.php | 18 +++---- Md/Http/Response.php | 4 +- Md/Http/Router.php | 2 +- 8 files changed, 135 insertions(+), 91 deletions(-) diff --git a/Md/Controllers/Controller.php b/Md/Controllers/Controller.php index 0a1a00f..4d60064 100644 --- a/Md/Controllers/Controller.php +++ b/Md/Controllers/Controller.php @@ -54,26 +54,21 @@ public function handleRequest(): IResponse } $this->{$a}(); - - if($this->view === false) { - return $this->response; - } - - $layout = new View($this->router->getViewsPath()); - $layout->setFile('_layout'); - $layout->setChild('page', $this->request->getView()); - $this->response->setView($layout); + return $this->response; } /** * Set Generic Repository from table name and primary key name - * Remember to open the connection with DbContext before use any repo + * Remember to open the default connection with DbContext before use any generic repo */ public function setRepository(string $_table, string $_pk) { $this->repo = new Repository($_table, $_pk); } + /** + * Default Controller Action + */ abstract public function indexAction(): void; } \ No newline at end of file diff --git a/Md/Db/DbContext.php b/Md/Db/DbContext.php index 97f814d..cd80358 100644 --- a/Md/Db/DbContext.php +++ b/Md/Db/DbContext.php @@ -15,7 +15,7 @@ class DbContext implements IDbContext { - /** @var IDbContext[] $context */ + /** @var IDbContext[] $context DbContext storage */ static protected array $context = []; static public function getContext(string $_context = 'default') : ?IDbContext @@ -23,13 +23,12 @@ static public function getContext(string $_context = 'default') : ?IDbContext return self::$context[$_context] ?? null; } - static public function setContext(string $_context, array $c): ?IDbContext + static public function setContext(string $_context, array $c): void { try { if (empty($c['db_type']) || empty($c['db_dsn'])) { - exit('Db Error 0'); - return null; + return; } switch ($c['db_type']) { @@ -53,23 +52,21 @@ static public function setContext(string $_context, array $c): ?IDbContext self::$context[$_context] = new DbContext($pdo); break; default: - exit('Db Error 1'); + return; break; } - return self::getContext($_context); - } catch (Exception $e) { - exit('Db Error 10'); + exit('DbContext Error'); } } /** - * Retourne le jeu de résultat d'une requête SELECT exécutée - * @param PDOStatement $stmt Le jeu de résultat de la requête exécutée - * @param bool $_all true = retourne toutes les lignes trouvées. false = retourne la 1ère ligne trouvée - * @return array le jeu de résultat ou un tableau vide + * Get fetch result from executed prepared statement (SELECT queries only) + * @param PDOStatement $stmt the prepared statement already executed + * @param bool $_all true = return all lines. false = return first line + * @return array result set or empty array */ static protected function fetchStmt(PDOStatement $stmt, bool $_all = false): array { @@ -79,34 +76,37 @@ static protected function fetchStmt(PDOStatement $stmt, bool $_all = false): arr } - /** @var PDO $db Représente une connexion vers une base de données */ + /** @var PDO $db PDO Connection */ protected ?PDO $pdo = null; + /** + * DbContext Constructor + */ public function __construct(PDO $_pdo) { $this->pdo = $_pdo; } - /** Exécute une requête de lecture simple - * @param string $_query La requête SQL à exécuter - * @param bool $_all true = retourne toutes les lignes trouvées. false = retourne la 1ère ligne trouvée - * @return mixed Le jeu de résultat ou empty si aucun résultat + /** Performs a simple read request + * @param string $_query SQL query to execute + * @param bool $_all true = return all rows. false = return first row + * @return mixed result set or empty array */ public function query(string $_query, bool $_all = false): array { try { return self::fetchStmt($this->pdo->query($_query), $_all); } catch (Exception $e) { - exit('Db Error 11'); + exit('DbQuery Error'); } } - /** Exécute une requête de lecture paramétrée - * @param string $_query La requête à exécuter - * @param array $_values Les paramètres de la requête - * @param bool $_all true = retourne toutes les lignes trouvées. false = retourne la 1er ligne trouvée - * @return mixed Le jeu de résultat ou empty si aucun résultat + /** Executes a parameterized read request + * @param string $_query SQL query to execute + * @param array $_values the values associated with the query parameters + * @param bool $_all true = return all rows. false = return first row + * @return mixed result set or empty array */ public function fetch(string $_query, array $_values = [], bool $_all = false): array { @@ -114,14 +114,14 @@ public function fetch(string $_query, array $_values = [], bool $_all = false): $stmt = $this->pdo->prepare($_query); return ($stmt->execute($_values) ? static::fetchStmt($stmt, $_all) : []); } catch (Exception $e) { - exit('Db Error 100' . $e->getMessage()); + exit('DbFetch Error' . $e->getMessage()); } } - /** Exécute une requête de lecture paramétrée et retourne toutes les lignes trouvées - * @param string $_query La requête à exécuter - * @param array $_values Les paramètres de la requête - * @return mixed Le jeu de résultat ou empty si aucun résultat + /** Execute a parameterized read request and return all rows + * @param string $_query SQL query to execute + * @param array $_values the values associated with the query parameters + * @return mixed result set or empty array */ public function fetchAll(string $_query, array $_values = []): array { @@ -129,10 +129,10 @@ public function fetchAll(string $_query, array $_values = []): array } - /** Exécute une requête d'écriture paramétrée et retourne le nombre de lignes affectées - * @param string $_query La requête à exécuter - * @param array $_values Les paramètres de la requête - * @return int Le nombre de lignes affectées + /** Executes a parameterized write request and returns the number of rows affected + * @param string $_query SQL query to execute + * @param array $_values the values associated with the query parameters + * @return int number of rows affected by the query */ public function exec(string $_query, array $_values = []): int { @@ -151,9 +151,10 @@ public function exec(string $_query, array $_values = []): int } - /** Insère un nouvel élément - * @param array|Db $_values Le tableau de valeurs correspondant à la table courante - * @return int Le nombre de lignes affectées + /** Add data to specific table + * @param string $_table the table + * @param array $_values data to insert (must match to table structure) + * @return int number of rows affected */ public function insert(string $_table, array $_values): int { @@ -164,9 +165,11 @@ public function insert(string $_table, array $_values): int return $this->exec("INSERT INTO " . $_table . " (" . $cols . ") VALUES (" . $vals . ");", $_values); } - /** Met à jour un élément - * @param array\Db $_values Le tableau de valeurs correspondant à la table courante. Doit contenir l'identifiant de la ligne à mettre à jour. - * @return int Le nombre de lignes affectées + /** Update a row in specific table + * @param string $_table the table + * @param string $_pk the primary key name + * @param array $_values The array of values corresponding to the current table. Must contain the identifier of the row to update. + * @return int number of rows affected */ public function update(string $_table, string $_pk, array $_values): int { @@ -187,12 +190,14 @@ public function update(string $_table, string $_pk, array $_values): int } } - /** Supprime un élément - * @param int $_id L'identifiant de la ligne à supprimer - * @return int Le nombre de lignes affectées + /** Delete a row in specific table + * @param string $_table the table + * @param string $_pk the primary key name + * @param string $_id row identifier + * @return int number of rows affected */ - /*static public function delete($_id): int + public function delete(string $_table, string $_pk, string $_id): int { - return static::exec("DELETE FROM " . static::$tableName . " WHERE " . static::$pkName . "=:id;", [':id' => $_id]); - }*/ + return $this->exec("DELETE FROM " . $_table . " WHERE " . $_pk . "=:id;", [':id' => $_id]); + } } diff --git a/Md/Db/IDbContext.php b/Md/Db/IDbContext.php index c648664..e0f706c 100644 --- a/Md/Db/IDbContext.php +++ b/Md/Db/IDbContext.php @@ -10,32 +10,32 @@ */ interface IDbContext { - /** Exécute une requête de lecture simple - * @param string $_query La requête SQL à exécuter - * @param bool $_all true = retourne toutes les lignes trouvées. false = retourne la 1ère ligne trouvée - * @return array Le jeu de résultat ou empty si aucun résultat + /** Performs a simple read request + * @param string $_query SQL query to execute + * @param bool $_all true = return all rows. false = return first row + * @return mixed result set or empty array */ public function query(string $_query, bool $_all = false): array; - /** Exécute une requête de lecture paramétrée - * @param string $_query La requête à exécuter - * @param array $_values Les paramètres de la requête - * @param bool $_all true = retourne toutes les lignes trouvées. false = retourne la 1er ligne trouvée - * @return array Le jeu de résultat ou empty si aucun résultat + /** Executes a parameterized read request + * @param string $_query SQL query to execute + * @param array $_values the values associated with the query parameters + * @param bool $_all true = return all rows. false = return first row + * @return mixed result set or empty array */ public function fetch(string $_query, array $_values = [], bool $_all = false): array; - /** Exécute une requête de lecture paramétrée et retourne toutes les lignes trouvées - * @param string $_query La requête à exécuter - * @param array $_values Les paramètres de la requête - * @return array Le jeu de résultat ou empty si aucun résultat + /** Execute a parameterized read request and return all rows + * @param string $_query SQL query to execute + * @param array $_values the values associated with the query parameters + * @return mixed result set or empty array */ public function fetchAll(string $_query, array $_values = []): array; - /** Exécute une requête d'écriture paramétrée - * @param string $_query La requête à exécuter - * @param array $_values Les paramètres de la requête - * @return int Le nombre de lignes affectées + /** Executes a parameterized write request and returns the number of rows affected + * @param string $_query SQL query to execute + * @param array $_values the values associated with the query parameters + * @return int number of rows affected by the query */ public function exec(string $_query, array $_values = []): int; } \ No newline at end of file diff --git a/Md/Db/IRepository.php b/Md/Db/IRepository.php index 13b5e7f..8eedd26 100644 --- a/Md/Db/IRepository.php +++ b/Md/Db/IRepository.php @@ -2,24 +2,70 @@ namespace Md\Db; - +/** + * Represents a Table with basics operations (CRUD) + */ interface IRepository { + /** + * Get total rows number + * @return int total rows number + */ public function count(): int; - public function exists($_id): bool; + /** + * Check if identifier exists in table + * @param string $_id identifier to search + * @return bool true if exists, false if not found + */ + public function exists( string $_id): bool; + /** + * Get all table rows + * @return array all table rows + */ public function getAll(): array; + /** + * Get by specific column value + * @param string $_col the column in table + * @param string $_value the value to search in column + * @param bool $_all true to get all result set, false to get the first row found + */ public function getBy(string $_col, string $_value, bool $_all = true): array; - public function getById($_id): array; + /** + * Get row by identifier + * @param string $_id identifier to search + * @return array row found or empty array + */ + public function getById(string $_id): array; + /** + * Clean & Validate array structure matching current table and clean data if necessary + * @param array $_input the array to validate + * @return bool true if array is valid, false if invalid + */ public function validate(array &$_input): bool; + /** + * Add row to current table + * @param array data corresponding to current table + * @return bool true if row added, false otherwise + */ public function add(array $_input) : bool; - public function update($id, array $_input): bool; + /** + * Update a row in current table + * @param string $_id row identifier + * @param array $_input data to update + */ + public function update(string $_id, array $_input): bool; - public function delete($_id): bool; + /** + * Delete a row in table + * @param string $_id row identifier + * @return bool true if deleted, false otherwise + */ + public function delete(string $_id): bool; } \ No newline at end of file diff --git a/Md/Http/IResponse.php b/Md/Http/IResponse.php index 86226ec..c37ab87 100644 --- a/Md/Http/IResponse.php +++ b/Md/Http/IResponse.php @@ -6,8 +6,8 @@ interface IResponse { - public const HTTP_JSON = 'application/json; charset=utf-8'; - public const HTTP_HTML = 'text/html'; + public const JSON = 'application/json; charset=utf-8'; + public const HTML = 'text/html'; /** * Get Response HTTP Code diff --git a/Md/Http/Request.php b/Md/Http/Request.php index 1fbae51..4f0c2f1 100644 --- a/Md/Http/Request.php +++ b/Md/Http/Request.php @@ -11,16 +11,14 @@ class Request implements IRequest private string $controller; private string $action; private ?string $id; - private string $view; public function __construct(array $_route) { $_route = Http::secure($_route); - $this->method = mb_convert_case($_SERVER['REQUEST_METHOD'] ?? 'get', MB_CASE_LOWER); + $this->method = mb_convert_case($_SERVER['REQUEST_METHOD'] ?? 'GET', MB_CASE_UPPER); $this->controller = mb_convert_case($_route[0] ?? 'Home', MB_CASE_TITLE); $this->action = mb_convert_case($_route[1] ?? 'index', MB_CASE_LOWER); $this->id = ($_route[2] ?? null); - $this->view = ($this->controller.'/'.$this->action); } public function getMethod(): string @@ -30,25 +28,25 @@ public function getMethod(): string public function getRoute(): string { - return sprintf('%s/%s/%s', $this->controller, $this->action, $this->id); + return ($this->controller . '/' . $this->action . '/' . $this->id); } public function getData(): array { switch($this->method) { - case 'get': - case 'delete': + case 'GET': + case 'DELETE': return Http::secure($_GET ?? []); break; - case 'post': + case 'POST': $a = Http::secure($_POST ?? []); if(!empty($_FILES)) { - $a['_files'] = &$_FILES; + $a['_files'] = $_FILES; } return $a; break; - case 'put': + case 'PUT': $a = null; parse_str(file_get_contents('php://input'), $a); return Http::secure($a); @@ -76,6 +74,6 @@ public function getId(): ?string public function getView(): string { - return $this->view; + return ($this->controller.'/'.$this->action); } } diff --git a/Md/Http/Response.php b/Md/Http/Response.php index 04b5174..81596f2 100644 --- a/Md/Http/Response.php +++ b/Md/Http/Response.php @@ -20,7 +20,7 @@ class Response implements IResponse public function __construct() { $this->code = 200; - $this->contentType = IResponse::HTTP_JSON; + $this->contentType = IResponse::JSON; $this->data = []; $this->view = null; } @@ -78,7 +78,7 @@ public function addData(string $_key, $_value = null): IResponse public function setView(?IView $_view): IResponse { $this->view = $_view; - $this->contentType = ($_view !== null) ? IResponse::HTTP_HTML : IResponse::HTTP_JSON; + $this->contentType = ($_view !== null) ? IResponse::HTML : IResponse::JSON; return $this; } } \ No newline at end of file diff --git a/Md/Http/Router.php b/Md/Http/Router.php index 905d41e..f5b76bd 100644 --- a/Md/Http/Router.php +++ b/Md/Http/Router.php @@ -33,7 +33,7 @@ public function getPath(): string public function getViewsPath(): string { - return ($this->path . '/Views/'); + return ($this->path . 'Views/'); } public function getRequest(): IRequest From fa691982584d921f9cf3af82dd4a446353a192d4 Mon Sep 17 00:00:00 2001 From: MDevoldere Date: Sun, 27 Jun 2021 23:23:29 +0200 Subject: [PATCH 12/24] working on --- Md/Controllers/Controller.php | 52 +++++++++++--------- Md/Controllers/ControllerView.php | 79 +++++++++++++++++++++++++++++++ Md/Http/IRequest.php | 9 ++++ Md/Http/IResponse.php | 47 ++++++++---------- Md/Http/IRouter.php | 10 ++-- Md/Http/Request.php | 12 +++-- Md/Http/Response.php | 57 ++++++++-------------- Md/Http/Router.php | 38 ++++++--------- 8 files changed, 182 insertions(+), 122 deletions(-) create mode 100644 Md/Controllers/ControllerView.php diff --git a/Md/Controllers/Controller.php b/Md/Controllers/Controller.php index 4d60064..23932e7 100644 --- a/Md/Controllers/Controller.php +++ b/Md/Controllers/Controller.php @@ -6,7 +6,6 @@ use Md\Db\Repository; use Md\Http\IRequest; use Md\Http\IResponse; -use Md\Http\IRouter; use Md\Http\Response; use Md\Views\View; @@ -15,9 +14,6 @@ */ abstract class Controller implements IController { - /** @var IRouter $router The router used by current App */ - protected IRouter $router; - /** @var IRequest $request The HTTP request extracted from $router */ protected IRequest $request; @@ -27,48 +23,58 @@ abstract class Controller implements IController /** @var null|IRepository $repo The Repository to use or null if no repository */ protected ?IRepository $repo; - /** @var bool $view defines if a view should be loaded (true) */ - protected bool $view; + /** @var array $data Data sent to response */ + protected array $data; - public function __construct(IRouter $_router) + public function __construct(IResponse $_response) { - $this->router = $_router; - $this->request = $_router->getRequest(); - $this->response = new Response(); + $this->request = $_response->getRequest(); + $this->response = $_response; $this->repo = null; - $this->view = false; + $this->data = []; $this->init(); } + + /** + * Default Controller Action + */ + abstract public function indexAction(): void; - protected function init() - { - - } + /** + * Load custom components + */ + protected function init() {} + /** + * Handle current request + */ public function handleRequest(): IResponse { $a = $this->request->getAction(); if(!method_exists($this, $a)) { - return $this->response->setCode(404)->addData('error', 'Invalid Action'); + return $this->response->setCode(404)->setBody('Error : Invalid Action'); } $this->{$a}(); - - return $this->response; + + if($this->response->getContentType() === IResponse::JSON) { + return $this->response->setBody(json_encode($this->data, JSON_PRETTY_PRINT | JSON_UNESCAPED_UNICODE | JSON_UNESCAPED_SLASHES)); + } + + $layout = new View($this->request->getLocalPath('Views/')); + $layout->setFile('_layout'); + $layout->setChild('page', $this->request->getView()); + return $this->response->setBody($layout->fetch($this->data)); } /** * Set Generic Repository from table name and primary key name - * Remember to open the default connection with DbContext before use any generic repo + * Remember to set the "default" connection with DbContext before use any generic repo */ public function setRepository(string $_table, string $_pk) { $this->repo = new Repository($_table, $_pk); } - /** - * Default Controller Action - */ - abstract public function indexAction(): void; } \ No newline at end of file diff --git a/Md/Controllers/ControllerView.php b/Md/Controllers/ControllerView.php new file mode 100644 index 0000000..1501002 --- /dev/null +++ b/Md/Controllers/ControllerView.php @@ -0,0 +1,79 @@ +router = $_router; + $this->request = $_router->getRequest(); + $this->response = new Response(); + $this->repo = null; + $this->view = false; + $this->init(); + } + + protected function init() + { + + } + + public function handleRequest(): IResponse + { + $a = $this->request->getAction(); + + if(!method_exists($this, $a)) { + return $this->response->setCode(404)->addData('error', 'Invalid Action'); + } + + $this->{$a}(); + + if($this->view === false) { + return $this->response; + } + + $layout = new View($this->router->getViewsPath()); + $layout->setFile('_layout'); + $layout->setChild('page', $this->request->getView()); + $this->response->setView($layout); + return $this->response; + } + + /** + * Set Generic Repository from table name and primary key name + * Remember to open the connection with DbContext before use any repo + */ + public function setRepository(string $_table, string $_pk) + { + $this->repo = new Repository($_table, $_pk); + } + + abstract public function indexAction(): void; +} \ No newline at end of file diff --git a/Md/Http/IRequest.php b/Md/Http/IRequest.php index 46c92d2..afae8b5 100644 --- a/Md/Http/IRequest.php +++ b/Md/Http/IRequest.php @@ -11,6 +11,8 @@ * @method string getController() * @method string getAction() * @method null|string getId() + * @method string getView() + * @method string getLocalPath() */ interface IRequest { @@ -58,4 +60,11 @@ public function getId(): ?string; * @return string the view relative path */ public function getView(): string; + + /** + * Get App absolute path + * @param null|string $_subpath + * @return string the app absolute path + */ + public function getLocalPath(?string $_subpath): string; } \ No newline at end of file diff --git a/Md/Http/IResponse.php b/Md/Http/IResponse.php index c37ab87..afdf501 100644 --- a/Md/Http/IResponse.php +++ b/Md/Http/IResponse.php @@ -9,6 +9,12 @@ interface IResponse public const JSON = 'application/json; charset=utf-8'; public const HTML = 'text/html'; + /** + * Get associated Request + * @return string the request + */ + public function getRequest(): IRequest; + /** * Get Response HTTP Code * @return int the response http code @@ -29,37 +35,22 @@ public function setCode(int $_code): IResponse; public function getContentType(): string; /** - * Get Response Body - * @return string the response body - */ - public function getBody(): string; - - /** - * Replace and set Response data - * @param array $_data the new response data - * @return $this - */ - public function setData(array $_data): IResponse; - - /** - * Append data to current Response - * @param array $_data data to append to the current response - * @return $this + * Set Response Content-Type + * @param string $_contentType + * @return self */ - public function appendData(array $_data): IResponse; + public function setContentType(string $_contentType): IResponse; /** - * Add 1 value to current Response data (if key already exists, replace data) - * @param string $_key the key for the value to add - * @param mixed $_value the associated value - * @return $this + * Get Response Body + * @return string the response body */ - public function addData(string $_key, $_value): IResponse; + public function getBody(): string; - /** - * Replace and set View for rendering - * @param IView|null $_data the view to use - * @return $this + /** + * Set Response Body + * @param string $_body + * @return self */ - public function setView(?IView $_view): IResponse; -} \ No newline at end of file + public function setBody(string $_body): IResponse; +} diff --git a/Md/Http/IRouter.php b/Md/Http/IRouter.php index b441ea4..0a7702d 100644 --- a/Md/Http/IRouter.php +++ b/Md/Http/IRouter.php @@ -9,15 +9,13 @@ * * Map Http Request with Local System * - * @method string getPath() get current App local path - * @method string getViewsPath() get current App Views path (relative to getPath()) - * @method IRequest getRequest() get current Http Request * @method null|IController getController() get controller using current Http Request + * @method IRequest getRequest() get current Http Request + * @method IResponse getResponse() get current Http Reponse */ interface IRouter { - public function getPath(): string; - public function getViewsPath(): string; - public function getRequest(): IRequest; public function getController(): ?IController; + public function getRequest(): IRequest; + public function getResponse(): IResponse; } diff --git a/Md/Http/Request.php b/Md/Http/Request.php index 4f0c2f1..a7917d7 100644 --- a/Md/Http/Request.php +++ b/Md/Http/Request.php @@ -6,15 +6,16 @@ class Request implements IRequest { - /** @var string $method HTTP Method */ + private string $localPath; private string $method; private string $controller; private string $action; private ?string $id; - public function __construct(array $_route) + public function __construct(array $_route, string $_localPath) { $_route = Http::secure($_route); + $this->localPath = $_localPath; $this->method = mb_convert_case($_SERVER['REQUEST_METHOD'] ?? 'GET', MB_CASE_UPPER); $this->controller = mb_convert_case($_route[0] ?? 'Home', MB_CASE_TITLE); $this->action = mb_convert_case($_route[1] ?? 'index', MB_CASE_LOWER); @@ -28,7 +29,7 @@ public function getMethod(): string public function getRoute(): string { - return ($this->controller . '/' . $this->action . '/' . $this->id); + return mb_convert_case($this->controller . '/' . $this->action . '/' . $this->id, MB_CASE_LOWER); } public function getData(): array @@ -72,6 +73,11 @@ public function getId(): ?string return $this->id; } + public function getLocalPath(?string $_subpath): string + { + return $this->localPath . ($_subpath); + } + public function getView(): string { return ($this->controller.'/'.$this->action); diff --git a/Md/Http/Response.php b/Md/Http/Response.php index 81596f2..03cb1cc 100644 --- a/Md/Http/Response.php +++ b/Md/Http/Response.php @@ -13,18 +13,23 @@ class Response implements IResponse private string $contentType; - private array $data; + private IRequest $request; - private ?View $view; + private string $body; - public function __construct() + public function __construct(IRequest $_request) { $this->code = 200; $this->contentType = IResponse::JSON; - $this->data = []; - $this->view = null; + $this->request = $_request; + $this->body = ""; } + public function getRequest(): IRequest + { + return $this->request; + } + public function getCode(): int { return $this->code; @@ -41,44 +46,20 @@ public function getContentType(): string return $this->contentType; } - public function getBody(): string - { - if($this->view === null) { - return json_encode($this->data, JSON_UNESCAPED_UNICODE | JSON_UNESCAPED_SLASHES | JSON_PRETTY_PRINT); - } - - return $this->view->fetch($this->data); - } - - public function getData(): array - { - return $this->data; - } - - public function setData(array $_data = []): IResponse + public function setContentType(string $_contentType): IResponse { - $this->data = $_data; + $this->contentType = $_contentType; return $this; } - public function appendData(array $_data = []): IResponse - { - foreach($_data as $k => $v) { - $this->addData($k, $v); - } - return $this; - } - - public function addData(string $_key, $_value = null): IResponse - { - $this->data[$_key] = $_value; - return $this; + public function getBody(): string + { + return $this->body; } - public function setView(?IView $_view): IResponse + public function setBody(string $_body): IResponse { - $this->view = $_view; - $this->contentType = ($_view !== null) ? IResponse::HTML : IResponse::JSON; + $this->body = $_body; return $this; - } -} \ No newline at end of file + } +} diff --git a/Md/Http/Router.php b/Md/Http/Router.php index f5b76bd..680bd13 100644 --- a/Md/Http/Router.php +++ b/Md/Http/Router.php @@ -3,47 +3,37 @@ namespace Md\Http; use Md\Controllers\IController; -use Md\Loader; -use function basename, str_replace, explode, trim, sprintf; +use function dirname, explode, str_replace, trim; class Router implements IRouter { - protected string $path; - protected string $controller; - protected IRequest $request; + protected IController $controller; + protected IResponse $response; public function __construct(string $_namespace, string $_path) { - $this->path = (dirname($_path) . DIRECTORY_SEPARATOR . str_replace('\\', DIRECTORY_SEPARATOR, $_namespace) . DIRECTORY_SEPARATOR); - $_route = str_replace('//', '/', $_SERVER['REQUEST_URI']); $_route = explode('?', $_route)[0] ?? '/'; $_route = explode('/', trim($_route, '/')); - - $this->request = new Request($_route); - - $this->controller = ('\\' . $_namespace . '\\Controllers\\' . $this->request->getController()); + $request = new Request($_route, (dirname($_path) . DIRECTORY_SEPARATOR . str_replace('\\', DIRECTORY_SEPARATOR, $_namespace) . DIRECTORY_SEPARATOR)); + $this->response = new Response($request); + $ctrl = ('\\' . $_namespace . '\\Controllers\\' . $request->getController()); + $this->controller = new $ctrl($this->response); } - public function getPath(): string - { - return $this->path; - } - - public function getViewsPath(): string + public function getRequest(): IRequest { - return ($this->path . 'Views/'); + return $this->response->getRequest(); } - public function getRequest(): IRequest + public function getResponse(): IResponse { - return $this->request; + return $this->response; } - public function getController(): ?IController + public function getController(): IController { - return (new $this->controller($this)); + return $this->controller; } - -} \ No newline at end of file +} From 3c870a6bf737a3b8efd3df43b2938a1b62b2866b Mon Sep 17 00:00:00 2001 From: MDevoldere Date: Mon, 28 Jun 2021 18:43:55 +0200 Subject: [PATCH 13/24] fix handleDatabases --- Md/App.php | 20 +++++--------------- Md/Views/View.php | 8 +++----- 2 files changed, 8 insertions(+), 20 deletions(-) diff --git a/Md/App.php b/Md/App.php index f18043d..f266eda 100644 --- a/Md/App.php +++ b/Md/App.php @@ -2,9 +2,9 @@ namespace Md; -use Md\Controllers\IController; use Md\Db\DbContext; use Md\Http\Http; +use Md\Http\IRequest; use Md\Http\IRouter; /** @@ -21,28 +21,18 @@ class App */ final static public function run(IRouter $_router): void { - self::handleDatabases($_router); + self::handleDatabases($_router->getRequest()); - if(null !== ($c = static::handleController($_router))) { + if(null !== ($c = $_router->getController())) { Http::response($c->handleRequest()); } Http::notFound('invalid controller'); } - /** - * Load Controller from given IRouter - * @param IRouter $_router the IRouter object to use - * @return IController|null the loaded IController or null if not found - */ - static protected function handleController(IRouter $_router): ?IController - { - return $_router->getController(); - } - - static protected function handleDatabases(IRouter $_router) : void + static protected function handleDatabases(IRequest $_request) : void { - $f = ($_router->getPath().'var/db.conf.php'); + $f = ($_request->getLocalPath().'var/db.conf.php'); if(is_file($f)) { $a = require $f; diff --git a/Md/Views/View.php b/Md/Views/View.php index 63a0006..e25d4e6 100644 --- a/Md/Views/View.php +++ b/Md/Views/View.php @@ -34,7 +34,7 @@ public function __construct(string $_path) public function setFile(string $_filename): IView { $this->file = ($this->path.$_filename.'.php'); - // echo ('
'.var_export($this, true));
+        
         if(!\is_file($this->file)) {
             Http::notFound('invalid view ('.$_filename.')');
         }
@@ -57,9 +57,7 @@ public function fetch(array $_vars = []) : string
 
     public function setChild(string $_key, string $_filename): IView
     {
-        $v = new self($this->path);
-        $v->setFile($_filename);
-        $this->childs[$_key] = $v;
-        return $v;
+        $this->childs[$_key] = (new self($this->path))->setFile($_filename);
+        return $this->childs[$_key];
     }
 }

From f25489d7adaea6fb1afae4f02a6de5a88681c1f8 Mon Sep 17 00:00:00 2001
From: MDevoldere 
Date: Mon, 28 Jun 2021 18:46:28 +0200
Subject: [PATCH 14/24] Update IRequest.php

---
 Md/Http/IRequest.php | 2 +-
 1 file changed, 1 insertion(+), 1 deletion(-)

diff --git a/Md/Http/IRequest.php b/Md/Http/IRequest.php
index afae8b5..e29cefb 100644
--- a/Md/Http/IRequest.php
+++ b/Md/Http/IRequest.php
@@ -66,5 +66,5 @@ public function getView(): string;
      * @param null|string $_subpath
      * @return string the app absolute path
      */
-    public function getLocalPath(?string $_subpath): string;
+    public function getLocalPath(?string $_subpath = null): string;
 }
\ No newline at end of file

From 5b45523b33c45d223d4ca283417c091fc69945c9 Mon Sep 17 00:00:00 2001
From: MDevoldere 
Date: Mon, 28 Jun 2021 18:48:18 +0200
Subject: [PATCH 15/24] Update Request.php

---
 Md/Http/Request.php | 2 +-
 1 file changed, 1 insertion(+), 1 deletion(-)

diff --git a/Md/Http/Request.php b/Md/Http/Request.php
index a7917d7..21f87fa 100644
--- a/Md/Http/Request.php
+++ b/Md/Http/Request.php
@@ -73,7 +73,7 @@ public function getId(): ?string
         return $this->id;
     }
 
-    public function getLocalPath(?string $_subpath): string 
+    public function getLocalPath(?string $_subpath = null): string 
     {
         return $this->localPath . ($_subpath);
     }

From c70521287e8adbc093cebc80a85b893c42f745d3 Mon Sep 17 00:00:00 2001
From: MDevoldere 
Date: Mon, 28 Jun 2021 18:54:36 +0200
Subject: [PATCH 16/24] Update Router.php

---
 Md/Http/Router.php | 7 +++----
 1 file changed, 3 insertions(+), 4 deletions(-)

diff --git a/Md/Http/Router.php b/Md/Http/Router.php
index 680bd13..d66b929 100644
--- a/Md/Http/Router.php
+++ b/Md/Http/Router.php
@@ -8,7 +8,7 @@
 
 class Router implements IRouter 
 {
-    protected IController $controller;
+    protected string $controller;
     protected IResponse $response;
 
     public function __construct(string $_namespace, string $_path)
@@ -18,8 +18,7 @@ public function __construct(string $_namespace, string $_path)
         $_route = explode('/', trim($_route, '/'));     
         $request = new Request($_route, (dirname($_path) . DIRECTORY_SEPARATOR . str_replace('\\', DIRECTORY_SEPARATOR, $_namespace) . DIRECTORY_SEPARATOR));
         $this->response = new Response($request);
-        $ctrl = ('\\' . $_namespace . '\\Controllers\\' . $request->getController());
-        $this->controller = new $ctrl($this->response);
+        $this->controller = ('\\' . $_namespace . '\\Controllers\\' . $request->getController());
     }  
 
     public function getRequest(): IRequest
@@ -34,6 +33,6 @@ public function getResponse(): IResponse
 
     public function getController(): IController
     {
-        return $this->controller;
+        return new $this->controller($this->response);
     }
 }

From e18f9fd781bed238150c951d652d9a6204cbfe65 Mon Sep 17 00:00:00 2001
From: MDevoldere 
Date: Mon, 28 Jun 2021 18:59:37 +0200
Subject: [PATCH 17/24] Update App.php

---
 Md/App.php | 4 +++-
 1 file changed, 3 insertions(+), 1 deletion(-)

diff --git a/Md/App.php b/Md/App.php
index f266eda..5001b45 100644
--- a/Md/App.php
+++ b/Md/App.php
@@ -7,6 +7,8 @@
 use Md\Http\IRequest;
 use Md\Http\IRouter;
 
+use function dirname, is_file;
+
 /**
  * MD MVC App Launcher
  * 
@@ -32,7 +34,7 @@ final static public function run(IRouter $_router): void
 
     static protected function handleDatabases(IRequest $_request) : void
     {
-        $f = ($_request->getLocalPath().'var/db.conf.php');
+        $f = (dirname($_request->getLocalPath()).'/var/db.conf.php');
 
         if(is_file($f)) {
             $a = require $f;

From 401d1886b18bbfbf3f320aac134f527ba6c72181 Mon Sep 17 00:00:00 2001
From: MDevoldere 
Date: Mon, 28 Jun 2021 19:03:41 +0200
Subject: [PATCH 18/24] Update Router.php

---
 Md/Http/Router.php | 8 +++++++-
 1 file changed, 7 insertions(+), 1 deletion(-)

diff --git a/Md/Http/Router.php b/Md/Http/Router.php
index d66b929..bcbab56 100644
--- a/Md/Http/Router.php
+++ b/Md/Http/Router.php
@@ -2,6 +2,7 @@
 
 namespace Md\Http;
 
+use Exception;
 use Md\Controllers\IController;
 
 use function dirname, explode, str_replace, trim;
@@ -33,6 +34,11 @@ public function getResponse(): IResponse
 
     public function getController(): IController
     {
-        return new $this->controller($this->response);
+        try {
+            return new $this->controller($this->response);
+        } catch(Exception $e) {
+            exit('Error : Invalid controller');
+        }
+        
     }
 }

From f92b9bb475f7ad3e15826b9c6188597eed7256b7 Mon Sep 17 00:00:00 2001
From: MDevoldere 
Date: Thu, 1 Jul 2021 18:50:25 +0200
Subject: [PATCH 19/24] fix Repo

---
 Md/App.php           | 2 +-
 Md/Db/Repository.php | 2 +-
 Md/Http/Router.php   | 2 ++
 3 files changed, 4 insertions(+), 2 deletions(-)

diff --git a/Md/App.php b/Md/App.php
index 5001b45..4c70190 100644
--- a/Md/App.php
+++ b/Md/App.php
@@ -34,7 +34,7 @@ final static public function run(IRouter $_router): void
 
     static protected function handleDatabases(IRequest $_request) : void
     {
-        $f = (dirname($_request->getLocalPath()).'/var/db.conf.php');
+        $f = $_request->getLocalPath('var/db.conf.php');
 
         if(is_file($f)) {
             $a = require $f;
diff --git a/Md/Db/Repository.php b/Md/Db/Repository.php
index 6cce170..f606214 100644
--- a/Md/Db/Repository.php
+++ b/Md/Db/Repository.php
@@ -4,7 +4,7 @@
 
 use function basename;
 
-class Repository implements Irepository
+class Repository implements IRepository
 {
 
     public string $table;
diff --git a/Md/Http/Router.php b/Md/Http/Router.php
index bcbab56..30b5dff 100644
--- a/Md/Http/Router.php
+++ b/Md/Http/Router.php
@@ -34,6 +34,8 @@ public function getResponse(): IResponse
 
     public function getController(): IController
     {
+        $c = str_replace('\\', '/', $this->controller);
+        if(!is_file($this->getRequest()->getLocalPath($c)))
         try {
             return new $this->controller($this->response);
         } catch(Exception $e) {

From 453a63ca69102f8422858cabcd24ba3372117dc0 Mon Sep 17 00:00:00 2001
From: MDevoldere 
Date: Tue, 6 Jul 2021 21:20:04 +0200
Subject: [PATCH 20/24] Update Router.php

---
 Md/Http/Router.php | 10 ++++++++--
 1 file changed, 8 insertions(+), 2 deletions(-)

diff --git a/Md/Http/Router.php b/Md/Http/Router.php
index 30b5dff..940374a 100644
--- a/Md/Http/Router.php
+++ b/Md/Http/Router.php
@@ -12,11 +12,17 @@ class Router implements IRouter
     protected string $controller;
     protected IResponse $response;
 
-    public function __construct(string $_namespace, string $_path)
+    public function __construct(string $_namespace, string $_path, string $_baseUrl = '/')
     {
         $_route = str_replace('//', '/', $_SERVER['REQUEST_URI']);
         $_route = explode('?', $_route)[0] ?? '/';
-        $_route = explode('/', trim($_route, '/'));     
+        $_route = trim($_route, '/');
+
+        if($_baseUrl !== '/') {
+            $_route = str_replace($_baseUrl, '', $_route);
+        }
+
+        $_route = explode('/', $_route);     
         $request = new Request($_route, (dirname($_path) . DIRECTORY_SEPARATOR . str_replace('\\', DIRECTORY_SEPARATOR, $_namespace) . DIRECTORY_SEPARATOR));
         $this->response = new Response($request);
         $this->controller = ('\\' . $_namespace . '\\Controllers\\' . $request->getController());

From 15f929b0a2745f2b7dd5e28ee3a81492940bf910 Mon Sep 17 00:00:00 2001
From: MDevoldere 
Date: Tue, 6 Jul 2021 21:41:54 +0200
Subject: [PATCH 21/24] Update Router.php

---
 Md/Http/Router.php | 4 +++-
 1 file changed, 3 insertions(+), 1 deletion(-)

diff --git a/Md/Http/Router.php b/Md/Http/Router.php
index 940374a..7d5a56f 100644
--- a/Md/Http/Router.php
+++ b/Md/Http/Router.php
@@ -41,7 +41,9 @@ public function getResponse(): IResponse
     public function getController(): IController
     {
         $c = str_replace('\\', '/', $this->controller);
-        if(!is_file($this->getRequest()->getLocalPath($c)))
+        if(!is_file($this->getRequest()->getLocalPath($c))) {
+            exit('ko');
+        }
         try {
             return new $this->controller($this->response);
         } catch(Exception $e) {

From 324e20a1a7274d714d4d084199ef21483f6da03d Mon Sep 17 00:00:00 2001
From: MDevoldere 
Date: Tue, 6 Jul 2021 21:47:40 +0200
Subject: [PATCH 22/24] Update Router.php

---
 Md/Http/Router.php | 2 +-
 1 file changed, 1 insertion(+), 1 deletion(-)

diff --git a/Md/Http/Router.php b/Md/Http/Router.php
index 7d5a56f..3ba225f 100644
--- a/Md/Http/Router.php
+++ b/Md/Http/Router.php
@@ -41,7 +41,7 @@ public function getResponse(): IResponse
     public function getController(): IController
     {
         $c = str_replace('\\', '/', $this->controller);
-        if(!is_file($this->getRequest()->getLocalPath($c))) {
+        if(!is_file(dirname($this->getRequest()->getLocalPath()) . '/' . $c)) {
             exit('ko');
         }
         try {

From 00b925d6a344c303312481c631c9baf6292f7d4c Mon Sep 17 00:00:00 2001
From: MDevoldere 
Date: Tue, 6 Jul 2021 21:57:58 +0200
Subject: [PATCH 23/24] Update Router.php

---
 Md/Http/Router.php | 4 ++--
 1 file changed, 2 insertions(+), 2 deletions(-)

diff --git a/Md/Http/Router.php b/Md/Http/Router.php
index 3ba225f..948caf2 100644
--- a/Md/Http/Router.php
+++ b/Md/Http/Router.php
@@ -16,12 +16,12 @@ public function __construct(string $_namespace, string $_path, string $_baseUrl
     {
         $_route = str_replace('//', '/', $_SERVER['REQUEST_URI']);
         $_route = explode('?', $_route)[0] ?? '/';
-        $_route = trim($_route, '/');
 
         if($_baseUrl !== '/') {
             $_route = str_replace($_baseUrl, '', $_route);
         }
 
+        $_route = trim($_route, '/');
         $_route = explode('/', $_route);     
         $request = new Request($_route, (dirname($_path) . DIRECTORY_SEPARATOR . str_replace('\\', DIRECTORY_SEPARATOR, $_namespace) . DIRECTORY_SEPARATOR));
         $this->response = new Response($request);
@@ -41,7 +41,7 @@ public function getResponse(): IResponse
     public function getController(): IController
     {
         $c = str_replace('\\', '/', $this->controller);
-        if(!is_file(dirname($this->getRequest()->getLocalPath()) . '/' . $c)) {
+        if(!is_file(dirname($this->getRequest()->getLocalPath()) . $c . '.php')) {
             exit('ko');
         }
         try {

From f2efa5dfb677ece3c0c7c348254f0a51fa1397b0 Mon Sep 17 00:00:00 2001
From: MDevoldere 
Date: Mon, 18 Oct 2021 23:26:47 +0200
Subject: [PATCH 24/24] simp-01

---
 {Md/Http => Http}/Http.php                 |  42 +++--
 Http/HttpFactory.php                       |  17 ++
 Http/Message.php                           | 103 +++++++++++
 Http/MessageInterface.php                  |  50 +++++
 Http/Request.php                           |  69 +++++++
 Http/RequestInterface.php                  |  36 ++++
 Http/Response.php                          |  26 +++
 Http/ResponseInterface.php                 |  23 +++
 Http/ServerRequest.php                     |  76 ++++++++
 Http/ServerRequestInterface.php            |  20 ++
 Http/Uri.php                               | 121 ++++++++++++
 Http/UriInterface.php                      |  20 ++
 Md/App.php                                 |  46 -----
 Md/Controllers/Controller.php              |  80 --------
 Md/Controllers/IController.php             |  16 --
 Md/Db/DbContext.php                        | 203 ---------------------
 Md/Db/IDbContext.php                       |  41 -----
 Md/Db/IRepository.php                      |  71 -------
 Md/Db/IRepositoryReader.php                |  15 --
 Md/Db/Model.php                            |  47 -----
 Md/Db/Repository.php                       |  74 --------
 Md/Db/RepositoryReader.php                 |  27 ---
 Md/Exceptions/ExceptionHandler.php         |  51 ------
 Md/Http/IRequest.php                       |  70 -------
 Md/Http/IResponse.php                      |  56 ------
 Md/Http/IRouter.php                        |  21 ---
 Md/Http/Request.php                        |  85 ---------
 Md/Http/Response.php                       |  65 -------
 Md/Http/Router.php                         |  54 ------
 Md/Views/IView.php                         |  12 --
 Mvc/App.php                                | 108 +++++++++++
 Mvc/AppInterface.php                       |  42 +++++
 Mvc/Controller.php                         |  96 ++++++++++
 Mvc/ControllerInterface.php                |  17 ++
 {Md/Controllers => Mvc}/ControllerView.php |   2 +-
 Mvc/Route.php                              | 111 +++++++++++
 Mvc/RouteInterface.php                     |  22 +++
 Mvc/RouterInterface.php                    |  18 ++
 {Md/Views => Mvc}/View.php                 |   8 +-
 Mvc/ViewInterface.php                      |  12 ++
 composer.json                              |   9 +-
 41 files changed, 1030 insertions(+), 1052 deletions(-)
 rename {Md/Http => Http}/Http.php (63%)
 create mode 100644 Http/HttpFactory.php
 create mode 100644 Http/Message.php
 create mode 100644 Http/MessageInterface.php
 create mode 100644 Http/Request.php
 create mode 100644 Http/RequestInterface.php
 create mode 100644 Http/Response.php
 create mode 100644 Http/ResponseInterface.php
 create mode 100644 Http/ServerRequest.php
 create mode 100644 Http/ServerRequestInterface.php
 create mode 100644 Http/Uri.php
 create mode 100644 Http/UriInterface.php
 delete mode 100644 Md/App.php
 delete mode 100644 Md/Controllers/Controller.php
 delete mode 100644 Md/Controllers/IController.php
 delete mode 100644 Md/Db/DbContext.php
 delete mode 100644 Md/Db/IDbContext.php
 delete mode 100644 Md/Db/IRepository.php
 delete mode 100644 Md/Db/IRepositoryReader.php
 delete mode 100644 Md/Db/Model.php
 delete mode 100644 Md/Db/Repository.php
 delete mode 100644 Md/Db/RepositoryReader.php
 delete mode 100644 Md/Exceptions/ExceptionHandler.php
 delete mode 100644 Md/Http/IRequest.php
 delete mode 100644 Md/Http/IResponse.php
 delete mode 100644 Md/Http/IRouter.php
 delete mode 100644 Md/Http/Request.php
 delete mode 100644 Md/Http/Response.php
 delete mode 100644 Md/Http/Router.php
 delete mode 100644 Md/Views/IView.php
 create mode 100644 Mvc/App.php
 create mode 100644 Mvc/AppInterface.php
 create mode 100644 Mvc/Controller.php
 create mode 100644 Mvc/ControllerInterface.php
 rename {Md/Controllers => Mvc}/ControllerView.php (98%)
 create mode 100644 Mvc/Route.php
 create mode 100644 Mvc/RouteInterface.php
 create mode 100644 Mvc/RouterInterface.php
 rename {Md/Views => Mvc}/View.php (86%)
 create mode 100644 Mvc/ViewInterface.php

diff --git a/Md/Http/Http.php b/Http/Http.php
similarity index 63%
rename from Md/Http/Http.php
rename to Http/Http.php
index 0782c49..adc81e5 100644
--- a/Md/Http/Http.php
+++ b/Http/Http.php
@@ -10,6 +10,25 @@
  */
 class Http 
 {
+    /** Current */
+    static private RequestInterface $request;
+
+    static public function getRequest() 
+    {
+        return self::$request = self::$request ?? new Request();
+    }
+
+    static public function setRequest($_request) 
+    {
+        self::$request = $_request;
+    }
+
+    static public function getResponse() 
+    {
+        return self::getRequest()->getResponse();
+    }
+
+
     /**
      * Secure data in array
      * Accept alphanumerics characters only
@@ -19,7 +38,8 @@ class Http
     static public function secure(array $_data = []): array
     {
         return array_filter(array_map(function ($v) {
-            if(!preg_match("/^[A-Za-z0-9]*$/", $v)) {
+            $v = basename($v);
+            if(!preg_match("/^[A-Za-z0-9\.\-]*$/", $v)) {
                 self::badRequest('Invalid data');
             }
             return $v;
@@ -27,54 +47,54 @@ static public function secure(array $_data = []): array
         }, $_data));
     }
 
-    static public function end(int $_code = 500, string $data = 'Internal Error')
+    static public function end(int $_code = 500, string $data = 'Internal Error'): void 
     {
         http_response_code($_code);
         exit($data);
     }
 
-    static public function response(IResponse $response)
+    static public function response(ResponseInterface $response): void 
     {
         header('Content-Type: ' . $response->getContentType());
         self::end($response->getCode(), $response->getBody());
     }
 
-    static public function ok(string $data)
+    static public function ok(string $data): void 
     {        
         self::end(200, $data);
     }
 
-    static public function accepted(string $data)
+    static public function accepted(string $data): void 
     {
         self::end(202, $data);
     }
 
-    static public function badRequest(string $msg = 'Invalid message received')
+    static public function badRequest(string $msg = 'Invalid message received'): void 
     {
         self::end(400, $msg);
     }
 
-    static public function unauthorized(string $msg = 'Invalid Token')
+    static public function unauthorized(string $msg = 'Invalid Token'): void 
     {
         self::end(401, $msg);
     }
 
-    static public function forbidden(string $msg = 'You are not allowed to access this resource')
+    static public function forbidden(string $msg = 'You are not allowed to access this resource'): void 
     {
         self::end(403, $msg);
     }
 
-    static public function notFound(string $msg = 'Not found')
+    static public function notFound(string $msg = 'Not found'): void 
     {
         self::end(404, ('No route for '.$_SERVER['REQUEST_URI']. ' ('.$msg.')'));
     }
 
-    static public function notAllowed()
+    static public function notAllowed(): void 
     {
         self::end(405, ('No route for '.$_SERVER['REQUEST_METHOD'].' '.$_SERVER['REQUEST_URI']));
     }
 
-    static public function notImplemented()
+    static public function notImplemented(): void 
     {
         self::end(501, 'Not implemented');
     }
diff --git a/Http/HttpFactory.php b/Http/HttpFactory.php
new file mode 100644
index 0000000..561bdbf
--- /dev/null
+++ b/Http/HttpFactory.php
@@ -0,0 +1,17 @@
+withContentType($_contentType)->withBody($_body);
+    }
+
+    /**
+     * Get the current message content-type
+     * @return string The message content-type
+     */
+    public function getContentType(): string 
+    {
+        return $this->contentType;
+    }
+
+    /**
+     * Set the current message content-type
+     * @param string $_contentType The content-type to apply 
+     * @return self
+     */
+    public function withContentType(string $_contentType): MessageInterface 
+    {
+        $this->contentType = mb_convert_case($_contentType, MB_CASE_LOWER);
+        return $this;
+    }
+
+    /**
+     * Get the current message body
+     * @return string The message body
+     */
+    public function getBody(): string 
+    {
+        return $this->body;
+    }
+
+
+    /**
+     * Set the current message body
+     * @param string $_body The message body
+     * @return self
+     */
+    public function withBody(string $_body): MessageInterface 
+    {
+        $this->body = trim($_body);
+        return $this;
+    }
+
+
+    /*public function getData(): array 
+    {
+        return [$this->body];
+    }
+    public function withData(array $_data): MessageInterface
+    {
+        if($this->contentType === Message::JSON) {
+            return $this->withBody(json_encode($_data, JSON_PRETTY_PRINT | JSON_UNESCAPED_UNICODE | JSON_UNESCAPED_SLASHES));
+        }
+
+        $s = [];
+        foreach($_data as $k => $v) {
+            $s[] = ($k.'='.$v);
+        }
+
+        return $this->withBody(implode(";", $s));
+    }*/
+}
diff --git a/Http/MessageInterface.php b/Http/MessageInterface.php
new file mode 100644
index 0000000..55e10f5
--- /dev/null
+++ b/Http/MessageInterface.php
@@ -0,0 +1,50 @@
+setMethod($_SERVER['REQUEST_METHOD'] ?? 'GET');
+        $this->uri = $_uri ?? new Uri($_SERVER['REQUEST_URI'] ?? '/');    
+        //$this->route = $this->uri->getParts();
+    }
+
+    public function setMethod(string $_method): RequestInterface
+    {
+        $this->method = $_method;
+        return $this;
+    }
+
+    public function getMethod(): string 
+    {
+        return $this->method;
+    }
+
+    public function getUri(): UriInterface 
+    {
+        return $this->uri;
+    }
+
+    /*public function getRoute(int $_pos): ?string
+    {
+        return $this->route[$_pos] ?? null;
+    }*/
+
+    public function getData(): array
+    {
+        switch($this->method)
+        {
+            case 'GET':
+            case 'DELETE':
+                return $this->uri->getQueryArray();
+            break;
+            case 'POST':
+                $a = Uri::secure($_POST ?? []);
+                if(!empty($_FILES)) {
+                    $a['_files'] = $_FILES;
+                }
+                return $a;
+            break;
+            case 'PATCH':
+            case 'PUT':
+                $a = [];
+                parse_str(file_get_contents('php://input'), $a);
+                return Uri::secure($a);
+            break;
+            default:
+                return [];
+            break;
+        }
+    }   
+}
diff --git a/Http/RequestInterface.php b/Http/RequestInterface.php
new file mode 100644
index 0000000..09227d0
--- /dev/null
+++ b/Http/RequestInterface.php
@@ -0,0 +1,36 @@
+code = 200;
+    }
+
+    public function getCode(): int
+    {
+        return $this->code;
+    }
+
+    public function withCode(int $_code): ResponseInterface 
+    {
+        $this->code = $_code;
+        return $this;
+    } 
+}
diff --git a/Http/ResponseInterface.php b/Http/ResponseInterface.php
new file mode 100644
index 0000000..781c3dd
--- /dev/null
+++ b/Http/ResponseInterface.php
@@ -0,0 +1,23 @@
+method = $_SERVER['REQUEST_METHOD'] ?? 'GET';
+        $this->contentType = explode(',', $_SERVER['HTTP_ACCEPT'] ?? '*/*')[0] ?? '*/*';
+        $this->uri = new Uri($_SERVER['REQUEST_URI']);
+    }
+
+    public function getMethod(): string 
+    {
+        return $this->method;
+    }
+
+    public function getContentType(): string 
+    {
+        return $this->contentType;
+    }
+
+    public function getUri(): UriInterface 
+    {
+        return $this->uri;
+    }
+
+    public function getData(): array
+    {
+        switch($this->method)
+        {
+            case 'GET':
+            case 'DELETE':
+                return $this->uri->getQueryArray();
+            break;
+            case 'POST':
+                $a = Uri::secure($_POST ?? []);
+                if(!empty($_FILES)) {
+                    $a['_files'] = $_FILES;
+                }
+                return $a;
+            break;
+            case 'PATCH':
+            case 'PUT':
+                $a = [];
+                parse_str(file_get_contents('php://input'), $a);
+                return Uri::secure($a);
+            break;
+            default:
+                return [];
+            break;
+        }
+    }
+}
diff --git a/Http/ServerRequestInterface.php b/Http/ServerRequestInterface.php
new file mode 100644
index 0000000..17ee48a
--- /dev/null
+++ b/Http/ServerRequestInterface.php
@@ -0,0 +1,20 @@
+withHost($u['host'] ?? $_SERVER['SERVER_NAME'] ?? 'localhost')
+             ->withPort($u['port'] ?? $_SERVER['SERVER_PORT'] ?? 0)
+             ->withPath($u['path'] ?? '')
+             ->withQuery($u['query'] ?? '');
+    }
+
+    public function __toString(): string
+    {
+        return ($this->scheme . '://' . $this->host . 
+            (!in_array($this->port, [0, 80, 443]) ? (':'. $this->port) : '') . '/' . 
+            $this->path . (!empty($this->query) ? '?'.$this->query : ''));
+    }
+
+    public function withHost(string $_host): UriInterface
+    {
+        $this->host = $_host;
+        return $this;
+    }
+
+    public function withPort(int $_port): UriInterface 
+    {
+        $this->port = (filter_var(
+            $_port, 
+            FILTER_VALIDATE_INT, 
+            ['options' => ['min_range' => 0, 'max_range' => 65535]]
+        ) !== false ? $_port : 0);
+        $this->scheme = ($this->port !== 80 ? 'https' : 'http');
+        return $this;
+    }
+
+    public function withPath(string $_path): UriInterface
+    {   
+        $this->path = mb_convert_case(preg_replace(['#\.+/#','#/+#'], '/', trim($_path ?? '', '/')), MB_CASE_LOWER);
+        return $this;
+    }
+
+    public function withQuery(string $_query): UriInterface
+    {
+        $this->query = $_query;
+        return $this;
+    }
+
+    public function withQueryArray(array $_query): UriInterface
+    {
+        return $this->withQuery(http_build_query($_query));
+    }
+
+    public function getHost(): string 
+    {
+        return $this->host;
+    }
+
+    public function getPort(): int 
+    {
+        return $this->host;
+    }
+
+    public function getPath(): string
+    {
+        return $this->path;
+    }
+
+    public function getParts(): array
+    {
+        return self::secure(explode('/', $this->path));;
+    }
+
+    public function getQuery(): string
+    {
+        return $this->query;
+    }
+
+    public function getQueryArray(): array 
+    {
+        $q = [];
+        parse_str($this->query ?? '', $q);
+        return self::secure($q);
+    }
+}
diff --git a/Http/UriInterface.php b/Http/UriInterface.php
new file mode 100644
index 0000000..0e97c2d
--- /dev/null
+++ b/Http/UriInterface.php
@@ -0,0 +1,20 @@
+getRequest());
-
-        if(null !== ($c = $_router->getController())) {
-            Http::response($c->handleRequest());
-        }
-
-        Http::notFound('invalid controller');
-    }
-
-    static protected function handleDatabases(IRequest $_request) : void
-    {
-        $f = $_request->getLocalPath('var/db.conf.php');
-
-        if(is_file($f)) {
-            $a = require $f;
-            foreach($a as $context => $params) {
-                DbContext::setContext($context, $params);
-            }
-        }
-    }
-}
diff --git a/Md/Controllers/Controller.php b/Md/Controllers/Controller.php
deleted file mode 100644
index 23932e7..0000000
--- a/Md/Controllers/Controller.php
+++ /dev/null
@@ -1,80 +0,0 @@
-request = $_response->getRequest();
-        $this->response = $_response;
-        $this->repo = null;
-        $this->data = [];
-        $this->init();
-    }
-    
-    /**
-     * Default Controller Action
-     */
-    abstract public function indexAction(): void;
-
-    /**
-     * Load custom components
-     */
-    protected function init() {}
-
-    /**
-     * Handle current request
-     */
-    public function handleRequest(): IResponse
-    {
-        $a = $this->request->getAction();
-
-        if(!method_exists($this, $a)) {
-            return $this->response->setCode(404)->setBody('Error : Invalid Action');
-        }
-
-        $this->{$a}();
-
-        if($this->response->getContentType() === IResponse::JSON) {
-            return $this->response->setBody(json_encode($this->data, JSON_PRETTY_PRINT | JSON_UNESCAPED_UNICODE | JSON_UNESCAPED_SLASHES));
-        }
-
-        $layout = new View($this->request->getLocalPath('Views/')); 
-        $layout->setFile('_layout');
-        $layout->setChild('page', $this->request->getView());  
-        return $this->response->setBody($layout->fetch($this->data));  
-    }
-
-    /**
-     * Set Generic Repository from table name and primary key name 
-     * Remember to set the "default" connection with DbContext before use any generic repo
-     */
-    public function setRepository(string $_table, string $_pk)
-    {
-        $this->repo = new Repository($_table, $_pk);
-    }
-
-}
\ No newline at end of file
diff --git a/Md/Controllers/IController.php b/Md/Controllers/IController.php
deleted file mode 100644
index 688882e..0000000
--- a/Md/Controllers/IController.php
+++ /dev/null
@@ -1,16 +0,0 @@
- PDO::ERRMODE_EXCEPTION,
-                            PDO::ATTR_DEFAULT_FETCH_MODE => PDO::FETCH_ASSOC,
-                            PDO::ATTR_EMULATE_PREPARES => false
-                        ]
-                    ));
-                    break;
-                case 'sqlite':
-                    $pdo = new PDO($c['db_dsn'], 'charset=utf8');
-                    $pdo->setAttribute(PDO::ATTR_ERRMODE, PDO::ERRMODE_EXCEPTION);
-                    $pdo->setAttribute(PDO::ATTR_DEFAULT_FETCH_MODE, PDO::FETCH_ASSOC);
-                    $pdo->exec('pragma synchronous = off;');
-                    self::$context[$_context] = new DbContext($pdo);
-                    break;
-                default:
-                    return;
-                    break;
-            }
-
-        } catch (Exception $e) {
-            exit('DbContext Error');
-        }
-    }
-
-
-    /**
-     * Get fetch result from executed prepared statement (SELECT queries only)
-     * @param PDOStatement $stmt the prepared statement already executed
-     * @param bool $_all true = return all lines. false = return first line
-     * @return array result set or empty array
-     */
-    static protected function fetchStmt(PDOStatement $stmt, bool $_all = false): array
-    {
-        $r = (($_all === false) ? $stmt->fetch() : $stmt->fetchAll());
-        $stmt->closeCursor();
-        return (!empty($r) ? $r : []);
-    }
-
-
-    /** @var PDO $db PDO Connection */
-    protected ?PDO $pdo = null;
-
-
-    /**
-     * DbContext Constructor
-     */
-    public function __construct(PDO $_pdo) 
-    {
-        $this->pdo = $_pdo;
-    }  
-
-    /** Performs a simple read request 
-     * @param string $_query SQL query to execute 
-     * @param bool $_all true = return all rows. false = return first row
-     * @return mixed result set or empty array 
-     */
-    public function query(string $_query, bool $_all = false): array
-    {
-        try {
-            return self::fetchStmt($this->pdo->query($_query), $_all);
-        } catch (Exception $e) {
-            exit('DbQuery Error');
-        }
-    }
-
-    /** Executes a parameterized read request
-     * @param string $_query SQL query to execute
-     * @param array $_values the values associated with the query parameters
-     * @param bool $_all true = return all rows. false = return first row
-     * @return mixed result set or empty array 
-     */
-    public function fetch(string $_query, array $_values = [], bool $_all = false): array
-    {
-        try {
-            $stmt = $this->pdo->prepare($_query);
-            return ($stmt->execute($_values) ? static::fetchStmt($stmt, $_all) : []);
-        } catch (Exception $e) {
-            exit('DbFetch Error' . $e->getMessage());
-        }
-    }
-
-    /** Execute a parameterized read request and return all rows  
-     * @param string $_query SQL query to execute
-     * @param array $_values the values associated with the query parameters
-     * @return mixed result set or empty array 
-     */
-    public function fetchAll(string $_query, array $_values = []): array
-    {
-        return $this->fetch($_query, $_values, true);
-    }
-
-
-    /** Executes a parameterized write request and returns the number of rows affected
-     * @param string $_query SQL query to execute
-     * @param array $_values the values associated with the query parameters
-     * @return int number of rows affected by the query
-     */
-    public function exec(string $_query, array $_values = []): int
-    {
-        try {
-            $stmt = $this->pdo->prepare($_query);
-
-            if ($stmt->execute($_values)) {
-                $r = $stmt->rowCount();
-                $stmt->closeCursor();
-                return $r;
-            }
-            return 0;
-        } catch (Exception $e) {
-            exit('Db Error 101');
-        }
-    }
-
-
-    /** Add data to specific table
-     * @param string $_table the table
-     * @param array $_values data to insert (must match to table structure)
-     * @return int number of rows affected
-     */
-    public function insert(string $_table, array $_values): int
-    {
-        $cols = \array_keys($_values);
-        $vals = (':' . \implode(', :', $cols));
-        $cols = \implode(',', $cols);
-
-        return $this->exec("INSERT INTO " . $_table . " (" . $cols . ") VALUES (" . $vals . ");", $_values);
-    }
-
-    /** Update a row in specific table
-     * @param string $_table the table
-     * @param string $_pk the primary key name
-     * @param array $_values The array of values corresponding to the current table. Must contain the identifier of the row to update.
-     * @return int number of rows affected
-     */
-    public function update(string $_table, string $_pk, array $_values): int
-    {
-        $id = null;
-        $cols = [];
-
-        foreach ($_values as $k => $v) {
-            if($k !== $_pk) {
-                $cols[$k] = ($k . '=:' . $k);
-            }
-            else {
-                $id = $v;
-            }            
-        }
-
-        if($id !== null) {
-            return $this->exec("UPDATE " . $_table  . " SET " . \implode(', ', $cols) . " WHERE " . $_pk  . "=:" . $id  . ";", $_values);
-        }
-    }
-
-    /** Delete a row in specific table
-     * @param string $_table the table
-     * @param string $_pk the primary key name
-     * @param string $_id row identifier
-     * @return int number of rows affected
-     */
-    public function delete(string $_table, string $_pk, string $_id): int
-    {
-        return $this->exec("DELETE FROM " . $_table  . " WHERE " . $_pk  . "=:id;", [':id' => $_id]);
-    }
-}
diff --git a/Md/Db/IDbContext.php b/Md/Db/IDbContext.php
deleted file mode 100644
index e0f706c..0000000
--- a/Md/Db/IDbContext.php
+++ /dev/null
@@ -1,41 +0,0 @@
-pk = $_pk;
-        $this->struct = $_struct;
-    }
-
-    abstract public function getId(): int;
-
-
-    public function validate(array &$_input, $_id = null): bool
-    {
-        if($_id === null) {
-            foreach($this->struct as $k => $v) {
-                if(!array_key_exists($k, $_input)) {
-                    return false;
-                }
-            }
-        }
-
-        foreach($_input as $k => $v) {
-            if(!array_key_exists($k, $this->struct)) {
-                return false;
-            }
-
-            /*switch($k) {
-            
-            }*/
-
-        }
-        return true;
-    }
-}
\ No newline at end of file
diff --git a/Md/Db/Repository.php b/Md/Db/Repository.php
deleted file mode 100644
index f606214..0000000
--- a/Md/Db/Repository.php
+++ /dev/null
@@ -1,74 +0,0 @@
-table = $_table;
-        $this->pk = $_pk;
-        $this->db = DbContext::getContext($_dbContext);
-
-        if(empty($this->db)) {
-            exit('Repository Error 1 ('.$_dbContext.')');
-        }
-    }
-
-    public function exists($_id): bool
-    {
-        return $this->db->fetch("SELECT COUNT(*) as nb FROM " . $this->table . " WHERE " . $this->pk . "=:cond;", [':cond' => $_id], false)['nb'] > 0;
-    }
-
-    public function count(): int
-    {
-        return $this->db->query(("SELECT COUNT(*) as nb FROM " . $this->table . ";"), false)['nb'];
-    }
-
-    public function getAll(): array
-    {
-        return $this->db->query(("SELECT * FROM " . $this->table . ";"), true);
-    }
-
-    public function getBy(string $_col, string $_value, bool $_all = false) : array
-    {
-        return $this->db->fetch("SELECT * FROM " . $this->table . " WHERE " . basename($_col) . "=:cond;", [':cond' => $_value], $_all);
-    }
-
-    public function getById($_id): array
-    {
-        return $this->getBy($this->pk, $_id, false);
-    }
-
-
-    public function validate(array &$_input): bool
-    {
-        return true;
-        /*$m = $this->getFirst();
-        return empty(array_diff_key($m, $_input));*/
-    }
-
-    public function add(array $_input) : bool
-    {
-        return true;
-    }
-
-    public function update($_id, array $_input): bool
-    {
-        return $this->exists($_id);
-    }
-
-    public function delete($_id): bool
-    {
-        return $this->exists($_id);
-    }
-}
\ No newline at end of file
diff --git a/Md/Db/RepositoryReader.php b/Md/Db/RepositoryReader.php
deleted file mode 100644
index 263ee78..0000000
--- a/Md/Db/RepositoryReader.php
+++ /dev/null
@@ -1,27 +0,0 @@
-dbContext::query(("SELECT MAX(" .$this->pk . ") as nb FROM " . $this->table . ";"), false)['nb'];
-    }
-    
-    public function getFirst(): array
-    {
-        return $this->dbContext::query("SELECT * FROM " . $this->table . " ORDER BY " . $this->pk . " ASC LIMIT 1;");
-    }
-
-    public function getLatest(): array
-    {
-        return $this->dbContext::query("SELECT * FROM " . $this->table . " ORDER BY " . $this->pk . " DESC LIMIT 1;");
-    }
-
-    public function getRandom(): array
-    {
-        return $this->dbContext::query("SELECT * FROM " . $this->table . " ORDER BY RAND() ASC LIMIT 1;");
-    }
-}
\ No newline at end of file
diff --git a/Md/Exceptions/ExceptionHandler.php b/Md/Exceptions/ExceptionHandler.php
deleted file mode 100644
index 68ced4e..0000000
--- a/Md/Exceptions/ExceptionHandler.php
+++ /dev/null
@@ -1,51 +0,0 @@
-getCode()) 
-        {
-            case 200:
-            case 201:
-            case 202:
-            case 204:
-            case 400:
-            case 401:
-            case 403:
-            case 404:
-            case 405:
-            case 406:
-            case 500:
-            case 501:
-                Http::end($exception->getCode(), $exception->getMessage());
-            break;
-            default:
-                Http::end(500, $exception->getCode() . ' ' . $exception->getMessage());
-            break;
-        }
-    }
-
-    public function errorHandler($error_level, $error_message, $error_file, $error_line)
-    {
-        $error_message = sprintf('%s %s %d %s', $error_level, $error_message, $error_line, $error_file);
-        
-        Http::end(500, $error_message);         
-    }
-}
\ No newline at end of file
diff --git a/Md/Http/IRequest.php b/Md/Http/IRequest.php
deleted file mode 100644
index e29cefb..0000000
--- a/Md/Http/IRequest.php
+++ /dev/null
@@ -1,70 +0,0 @@
-localPath = $_localPath;
-        $this->method = mb_convert_case($_SERVER['REQUEST_METHOD'] ?? 'GET', MB_CASE_UPPER);
-        $this->controller = mb_convert_case($_route[0] ?? 'Home', MB_CASE_TITLE);
-        $this->action = mb_convert_case($_route[1] ?? 'index', MB_CASE_LOWER);
-        $this->id = ($_route[2] ?? null);
-    }
-
-    public function getMethod(): string
-    {
-        return $this->method;
-    }
-
-    public function getRoute(): string
-    {
-        return mb_convert_case($this->controller . '/' . $this->action . '/' . $this->id, MB_CASE_LOWER);
-    }
-
-    public function getData(): array
-    {
-        switch($this->method)
-        {
-            case 'GET':
-            case 'DELETE':
-                return Http::secure($_GET ?? []);
-            break;
-            case 'POST':
-                $a = Http::secure($_POST ?? []);
-                if(!empty($_FILES)) {
-                    $a['_files'] = $_FILES;
-                }
-                return $a;
-            break;
-            case 'PUT':
-                $a = null;
-                parse_str(file_get_contents('php://input'), $a);
-                return Http::secure($a);
-            break;
-            default:
-                return [];
-            break;
-        }
-    }
-
-    public function getController(): string
-    {
-        return ($this->controller . 'Controller');
-    }
-
-    public function getAction(): string
-    {
-        return ($this->action . 'Action');
-    }
-
-    public function getId(): ?string
-    {
-        return $this->id;
-    }
-
-    public function getLocalPath(?string $_subpath = null): string 
-    {
-        return $this->localPath . ($_subpath);
-    }
-
-    public function getView(): string
-    {
-        return ($this->controller.'/'.$this->action);
-    }
-}
diff --git a/Md/Http/Response.php b/Md/Http/Response.php
deleted file mode 100644
index 03cb1cc..0000000
--- a/Md/Http/Response.php
+++ /dev/null
@@ -1,65 +0,0 @@
-code = 200;
-        $this->contentType = IResponse::JSON;
-        $this->request = $_request;
-        $this->body = "";
-    }
-
-    public function getRequest(): IRequest
-    {
-        return $this->request;
-    }  
-
-    public function getCode(): int
-    {
-        return $this->code;
-    }
-
-    public function setCode(int $_code): IResponse 
-    {
-        $this->code = $_code;
-        return $this;
-    }
-
-    public function getContentType(): string
-    {
-        return $this->contentType;
-    }
-
-    public function setContentType(string $_contentType): IResponse
-    {
-        $this->contentType = $_contentType;
-        return $this;
-    }
-
-    public function getBody(): string
-    {        
-        return $this->body;        
-    }
-
-    public function setBody(string $_body): IResponse
-    {
-        $this->body = $_body;
-        return $this;
-    }  
-}
diff --git a/Md/Http/Router.php b/Md/Http/Router.php
deleted file mode 100644
index 948caf2..0000000
--- a/Md/Http/Router.php
+++ /dev/null
@@ -1,54 +0,0 @@
-response = new Response($request);
-        $this->controller = ('\\' . $_namespace . '\\Controllers\\' . $request->getController());
-    }  
-
-    public function getRequest(): IRequest
-    {
-        return $this->response->getRequest();
-    }
-
-    public function getResponse(): IResponse
-    {
-        return $this->response;
-    }
-
-    public function getController(): IController
-    {
-        $c = str_replace('\\', '/', $this->controller);
-        if(!is_file(dirname($this->getRequest()->getLocalPath()) . $c . '.php')) {
-            exit('ko');
-        }
-        try {
-            return new $this->controller($this->response);
-        } catch(Exception $e) {
-            exit('Error : Invalid controller');
-        }
-        
-    }
-}
diff --git a/Md/Views/IView.php b/Md/Views/IView.php
deleted file mode 100644
index cc6e79e..0000000
--- a/Md/Views/IView.php
+++ /dev/null
@@ -1,12 +0,0 @@
-__toString(), $t, $t->__toString()]);
+
+       /* $web = new LocalPath($_path);
+        $root = new LocalPath(dirname($_path));
+        $app = new Module($root->getPath($_namespace));
+
+        $uri = new Uri($_SERVER['REQUEST_URI']);
+        $request = new Request($uri);*/
+
+       // $router = new Router(new Route(new Request(), $_options));
+
+        //$serverRequest = new ServerRequest();
+
+        //Debug::e([]);
+
+        $app = new App(new Route($_options), new Response());
+        
+        $app->handleRequest();
+    }
+
+    private RouterInterface $router;
+
+    public function __construct(RouterInterface $_router, ResponseInterface $_response)
+    {        
+        $this->router = $_router;
+
+        //Debug::e($this);
+
+        if(null !== ($controller = $this->router->createController())) {
+            Debug::e($this);
+        }
+        
+        Http::notFound();
+    }
+
+    public function getPath(string $_subPath = ''): string
+    {
+        return ($this->path . $_subPath);
+    }
+
+    /**
+     * Override to Load customs components
+     */
+    protected function initComponents() : void {}
+
+    /**
+     * Override to cutomize Databases components 
+     */
+    protected function initDatabases(): void
+    {
+        $conf = is_file($this->getPath('var/db.conf.php')) ? (require $this->router->getPath('var/db.conf.php')) : [];
+
+        foreach($conf as $context => $params) {
+            Db::register($context, $params);
+        }
+    }
+
+    /**
+     * Run MVC App using IRouter
+     */
+    public function handleRequest(): void 
+    {  
+        Debug::e($this);
+        $this->initDatabases();
+        $this->initComponents();
+        $response = $this->controller->handleRequest();
+
+        if($response->getContentType() === Message::HTML) {
+            $layout = new View($this->router->getPath('Views/')); 
+            $layout->setFile('_layout');
+            View::setVar('page', $response->getBody()); 
+            $response->setBody($layout->fetch());
+        }
+
+        Http::response($response);
+    }
+}
diff --git a/Mvc/AppInterface.php b/Mvc/AppInterface.php
new file mode 100644
index 0000000..57b4d48
--- /dev/null
+++ b/Mvc/AppInterface.php
@@ -0,0 +1,42 @@
+repo = null;
+        $this->data = [];
+        $this->onLoad();
+    }
+    
+    /**
+     * Default Controller Action
+     */
+    // abstract public function indexAction(): void;
+
+    /**
+     * Load custom components when loading the controller
+     */
+    protected function onLoad() {}
+
+    /**
+     * Load custom components before processing the request
+     */
+    protected function beforeRequest() {}
+
+    /**
+     * Execute actions after processing the request
+     */
+    protected function afterRequest() {}
+
+    public function getResponse(): ResponseInterface
+    {
+        return $this->response;
+    }
+
+    /**
+     * Handle current request
+     */
+    public function handleRequest(RouteInterface $_route, ResponseInterface $_response): ResponseInterface
+    {
+        $this->route = $_route;
+        $this->response = $_response;
+
+        $a = ($this->route->getAction());
+
+        if(!method_exists($this, $a)) {
+            return $this->response->withCode(404)->withBody('Invalid Action');
+        }
+
+        $this->beforeRequest();
+        $this->{$a}();
+        $this->afterRequest();
+
+        if($this->response->getContentType() === ResponseInterface::JSON) {
+            return $this->response->setBody(json_encode($this->data, JSON_PRETTY_PRINT | JSON_UNESCAPED_UNICODE | JSON_UNESCAPED_SLASHES));
+        }
+
+        $view = new View($this->request->getLocalPath('Views/')); 
+        return $this->response->setBody($view->setFile($this->request->getView())->fetch($this->data));
+    }
+
+    /**
+     * Set Generic Repository from table name and primary key name 
+     * A generic repo use Db::getContext('default')
+     */
+    public function setRepository(string $_table, string $_pk)
+    {
+        $this->repo = new Repository($_table, $_pk);
+    }
+
+}
diff --git a/Mvc/ControllerInterface.php b/Mvc/ControllerInterface.php
new file mode 100644
index 0000000..4914acb
--- /dev/null
+++ b/Mvc/ControllerInterface.php
@@ -0,0 +1,17 @@
+path = (dirname($_options['path']) . DIRECTORY_SEPARATOR);
+        $this->realPath = $this->path;
+        $this->namespace = '\\'.$_options['namespace'];
+        $this->instance = basename($_options['path']);
+        $this->request = ServerRequest::get();
+        $this->route = $this->request->getUri()->getParts();
+
+        $this->route[0] = mb_convert_case($this->route[0] ?? 'Home', MB_CASE_TITLE);
+
+        if($this->route[0] === 'Api') {
+            $this->namespace .= '\\Api';
+            $this->realPath .= 'Api' . DIRECTORY_SEPARATOR;
+        } 
+        elseif(is_dir($this->path . 'App' . DIRECTORY_SEPARATOR . $this->route[0])) {
+            $this->namespace .= '\\App\\'.$this->route[0];
+            $this->realPath .= 'App' . DIRECTORY_SEPARATOR . $this->route[0] . DIRECTORY_SEPARATOR;
+        }
+
+        if($this->path !== $this->realPath) {
+            array_shift($this->route);
+            $this->route[0] = mb_convert_case($this->route[0] ?? 'Home', MB_CASE_TITLE);
+        }
+
+        $this->route[1] = $this->route[1] ?? 'index';
+        $this->route[2] = $this->route[2] ?? null;
+    }
+
+    public function getPath(string $_sub = ''): string 
+    {
+        return ($this->path . $_sub);
+    }
+
+    public function getRealPath(string $_sub = ''): string 
+    {
+        return ($this->realPath . $_sub);
+    }
+
+    public function getWebPath(string $_sub = ''): string
+    {
+        return ($this->path . $this->instance . DIRECTORY_SEPARATOR . $_sub);
+    }
+
+    public function getRequest(): ServerRequestInterface
+    {
+        return $this->request;
+    }
+
+    public function getController(): string
+    {
+        return ($this->route[0] . 'Controller');
+    }
+
+    public function getAction(): string
+    {
+        return ($this->route[1] . 'Action');
+    }
+
+    public function getId(): ?string
+    {
+        return $this->route[2];
+    }
+
+    public function getRoute(): RouteInterface
+    {
+        return $this;
+    }
+
+    public function createController(): ?ControllerInterface
+    {
+        if(is_file($this->getRealPath('Controllers' . DIRECTORY_SEPARATOR . $this->getController() .'.php'))) {
+            $c = ($this->namespace . 'Controllers\\' . $this->getController());
+            return new $c();
+        }
+
+        return null;
+    }
+}
diff --git a/Mvc/RouteInterface.php b/Mvc/RouteInterface.php
new file mode 100644
index 0000000..2c55edf
--- /dev/null
+++ b/Mvc/RouteInterface.php
@@ -0,0 +1,22 @@
+childs = [];
     }
 
-    public function setFile(string $_filename): IView
+    public function setFile(string $_filename): ViewInterface
     {
         $this->file = ($this->path.$_filename.'.php');
         
@@ -55,7 +55,7 @@ public function fetch(array $_vars = []) : string
         return \ob_get_clean();
     }
 
-    public function setChild(string $_key, string $_filename): IView
+    public function setChild(string $_key, string $_filename): ViewInterface
     {
         $this->childs[$_key] = (new self($this->path))->setFile($_filename);
         return $this->childs[$_key];
diff --git a/Mvc/ViewInterface.php b/Mvc/ViewInterface.php
new file mode 100644
index 0000000..7bdaf4b
--- /dev/null
+++ b/Mvc/ViewInterface.php
@@ -0,0 +1,12 @@
+=7.4",
-        "ext-mbstring": "*"
+        "ext-mbstring": "*",
+        "mdevoldere/edu-php-db": "dev-mdev"
     },
     "autoload": {
         "psr-4": {
-          "Md\\": "Md"
+            "Md\\": ""
         }
       }
 }