From 5a253d47b39857b61dcc37ba0deea2497e256822 Mon Sep 17 00:00:00 2001 From: mychidarko Date: Tue, 1 Oct 2024 04:05:09 +0000 Subject: [PATCH 1/9] chore: merge router into core --- composer.json | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/composer.json b/composer.json index 63797de..bceb296 100644 --- a/composer.json +++ b/composer.json @@ -1,6 +1,6 @@ { "name": "leafs/leaf", - "description": "Simple, performant and powerful PHP micro-framework for rapid web app & API development", + "description": "Elegant PHP for modern developers", "keywords": [ "microframework", "rest", @@ -33,7 +33,6 @@ "require": { "php": "^7.4|^8.0", "leafs/http": "*", - "leafs/router": "*", "leafs/anchor": "*", "leafs/exception": "*" }, From 30e4973f64cc87f686d5bc029ac3a0d464addf4f Mon Sep 17 00:00:00 2001 From: mychidarko Date: Tue, 1 Oct 2024 04:06:02 +0000 Subject: [PATCH 2/9] feat: use env as default mode --- src/App.php | 21 +++------------------ 1 file changed, 3 insertions(+), 18 deletions(-) diff --git a/src/App.php b/src/App.php index 49aa03e..0947fe6 100755 --- a/src/App.php +++ b/src/App.php @@ -49,11 +49,12 @@ public function __construct(array $userSettings = []) protected function loadConfig(array $userSettings = []) { if (!empty($userSettings)) { - Config::set($userSettings); + Config::set(array_merge($userSettings, [ + 'mode' => _env('APP_ENV', Config::getStatic('mode')) + ])); } $this->setupDefaultContainer(); - $this->loadViewEngines(); } protected function setupErrorHandler() @@ -90,22 +91,6 @@ public function register($name, $value) Config::singleton($name, $value); } - /** - * This method loads all added view engines - */ - public function loadViewEngines() - { - $views = View::$engines; - - if (!empty($views)) { - foreach ($views as $key => $value) { - Config::singleton($key, function () use ($value) { - return $value; - }); - } - } - } - private function setupDefaultContainer() { Config::singleton('request', function () { From 99534731160982fbc1a39a095107d8fd639e2ed8 Mon Sep 17 00:00:00 2001 From: mychidarko Date: Tue, 1 Oct 2024 04:06:39 +0000 Subject: [PATCH 3/9] chore: remove unused files --- src/Middleware.php | 80 ---------------------------------------------- src/View.php | 48 ---------------------------- 2 files changed, 128 deletions(-) delete mode 100755 src/Middleware.php delete mode 100644 src/View.php diff --git a/src/Middleware.php b/src/Middleware.php deleted file mode 100755 index 93f50aa..0000000 --- a/src/Middleware.php +++ /dev/null @@ -1,80 +0,0 @@ -next = $nextMiddleware; - } - - /** - * Get next middleware - * - * This method retrieves the next downstream middleware - * previously injected into this middleware. - * - * @return \Leaf\Middleware - */ - final public function getNextMiddleware() - { - return $this->next; - } - - /** - * Call the next middleware - * - * @param mixed $data Data to pass to the next middleware/route - */ - final public function next($data = null) - { - $nextMiddleware = $this->next; - - if ($data) { - \Leaf\Config::set('middleware.data', $data); - } - - if (!$nextMiddleware) { - return; - } - - $nextMiddleware->call(); - } - - /** - * Call - * - * Perform actions specific to this middleware and optionally - * call the next downstream middleware. - */ - abstract public function call(); -} diff --git a/src/View.php b/src/View.php deleted file mode 100644 index 539b712..0000000 --- a/src/View.php +++ /dev/null @@ -1,48 +0,0 @@ - - * @since v2.4.4 - * @deprecated All functionality is now available through the Config class - */ -class View -{ - public static $engines = []; - - /** - * Attach view engine to Leaf view - * - * @param mixed $className The class to attach - * @param string|null $name The key to save view engine with - * @deprecated use Config::attachView instead - */ - public static function attach($className, $name = null) - { - $class = new $className(); - - static::$engines[$name ?? static::getDiIndex($class)] = $class; - - Config::set('views.engine', $name ?? static::getDiIndex($class)); - } - - private static function getDiIndex($class) - { - $fullName = \explode("\\", \strtolower(\get_class($class))); - $className = $fullName[\count($fullName) - 1]; - - return $className; - } - - public static function __callstatic($name, $arguments) - { - return static::$engines[$name]; - } -} From 160b2734211a924842672c2fcb0241194e077426 Mon Sep 17 00:00:00 2001 From: mychidarko Date: Tue, 1 Oct 2024 04:09:10 +0000 Subject: [PATCH 4/9] feat: update router internals --- src/Router.php | 827 +++++++++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 827 insertions(+) create mode 100644 src/Router.php diff --git a/src/Router.php b/src/Router.php new file mode 100644 index 0000000..596d5df --- /dev/null +++ b/src/Router.php @@ -0,0 +1,827 @@ + false, + 'router.before.route' => false, + 'router.after.route' => false, + 'router.after' => false, + ]; + + /** + * All middleware that should be run + */ + protected static $middleware = []; + + /** + * Named middleware + */ + protected static $namedMiddleware = []; + + /** + * All added routes and their handlers + */ + protected static $routes = []; + + /** + * Sorted list of routes and their handlers + */ + protected static $appRoutes = []; + + /** + * All named routes + */ + protected static $namedRoutes = []; + + /** + * Route based middleware + */ + protected static $routeGroupMiddleware = []; + + /** + * Current group base path + */ + protected static $groupRoute = ''; + + /** + * Default controller namespace + */ + protected static $namespace = ''; + + /** + * The Server Base Path for Router Execution + */ + protected static $serverBasePath = ''; + + /** + * Set the 404 handling function. + * + * @param object|callable $handler The function to be executed + */ + public static function set404($handler = null) + { + if (is_callable($handler)) { + static::$notFoundHandler = $handler; + } else { + static::$notFoundHandler = function () { + \Leaf\Exception\General::default404(); + }; + } + } + + /** + * Set a custom maintenance mode callback. + * + * @param callable|null $handler The function to be executed + */ + public static function setDown(?callable $handler = null) + { + static::$downHandler = $handler; + } + + /** + * Mounts a collection of callbacks onto a base route. + * + * @param string $path The route sub pattern/path to mount the callbacks on + * @param callable|array $handler The callback method + */ + public static function mount(string $path, $handler) + { + list($handler, $groupOptions) = static::mapHandler($handler); + + $initialNamespace = static::$namespace; + $initialGroupRoute = static::$groupRoute; + $initialGroupMiddleware = static::$routeGroupMiddleware; + + if ($groupOptions['namespace']) { + static::$namespace = $groupOptions['namespace']; + } + + static::$groupRoute = static::$groupRoute . ($path === '/' ? '' : (strpos($path, '/') !== 0 ? "/$path" : $path)); + + if ($groupOptions['middleware']) { + static::$routeGroupMiddleware = $groupOptions['middleware']; + } + + call_user_func($handler); + + static::$namespace = $initialNamespace; + static::$groupRoute = $initialGroupRoute; + static::$routeGroupMiddleware = $initialGroupMiddleware; + } + + /** + * Alias for mount + * + * @param string $path The route sub pattern/path to mount the callbacks on + * @param callable|array $handler The callback method + */ + public static function group(string $path, $handler) + { + static::mount($path, $handler); + } + + // ------------------- main routing stuff ----------------------- + + /** + * Store a route and it's handler + * + * @param string $methods Allowed HTTP methods (separated by `|`) + * @param string $pattern The route pattern/path to match + * @param string|array|callable $handler The handler for route when matched + */ + public static function match(string $allowedMethods, string $pattern, $handler) + { + $methods = explode('|', $allowedMethods); + + $pattern = static::$groupRoute . '/' . trim($pattern, '/'); + $pattern = static::$groupRoute ? rtrim($pattern, '/') : $pattern; + + list($handler, $routeOptions) = static::mapHandler($handler); + + if (is_string($handler)) { + $namespace = static::$namespace; + + if ($routeOptions['namespace']) { + static::$namespace = $routeOptions['namespace']; + } + + $handler = str_replace('\\\\', '\\', static::$namespace . "\\$handler"); + + static::$namespace = $namespace; + } + + foreach ($methods as $method) { + static::$routes[$method][] = [ + 'pattern' => $pattern, + 'handler' => $handler, + 'name' => $routeOptions['name'] ?? '' + ]; + + if ($routeOptions['middleware'] || !empty(static::$routeGroupMiddleware)) { + static::$middleware[$method][] = [ + 'pattern' => $pattern, + 'handler' => $routeOptions['middleware'] ?? static::$routeGroupMiddleware, + ]; + } + } + + static::$appRoutes[] = [ + 'methods' => $methods, + 'pattern' => $pattern, + 'handler' => $handler, + 'name' => $routeOptions['name'] ?? '' + ]; + + if ($routeOptions['name']) { + static::$namedRoutes[$routeOptions['name']] = $pattern; + } + } + + /** + * Add a route with all available HTTP methods + * + * @param string $pattern The route pattern/path to match + * @param string|array|callable The handler for route when matched + */ + public static function all(string $pattern, $handler) + { + static::match( + 'GET|POST|PUT|DELETE|OPTIONS|PATCH|HEAD', + $pattern, + $handler + ); + } + + /** + * Add a route with GET method + * + * @param string $pattern The route pattern/path to match + * @param string|array|callable The handler for route when matched + */ + public static function get(string $pattern, $handler) + { + static::match('GET', $pattern, $handler); + } + + /** + * Add a route with POST method + * + * @param string $pattern The route pattern/path to match + * @param string|array|callable The handler for route when matched + */ + public static function post(string $pattern, $handler) + { + static::match('POST', $pattern, $handler); + } + + /** + * Add a route with PUT method + * + * @param string $pattern The route pattern/path to match + * @param string|array|callable The handler for route when matched + */ + public static function put(string $pattern, $handler) + { + static::match('PUT', $pattern, $handler); + } + + /** + * Add a route with PATCH method + * + * @param string $pattern The route pattern/path to match + * @param string|array|callable The handler for route when matched + */ + public static function patch(string $pattern, $handler) + { + static::match('PATCH', $pattern, $handler); + } + + /** + * Add a route with OPTIONS method + * + * @param string $pattern The route pattern/path to match + * @param string|array|callable The handler for route when matched + */ + public static function options(string $pattern, $handler) + { + static::match('OPTIONS', $pattern, $handler); + } + + /** + * Add a route with DELETE method + * + * @param string $pattern The route pattern/path to match + * @param string|array|callable The handler for route when matched + */ + public static function delete(string $pattern, $handler) + { + static::match('DELETE', $pattern, $handler); + } + + /** + * Add a route with HEAD method + * + * @param string $pattern The route pattern/path to match + * @param string|array|callable The handler for route when matched + */ + public static function head(string $pattern, $handler) + { + static::match('HEAD', $pattern, $handler); + } + + /** + * Add a route that sends an HTTP redirect + * + * @param string $from The url to redirect from + * @param string $to The url to redirect to + * @param int $status The http status code for redirect + */ + public static function redirect( + string $from, + string $to, + int $status = 302 + ) { + static::get($from, function () use ($to, $status) { + return header("location: $to", true, $status); + }); + } + + /** + * Create a resource route for using controllers. + * + * This creates a routes that implement CRUD functionality in a controller + * `/posts` creates: + * - `/posts` - GET | HEAD - Controller@index + * - `/posts` - POST - Controller@store + * - `/posts/{id}` - GET | HEAD - Controller@show + * - `/posts/create` - GET | HEAD - Controller@create + * - `/posts/{id}/edit` - GET | HEAD - Controller@edit + * - `/posts/{id}/edit` - POST | PUT | PATCH - Controller@update + * - `/posts/{id}/delete` - POST | DELETE - Controller@destroy + * + * @param string $pattern The base route to use eg: /post + * @param string $controller to handle route eg: PostController + */ + public static function resource(string $pattern, string $controller) + { + static::match('GET|HEAD', $pattern, "$controller@index"); + static::post($pattern, "$controller@store"); + static::match('GET|HEAD', "$pattern/create", "$controller@create"); + static::match('POST|DELETE', "$pattern/{id}/delete", "$controller@destroy"); + static::match('POST|PUT|PATCH', "$pattern/{id}/edit", "$controller@update"); + static::match('GET|HEAD', "$pattern/{id}/edit", "$controller@edit"); + static::match('GET|HEAD', "$pattern/{id}", "$controller@show"); + } + + /** + * Create a resource route for using controllers without the create and edit actions. + * + * This creates a routes that implement CRUD functionality in a controller + * `/posts` creates: + * - `/posts` - GET | HEAD - Controller@index + * - `/posts` - POST - Controller@store + * - `/posts/{id}` - GET | HEAD - Controller@show + * - `/posts/{id}/edit` - POST | PUT | PATCH - Controller@update + * - `/posts/{id}/delete` - POST | DELETE - Controller@destroy + * + * @param string $pattern The base route to use eg: /post + * @param string $controller to handle route eg: PostController + */ + public static function apiResource(string $pattern, string $controller) + { + static::match('GET|HEAD', $pattern, "$controller@index"); + static::post($pattern, "$controller@store"); + static::match('POST|DELETE', "$pattern/{id}/delete", "$controller@destroy"); + static::match('POST|PUT|PATCH', "$pattern/{id}/edit", "$controller@update"); + static::match('GET|HEAD', "$pattern/{id}", "$controller@show"); + } + + /** + * Redirect to another route + * + * @param string|array $route The route to redirect to + * @param array|null $data Data to pass to the next route + */ + public static function push($route, ?array $data = null) + { + if (is_array($route)) { + if (!isset(static::$namedRoutes[$route[0]])) { + trigger_error('Route named ' . $route[0] . ' not found'); + } + + $route = static::$namedRoutes[$route[0]]; + } + + if ($data) { + $args = '?'; + + foreach ($data as $key => $value) { + $args .= "$key=$value&"; + } + + $data = rtrim($args, '&'); + } + + return header("location: $route$data"); + } + + /** + * Get route url by defined route name + * + * @param string $routeName + * @param array|string|null $params + * + * @return string + */ + public static function route(string $routeName, $params = null): string + { + if (!isset(static::$namedRoutes[$routeName])) { + trigger_error('Route named ' . $routeName . ' not found'); + } + + $routePath = static::$namedRoutes[$routeName]; + if ($params) { + if (is_array($params)) { + foreach ($params as $key => $value) { + if (!preg_match('/{(' . $key . ')}/', $routePath)) { + trigger_error('Param "' . $key . '" not found in route "' . static::$namedRoutes[$routeName] . '"'); + } + $routePath = str_replace('{' . $key . '}', $value, $routePath); + } + } + if (is_string($params)) { + $routePath = preg_replace('/{(.*?)}/', $params, $routePath); + } + } + + return $routePath; + } + + /** + * Force call the Leaf URL handler + * + * @param string $method The method to call + * @param string $url The uri to force + */ + public static function handleUrl(string $method, string $url) + { + if (isset(static::$routes[$method])) { + static::handle( + static::$routes[$method], + true, + $url + ); + } + } + + /** + * Get all routes registered in your leaf app + */ + public static function routes(): array + { + return static::$appRoutes; + } + + /** + * Set a global namespace for your handlers + * + * @param string $namespace The global namespace to set + */ + public static function setNamespace(string $namespace) + { + static::$namespace = $namespace; + } + + /** + * Get the global handler namespace. + * + * @return string The given namespace if exists + */ + public static function getNamespace(): string + { + return static::$namespace; + } + + /** + * Map handler and options + */ + protected static function mapHandler( + $handler, + $options = [ + 'name' => null, + 'middleware' => null, + 'namespace' => null, + ] + ): array { + $parsedHandler = $handler; + $parsedOptions = $options; + + if (is_array($handler)) { + if (is_string($handler['middleware'] ?? null)) { + $parsedOptions['middleware'] = static::$namedMiddleware[$handler['middleware']] ?? null; + } + + if (isset($handler['handler'])) { + $parsedHandler = $handler['handler']; + unset($handler['handler']); + } else { + foreach ($handler as $key => $value) { + if ( + (is_numeric($key) && is_callable($value)) + || is_numeric($key) && is_string($value) && strpos($value, '@') + ) { + $parsedHandler = $value; + unset($handler[$key]); + } else { + $parsedOptions[$key] ??= $value; + } + } + } + + // $parsedOptions = array_merge($handler, $parsedOptions); + } + + return [$parsedHandler, $parsedOptions]; + } + + /** + * Add a router hook + * + * Available hooks + * - router.before + * - router.before.route + * - router.before.dispatch + * - router.after.dispatch + * - router.after.route + * - router.after + * + * @param string $name The hook to set + * @param callable|null $handler The hook handler + */ + public static function hook(string $name, callable $handler) + { + if (!isset(static::$hooks[$name])) { + trigger_error("$name is not a valid hook! Refer to the docs for all supported hooks"); + } + + static::$hooks[$name] = $handler; + } + + /** + * Call a router hook + * + * @param string $name The hook to call + */ + private static function callHook(string $name) + { + return is_callable(static::$hooks[$name]) ? static::$hooks[$name]() : null; + } + + /** + * Add middleware + * + * This method prepends new middleware to the application middleware stack. + * The argument must be an instance that subclasses Leaf_Middleware. + * + * @param callable|string $middleware The middleware to set + */ + public static function use($middleware) + { + // if (in_array($middleware, static::$middleware)) { + // throw new \RuntimeException('Circular Middleware setup detected. Tried to queue the same Middleware twice.'); + // } + + if (is_string($middleware)) { + $middleware = static::$namedMiddleware[$middleware]; + } + + static::$middleware['GET'][] = [ + 'pattern' => '/.*', + 'handler' => $middleware, + ]; + } + + /** + * Register a middleware in your Leaf application by name + * + * @param string $name The name of the middleware + * @param callable $middleware The middleware to register + */ + public function registerMiddleware(string $name, callable $middleware) + { + static::$namedMiddleware[$name] = $middleware; + } + + /** + * Run middleware + */ + protected static function runMiddleware() + { + $currentMiddleware = array_shift(static::$middleware); + $currentMiddleware(); + + if (!empty(static::$middleware)) { + static::runMiddleware(); + } + } + + /** + * Return server base Path, and define it if isn't defined. + * + * @return string + */ + public static function getBasePath(): string + { + if (static::$serverBasePath === '') { + static::$serverBasePath = implode('/', array_slice(explode('/', $_SERVER['SCRIPT_NAME']), 0, -1)) . '/'; + } + + return static::$serverBasePath; + } + + /** + * Explicilty sets the server base path. To be used when your entry script path differs from your entry URLs. + * @see https://github.com/bramus/router/issues/82#issuecomment-466956078 + * + * @param string + */ + public static function setBasePath($serverBasePath) + { + static::$serverBasePath = $serverBasePath; + } + + /** + * Define the current relative URI. + * + * @return string + */ + public static function getCurrentUri(): string + { + // Get the current Request URI and remove rewrite base path from it (= allows one to run the router in a sub folder) + $uri = substr(rawurldecode($_SERVER['REQUEST_URI']), strlen(static::getBasePath())); + + if (strstr($uri, '?')) { + $uri = substr($uri, 0, strpos($uri, '?')); + } + + return '/' . trim($uri, '/'); + } + + /** + * Get route info of the current route + * + * @return array The route info array + */ + public static function getRoute(): array + { + $route = []; + $currentRoute = static::findRoute(); + + if (isset($currentRoute[0])) { + $route = array_merge($route, [ + 'pattern' => $currentRoute[0]['route']['pattern'], + 'path' => static::getCurrentUri(), + 'method' => \Leaf\Http\Request::getMethod(), + 'name' => $currentRoute[0]['route']['name'] ?? null, + 'handler' => $currentRoute[0]['route']['handler'], + 'params' => $currentRoute[0]['params'] ?? [], + ]); + } + + return array_merge($route); + } + + /** + * Find the current route + * + * @return array + */ + private static function findRoute( + ?array $routes = null, + ?string $uri = null, + $returnFirst = true + ): array { + $handledRoutes = []; + $uri = $uri ?? static::getCurrentUri(); + $routes = $routes ?? static::$routes[\Leaf\Http\Request::getMethod()]; + + foreach ($routes as $route) { + // Replace all curly braces matches {} into word patterns (like Laravel) + $route['pattern'] = preg_replace('/\/{(.*?)}/', '/(.*?)', $route['pattern']); + + // we have a match! + if (preg_match_all('#^' . $route['pattern'] . '$#', $uri, $matches, PREG_OFFSET_CAPTURE)) { + // Rework matches to only contain the matches, not the orig string + $matches = array_slice($matches, 1); + + // Extract the matched URL parameters (and only the parameters) + $params = array_map(function ($match, $index) use ($matches) { + // We have a following parameter: take the substring from the current param position until the next one's position (thank you PREG_OFFSET_CAPTURE) + if (isset($matches[$index + 1]) && isset($matches[$index + 1][0]) && $matches[$index + 1][0][1] != -1 && is_array($matches[$index + 1][0])) { + return trim(substr($match[0][0], 0, $matches[$index + 1][0][1] - $match[0][1]), '/'); + } + + // We have no following parameters: return the whole lot + return isset($match[0][0]) ? trim($match[0][0], '/') : null; + }, $matches, array_keys($matches)); + + $routeData = [ + 'params' => $params, + 'handler' => $route['handler'], + 'route' => $route, + ]; + + $handledRoutes[] = $routeData; + + if ($returnFirst) { + break; + } + } + } + + return $handledRoutes; + } + + /** + * Dispatch your application routes + */ + public static function run(?callable $callback = null) + { + $requestedMethod = \Leaf\Http\Request::getMethod(); + $appDown = _env('APP_DOWN', \Leaf\Anchor::toBool(\Leaf\Config::getStatic('app.down')) ?? false); + + if ($appDown === true) { + if (!static::$downHandler) { + static::$downHandler = function () { + \Leaf\Exception\General::defaultDown(); + }; + } + + return static::invoke(static::$downHandler); + } + + if (is_callable($callback)) { + static::hook('router.after', $callback); + } + + static::callHook('router.before'); + + if (isset(static::$middleware[$requestedMethod])) { + static::handle(static::$middleware[$requestedMethod]); + } + + static::callHook('router.before.route'); + + $numHandled = 0; + + if (isset(static::$routes[$requestedMethod])) { + $numHandled = static::handle( + null, + true + ); + } + + if ($numHandled === 0) { + if (!static::$notFoundHandler) { + static::$notFoundHandler = function () { + \Leaf\Exception\General::default404(); + }; + } + + static::invoke(static::$notFoundHandler); + } + + // if it originally was a HEAD request, clean up after ourselves by emptying the output buffer + if ($_SERVER['REQUEST_METHOD'] == 'HEAD') { + ob_end_clean(); + } + + static::callHook('router.after.route'); + + restore_error_handler(); + + return static::callHook('router.after') ?? ($numHandled !== 0); + } + + /** + * Handle a set of routes: if a match is found, execute the relating handling function. + * + * @param array $routes Collection of route patterns and their handling functions + * @param bool $quitAfterRun Does the handle function need to quit after one route was matched? + * @param string|null $uri The URI to call (automatically set if nothing is passed). + * + * @return int The number of routes handled + */ + private static function handle(?array $routes = null, bool $quitAfterRun = false, ?string $uri = null): int + { + $routeToHandle = static::findRoute($routes, $uri, $quitAfterRun); + + if (!empty($routeToHandle)) { + if (count($routeToHandle) > 1) { + foreach ($routeToHandle as $route) { + static::invoke($route['handler'], $route['params']); + } + } else { + static::invoke($routeToHandle[0]['handler'], $routeToHandle[0]['params']); + } + } + + return count($routeToHandle); + } + + private static function invoke($handler, $params = []) + { + if (is_callable($handler)) { + call_user_func_array( + $handler, + $params + ); + } else if (stripos($handler, '@') !== false) { + list($controller, $method) = explode('@', $handler); + + if (!class_exists($controller)) { + trigger_error("$controller not found. Cross-check the namespace if you're sure the file exists"); + } + + if (!method_exists($controller, $method)) { + trigger_error("$method method not found in $controller"); + } + + // First check if is a static method, directly trying to invoke it. + // If isn't a valid static method, we will try as a normal method invocation. + if (call_user_func_array([new $controller(), $method], $params) === false) { + // Try to call the method as a non-static method. (the if does nothing, only avoids the notice) + if (forward_static_call_array([$controller, $method], $params) === false) + ; + } + } + } +} From 632da9054ac22cab303620d5ffea7f88b08b384b Mon Sep 17 00:00:00 2001 From: mychidarko Date: Tue, 1 Oct 2024 04:09:39 +0000 Subject: [PATCH 5/9] chore: update middleware internals --- src/Config.php | 2 +- tests/middleware.test.php | 109 ++++++++------------------------------ 2 files changed, 24 insertions(+), 87 deletions(-) diff --git a/src/Config.php b/src/Config.php index 50df384..f1d8d9d 100644 --- a/src/Config.php +++ b/src/Config.php @@ -25,7 +25,7 @@ class Config 'log.level' => null, 'log.enabled' => false, 'log.dir' => __DIR__ . '/../../../../storage/logs/', - 'log.file' => 'log.txt', + 'log.file' => 'app.log', 'log.open' => true, 'mode' => 'development', 'scripts' => [], diff --git a/tests/middleware.test.php b/tests/middleware.test.php index 54e9169..8c66fbb 100644 --- a/tests/middleware.test.php +++ b/tests/middleware.test.php @@ -7,22 +7,16 @@ class StaticTestClassMid afterEach(function () { StaticTestClassMid::$called = false; + response()->next([]); }); test('leaf middleware', function () { - class AppMid extends \Leaf\Middleware - { - public function call() - { - StaticTestClassMid::$called = true; - $this->next(); - } - } - $_SERVER['REQUEST_METHOD'] = 'GET'; $_SERVER['REQUEST_URI'] = '/'; - app()->use(new AppMid()); + app()->use(function () { + StaticTestClassMid::$called = true; + }); app()->get('/', function () {}); app()->run(); @@ -30,21 +24,14 @@ public function call() }); test('leaf middleware with next data', function () { - - class AppMid2 extends \Leaf\Middleware - { - public function call() - { - $this->next([ - 'data' => 'Some data', - ]); - } - } - $_SERVER['REQUEST_METHOD'] = 'GET'; $_SERVER['REQUEST_URI'] = '/'; - app()->use(new AppMid2()); + app()->use(function () { + response()->next([ + 'data' => 'Some data', + ]); + }); app()->get('/', function () {}); @@ -59,14 +46,17 @@ public function call() $app = new Leaf\App(); - $m = function () use ($app) { - StaticTestClassMid::$called = true; + $m = function () { + response()->next([ + 'data' => 'in-route middleware', + ]); }; $app->get('/', ['middleware' => $m, function () {}]); + $app->run(); - expect(StaticTestClassMid::$called)->toBe(true); + expect(request()->next('data'))->toBe('in-route middleware'); }); test('in-route named middleware', function () { @@ -85,74 +75,21 @@ public function call() expect(StaticTestClassMid::$called)->toBe(true); }); -test('in-route middleware + group', function () { - $_SERVER['REQUEST_METHOD'] = 'GET'; - $_SERVER['REQUEST_URI'] = '/group-test'; - - $app = new Leaf\App(); - $app->config('useMiddlewares', false); - - $m = function () use ($app) { - $app->config('useMiddlewares', true); - }; - - $app->group('/group-test', function () use ($app, $m) { - $app->get('/', ['middleware' => $m, function () {}]); - }); - - $app->run(); - - expect($app->config('useMiddlewares'))->toBe(true); -}); - test('grouped in-route named middleware', function () { $_SERVER['REQUEST_METHOD'] = 'GET'; $_SERVER['REQUEST_URI'] = '/groups/test'; - $app = new Leaf\App(); - $app->config('useGroupNamedMiddleware', false); - $app->registerMiddleware('mid2', function () use ($app) { - $app->config('useGroupNamedMiddleware', true); - }); - - $app->group('/groups', function () use ($app) { - $app->get('/test', ['middleware' => 'mid2', function () {}]); + app()->registerMiddleware('mid34', function () { + app()->response()->next([ + 'data' => 'grouped in-route named middleware', + ]); }); - $app->run(); - - expect($app->config('useGroupNamedMiddleware'))->toBe(true); -}); - -test('before route middleware', function () { - $_SERVER['REQUEST_METHOD'] = 'GET'; - $_SERVER['REQUEST_URI'] = '/'; - - $app = new Leaf\App(); - - $app->config('inTest', 'true'); - $app->before('GET', '/', function () use ($app) { - $app->config('inTest', 'false'); + app()->group('/groups', function () { + app()->get('/test', ['middleware' => 'mid34', function () {}]); }); - $app->get('/', function () {}); - $app->run(); - - expect($app->config('inTest'))->toBe('false'); -}); -test('before router middleware', function () { - $_SERVER['REQUEST_METHOD'] = 'GET'; - $_SERVER['REQUEST_URI'] = '/test'; - - $app = new Leaf\App(); - - $app->config('inTest2', 'true'); - - $app->before('GET', '/.*', function () use ($app) { - $app->config('inTest2', 'false'); - }); - $app->get('/test', function () {}); - $app->run(); + app()->run(); - expect($app->config('inTest2'))->toBe('false'); + expect(app()->request()->next('data'))->toBe('grouped in-route named middleware'); }); From 0e254a53cbb70337e1c7b908126cda932a27fdcd Mon Sep 17 00:00:00 2001 From: mychidarko Date: Tue, 1 Oct 2024 04:10:00 +0000 Subject: [PATCH 6/9] chore: fix styling --- src/App.php | 2 +- src/Router.php | 12 ++++++------ 2 files changed, 7 insertions(+), 7 deletions(-) diff --git a/src/App.php b/src/App.php index 0947fe6..be02481 100755 --- a/src/App.php +++ b/src/App.php @@ -50,7 +50,7 @@ protected function loadConfig(array $userSettings = []) { if (!empty($userSettings)) { Config::set(array_merge($userSettings, [ - 'mode' => _env('APP_ENV', Config::getStatic('mode')) + 'mode' => _env('APP_ENV', Config::getStatic('mode')), ])); } diff --git a/src/Router.php b/src/Router.php index 596d5df..e027e70 100644 --- a/src/Router.php +++ b/src/Router.php @@ -182,7 +182,7 @@ public static function match(string $allowedMethods, string $pattern, $handler) static::$routes[$method][] = [ 'pattern' => $pattern, 'handler' => $handler, - 'name' => $routeOptions['name'] ?? '' + 'name' => $routeOptions['name'] ?? '', ]; if ($routeOptions['middleware'] || !empty(static::$routeGroupMiddleware)) { @@ -197,7 +197,7 @@ public static function match(string $allowedMethods, string $pattern, $handler) 'methods' => $methods, 'pattern' => $pattern, 'handler' => $handler, - 'name' => $routeOptions['name'] ?? '' + 'name' => $routeOptions['name'] ?? '', ]; if ($routeOptions['name']) { @@ -571,7 +571,7 @@ public static function use($middleware) /** * Register a middleware in your Leaf application by name - * + * * @param string $name The name of the middleware * @param callable $middleware The middleware to register */ @@ -661,7 +661,7 @@ public static function getRoute(): array /** * Find the current route - * + * * @return array */ private static function findRoute( @@ -804,7 +804,7 @@ private static function invoke($handler, $params = []) $handler, $params ); - } else if (stripos($handler, '@') !== false) { + } elseif (stripos($handler, '@') !== false) { list($controller, $method) = explode('@', $handler); if (!class_exists($controller)) { @@ -820,7 +820,7 @@ private static function invoke($handler, $params = []) if (call_user_func_array([new $controller(), $method], $params) === false) { // Try to call the method as a non-static method. (the if does nothing, only avoids the notice) if (forward_static_call_array([$controller, $method], $params) === false) - ; + ; } } } From 54d3b5dfc2564f673e2f09ac1014b341ad7a6838 Mon Sep 17 00:00:00 2001 From: mychidarko Date: Wed, 2 Oct 2024 09:24:19 +0000 Subject: [PATCH 7/9] feat: update middleware to run on all http methods --- src/Router.php | 14 +++++++++----- 1 file changed, 9 insertions(+), 5 deletions(-) diff --git a/src/Router.php b/src/Router.php index e027e70..4cb7025 100644 --- a/src/Router.php +++ b/src/Router.php @@ -563,10 +563,14 @@ public static function use($middleware) $middleware = static::$namedMiddleware[$middleware]; } - static::$middleware['GET'][] = [ - 'pattern' => '/.*', - 'handler' => $middleware, - ]; + $methods = ['GET', 'POST', 'PUT', 'PATCH', 'DELETE', 'OPTIONS', 'HEAD']; + + for ($i=0; $i < count($methods); $i++) { + static::$middleware[$methods[$i]][] = [ + 'pattern' => '/.*', + 'handler' => $middleware, + ]; + } } /** @@ -664,7 +668,7 @@ public static function getRoute(): array * * @return array */ - private static function findRoute( + public static function findRoute( ?array $routes = null, ?string $uri = null, $returnFirst = true From 94823fa54cc17c5875ea6ff110ada2bc85d28ea0 Mon Sep 17 00:00:00 2001 From: mychidarko Date: Wed, 2 Oct 2024 09:25:20 +0000 Subject: [PATCH 8/9] feat: add csrf method --- src/App.php | 21 +++++++++++++++++++++ 1 file changed, 21 insertions(+) diff --git a/src/App.php b/src/App.php index be02481..a291b40 100755 --- a/src/App.php +++ b/src/App.php @@ -187,6 +187,27 @@ public function cors($options = []) } } + /** + * Add CSRF protection to your app + * + * @param array $options Config for csrf + */ + public function csrf($options = []) + { + if (!\class_exists('Leaf\Anchor\CSRF')) { + \trigger_error('CSRF module not found! Run `leaf install csrf` or `composer require leafs/csrf` to install the CSRF module. This is required to configure CSRF.'); + } + + if (!Anchor\CSRF::token()) { + Anchor\CSRF::init(); + Anchor\CSRF::config($options); + } + + $this->use(function () { + Anchor\CSRF::validate(); + }); + } + /** * Create a route handled by websocket (requires Eien module) * From 2fcdd4f00e59b2e46e64082040eccbd7dac1c67c Mon Sep 17 00:00:00 2001 From: mychidarko Date: Wed, 2 Oct 2024 09:25:47 +0000 Subject: [PATCH 9/9] chore: fix styling --- src/App.php | 2 +- src/Router.php | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/src/App.php b/src/App.php index a291b40..7f1b7c8 100755 --- a/src/App.php +++ b/src/App.php @@ -189,7 +189,7 @@ public function cors($options = []) /** * Add CSRF protection to your app - * + * * @param array $options Config for csrf */ public function csrf($options = []) diff --git a/src/Router.php b/src/Router.php index 4cb7025..74c8f5d 100644 --- a/src/Router.php +++ b/src/Router.php @@ -565,7 +565,7 @@ public static function use($middleware) $methods = ['GET', 'POST', 'PUT', 'PATCH', 'DELETE', 'OPTIONS', 'HEAD']; - for ($i=0; $i < count($methods); $i++) { + for ($i = 0; $i < count($methods); $i++) { static::$middleware[$methods[$i]][] = [ 'pattern' => '/.*', 'handler' => $middleware,