diff --git a/controller/indexController.php b/controller/exampleController.php similarity index 92% rename from controller/indexController.php rename to controller/exampleController.php index 3e4566c..6dceb06 100644 --- a/controller/indexController.php +++ b/controller/exampleController.php @@ -16,7 +16,7 @@ use Wepesi\Core\Http\Redirect; use Wepesi\Core\Session; -class indexController extends Controller +class exampleController extends Controller { public function __construct() { diff --git a/index.php b/index.php index 581ee30..7413128 100644 --- a/index.php +++ b/index.php @@ -17,7 +17,7 @@ (new DotEnv($ROOT_DIR . '/.env'))->load(); /** - * Generate and index file for redirection (protection) while APP_DEV in production + * Generate and index a file for redirection (protection) while APP_DEV in production */ if (getenv('APP_ENV') === 'prod') { autoIndexFolder(['assets']); @@ -31,6 +31,4 @@ $app = new Application($ROOT_DIR, $configuration); -require_once $app::$ROOT_DIR . '/router/route.php'; - $app->run(); diff --git a/router/api.php b/router/api.php deleted file mode 100644 index 9be1e80..0000000 --- a/router/api.php +++ /dev/null @@ -1,6 +0,0 @@ -api('/', function () use ($router) { - $router->get('/home', function () { - \Wepesi\Core\Http\Response::send(['message' => 'Welcom to api routing']); - }); -}); diff --git a/routes/api.php b/routes/api.php new file mode 100644 index 0000000..1406985 --- /dev/null +++ b/routes/api.php @@ -0,0 +1,10 @@ +router(); +$router->get('/', function () { + Response::send('Welcome to Wepesi API'); +}); \ No newline at end of file diff --git a/router/route.php b/routes/web.php similarity index 52% rename from router/route.php rename to routes/web.php index 396b5c6..6197dc0 100644 --- a/router/route.php +++ b/routes/web.php @@ -1,10 +1,16 @@ router(); + // setup get started pages index $router->get('/', function () { (new View)->display('/home'); @@ -13,9 +19,12 @@ $router->get('/helloworld', function () { (new View)->renderHTML('
'); var_dump($ex); @@ -113,32 +91,53 @@ public static function dumper($ex) } /** - * Set the layout at the top of your application to be available everywhere. + * Define a layout to be used by all pages in the application. + * can be set at the top of your application to be available everywhere. * @param string $layout * @return void */ public static function setLayout(string $layout) { - self::$LAYOUT = self::$ROOT_DIR.'/views/'.$layout; + self::$layout = self::getRootDir() . '/views/' . trim($layout, '/'); } - public static function setLayoutContent(string $layout_name) + + /** + * @param string $layout_name + * @return void + */ + public static function setLayoutcontentparam(string $layout_name) { - self::$LAYOUT_CONTENT = $layout_name; + self::$layout_content_param = $layout_name; } + /** + * @param string $folder_name + * @return void + */ public static function setViewFolder(string $folder_name) { self::$VIEW_FOLDER = $folder_name; } - public static function getLayout() + + /** + * @return string + */ + public static function getLayout(): string { - return self::$LAYOUT ; + return trim(self::$layout ); } - public static function getLayoutContent() + + /** + * @return string + */ + public static function getLayoutContentParam(): string { - return self::$LAYOUT_CONTENT ; + return self::$layout_content_param ; } + /** + * @return string + */ public static function getViewFolder() { return self::$VIEW_FOLDER ; @@ -154,9 +153,69 @@ public function router(): Router /** * @return void + * @throws \Exception + */ + protected function routeProvider(): void + { + $base_route_path = self::getRootDir() . '/routes'; + $api_route_path = $base_route_path . '/api.php'; + if (file_exists($api_route_path)) { + $this->router->group([ + 'pattern' => '/api' + ], function (Router $router) { + if (isset($_SERVER['HTTP_ORIGIN'])) { + // Decide if the origin in $_SERVER['HTTP_ORIGIN'] is one + // you want to allow, and if so: + header('Access-Control-Allow-Origin: *'); + header('Access-Control-Allow-Credentials: true'); + header('Access-Control-Max-Age: 86400'); // cache for 1 day + } + header('Access-Control-Allow-Methods: GET, POST,PUT, PATCH, HEAD, OPTIONS'); + // Access-Control headers are received during OPTIONS requests + if ($_SERVER['REQUEST_METHOD'] == 'OPTIONS') { + // may also be using PUT, PATCH, HEAD etc. + if (isset($_SERVER['HTTP_ACCESS_CONTROL_REQUEST_HEADERS'])) + header("Access-Control-Allow-Headers: {$_SERVER['HTTP_ACCESS_CONTROL_REQUEST_HEADERS']}"); + + exit(0); + } + $router->group([], $this->registerRoute('/api.php')); + }); + } + $web_route_path = $base_route_path . '/web.php'; + if (file_exists($web_route_path)) { + $this->router->group([], $this->registerRoute('/web.php')); + } + if (!file_exists($web_route_path) && !file_exists($api_route_path)) { + throw new \Exception('No Route file not found.'); + } + } + + /** + * route path + * @param string $path + * @return string + */ + public function registerRoute(string $path): string + { + return $this->basePath('/routes' . '/' . trim($path,'/')); + } + /** + * @param string $path + * @return string + */ + public function basePath(string $path): string + { + return self::$root_dir . '/' . trim($path,'/'); + } + + /** + * @return void + * @throws \Exception */ - public function run() + public function run(): void { + $this->routeProvider(); $this->router->run(); } } \ No newline at end of file diff --git a/src/Core/Bundles.php b/src/Core/Bundles.php index ef920e9..f8f1673 100644 --- a/src/Core/Bundles.php +++ b/src/Core/Bundles.php @@ -10,12 +10,12 @@ class Bundles private static array $header_link = []; /** - * manage to add a javascript script on the page + * manage to add a JavaScript script on the page * @param string $file */ static function insertCSS(string $file) { - if (is_file(Application::$ROOT_DIR . '/assets/css/' . $file . '.css')) { + if (is_file(Application::getRootDir() . '/assets/css/' . $file . '.css')) { $href = WEB_ROOT . "assets/css/$file.css"; $link = <<diff --git a/src/Core/Routing/Providers/Contracts/RouteContract.php b/src/Core/Routing/Providers/Contracts/RouteContract.php new file mode 100644 index 0000000..6438ad8 --- /dev/null +++ b/src/Core/Routing/Providers/Contracts/RouteContract.php @@ -0,0 +1,14 @@ + */ -class Route +class Route implements RouteContract { /** * @var string @@ -42,10 +44,11 @@ class Route use routeBuilder; /** - * @param $path + * @param class-string $path * @param $callable + * @param array $middleware */ - function __construct($path, $callable, array $middleware = []) + function __construct(string $path, $callable, array $middleware = []) { $this->pattern = trim($path, '/'); $this->callable = $callable; @@ -56,10 +59,10 @@ function __construct($path, $callable, array $middleware = []) } /** - * @param $url + * @param class-string $url * @return bool */ - function match($url): bool + function match(string $url): bool { $url = trim($url, '/'); $path = preg_replace_callback('#:([\w]+)#', [$this, 'paramMatch'], $this->pattern); @@ -80,16 +83,16 @@ function match($url): bool /** * */ - public function call() + public function call(): void { try { if (count($this->middleware_tab) > 0) { foreach ($this->middleware_tab as $middleware) { - $this->routeFunctionCall($middleware, true, $this->_matches); + $this->executeMiddleware($middleware, $this->_matches); } $this->middleware_tab = []; } - $this->routeFunctionCall($this->callable, false, $this->_matches); + $this->executeController($this->callable, $this->_matches); } catch (\Exception $ex) { echo $ex->getMessage(); } @@ -126,7 +129,7 @@ public function getPattern(): string * @param $params * @return array|string|string[] */ - public function getUrl($params) + public function getUrl($params): array|string { $path = $this->pattern; foreach ($params as $k => $v) { diff --git a/src/Core/Routing/RouteFileRegistrar.php b/src/Core/Routing/RouteFileRegistrar.php new file mode 100644 index 0000000..ab97e15 --- /dev/null +++ b/src/Core/Routing/RouteFileRegistrar.php @@ -0,0 +1,46 @@ + + */ +final class RouteFileRegistrar implements RouterProviders +{ + /** + * @var RouterContract + */ + protected RouterContract $router; + + /** + * Create a new route file registrar instance. + * + * @param Router $router + */ + public function __construct(RouterContract $router) + { + $this->router = $router; + } + + /** + * Require the given routes file. + * + * @param class-string $routes + * @return void + */ + public function register(string $routes): void + { + $router = $this->router; + if (is_file($routes)) { + require $routes; + } + } +} \ No newline at end of file diff --git a/src/Core/Routing/Router.php b/src/Core/Routing/Router.php index 1b348e6..c90d51c 100644 --- a/src/Core/Routing/Router.php +++ b/src/Core/Routing/Router.php @@ -5,18 +5,23 @@ namespace Wepesi\Core\Routing; +use Closure; use Wepesi\Core\Application; +use Wepesi\Core\Escape; use Wepesi\Core\Exceptions\RoutingException; use Wepesi\Core\Http\Response; +use Wepesi\Core\Routing\Providers\Contracts\RouteContract; +use Wepesi\Core\Routing\Providers\Contracts\RouterContract; use Wepesi\Core\Routing\Traits\routeBuilder; /** - * Wepesi API Router provider + * @template T + * @template-implements RouterContract */ -class Router +class Router implements RouterContract { /** - * @var array|null + * @var array */ protected array $baseMiddleware; /** @@ -56,7 +61,7 @@ public function __construct() } /** - * @param $path + * @param string $path * @param $callable * @param null $name * @return Route @@ -73,9 +78,9 @@ public function get(string $path, $callable, $name = null): Route * @param string $methode * @return Route */ - private function add(string $pattern, $callable, ?string $name, string $methode): Route + private function add(string $pattern, $callable, ?string $name, string $methode): RouteContract { - $pattern = $this->baseRoute . '/' . trim($pattern, '/'); + $pattern = $this->baseRoute . Escape::addSlashes(trim($pattern, '/')); $pattern = $this->baseRoute ? rtrim($pattern, '/') : $pattern; $route = new Route($pattern, $callable, $this->baseMiddleware); $this->routes[$methode][] = $route; @@ -91,7 +96,7 @@ private function add(string $pattern, $callable, ?string $name, string $methode) } /** - * @param $path + * @param string $path * @param $callable * @param null $name * @return Route @@ -117,24 +122,6 @@ public function delete(string $path, $callable, $name = null): Route return $this->add($path, $callable, $name, 'DELETE'); } - /** - * API base group routing - * @param string|array $base_route it can be defined as we did for group routing, but you don't need to specify api, it will be added automatically - * @param callable $callable - * @return null - */ - public function api($base_route, callable $callable) - { - $api_pattern = '/api'; - if (is_array($base_route)) { - $base_route['pattern'] = $api_pattern . (isset($base_route['pattern']) ? $this->trimPath($base_route['pattern']) : ''); - } else { - - $base_route = $api_pattern . $this->trimPath($base_route); - } - return $this->group($base_route, $callable); - } - /** * @param string $path * @return string @@ -142,7 +129,7 @@ public function api($base_route, callable $callable) private function trimPath(string $path): string { $trim_path = trim($path, '/'); - return strlen($trim_path) > 0 ? '/' . $trim_path : ''; + return strlen($trim_path) > 0 ? Escape::addSlashes($trim_path) : ''; } /** @@ -151,7 +138,7 @@ private function trimPath(string $path): string * @param array|string $base_route can be a string or an array to defined middleware for the group routing * @param callable $callable a callable method can be a controller method or an anonymous callable method */ - public function group($base_route, callable $callable) + public function group(array|string $base_route, $callable): void { $pattern = $base_route; if (is_array($base_route)) { @@ -162,7 +149,12 @@ public function group($base_route, callable $callable) } $cur_base_route = $this->baseRoute; $this->baseRoute .= $pattern; - call_user_func($callable); + + if ($callable instanceof Closure) { + call_user_func($callable, $this); + } else { + (new RouteFileRegistrar($this))->register($callable); + } $this->baseRoute = $cur_base_route; } @@ -206,12 +198,12 @@ protected function getUrl() } /** - * Set the 404 handling function. + * Set the 404 handling functions. * - * @param object|callable|string $match_fn The function to be executed + * @param callable|object|string $match_fn The function to be executed * @param $callable */ - public function set404($match_fn, $callable = null) + public function set404(callable|object|string $match_fn, $callable = null): void { if (!$callable) { $this->notFoundCallback = $match_fn; @@ -221,7 +213,15 @@ public function set404($match_fn, $callable = null) } /** - * @return void + * list all routes elements + * @return array + */ + public function getRoutes(): array + { + return $this->routes; + } + /** + * Execute the route request */ public function run() { @@ -240,7 +240,6 @@ public function run() } if (count($routesRequestMethod) === $i) { $this->trigger404($this->notFoundCallback); - Response::setStatusCode(404); } } catch (RoutingException $ex) { Application::dumper($ex); @@ -250,9 +249,9 @@ public function run() } /** - * @return void + * trigger 404 error while no route match */ - protected function trigger404($match = null) + protected function trigger404($match = null): void { if ($match) { $this->routeFunctionCall($match); diff --git a/src/Core/Routing/Traits/routeBuilder.php b/src/Core/Routing/Traits/routeBuilder.php index cfd7315..f2db849 100644 --- a/src/Core/Routing/Traits/routeBuilder.php +++ b/src/Core/Routing/Traits/routeBuilder.php @@ -7,39 +7,45 @@ use Wepesi\Core\Application; +/** + * + */ trait routeBuilder { - protected function routeFunctionCall($callable, bool $is_middleware = false, array $matches = []): void + /** + * @param $callable + * @param array $matches + * @param bool $is_middleware + * @return void + */ + protected function routeFunctionCall($callable, array $matches = [], bool $is_middleware = false): void { - $controller = !$is_middleware ? 'controller' : 'middleware'; + $class_element = !$is_middleware ? 'controller' : 'middleware'; try { if (is_string($callable) || is_array($callable)) { - $params = is_string($callable) ? explode('#', $callable) : $callable; - if (count($params) != 2) { - throw new \InvalidArgumentException("Error : on `$controller` class/method is not well defined"); + $callable_params = is_string($callable) ? explode('#', $callable) : $callable; + if (count($callable_params) != 2) { + throw new \InvalidArgumentException("Error : on `$class_element` class/method is not well defined"); } - $classCallable = $params[0]; - $class_method = $params[1]; - if (!class_exists($classCallable, true)) { - throw new \InvalidArgumentException("$classCallable class not defined, not a valid $controller", 500); + $class_name = $callable_params[0]; + $class_method_name = $callable_params[1]; + if (!class_exists($class_name, true)) { + throw new \InvalidArgumentException("$class_name class not defined, not a valid $class_element", 500); } - $reflection = new \ReflectionClass($classCallable); + $reflection = new \ReflectionClass($class_name); $class_instance = $reflection->newInstance(); if (!$reflection->isInstance($class_instance)) { throw new \ReflectionException('Only instantiable class can be used. Not abstract,interface, or trait can be used.'); } - if (!method_exists($class_instance, $class_method)) { - throw new \BadMethodCallException("method : $class_method does not belong the class : $classCallable.", 500); - } - call_user_func_array([$class_instance, $class_method], $matches); - } else { - $closure = $callable; - if (isset($closure) && is_callable($closure, true)) { - call_user_func_array($closure, $matches); + if (!method_exists($class_instance, $class_method_name)) { + throw new \BadMethodCallException("method : $class_method_name does not belong the class : $class_name.", 500); } + call_user_func_array([$class_instance, $class_method_name], $matches); + } else if (isset($callable) && is_callable($callable, true)) { + call_user_func_array($callable, $matches); } return; } catch (\Exception $ex) { @@ -47,4 +53,14 @@ protected function routeFunctionCall($callable, bool $is_middleware = false, arr } } + protected function executeController($controller, array $matches = []): void + { + $this->routeFunctionCall($controller, $matches); + } + + protected function executeMiddleware($controller, array $matches = []): void + { + $this->routeFunctionCall($controller, $matches, true); + } + } \ No newline at end of file diff --git a/src/Core/Views/Provider/ViewBuilderProvider.php b/src/Core/Views/Provider/ViewBuilderProvider.php index 03923c8..620ed9e 100644 --- a/src/Core/Views/Provider/ViewBuilderProvider.php +++ b/src/Core/Views/Provider/ViewBuilderProvider.php @@ -87,7 +87,7 @@ public function renderHTML(string $html): void */ public function setLayout(string $template): void { - $this->layout = Application::$ROOT_DIR . '/views' . $template; + $this->layout = Application::getRootDir() . '/views' . $template; } /** diff --git a/src/Core/Views/View.php b/src/Core/Views/View.php index 92a7763..4819273 100644 --- a/src/Core/Views/View.php +++ b/src/Core/Views/View.php @@ -77,15 +77,22 @@ public function display(string $view): void { $view_file = $this->buildFilePath($view); $render = $this->renderView($view_file); - if ($this->layout === '' && !$this->reset) { + if (! $this->getLayout() && !$this->reset) { $this->layout = Application::getLayout(); } - if ($this->layout !== '') { + if ($this->getLayout()) { $render = $this->renderLayout($render); } $this->buildAssetHead($render); } + /** + * @return string|null + */ + public function getLayout(): ?string + { + return strlen(trim($this->layout)) > 0 ? $this->layout : null; + } /** * @param class-string $file_name * @@ -96,7 +103,7 @@ private function buildFilePath(string $file_name): ?string $folder = strlen(trim($this->folder_name)) > 0 ? $this->folder_name : Application::getViewFolder();; $view_file = Escape::checkFileExtension($file_name); $file_source = $folder . Escape::addSlashes($view_file); - return Application::$ROOT_DIR . '/views' . $file_source; + return Application::getRootDir() . '/views' . $file_source; } /** @@ -137,7 +144,7 @@ private function renderNotDefined(string $file_name): void protected function renderLayout(string $view) { if ($this->layout_content === '') { - $this->layout_content = Application::getLayoutContent(); + $this->layout_content = Application::getLayoutContentParam(); } if ($this->layout && is_file($this->layout)) { $layout_data = $this->data; @@ -221,5 +228,4 @@ private function generateJSLink(string $path): string private function generateStyleLink(string $path): string { return ''; } - }