Compare commits
10 commits
df6abe3ed3
...
1ace4c95b5
Author | SHA1 | Date | |
---|---|---|---|
|
1ace4c95b5 | ||
|
9566ee3ead | ||
|
eb3e310b0a | ||
|
9b80b16380 | ||
|
6eacc7f523 | ||
|
591e0c194d | ||
|
83bc1a1661 | ||
|
ab5fa6d69e | ||
|
d0760ed95c | ||
|
e8d8e0e95b |
28 changed files with 807 additions and 229 deletions
14
app/Application.php
Normal file
14
app/Application.php
Normal file
|
@ -0,0 +1,14 @@
|
||||||
|
<?php
|
||||||
|
|
||||||
|
namespace App;
|
||||||
|
|
||||||
|
class Application
|
||||||
|
{
|
||||||
|
/**
|
||||||
|
* @return void
|
||||||
|
*/
|
||||||
|
public function bootstrap(): void
|
||||||
|
{
|
||||||
|
// init application
|
||||||
|
}
|
||||||
|
}
|
|
@ -3,8 +3,8 @@
|
||||||
namespace App\Controllers\Api;
|
namespace App\Controllers\Api;
|
||||||
|
|
||||||
use App\Services\Subnet;
|
use App\Services\Subnet;
|
||||||
use Core\Controllers\Controller;
|
use Core\Http\Controllers\Controller;
|
||||||
use Core\View\Render;
|
use Core\Http\View\Render;
|
||||||
use Exception;
|
use Exception;
|
||||||
|
|
||||||
class SubnetController extends Controller
|
class SubnetController extends Controller
|
||||||
|
@ -12,7 +12,7 @@ class SubnetController extends Controller
|
||||||
/**
|
/**
|
||||||
* Get all subnet data
|
* Get all subnet data
|
||||||
*
|
*
|
||||||
* @return \Core\View\Render
|
* @return \Core\Http\View\Render
|
||||||
*/
|
*/
|
||||||
public function data(): Render
|
public function data(): Render
|
||||||
{
|
{
|
||||||
|
|
|
@ -2,15 +2,15 @@
|
||||||
|
|
||||||
namespace App\Controllers;
|
namespace App\Controllers;
|
||||||
|
|
||||||
use Core\Controllers\Controller;
|
use Core\Http\Controllers\Controller;
|
||||||
use Core\View\Render;
|
use Core\Http\View\Render;
|
||||||
|
|
||||||
class HomeController extends Controller
|
class HomeController extends Controller
|
||||||
{
|
{
|
||||||
/**
|
/**
|
||||||
* Render index
|
* Render index
|
||||||
*
|
*
|
||||||
* @return \Core\View\Render
|
* @return \Core\Http\View\Render
|
||||||
*/
|
*/
|
||||||
public function index(): Render
|
public function index(): Render
|
||||||
{
|
{
|
||||||
|
|
22
app/Controllers/TestController.php
Normal file
22
app/Controllers/TestController.php
Normal file
|
@ -0,0 +1,22 @@
|
||||||
|
<?php
|
||||||
|
|
||||||
|
namespace App\Controllers;
|
||||||
|
|
||||||
|
use Core\Http\Controllers\Controller;
|
||||||
|
use Core\Http\View\Render;
|
||||||
|
|
||||||
|
class TestController extends Controller
|
||||||
|
{
|
||||||
|
/**
|
||||||
|
* Render index
|
||||||
|
*
|
||||||
|
* @return \Core\Http\View\Render
|
||||||
|
*/
|
||||||
|
public function test(int $id): Render
|
||||||
|
{
|
||||||
|
|
||||||
|
dd($id);
|
||||||
|
|
||||||
|
return $this->response->view('subnet');
|
||||||
|
}
|
||||||
|
}
|
|
@ -20,7 +20,11 @@
|
||||||
"psr-4": {
|
"psr-4": {
|
||||||
"App\\": "app/",
|
"App\\": "app/",
|
||||||
"Core\\": "src/"
|
"Core\\": "src/"
|
||||||
}
|
},
|
||||||
|
"files": [
|
||||||
|
"src/Helpers/helpers.php"
|
||||||
|
]
|
||||||
|
|
||||||
},
|
},
|
||||||
"config": {
|
"config": {
|
||||||
"optimize-autoloader": true
|
"optimize-autoloader": true
|
||||||
|
|
|
@ -2,7 +2,10 @@
|
||||||
|
|
||||||
use App\Controllers\Api\SubnetController;
|
use App\Controllers\Api\SubnetController;
|
||||||
use App\Controllers\HomeController;
|
use App\Controllers\HomeController;
|
||||||
use Core\Router\Router;
|
use App\Controllers\TestController;
|
||||||
|
use Core\Routing\Route;
|
||||||
|
|
||||||
Router::get('/', HomeController::class, 'index');
|
Route::get('/', HomeController::class, 'index');
|
||||||
Router::post('/api/subnet', SubnetController::class, 'data');
|
Route::post('/api/subnet', SubnetController::class, 'data');
|
||||||
|
Route::get('/test/{id}', TestController::class, 'test');
|
||||||
|
Route::get('/test/{id}/update', TestController::class, 'test');
|
|
@ -1,15 +1,8 @@
|
||||||
<?php
|
<?php
|
||||||
|
|
||||||
use Core\Env\Env;
|
use Core\Bootstrap;
|
||||||
use Core\Router\Router;
|
|
||||||
|
|
||||||
require '../vendor/autoload.php';
|
require '../vendor/autoload.php';
|
||||||
|
|
||||||
// Load routes
|
$bootstrap = Bootstrap::createInstance();
|
||||||
require '../config/routes.php';
|
$bootstrap->handle();
|
||||||
|
|
||||||
// Load env
|
|
||||||
Env::load();
|
|
||||||
|
|
||||||
// Dispatch router
|
|
||||||
Router::dispatch();
|
|
45
src/Bootstrap.php
Normal file
45
src/Bootstrap.php
Normal file
|
@ -0,0 +1,45 @@
|
||||||
|
<?php
|
||||||
|
|
||||||
|
namespace Core;
|
||||||
|
|
||||||
|
use Core\Factory\BootstrapFactory;
|
||||||
|
|
||||||
|
class Bootstrap {
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @var \Core\Factory\BootstrapFactory
|
||||||
|
*/
|
||||||
|
private static BootstrapFactory $instance;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Start handling process
|
||||||
|
*
|
||||||
|
* @return \Core\Factory\BootstrapFactory
|
||||||
|
*/
|
||||||
|
public static function createInstance(): BootstrapFactory
|
||||||
|
{
|
||||||
|
self::$instance = (new Bootstrap())->run();
|
||||||
|
|
||||||
|
return Bootstrap::getInstance();
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Get the instance
|
||||||
|
*
|
||||||
|
* @return \Core\Factory\BootstrapFactory
|
||||||
|
*/
|
||||||
|
public static function getInstance(): BootstrapFactory
|
||||||
|
{
|
||||||
|
return self::$instance;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Create new factory instance
|
||||||
|
*
|
||||||
|
* @return \Core\Factory\BootstrapFactory
|
||||||
|
*/
|
||||||
|
protected function run(): BootstrapFactory
|
||||||
|
{
|
||||||
|
return new BootstrapFactory();
|
||||||
|
}
|
||||||
|
}
|
120
src/Exceptions/ExceptionHandler.php
Normal file
120
src/Exceptions/ExceptionHandler.php
Normal file
|
@ -0,0 +1,120 @@
|
||||||
|
<?php
|
||||||
|
|
||||||
|
namespace Core\Exceptions;
|
||||||
|
|
||||||
|
use Throwable;
|
||||||
|
use Whoops\Handler\Handler;
|
||||||
|
use Whoops\Handler\JsonResponseHandler;
|
||||||
|
use Whoops\Handler\PlainTextHandler;
|
||||||
|
use Whoops\Handler\PrettyPageHandler;
|
||||||
|
use Whoops\Run as Whoops;
|
||||||
|
|
||||||
|
class ExceptionHandler
|
||||||
|
{
|
||||||
|
/**
|
||||||
|
* Exceptions reporter instance
|
||||||
|
*
|
||||||
|
* @var Whoops
|
||||||
|
*/
|
||||||
|
private static Whoops $reporter;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Exceptions handler instance
|
||||||
|
*
|
||||||
|
* @var \Core\Exceptions\ExceptionHandler
|
||||||
|
*/
|
||||||
|
private static ExceptionHandler $instance;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Get instance of exceptions handler
|
||||||
|
*
|
||||||
|
* @return \Core\Exceptions\ExceptionHandler
|
||||||
|
*/
|
||||||
|
public static function instance(): ExceptionHandler
|
||||||
|
{
|
||||||
|
if (!isset(self::$instance)) {
|
||||||
|
self::$instance = new self();
|
||||||
|
}
|
||||||
|
|
||||||
|
return self::$instance;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Get exceptions reporter instance
|
||||||
|
*
|
||||||
|
* @return \Whoops\Run
|
||||||
|
*/
|
||||||
|
public static function reporter(): Whoops
|
||||||
|
{
|
||||||
|
if (!isset(self::$reporter)) {
|
||||||
|
$reporter = new Whoops();
|
||||||
|
$reporter->pushHandler(self::handler());
|
||||||
|
|
||||||
|
self::$reporter = $reporter;
|
||||||
|
}
|
||||||
|
|
||||||
|
return self::$reporter;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Get correct handler
|
||||||
|
*
|
||||||
|
* @return \Whoops\Handler\Handler
|
||||||
|
*/
|
||||||
|
private static function handler(): Handler
|
||||||
|
{
|
||||||
|
if (env('debug')) {
|
||||||
|
if (request()?->is('post')) {
|
||||||
|
return new JsonResponseHandler();
|
||||||
|
}
|
||||||
|
|
||||||
|
return new PrettyPageHandler();
|
||||||
|
}
|
||||||
|
|
||||||
|
return new PlainTextHandler();
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Catch all exceptions
|
||||||
|
*
|
||||||
|
* @return void
|
||||||
|
*/
|
||||||
|
public function register(): void
|
||||||
|
{
|
||||||
|
self::reporter()->register();
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Catch single exception
|
||||||
|
*
|
||||||
|
* @param \Throwable $exception
|
||||||
|
* @return never
|
||||||
|
*/
|
||||||
|
public function handle(Throwable $exception): never
|
||||||
|
{
|
||||||
|
self::reporter()->handleException($exception);
|
||||||
|
|
||||||
|
exit(0);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Make new exception
|
||||||
|
*
|
||||||
|
* @param null $abstract
|
||||||
|
* @param string|null $message
|
||||||
|
* @return never
|
||||||
|
*/
|
||||||
|
public function make(mixed $abstract = null, string|null $message = null): never
|
||||||
|
{
|
||||||
|
if(is_string($abstract)) {
|
||||||
|
$abstract = app()->make($abstract, $message);
|
||||||
|
}
|
||||||
|
|
||||||
|
if(is_subclass_of($abstract, 'Exception')) {
|
||||||
|
$this->handle($abstract);
|
||||||
|
}
|
||||||
|
|
||||||
|
exit(0);
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
|
@ -1,81 +0,0 @@
|
||||||
<?php
|
|
||||||
|
|
||||||
namespace Core\Exceptions;
|
|
||||||
|
|
||||||
use Core\Env\Env;
|
|
||||||
use Core\Http\Request;
|
|
||||||
use Throwable;
|
|
||||||
use Whoops\Handler\Handler;
|
|
||||||
use Whoops\Handler\JsonResponseHandler;
|
|
||||||
use Whoops\Handler\PlainTextHandler;
|
|
||||||
use Whoops\Handler\PrettyPageHandler;
|
|
||||||
use Whoops\Run as Whoops;
|
|
||||||
|
|
||||||
class Exceptions
|
|
||||||
{
|
|
||||||
/**
|
|
||||||
* Exceptions handler instance
|
|
||||||
*
|
|
||||||
* @var Whoops
|
|
||||||
*/
|
|
||||||
private static Whoops $instance;
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Get exceptions handler instance
|
|
||||||
*
|
|
||||||
* @param \Core\Http\Request|null $request
|
|
||||||
* @return \Whoops\Run
|
|
||||||
*/
|
|
||||||
public static function instance(Request|null $request = null): Whoops
|
|
||||||
{
|
|
||||||
if (!isset(self::$instance)) {
|
|
||||||
$instance = new Whoops();
|
|
||||||
$instance->pushHandler(self::handler($request));
|
|
||||||
|
|
||||||
self::$instance = $instance;
|
|
||||||
}
|
|
||||||
|
|
||||||
return self::$instance;
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Get correct handler
|
|
||||||
*
|
|
||||||
* @param \Core\Http\Request|null $request
|
|
||||||
* @return \Whoops\Handler\Handler
|
|
||||||
*/
|
|
||||||
private static function handler(Request|null $request): Handler
|
|
||||||
{
|
|
||||||
if (Env::get('debug')) {
|
|
||||||
if ($request?->is('post')) {
|
|
||||||
return new JsonResponseHandler();
|
|
||||||
}
|
|
||||||
|
|
||||||
return new PrettyPageHandler();
|
|
||||||
}
|
|
||||||
|
|
||||||
return new PlainTextHandler();
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Catch all exceptions
|
|
||||||
*
|
|
||||||
* @param \Core\Http\Request $request
|
|
||||||
* @return void
|
|
||||||
*/
|
|
||||||
public static function catch(Request $request): void
|
|
||||||
{
|
|
||||||
self::instance($request)->register();
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Catch single exception
|
|
||||||
*
|
|
||||||
* @param \Throwable $exception
|
|
||||||
* @return void
|
|
||||||
*/
|
|
||||||
public static function catchOne(Throwable $exception): void
|
|
||||||
{
|
|
||||||
self::instance()->handleException($exception);
|
|
||||||
}
|
|
||||||
}
|
|
5
src/Exceptions/Exceptions/ClassNotFoundException.php
Normal file
5
src/Exceptions/Exceptions/ClassNotFoundException.php
Normal file
|
@ -0,0 +1,5 @@
|
||||||
|
<?php
|
||||||
|
|
||||||
|
namespace Core\Exceptions\Exceptions;
|
||||||
|
|
||||||
|
class ClassNotFoundException extends \Exception {}
|
5
src/Exceptions/Exceptions/HttpException.php
Normal file
5
src/Exceptions/Exceptions/HttpException.php
Normal file
|
@ -0,0 +1,5 @@
|
||||||
|
<?php
|
||||||
|
|
||||||
|
namespace Core\Exceptions\Exceptions;
|
||||||
|
|
||||||
|
class HttpException extends \Exception {}
|
5
src/Exceptions/Exceptions/InvalidArgumentException.php
Normal file
5
src/Exceptions/Exceptions/InvalidArgumentException.php
Normal file
|
@ -0,0 +1,5 @@
|
||||||
|
<?php
|
||||||
|
|
||||||
|
namespace Core\Exceptions\Exceptions;
|
||||||
|
|
||||||
|
class InvalidArgumentException extends \InvalidArgumentException {}
|
5
src/Exceptions/Exceptions/NotFoundHttpException.php
Normal file
5
src/Exceptions/Exceptions/NotFoundHttpException.php
Normal file
|
@ -0,0 +1,5 @@
|
||||||
|
<?php
|
||||||
|
|
||||||
|
namespace Core\Exceptions\Exceptions;
|
||||||
|
|
||||||
|
class NotFoundHttpException extends HttpException {}
|
93
src/Factory/BootstrapFactory.php
Normal file
93
src/Factory/BootstrapFactory.php
Normal file
|
@ -0,0 +1,93 @@
|
||||||
|
<?php
|
||||||
|
|
||||||
|
namespace Core\Factory;
|
||||||
|
|
||||||
|
use App\Application;
|
||||||
|
use Core\Env\Env;
|
||||||
|
use Core\Exceptions\ExceptionHandler;
|
||||||
|
use Core\Exceptions\Exceptions\ClassNotFoundException;
|
||||||
|
use Core\Http\Request;
|
||||||
|
use Core\Routing\RouteDispatcher;
|
||||||
|
|
||||||
|
class BootstrapFactory
|
||||||
|
{
|
||||||
|
private Request $request;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Handle Http core
|
||||||
|
*/
|
||||||
|
public function handle(): void
|
||||||
|
{
|
||||||
|
// Load env
|
||||||
|
Env::load();
|
||||||
|
|
||||||
|
// Create request
|
||||||
|
$this->request = app()->make(Request::class, [$_POST + $_FILES]);
|
||||||
|
|
||||||
|
// Register exceptions handler for error reporting
|
||||||
|
app()->make(ExceptionHandler::class)->register();
|
||||||
|
|
||||||
|
// Load routes
|
||||||
|
require '../config/routes.php';
|
||||||
|
|
||||||
|
try {
|
||||||
|
// Boot application
|
||||||
|
app()->make(Application::class)->bootstrap();
|
||||||
|
|
||||||
|
// Dispatch router
|
||||||
|
app()->make(RouteDispatcher::class)->dispatch($this->request);
|
||||||
|
} catch (\Exception $e) {
|
||||||
|
exceptions()->handle($e);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @param string $abstract
|
||||||
|
* @param mixed $arguments
|
||||||
|
* @return mixed
|
||||||
|
*/
|
||||||
|
public function make(string $abstract, mixed $arguments = []): mixed
|
||||||
|
{
|
||||||
|
return $this->resolve($abstract, $arguments);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @param string $abstract
|
||||||
|
* @param mixed $arguments
|
||||||
|
* @return mixed|null
|
||||||
|
*/
|
||||||
|
private function resolve(string $abstract, mixed $arguments = []): mixed
|
||||||
|
{
|
||||||
|
if (class_exists($abstract)) {
|
||||||
|
try {
|
||||||
|
$reflection = new \ReflectionClass($abstract);
|
||||||
|
return $reflection->newInstanceArgs($arguments);
|
||||||
|
} catch (\ReflectionException $e) {
|
||||||
|
exceptions()->handle($e);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
exceptions()->make(ClassNotFoundException::class, sprintf("Class '%s' not found", $abstract));
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Get the request instance
|
||||||
|
*
|
||||||
|
* @return \Core\Http\Request
|
||||||
|
*/
|
||||||
|
public function request(): Request
|
||||||
|
{
|
||||||
|
return $this->request;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Get path to file/folder in resources folder
|
||||||
|
*
|
||||||
|
* @param string $path
|
||||||
|
* @return string
|
||||||
|
*/
|
||||||
|
public function resourcePath(string $path = ''): string
|
||||||
|
{
|
||||||
|
return "../resources/" . $path;
|
||||||
|
}
|
||||||
|
}
|
64
src/Helpers/helpers.php
Normal file
64
src/Helpers/helpers.php
Normal file
|
@ -0,0 +1,64 @@
|
||||||
|
<?php
|
||||||
|
|
||||||
|
use Core\Bootstrap;
|
||||||
|
use Core\Env\Env;
|
||||||
|
use Core\Exceptions\ExceptionHandler;
|
||||||
|
use Core\Http\Request;
|
||||||
|
|
||||||
|
|
||||||
|
if(!function_exists('app'))
|
||||||
|
{
|
||||||
|
/**
|
||||||
|
* Make abstract of instance or get application instance
|
||||||
|
*
|
||||||
|
* @param mixed $abstract
|
||||||
|
* @param mixed $arguments
|
||||||
|
* @return \Core\Factory\BootstrapFactory|mixed
|
||||||
|
*/
|
||||||
|
function app(mixed $abstract = null, mixed $arguments = []): mixed
|
||||||
|
{
|
||||||
|
if (is_null($abstract)) {
|
||||||
|
return Bootstrap::getInstance();
|
||||||
|
}
|
||||||
|
|
||||||
|
return Bootstrap::getInstance()->make($abstract, $arguments);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if(!function_exists('env'))
|
||||||
|
{
|
||||||
|
/**
|
||||||
|
* Get env variable
|
||||||
|
*
|
||||||
|
* @param string $key
|
||||||
|
* @return bool
|
||||||
|
*/
|
||||||
|
function env(string $key): mixed
|
||||||
|
{
|
||||||
|
return Env::get($key);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if (!function_exists('request')) {
|
||||||
|
/**
|
||||||
|
* Get the request instance
|
||||||
|
*
|
||||||
|
* @return \Core\Http\Request
|
||||||
|
*/
|
||||||
|
function request(): Request
|
||||||
|
{
|
||||||
|
return app()->request();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if (!function_exists('exceptions')) {
|
||||||
|
/**
|
||||||
|
* Get error handler instance
|
||||||
|
*
|
||||||
|
* @return \Core\Exceptions\ExceptionHandler
|
||||||
|
*/
|
||||||
|
function exceptions(): ExceptionHandler
|
||||||
|
{
|
||||||
|
return ExceptionHandler::instance();
|
||||||
|
}
|
||||||
|
}
|
|
@ -1,6 +1,6 @@
|
||||||
<?php
|
<?php
|
||||||
|
|
||||||
namespace Core\Controllers;
|
namespace Core\Http\Controllers;
|
||||||
|
|
||||||
use Core\Http\Request;
|
use Core\Http\Request;
|
||||||
use Core\Http\Response;
|
use Core\Http\Response;
|
|
@ -4,6 +4,18 @@ namespace Core\Http;
|
||||||
|
|
||||||
class Request
|
class Request
|
||||||
{
|
{
|
||||||
|
private array $data = [];
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Build request object
|
||||||
|
*
|
||||||
|
* @param array $data
|
||||||
|
*/
|
||||||
|
public function __construct(array $data)
|
||||||
|
{
|
||||||
|
$this->data = $data;
|
||||||
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Get request method
|
* Get request method
|
||||||
*
|
*
|
||||||
|
@ -43,7 +55,7 @@ class Request
|
||||||
*/
|
*/
|
||||||
public final function has(string $param): bool
|
public final function has(string $param): bool
|
||||||
{
|
{
|
||||||
return isset($_POST[$param]);
|
return isset($this->data[$param]);
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
@ -56,11 +68,11 @@ class Request
|
||||||
public final function get(string|null $param = null, mixed $default = null): mixed
|
public final function get(string|null $param = null, mixed $default = null): mixed
|
||||||
{
|
{
|
||||||
if($param == null) {
|
if($param == null) {
|
||||||
return $_POST;
|
return $this->data;
|
||||||
}
|
}
|
||||||
|
|
||||||
if($this->has($param)) {
|
if($this->has($param)) {
|
||||||
return $_POST[$param];
|
return $this->data[$param];
|
||||||
}
|
}
|
||||||
|
|
||||||
return $default;
|
return $default;
|
||||||
|
|
|
@ -2,9 +2,9 @@
|
||||||
|
|
||||||
namespace Core\Http;
|
namespace Core\Http;
|
||||||
|
|
||||||
use Core\View\Render;
|
use Core\Http\View\Engine\HtmlEngine;
|
||||||
use Core\View\Render\HtmlRender;
|
use Core\Http\View\Engine\JsonEngine;
|
||||||
use Core\View\Render\JsonRender;
|
use Core\Http\View\Render;
|
||||||
|
|
||||||
class Response
|
class Response
|
||||||
{
|
{
|
||||||
|
@ -25,20 +25,20 @@ class Response
|
||||||
* Render HTML
|
* Render HTML
|
||||||
*
|
*
|
||||||
* @param string $view
|
* @param string $view
|
||||||
* @return \Core\View\Render
|
* @return \Core\Http\View\Render
|
||||||
*/
|
*/
|
||||||
public function view(string $view): Render
|
public function view(string $view): Render
|
||||||
{
|
{
|
||||||
return (new HtmlRender())->view($view);
|
return (new HtmlEngine())->view($view);
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Render JSON
|
* Render JSON
|
||||||
*
|
*
|
||||||
* @return \Core\View\Render
|
* @return \Core\Http\View\Render
|
||||||
*/
|
*/
|
||||||
public function json(): Render
|
public function json(): Render
|
||||||
{
|
{
|
||||||
return new JsonRender();
|
return new JsonEngine();
|
||||||
}
|
}
|
||||||
}
|
}
|
|
@ -1,10 +1,10 @@
|
||||||
<?php
|
<?php
|
||||||
|
|
||||||
namespace Core\View\Render;
|
namespace Core\Http\View\Engine;
|
||||||
|
|
||||||
use Core\View\Render;
|
use Core\Http\View\Render;
|
||||||
|
|
||||||
class HtmlRender extends Render
|
class HtmlEngine extends Render
|
||||||
{
|
{
|
||||||
/**
|
/**
|
||||||
* @inheritDoc
|
* @inheritDoc
|
||||||
|
@ -13,7 +13,7 @@ class HtmlRender extends Render
|
||||||
public function render(): void
|
public function render(): void
|
||||||
{
|
{
|
||||||
$basePath = $_SERVER['DOCUMENT_ROOT'];
|
$basePath = $_SERVER['DOCUMENT_ROOT'];
|
||||||
$viewsPath = $basePath . '/../resources/views/' . str_replace('.', '/', $this->view) . '.php';
|
$viewsPath = app()->resourcePath('views/' . str_replace('.', '/', $this->view) . '.php');
|
||||||
|
|
||||||
if (file_exists($viewsPath)) {
|
if (file_exists($viewsPath)) {
|
||||||
extract($this->data);
|
extract($this->data);
|
|
@ -1,10 +1,11 @@
|
||||||
<?php
|
<?php
|
||||||
|
|
||||||
namespace Core\View\Render;
|
namespace Core\Http\View\Engine;
|
||||||
|
|
||||||
use Core\View\Render;
|
|
||||||
|
|
||||||
class JsonRender extends Render
|
use Core\Http\View\Render;
|
||||||
|
|
||||||
|
class JsonEngine extends Render
|
||||||
{
|
{
|
||||||
/**
|
/**
|
||||||
* @inheritDoc
|
* @inheritDoc
|
|
@ -1,6 +1,6 @@
|
||||||
<?php
|
<?php
|
||||||
|
|
||||||
namespace Core\View;
|
namespace Core\Http\View;
|
||||||
|
|
||||||
abstract class Render
|
abstract class Render
|
||||||
{
|
{
|
|
@ -1,109 +0,0 @@
|
||||||
<?php
|
|
||||||
|
|
||||||
namespace Core\Router;
|
|
||||||
|
|
||||||
use Core\Exceptions\Exceptions;
|
|
||||||
use Core\Http\Request;
|
|
||||||
use Core\View\Render;
|
|
||||||
use Exception;
|
|
||||||
|
|
||||||
class Router
|
|
||||||
{
|
|
||||||
/**
|
|
||||||
* List of routes
|
|
||||||
*
|
|
||||||
* @var array
|
|
||||||
*/
|
|
||||||
protected static array $routes = [];
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Add GET route
|
|
||||||
*
|
|
||||||
* @param string $route
|
|
||||||
* @param string $controller
|
|
||||||
* @param string $action
|
|
||||||
* @return void
|
|
||||||
*/
|
|
||||||
public static function get(string $route, string $controller, string $action): void
|
|
||||||
{
|
|
||||||
self::register($route, $controller, $action, "GET");
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Add POST route
|
|
||||||
*
|
|
||||||
* @param string $route
|
|
||||||
* @param string $controller
|
|
||||||
* @param string $action
|
|
||||||
* @return void
|
|
||||||
*/
|
|
||||||
public static function post(string $route, string $controller, string $action): void
|
|
||||||
{
|
|
||||||
self::register($route, $controller, $action, "POST");
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Register route
|
|
||||||
*
|
|
||||||
* @param string $route
|
|
||||||
* @param string $controller
|
|
||||||
* @param string $action
|
|
||||||
* @param string $method
|
|
||||||
* @return void
|
|
||||||
*/
|
|
||||||
public static function register(string $route, string $controller, string $action, string $method): void
|
|
||||||
{
|
|
||||||
self::$routes[$method][$route] = [
|
|
||||||
'controller' => $controller,
|
|
||||||
'action' => $action,
|
|
||||||
];
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Dispatch router and run application
|
|
||||||
*
|
|
||||||
* @return void
|
|
||||||
*/
|
|
||||||
public static function dispatch(): void
|
|
||||||
{
|
|
||||||
// Init request
|
|
||||||
$request = new Request();
|
|
||||||
$url = $request->url();
|
|
||||||
$method = $request->method();
|
|
||||||
|
|
||||||
// Capture all exceptions
|
|
||||||
Exceptions::catch($request);
|
|
||||||
|
|
||||||
try {
|
|
||||||
if (array_key_exists($url, self::$routes[$method])) {
|
|
||||||
$controllerName = self::$routes[$method][$url]['controller'];
|
|
||||||
$actionName = self::$routes[$method][$url]['action'];
|
|
||||||
|
|
||||||
// Check for controller existence
|
|
||||||
if (!class_exists($controllerName)) {
|
|
||||||
throw new Exception(sprintf("Controller '%s' missing", $controllerName));
|
|
||||||
}
|
|
||||||
|
|
||||||
// Create controller object
|
|
||||||
$controller = new $controllerName($request);
|
|
||||||
|
|
||||||
// Check for method on controller
|
|
||||||
if (!method_exists($controller, $actionName)) {
|
|
||||||
throw new Exception(sprintf("Method '%s' not found on '%s'", $actionName, $controllerName));
|
|
||||||
}
|
|
||||||
|
|
||||||
// Build response object of action
|
|
||||||
$response = $controller->$actionName();
|
|
||||||
|
|
||||||
// Render action
|
|
||||||
if ($response instanceof Render) {
|
|
||||||
$response->render();
|
|
||||||
}
|
|
||||||
} else {
|
|
||||||
throw new Exception(sprintf("No route found for: %s", $url));
|
|
||||||
}
|
|
||||||
} catch (Exception $e) {
|
|
||||||
Exceptions::catchOne($e);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
32
src/Routing/Route.php
Normal file
32
src/Routing/Route.php
Normal file
|
@ -0,0 +1,32 @@
|
||||||
|
<?php
|
||||||
|
|
||||||
|
namespace Core\Routing;
|
||||||
|
|
||||||
|
class Route
|
||||||
|
{
|
||||||
|
/**
|
||||||
|
* Add GET route
|
||||||
|
*
|
||||||
|
* @param string $route
|
||||||
|
* @param string $controller
|
||||||
|
* @param string $action
|
||||||
|
* @return void
|
||||||
|
*/
|
||||||
|
public static function get(string $route, string $controller, string $action): void
|
||||||
|
{
|
||||||
|
RouteCollection::register($route, $controller, $action, 'GET');
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Add POST route
|
||||||
|
*
|
||||||
|
* @param string $route
|
||||||
|
* @param string $controller
|
||||||
|
* @param string $action
|
||||||
|
* @return void
|
||||||
|
*/
|
||||||
|
public static function post(string $route, string $controller, string $action): void
|
||||||
|
{
|
||||||
|
RouteCollection::register($route, $controller, $action, 'POST');
|
||||||
|
}
|
||||||
|
}
|
90
src/Routing/RouteCollection.php
Normal file
90
src/Routing/RouteCollection.php
Normal file
|
@ -0,0 +1,90 @@
|
||||||
|
<?php
|
||||||
|
|
||||||
|
namespace Core\Routing;
|
||||||
|
|
||||||
|
class RouteCollection
|
||||||
|
{
|
||||||
|
/**
|
||||||
|
* A majestic vault of all defined routes.
|
||||||
|
*
|
||||||
|
* @var array
|
||||||
|
*/
|
||||||
|
protected static array $routes = [];
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Add a route to the sacred collection.
|
||||||
|
*
|
||||||
|
* @param string $route
|
||||||
|
* @param string $controller
|
||||||
|
* @param string $action
|
||||||
|
* @param string $method
|
||||||
|
* @return void
|
||||||
|
*/
|
||||||
|
public static function register(string $route, string $controller, string $action, string $method): void
|
||||||
|
{
|
||||||
|
// Transform human-readable route syntax into regex.
|
||||||
|
$routeRegex = self::convertToRegex($route);
|
||||||
|
|
||||||
|
// Store the route in the sacred collection
|
||||||
|
self::$routes[$method][$routeRegex] = [
|
||||||
|
'controller' => $controller,
|
||||||
|
'action' => $action,
|
||||||
|
'original' => $route,
|
||||||
|
];
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Reveal the treasure trove of routes.
|
||||||
|
*
|
||||||
|
* @return array
|
||||||
|
*/
|
||||||
|
public static function retrieve(): array
|
||||||
|
{
|
||||||
|
return self::$routes;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Convert a route pattern into a regex for matching requests.
|
||||||
|
*
|
||||||
|
* @param string $route
|
||||||
|
* @return string
|
||||||
|
*/
|
||||||
|
protected static function convertToRegex(string $route): string
|
||||||
|
{
|
||||||
|
$regex = preg_replace('/\{([a-zA-Z0-9_]+)\}/', '(?P<\1>[a-zA-Z0-9_-]+)', $route);
|
||||||
|
return '#^' . $regex . '$#';
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Retrieve a specific route by method and URL.
|
||||||
|
*
|
||||||
|
* @param string $method
|
||||||
|
* @param string $url
|
||||||
|
* @return array|null
|
||||||
|
*/
|
||||||
|
public static function find(string $method, string $url): ?array
|
||||||
|
{
|
||||||
|
foreach (self::$routes[$method] ?? [] as $routeRegex => $route) {
|
||||||
|
if (preg_match($routeRegex, $url, $matches)) {
|
||||||
|
$route['params'] = array_filter(
|
||||||
|
$matches,
|
||||||
|
fn($key) => !is_numeric($key),
|
||||||
|
ARRAY_FILTER_USE_KEY
|
||||||
|
);
|
||||||
|
return $route;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Purge all routes from the collection.
|
||||||
|
*
|
||||||
|
* @return void
|
||||||
|
*/
|
||||||
|
public static function clear(): void
|
||||||
|
{
|
||||||
|
self::$routes = [];
|
||||||
|
}
|
||||||
|
}
|
144
src/Routing/RouteDispatcher.php
Normal file
144
src/Routing/RouteDispatcher.php
Normal file
|
@ -0,0 +1,144 @@
|
||||||
|
<?php
|
||||||
|
|
||||||
|
namespace Core\Routing;
|
||||||
|
|
||||||
|
use Core\Exceptions\ExceptionHandler;
|
||||||
|
use Core\Exceptions\Exceptions\NotFoundHttpException;
|
||||||
|
use Core\Http\Request;
|
||||||
|
use Core\Http\View\Render;
|
||||||
|
use Exception;
|
||||||
|
|
||||||
|
class RouteDispatcher
|
||||||
|
{
|
||||||
|
/**
|
||||||
|
* Current request instance
|
||||||
|
*
|
||||||
|
* @var \Core\Http\Request
|
||||||
|
*/
|
||||||
|
private Request $request;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Dispatch the router dispatcher
|
||||||
|
*
|
||||||
|
* @param \Core\Http\Request $request
|
||||||
|
* @return void
|
||||||
|
*/
|
||||||
|
public function dispatch(Request $request): void
|
||||||
|
{
|
||||||
|
$this->request = $request;
|
||||||
|
|
||||||
|
try {
|
||||||
|
$route = $this->findMatchingRoute();
|
||||||
|
$controller = $this->instantiateController($route['controller']);
|
||||||
|
$action = $this->validateActionExists($controller, $route['action']);
|
||||||
|
$params = $this->resolveParameters($controller, $action, $route['params'] ?? []);
|
||||||
|
|
||||||
|
$this->executeAction($controller, $action, $params);
|
||||||
|
} catch (NotFoundHttpException $e) {
|
||||||
|
$this->handleException($e, 404, 'page not found');
|
||||||
|
} catch (Exception $e) {
|
||||||
|
$this->handleException($e, 500, 'something went wrong');
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Locate a matching route for the incoming request.
|
||||||
|
*
|
||||||
|
* @return array
|
||||||
|
* @throws \Core\Exceptions\Exceptions\NotFoundHttpException
|
||||||
|
*/
|
||||||
|
private function findMatchingRoute(): array
|
||||||
|
{
|
||||||
|
$url = $this->request->url();
|
||||||
|
$method = $this->request->method();
|
||||||
|
|
||||||
|
$route = RouteCollection::find($method, $url);
|
||||||
|
if ($route) {
|
||||||
|
return $route;
|
||||||
|
}
|
||||||
|
|
||||||
|
throw new NotFoundHttpException(sprintf("No route found for: %s", $url));
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Create an instance of the requested controller.
|
||||||
|
*
|
||||||
|
* @param string $controllerName
|
||||||
|
* @return object
|
||||||
|
* @throws \Core\Exceptions\Exceptions\NotFoundHttpException
|
||||||
|
*/
|
||||||
|
private function instantiateController(string $controllerName): object
|
||||||
|
{
|
||||||
|
if (!class_exists($controllerName)) {
|
||||||
|
throw new NotFoundHttpException(sprintf("Controller '%s' missing", $controllerName));
|
||||||
|
}
|
||||||
|
|
||||||
|
return new $controllerName($this->request);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Validate that the action exists in the controller.
|
||||||
|
*
|
||||||
|
* @param object $controller
|
||||||
|
* @param string $actionName
|
||||||
|
* @return string
|
||||||
|
* @throws \Core\Exceptions\Exceptions\NotFoundHttpException
|
||||||
|
*/
|
||||||
|
private function validateActionExists(object $controller, string $actionName): string
|
||||||
|
{
|
||||||
|
if (!method_exists($controller, $actionName)) {
|
||||||
|
throw new NotFoundHttpException(sprintf("Method '%s' not found on '%s'", $actionName, get_class($controller)));
|
||||||
|
}
|
||||||
|
|
||||||
|
return $actionName;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Validate and resolve parameters for the controller action.
|
||||||
|
*
|
||||||
|
* @param object $controller
|
||||||
|
* @param string $action
|
||||||
|
* @param array $params
|
||||||
|
* @return array
|
||||||
|
* @throws \Exception
|
||||||
|
*/
|
||||||
|
private function resolveParameters(object $controller, string $action, array $params): array
|
||||||
|
{
|
||||||
|
return RouteValidator::resolve($controller, $action, $params);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Execute the resolved action on the controller with validated parameters.
|
||||||
|
*
|
||||||
|
* @param object $controller
|
||||||
|
* @param string $action
|
||||||
|
* @param array $params
|
||||||
|
* @return void
|
||||||
|
*/
|
||||||
|
private function executeAction(object $controller, string $action, array $params): void
|
||||||
|
{
|
||||||
|
$response = $controller->$action(...$params);
|
||||||
|
|
||||||
|
if ($response instanceof Render) {
|
||||||
|
$response->render();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Handle exceptions gracefully.
|
||||||
|
*
|
||||||
|
* @param Exception $e
|
||||||
|
* @param int $statusCode
|
||||||
|
* @param string $message
|
||||||
|
* @return void
|
||||||
|
*/
|
||||||
|
private function handleException(Exception $e, int $statusCode, string $message): void
|
||||||
|
{
|
||||||
|
if (env('debug')) {
|
||||||
|
exceptions()->handle($e);
|
||||||
|
}
|
||||||
|
|
||||||
|
http_response_code($statusCode);
|
||||||
|
echo $message;
|
||||||
|
}
|
||||||
|
}
|
89
src/Routing/RouteValidator.php
Normal file
89
src/Routing/RouteValidator.php
Normal file
|
@ -0,0 +1,89 @@
|
||||||
|
<?php
|
||||||
|
|
||||||
|
namespace Core\Routing;
|
||||||
|
|
||||||
|
use Core\Exceptions\Exceptions\NotFoundHttpException;
|
||||||
|
use ReflectionMethod;
|
||||||
|
use ReflectionNamedType;
|
||||||
|
|
||||||
|
class RouteValidator
|
||||||
|
{
|
||||||
|
/**
|
||||||
|
* Validate parameters against the method signature.
|
||||||
|
*
|
||||||
|
* @param object $controller
|
||||||
|
* @param string $action
|
||||||
|
* @param array $params
|
||||||
|
* @return array
|
||||||
|
* @throws \Core\Exceptions\Exceptions\NotFoundHttpException
|
||||||
|
* @throws \ReflectionException
|
||||||
|
*/
|
||||||
|
public static function resolve(object $controller, string $action, array $params): array
|
||||||
|
{
|
||||||
|
$reflection = new ReflectionMethod($controller, $action);
|
||||||
|
$methodParameters = $reflection->getParameters();
|
||||||
|
$validatedParams = [];
|
||||||
|
|
||||||
|
foreach ($methodParameters as $methodParameter) {
|
||||||
|
$paramName = $methodParameter->getName();
|
||||||
|
$paramType = $methodParameter->getType();
|
||||||
|
$isOptional = $methodParameter->isOptional();
|
||||||
|
$defaultValue = $isOptional ? $methodParameter->getDefaultValue() : null;
|
||||||
|
|
||||||
|
if (!array_key_exists($paramName, $params)) {
|
||||||
|
if ($isOptional) {
|
||||||
|
$validatedParams[$paramName] = $defaultValue;
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
throw new NotFoundHttpException(sprintf("Missing parameter '%s' for action '%s'", $paramName, $action));
|
||||||
|
}
|
||||||
|
|
||||||
|
$value = $params[$paramName];
|
||||||
|
$validatedParams[$paramName] = self::validateType($paramName, $value, $paramType, $action);
|
||||||
|
}
|
||||||
|
|
||||||
|
return $validatedParams;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Validate and cast a parameter based on its type.
|
||||||
|
*
|
||||||
|
* @param string $paramName
|
||||||
|
* @param mixed $value
|
||||||
|
* @param ReflectionNamedType|null $paramType
|
||||||
|
* @param string $action
|
||||||
|
* @return mixed
|
||||||
|
* @throws \Core\Exceptions\Exceptions\NotFoundHttpException
|
||||||
|
*/
|
||||||
|
private static function validateType(string $paramName, mixed $value, ?ReflectionNamedType $paramType, string $action): mixed
|
||||||
|
{
|
||||||
|
if (!$paramType || $paramType->allowsNull() && $value === null) {
|
||||||
|
return $value; // No type or allows null, return as-is.
|
||||||
|
}
|
||||||
|
|
||||||
|
$typeName = $paramType->getName();
|
||||||
|
|
||||||
|
switch ($typeName) {
|
||||||
|
case 'int':
|
||||||
|
if (!ctype_digit((string)$value)) {
|
||||||
|
throw new NotFoundHttpException(sprintf("Invalid type for parameter '%s'. Expected integer, got '%s'", $paramName, gettype($value)));
|
||||||
|
}
|
||||||
|
return (int)$value;
|
||||||
|
|
||||||
|
case 'float':
|
||||||
|
if (!is_numeric($value)) {
|
||||||
|
throw new NotFoundHttpException(sprintf("Invalid type for parameter '%s'. Expected float, got '%s'", $paramName, gettype($value)));
|
||||||
|
}
|
||||||
|
return (float)$value;
|
||||||
|
|
||||||
|
case 'string':
|
||||||
|
if (!is_string($value)) {
|
||||||
|
throw new NotFoundHttpException(sprintf("Invalid type for parameter '%s'. Expected string, got '%s'", $paramName, gettype($value)));
|
||||||
|
}
|
||||||
|
return $value;
|
||||||
|
|
||||||
|
default:
|
||||||
|
throw new NotFoundHttpException(sprintf("Unsupported type '%s' for parameter '%s'", $typeName, $paramName));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
22
src/Routing/Router.php
Normal file
22
src/Routing/Router.php
Normal file
|
@ -0,0 +1,22 @@
|
||||||
|
<?php
|
||||||
|
|
||||||
|
namespace Core\Routing;
|
||||||
|
|
||||||
|
use Core\Http\Request;
|
||||||
|
|
||||||
|
class Router
|
||||||
|
{
|
||||||
|
/**
|
||||||
|
* Dispatch router and run application
|
||||||
|
*
|
||||||
|
* @return void
|
||||||
|
*/
|
||||||
|
public static function dispatch(): void
|
||||||
|
{
|
||||||
|
// Create request
|
||||||
|
$request = new Request($_POST + $_FILES);
|
||||||
|
|
||||||
|
// Dispatch router
|
||||||
|
RouteDispatcher::dispatch($request, RouteCollection::retrieve());
|
||||||
|
}
|
||||||
|
}
|
Loading…
Add table
Add a link
Reference in a new issue