commit 13c80f5642bb1238458180b3cd4c390db8f5003e Author: Gregory Letellier Date: Tue Nov 14 11:18:23 2023 +0100 init diff --git a/.env.default b/.env.default new file mode 100644 index 0000000..1d62b8f --- /dev/null +++ b/.env.default @@ -0,0 +1,8 @@ +CONNECTIONS= +DB_DEFAULT_TYPE=mysql +DB_DEFAULT_HOST=localhost +DB_DEFAULT_PORT=3306 +DB_DEFAULT_DATABASE=database +DB_DEFAULT_USER=root +DB_DEFAULT_PASSWORD=password +DEBUG=true \ No newline at end of file diff --git a/.gitignore b/.gitignore new file mode 100644 index 0000000..b51d797 --- /dev/null +++ b/.gitignore @@ -0,0 +1,10 @@ +vendor/ +.phpunit.result.cache +!var/cache/.gitkeep +var/cache/* +!var/log/.gitkeep +var/log/* +.env +.vscode +cache/ +composer.lock \ No newline at end of file diff --git a/.phpcs.xml b/.phpcs.xml new file mode 100644 index 0000000..c28747a --- /dev/null +++ b/.phpcs.xml @@ -0,0 +1,7 @@ + + + + src/ + console + + \ No newline at end of file diff --git a/Makefile b/Makefile new file mode 100644 index 0000000..c456750 --- /dev/null +++ b/Makefile @@ -0,0 +1,45 @@ +COMPOSER ?= $(shell which composer) + +.PHONY: init +init: + $(RM) -r .git + git init + +.PHONY: up +up: install-vendor + +.PHONY: install-vendor +install-vendor: + $(PHP) $(COMPOSER) install + +.PHONY: clean +clean: clean-vendor clean-composerlock + +.PHONY: clean-vendor +clean-vendor: + $(RM) -r ./vendor + +.PHONY: clean-composerlock +clean-composerlock: + $(RM) composer.lock + +.PHONY: unit-tests +unit-tests: + ./vendor/bin/pest + +.PHONY: unit-tests-coverage +unit-tests-coverage: + XDEBUG_MODE=coverage ./vendor/bin/pest --coverage + +.PHONY: mutation +mutation: + ./vendor/bin/infection --test-framework=pest --show-mutations + +.PHONY: code-sniffer +code-sniffer: + ./vendor/bin/phpcs + +.PHONY: code-sniffer-fix +code-sniffer-fix: + ./vendor/bin/phpcbf + diff --git a/composer.json b/composer.json new file mode 100644 index 0000000..0793cc2 --- /dev/null +++ b/composer.json @@ -0,0 +1,44 @@ +{ + "name": "kletellier/miniweb", + "description": "Mini website template", + "type": "project", + "license": "MIT", + "require": { + "php": "~8.1", + "kletellier/pdowrapper": "dev-master", + "symfony/http-kernel": "^6.3", + "bramus/router": "~1.6", + "filp/whoops": "^2.15", + "symfony/var-dumper": "^6.3", + "eftec/bladeone": "^4.9", + "symfony/console": "^6.3" + }, + "repositories": [ + { + "type": "git", + "url": "https://git.kletellier.xyz/greg/PdoWrapper.git" + } + ], + "require-dev": { + "squizlabs/php_codesniffer": "^3.4", + "pestphp/pest": "^1.21", + "infection/infection": "^0.26.15" + }, + "autoload": { + "psr-4": { + "Kletellier\\MiniWeb\\": "src" + }, + "files": ["src/helpers.php"] + }, + "autoload-dev": { + "psr-4": { + "Kletellier\\MiniWeb\\tests\\": "tests/" + } + }, + "config": { + "allow-plugins": { + "pestphp/pest-plugin": true, + "infection/extension-installer": true + } + } +} diff --git a/infection.json5 b/infection.json5 new file mode 100644 index 0000000..075f3fc --- /dev/null +++ b/infection.json5 @@ -0,0 +1,17 @@ +{ + "$schema": "vendor/infection/infection/resources/schema.json", + "source": { + "directories": [ + "src" + ] + }, + "phpUnit": { + "configDir": "" + }, + "logs": { + "text": "./var/log/infection.log" + }, + "mutators": { + "@default": true + } +} \ No newline at end of file diff --git a/phpunit.xml b/phpunit.xml new file mode 100644 index 0000000..3ee0a4f --- /dev/null +++ b/phpunit.xml @@ -0,0 +1,18 @@ + + + + + ./tests + + + + + ./src + + + diff --git a/src/.gitkeep b/src/.gitkeep new file mode 100644 index 0000000..e69de29 diff --git a/src/Core/Controllers/Controller.php b/src/Core/Controllers/Controller.php new file mode 100644 index 0000000..b6e6c31 --- /dev/null +++ b/src/Core/Controllers/Controller.php @@ -0,0 +1,112 @@ +_cookies = array(); + $this->_cookiestodelete = array(); + $this->_request = Request::createFromGlobals(); + } + + public function render($content, $status = 200, $headers = array('Content-Type' => 'text/html')): void + { + $this->getResponse($content, $status, $headers)->send(); + } + + /** + * Return instance of Symfony Response Component + * @return type + */ + private function getResponse($content, $status = 200, $headers = array('Content-Type' => 'text/html')): Response + { + $response = new Response($content, $status, $headers); + foreach ($this->_cookies as $cookie) { + $response->headers->setCookie($cookie); + } + foreach ($this->_cookiestodelete as $cookie) { + $response->headers->clearCookie($cookie); + } + return $response; + } + + /** + * Return instance of Symfony Request component + */ + public function request(): Request + { + return $this->_request; + } + + /** + * Return Html from view + */ + public function renderView($view, $params = []): string + { + return Templating::getInstance()->render($view, $params); + } + + /** + * Add cookie to response + * @param type \Symfony\Component\HttpFoundation\Cookie $cookie + * @return void + */ + public function addCookie(\Symfony\Component\HttpFoundation\Cookie $cookie) + { + array_push($this->_cookies, $cookie); + } + + /** + * Cookie to remove in response + * @param string $cookie cookie name to delete + * @return void + */ + public function removeCookie($cookie) + { + array_push($this->_cookiestodelete, $cookie); + } + + /** + * Return a Json Response + * + * @param object $var object to be serialized in JSON + */ + public function renderJSON($var) + { + $response = new Response(json_encode($var)); + $response->headers->set('Content-Type', 'application/json'); + $response->send(); + } + + public function redirecting($url) + { + $response = new \Symfony\Component\HttpFoundation\RedirectResponse($url); + foreach ($this->_cookies as $cookie) { + $response->headers->setCookie($cookie); + } + foreach ($this->_cookiestodelete as $cookie) { + $response->headers->clearCookie($cookie); + } + $response->send(); + } + + function __destruct() + { + } +} diff --git a/src/Core/Kernel/Application.php b/src/Core/Kernel/Application.php new file mode 100644 index 0000000..5238b4d --- /dev/null +++ b/src/Core/Kernel/Application.php @@ -0,0 +1,109 @@ +_root = $root; + + // enable error reporting + $this->setReporting(); + + try { + $dotenv = Dotenv::createImmutable($this->_root); + $dotenv->load(); + } catch (\Throwable $th) { + e($th->getMessage()); + die(); + } + + if ($this->isDebug()) { + $this->_whoops = new \Whoops\Run(); + $handler = new \Whoops\Handler\PrettyPageHandler(); + $this->_whoops->pushHandler($handler); + $this->_whoops->register(); + } + + // create container + Container::set("ROOT",$this->_root); + + $db_config = $this->getDatabaseConfig(); + + if (!empty($db_config)) { + // Initialize DB system + Connection::init($db_config, array_keys($db_config)); + } + + // Init router + $this->router = new Router(); + } + + private function isDebug(): bool + { + $ret = true; + if (isset($_ENV["DEBUG"])) { + $ret = ($_ENV["DEBUG"] == "true"); + } + return $ret; + } + + private function getDatabaseConfig(): array + { + $ret = []; + $connections = isset($_ENV["CONNECTIONS"]) ? explode(",", $_ENV["CONNECTIONS"]) : []; + foreach ($connections as $connection) { + if ($connection !== "") { + $conn = []; + $key = (strtoupper(trim($connection))); + $conn["TYPE"] = isset($_ENV[$this->getKey("TYPE", $key)]) ? $_ENV[$this->getKey("TYPE", $key)] : null; + $conn["USER"] = isset($_ENV[$this->getKey("USER", $key)]) ? $_ENV[$this->getKey("USER", $key)] : null; + $conn["HOST"] = isset($_ENV[$this->getKey("HOST", $key)]) ? $_ENV[$this->getKey("HOST", $key)] : null; + $conn["PASSWORD"] = isset($_ENV[$this->getKey("PASSWORD", $key)]) ? $_ENV[$this->getKey("PASSWORD", $key)] : null; + $conn["DATABASE"] = isset($_ENV[$this->getKey("DATABASE", $key)]) ? $_ENV[$this->getKey("DATABASE", $key)] : null; + $conn["PORT"] = isset($_ENV[$this->getKey("PORT", $key)]) ? $_ENV[$this->getKey("PORT", $key)] : null; + if ($conn["TYPE"] == "sqlite") { + if (!is_file($conn["DATABASE"])) { + $conn["DATABASE"] = $this->_root . DIRECTORY_SEPARATOR . "databases" . DIRECTORY_SEPARATOR . $conn["DATABASE"]; + } + } + $ret[$connection] = $conn; + } + } + return $ret; + } + + private function getKey(string $type, string $name): string + { + return "DB_" . $name . "_" . $type; + } + + /** + * Enable/Disable error reporting to output buffer + */ + private function setReporting() + { + error_reporting(E_ALL); + ini_set('display_errors', 'On'); + } + + public function handle() + { + try { + $this->router->run(); + } catch (\Exception $ex) { + e($ex->getMessage()); + die(); + } + } +} diff --git a/src/Core/Kernel/Container.php b/src/Core/Kernel/Container.php new file mode 100644 index 0000000..3d55fff --- /dev/null +++ b/src/Core/Kernel/Container.php @@ -0,0 +1,41 @@ +container = []; + } + + public static function getInstance() { + if (self::$instance === null) { + self::$instance = new self(); + } + + return self::$instance; + } + + public function setValue($key, $value) { + $this->container[$key] = $value; + } + + public function getValue($key) { + return isset($this->container[$key]) ? $this->container[$key] : null; + } + + public static function get(string $key) + { + $inst = self::getInstance(); + return $inst->getValue($key); + } + + public static function set(string $key, mixed $value) + { + $inst = self::getInstance(); + return $inst->setValue($key, $value); + } +} \ No newline at end of file diff --git a/src/Core/Kernel/Templating.php b/src/Core/Kernel/Templating.php new file mode 100644 index 0000000..d92f7c7 --- /dev/null +++ b/src/Core/Kernel/Templating.php @@ -0,0 +1,45 @@ +_root = Container::get("ROOT"); + $this->_views = $this->_root . DIRECTORY_SEPARATOR . "views"; + $this->_cache = $this->_root . DIRECTORY_SEPARATOR . "cache"; + $mode = ($this->isDebug()) ? BladeOne::MODE_AUTO : BladeOne::MODE_FAST; + $this->_blade = new BladeOne($this->_views, $this->_cache, $mode); + } + + public static function getInstance() + { + if (self::$_instance == null) { + self::$_instance = new Templating(); + } + return self::$_instance; + } + + private function isDebug(): bool + { + $ret = true; + if (isset($_ENV["DEBUG"])) { + $ret = ($_ENV["DEBUG"] == "true"); + } + return $ret; + } + + public function render(string $template, array $params = []): string + { + return $this->_blade->run($template, $params); + } +} diff --git a/src/Core/Routing/Router.php b/src/Core/Routing/Router.php new file mode 100644 index 0000000..6785db2 --- /dev/null +++ b/src/Core/Routing/Router.php @@ -0,0 +1,38 @@ +_root = Container::get("ROOT"); + $this->init(); + } + + public function run() + { + $this->_router->run(); + } + + private function init() + { + $this->_router = new RouterRouter(); + $this->loadFromFile($this->_root . DIRECTORY_SEPARATOR . "routes"); + $this->_router->set404("404 - Not found"); + } + + private function loadFromFile($path) + { + if (is_dir($path)) { + $router = $this->_router; + require $path . DIRECTORY_SEPARATOR . "routes.php"; + } + } +} diff --git a/src/helpers.php b/src/helpers.php new file mode 100644 index 0000000..b368964 --- /dev/null +++ b/src/helpers.php @@ -0,0 +1,15 @@ +