This commit is contained in:
Gregory Letellier
2023-11-08 16:59:13 +01:00
commit 80aeacdade
19 changed files with 5673 additions and 0 deletions

8
.env.default Normal file
View File

@@ -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

8
.gitignore vendored Normal file
View File

@@ -0,0 +1,8 @@
vendor/
.phpunit.result.cache
!var/cache/.gitkeep
var/cache/*
!var/log/.gitkeep
var/log/*
.env
.vscode

6
.phpcs.xml Normal file
View File

@@ -0,0 +1,6 @@
<?xml version="1.0"?>
<ruleset>
<rule ref="PSR12"/>
<file>src/</file>
<arg name="colors"/>
</ruleset>

45
Makefile Normal file
View File

@@ -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

42
composer.json Normal file
View File

@@ -0,0 +1,42 @@
{
"name": "kletellier/web",
"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"
},
"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": {
"App\\": "src"
},
"files": ["src/helpers.php"]
},
"autoload-dev": {
"psr-4": {
"App\\tests\\": "tests/"
}
},
"config": {
"allow-plugins": {
"pestphp/pest-plugin": true,
"infection/extension-installer": true
}
}
}

5180
composer.lock generated Normal file

File diff suppressed because it is too large Load Diff

17
infection.json5 Normal file
View File

@@ -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
}
}

18
phpunit.xml Normal file
View File

@@ -0,0 +1,18 @@
<?xml version="1.0" encoding="UTF-8"?>
<phpunit xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:noNamespaceSchemaLocation="./vendor/phpunit/phpunit/phpunit.xsd"
bootstrap="vendor/autoload.php"
colors="true"
cacheResultFile="./var/cache/"
>
<testsuites>
<testsuite name="Test Suite">
<directory suffix="Test.php">./tests</directory>
</testsuite>
</testsuites>
<coverage processUncoveredFiles="true" cacheDirectory="./var/cache/">
<include>
<directory suffix=".php">./src</directory>
</include>
</coverage>
</phpunit>

9
public/.htaccess Normal file
View File

@@ -0,0 +1,9 @@
RewriteEngine On
RewriteCond %{REQUEST_FILENAME} !-f
RewriteCond %{REQUEST_FILENAME} !-d
# Rewrite all other URLs to index.php/URL
RewriteRule ^(.*)$ index.php?url=$1 [QSA]
ErrorDocument 404 index.php

20
public/index.php Normal file
View File

@@ -0,0 +1,20 @@
<?php
/**
* Loading path constant
*/
define('DS', DIRECTORY_SEPARATOR);
define('ROOT', dirname(dirname(__FILE__)));
/**
* Enable autoload
*/
require_once ROOT . DS . 'vendor'. DS . 'autoload.php';
// start application
$app = new \App\Core\Kernel\Application();
// Handle Url and render Response
$app->handle();

3
routes/routes.php Normal file
View File

@@ -0,0 +1,3 @@
<?php
$router->get('/', '\App\Controllers\Home@index');

0
src/.gitkeep Normal file
View File

14
src/Controllers/Home.php Normal file
View File

@@ -0,0 +1,14 @@
<?php
namespace App\Controllers;
use App\Core\Controllers\Controller;
class Home extends Controller
{
public function index()
{
$html = $this->renderView("index", []);
$this->render($html);
}
}

View File

@@ -0,0 +1,119 @@
<?php
namespace App\Core\Controllers;
use Symfony\Component\HttpFoundation\Request;
use Symfony\Component\HttpFoundation\Response;
abstract class Controller
{
protected $_cookies;
protected $_cookiestodelete;
protected Request $_request;
/**
* Controller constructor
*
* @param String $controller controller name
* @param String $action action to execute
*/
function __construct()
{
$this->_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
{
extract($params);
$path = ROOT . DS . "views" . DS . str_replace(".", DS, $view) . ".php";
ob_start();
try {
include $path;
} catch (\Exception $e) {
}
$buffer = ltrim(ob_get_clean());
return $buffer;
}
/**
* 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()
{
}
}

View File

@@ -0,0 +1,99 @@
<?php
namespace App\Core\Kernel;
use App\Core\Routing\Router;
use Dotenv\Dotenv;
use Kletellier\PdoWrapper\Connection;
use Symfony\Component\HttpFoundation\Request;
use Symfony\Component\HttpFoundation\Response;
class Application
{
protected Router $router;
protected $_whoops;
public function __construct()
{
// enable error reporting
$this->setReporting();
try {
$dotenv = Dotenv::createImmutable(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();
}
$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;
$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()
{
ob_start(null, 0, PHP_OUTPUT_HANDLER_CLEANABLE | PHP_OUTPUT_HANDLER_FLUSHABLE);
try {
$this->router->run();
} catch (\Exception $ex) {
e($ex->getMessage());
die();
}
}
}

View File

@@ -0,0 +1,35 @@
<?php
namespace App\Core\Routing;
use Bramus\Router\Router as RouterRouter;
class Router
{
private $_router;
public function __construct()
{
$this->init();
}
public function run()
{
$this->_router->run();
}
private function init()
{
$this->_router = new RouterRouter();
$this->loadFromFile(ROOT . DS . "routes");
$this->_router->set404("404 - Not found");
}
private function loadFromFile($path)
{
if (is_dir($path)) {
$router = $this->_router;
require $path . DS . "routes.php";
}
}
}

15
src/helpers.php Normal file
View File

@@ -0,0 +1,15 @@
<?php
if (! function_exists('e')) {
/**
* Encode HTML special characters in a string.
*
* @param string|null $value
* @param bool $doubleEncode
* @return string
*/
function e($value, $doubleEncode = true)
{
return htmlspecialchars($value ?? '', ENT_QUOTES | ENT_SUBSTITUTE, 'UTF-8', $doubleEncode);
}
}

0
tests/Unit/.gitkeep Normal file
View File

35
views/index.php Normal file
View File

@@ -0,0 +1,35 @@
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>Success Page</title>
<style>
body {
background-color: #f0f0f0;
display: flex;
align-items: center;
justify-content: center;
height: 100vh;
margin: 0;
font-family: Arial, sans-serif;
}
.message-container {
text-align: center;
padding: 20px;
border-radius: 8px;
background-color: #4caf50;
color: #fff;
box-shadow: 0 4px 8px rgba(0, 0, 0, 0.1);
background: linear-gradient(to bottom, #4caf50, #388e3c 50%, #4caf50 100%);
background: linear-gradient(to right, #4caf50, #388e3c 50%, #4caf50 100%);
}
</style>
</head>
<body>
<div class="message-container">
<h1>"It works!"</h1>
</div>
</body>
</html>