diff options
Diffstat (limited to 'src')
| -rw-r--r-- | src/Config.php | 19 | ||||
| -rw-r--r-- | src/ERPHP.php | 43 | ||||
| -rw-r--r-- | src/Hook.php | 70 | ||||
| -rw-r--r-- | src/Router/Route.php | 39 | ||||
| -rw-r--r-- | src/Router/Router.php | 68 | ||||
| -rw-r--r-- | src/Router/RoutesDefinitionInterface.php | 11 | ||||
| -rw-r--r-- | src/routes.php | 20 |
7 files changed, 270 insertions, 0 deletions
diff --git a/src/Config.php b/src/Config.php new file mode 100644 index 0000000..06b32d0 --- /dev/null +++ b/src/Config.php @@ -0,0 +1,19 @@ +<?php + +namespace ERPHP; + +class Config +{ + public static function load(): void + { + $_ENV = array_replace_recursive( + $_ENV, + json_decode(file_get_contents(dirname(__DIR__) . "/config.json"), true) ?: [] + ); + } + + public static function get(string $key, mixed $default = null): mixed + { + return $_ENV[$key] ?? getenv($key) === false ? $default : getenv($key); + } +} diff --git a/src/ERPHP.php b/src/ERPHP.php new file mode 100644 index 0000000..b688361 --- /dev/null +++ b/src/ERPHP.php @@ -0,0 +1,43 @@ +<?php + +namespace ERPHP; + +use ERPHP\Router\Router; + +class ERPHP +{ + public function run(): void + { + // start session + session_start(); + + // load config + Config::load(); + + // load plugins + $this->loadPlugins(); + + // load routes + Hook::addFilter("Router::getRouteFiles", function (array $routeFiles) { + $routeFiles[] = __DIR__ . "/routes.php"; + return $routeFiles; + }); + + Router::getInstance()->run(); + } + + private function loadPlugins(): void + { + $pluginsDirectory = Config::get("plugins_directory", dirname(__DIR__) . "/plugins"); + $pluginDirectories = scandir($pluginsDirectory); + + foreach ($pluginDirectories as $directory) { + if ($directory === "." || $directory === "..") { + continue; + } + + $composerConfig = file_get_contents("$pluginsDirectory/$directory/composer.json"); + include_once "$pluginsDirectory/$directory/index.php"; + } + } +} diff --git a/src/Hook.php b/src/Hook.php new file mode 100644 index 0000000..0b292ce --- /dev/null +++ b/src/Hook.php @@ -0,0 +1,70 @@ +<?php + +namespace ERPHP; + +class Hook +{ + /** + * @var array<string, array<int, callable[]>> $actions + */ + private static array $actions; + + /** + * @var array<string, array<int, callable[]>> $filters + */ + private static array $filters; + + /** + * @param string $name + * @param callable $function + */ + public static function addAction(string $name, callable $function, int $priority = 10): void + { + self::$actions[$name][$priority][] = $function; + } + + /** + * @param string $name + * @param array $parameters + */ + public static function runAction(string $name, ...$parameters): void + { + $actions = self::$actions[$name] ?? []; + + foreach ($actions as $priority => $functions) { + foreach ($functions as $function) { + call_user_func_array($function, $parameters); + } + } + } + + /** + * @param string $name + * @param callable $function + */ + public static function addFilter(string $name, callable $function, int $priority = 10): void + { + self::$filters[$name][$priority][] = $function; + } + + /** + * @param string $name + * @param mixed $value + * @param array $parameters + * + * @return mixed + */ + public static function applyFilter(string $name, mixed $value, ...$parameters): mixed + { + $filteredValue = $value; + $filters = self::$filters[$name] ?? []; + + foreach ($filters as $priority => $functions) { + foreach ($functions as $function) { + $filteredValue = call_user_func_array($function, [$value, ...$parameters]); + } + } + + return $filteredValue; + } +} diff --git a/src/Router/Route.php b/src/Router/Route.php new file mode 100644 index 0000000..ee9637b --- /dev/null +++ b/src/Router/Route.php @@ -0,0 +1,39 @@ +<?php + +namespace ERPHP\Router; + +use Symfony\Component\Routing\Route as SymfonyRoute; + +class Route extends SymfonyRoute +{ + public function __construct( + string $path, + callable $action, + private string $name = "", + array $defaults = [], + array $requirements = [], + array $options = [], + ?string $host = '', + string|array $schemes = [], + string|array $methods = [], + ?string $condition = '', + ) + { + if (empty($name)) { + $this->name = $path; + } + + $defaults = array_merge([ + "_" => [ + "action" => $action, + ], + ], $defaults); + + parent::__construct($path, $defaults, $requirements, $options, $host, $schemes, $methods, $condition); + } + + public function getName(): string + { + return $this->name; + } +} diff --git a/src/Router/Router.php b/src/Router/Router.php new file mode 100644 index 0000000..d770f15 --- /dev/null +++ b/src/Router/Router.php @@ -0,0 +1,68 @@ +<?php + +namespace ERPHP\Router; + +use ERPHP\Hook; +use Symfony\Component\HttpFoundation\Request; +use Symfony\Component\HttpFoundation\Response; +use Symfony\Component\Routing\Matcher\UrlMatcher; +use Symfony\Component\Routing\RequestContext; +use Symfony\Component\Routing\RouteCollection; + +class Router +{ + private RouteCollection $routes; + + private static self $instance; + + public static function getInstance(): self + { + if (! isset(self::$instance)) { + self::$instance = new self(); + } + + return self::$instance; + } + + public function __construct() + { + $this->routes = new RouteCollection(); + } + + public function run(): void + { + // get defined routes + $routeFiles = []; + $routeFiles = Hook::applyFilter("Router::getRouteFiles", $routeFiles); + + foreach ($routeFiles as $routesFile) { + /** @var RoutesDefinitionInterface $routesDefinition */ + $routesDefinition = include $routesFile; + $routes = $routesDefinition->get(); + + foreach ($routes as $route) { + /** @var Route[] $route */ + if (! is_null($this->routes->get($route->getName()))) { + throw new \Exception("Route \"{$route->getName()}\" already defined."); + } + + $this->routes->add($route->getName(), $route); + } + } + + $request = Request::createFromGlobals(); + $response = new Response(); + + // set semantically correct request method + if ($request->get("_method", false)) { + $request->setMethod($request->get("_method")); + } + + $matcher = new UrlMatcher($this->routes, new RequestContext()); + $match = $matcher->matchRequest($request); + + /** @var Response $response */ + $response = call_user_func_array($match["_"]["action"], [$request]); + $response->send(); + } +} diff --git a/src/Router/RoutesDefinitionInterface.php b/src/Router/RoutesDefinitionInterface.php new file mode 100644 index 0000000..614c7cd --- /dev/null +++ b/src/Router/RoutesDefinitionInterface.php @@ -0,0 +1,11 @@ +<?php + +namespace ERPHP\Router; + +interface RoutesDefinitionInterface +{ + /** + * @return Route[] + */ + public function get(): array; +} diff --git a/src/routes.php b/src/routes.php new file mode 100644 index 0000000..19a81b2 --- /dev/null +++ b/src/routes.php @@ -0,0 +1,20 @@ +<?php + +namespace ERPHP; + +use ERPHP\Router\Route; +use ERPHP\Router\RoutesDefinitionInterface; +use Symfony\Component\HttpFoundation\Request; +use Symfony\Component\HttpFoundation\Response; + +return new class implements RoutesDefinitionInterface +{ + public function get(): array + { + return [ + new Route("/", function (Request $request) { + return new Response("/ route response"); + }), + ]; + } +}; |
