From 13c80f5642bb1238458180b3cd4c390db8f5003e Mon Sep 17 00:00:00 2001 From: Gregory Letellier Date: Tue, 14 Nov 2023 11:18:23 +0100 Subject: [PATCH] init --- .env.default | 8 ++ .gitignore | 10 +++ .phpcs.xml | 7 ++ Makefile | 45 +++++++++++ composer.json | 44 +++++++++++ infection.json5 | 17 +++++ phpunit.xml | 18 +++++ src/.gitkeep | 0 src/Core/Controllers/Controller.php | 112 ++++++++++++++++++++++++++++ src/Core/Kernel/Application.php | 109 +++++++++++++++++++++++++++ src/Core/Kernel/Container.php | 41 ++++++++++ src/Core/Kernel/Templating.php | 45 +++++++++++ src/Core/Routing/Router.php | 38 ++++++++++ src/helpers.php | 15 ++++ tests/Unit/.gitkeep | 0 15 files changed, 509 insertions(+) create mode 100644 .env.default create mode 100644 .gitignore create mode 100644 .phpcs.xml create mode 100644 Makefile create mode 100644 composer.json create mode 100644 infection.json5 create mode 100644 phpunit.xml create mode 100644 src/.gitkeep create mode 100644 src/Core/Controllers/Controller.php create mode 100644 src/Core/Kernel/Application.php create mode 100644 src/Core/Kernel/Container.php create mode 100644 src/Core/Kernel/Templating.php create mode 100644 src/Core/Routing/Router.php create mode 100644 src/helpers.php create mode 100644 tests/Unit/.gitkeep 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 @@ +