diff --git a/.phpunit.result.cache b/.phpunit.result.cache new file mode 100644 index 0000000..37c9cba --- /dev/null +++ b/.phpunit.result.cache @@ -0,0 +1 @@ +{"version":1,"defects":{"Rudra\\Router\\Tests\\RouterAnnotationTraitTest::testAnnotation":8,"Rudra\\Router\\Tests\\RouterAnnotationTraitTest::testAnnotationCollector":8,"Rudra\\Router\\Tests\\RouterAnnotationTraitTest::testAnnotationCollectorMultilevel":8},"times":{"Rudra\\Router\\Tests\\RouterAnnotationTraitTest::testAnnotation":0.003,"Rudra\\Router\\Tests\\RouterAnnotationTraitTest::testAnnotationCollector":0.001,"Rudra\\Router\\Tests\\RouterAnnotationTraitTest::testAnnotationCollectorMultilevel":0}} \ No newline at end of file diff --git a/phpunit.xml b/phpunit.xml index a16a2bb..9e72d72 100755 --- a/phpunit.xml +++ b/phpunit.xml @@ -21,4 +21,8 @@ ./src + + + + diff --git a/src/MiddlewareInterface.php b/src/MiddlewareInterface.php index ac0bbb8..2ac95b2 100755 --- a/src/MiddlewareInterface.php +++ b/src/MiddlewareInterface.php @@ -3,8 +3,8 @@ declare(strict_types=1); /** - * @author : Jagepard - * @license https://mit-license.org/ MIT + * @author : Jagepard + * @license https://mit-license.org/ MIT */ namespace Rudra\Router; diff --git a/src/Router.php b/src/Router.php index b956bef..70e223b 100755 --- a/src/Router.php +++ b/src/Router.php @@ -3,8 +3,8 @@ declare(strict_types=1); /** - * @author : Jagepard - * @license https://mit-license.org/ MIT + * @author : Jagepard + * @license https://mit-license.org/ MIT */ namespace Rudra\Router; @@ -21,121 +21,69 @@ class Router implements RouterInterface use RouterRequestMethodTrait; /** - * @param array $route - * @throws RouterException - * @throws ReflectionException + * @param array $route + * @return void */ public function set(array $route): void { - if (str_contains($route['method'], '|')) { - $httpMethods = explode('|', $route['method']); + $httpMethods = str_contains($route['method'], '|') + ? explode('|', $route['method']) + : [$route['method']]; - foreach ($httpMethods as $httpMethod) { - $route['method'] = $httpMethod; - $this->handleRequestUri($route); - } + foreach ($httpMethods as $httpMethod) { + $route['method'] = $httpMethod; + $this->handleRequestUri($route); } - - $this->handleRequestUri($route); } /** - * @param array $route - * @param null $params - * @throws RouterException|ReflectionException + * @param array $route + * @return void */ - public function directCall(array $route, $params = null): void + protected function handleRequestUri(array $route): void { - $controller = $this->rudra->get($route['controller']); - $action = $route['action']; + $this->handleRequestMethod(); - if (!method_exists($controller, $action)) { - throw new RouterException("503"); - } + $request = $this->rudra->request(); + $server = $request->server(); - $controller->shipInit(); - $controller->containerInit(); - $controller->init(); - $controller->before(); - !isset($route['middleware']["before"]) ?: $this->handleMiddleware($route['middleware']["before"]); - $this->callAction($params, $action, $controller); - !isset($route['middleware']["after"]) ?: $this->handleMiddleware($route['middleware']["after"]); - $controller->after(); - - if ($this->rudra->config()->get("environment") !== "test") { - exit(); + // Проверяем соответствие HTTP-метода + if ($route['method'] !== $server->get('REQUEST_METHOD')) { + return; } - } - protected function handleRequestMethod(): void - { - $requestMethod = $this->rudra->request()->server()->get("REQUEST_METHOD"); + $uriRaw = $server->get('REQUEST_URI'); + $parsed = parse_url($uriRaw); + $requestPath = $parsed && isset($parsed['path']) ? ltrim($parsed['path'], '/') : ''; + $uriSegments = explode('/', $requestPath); - if ($requestMethod === "POST" && $this->rudra->request()->post()->has("_method")) { - $this->rudra->request()->server()->set(["REQUEST_METHOD" => $this->rudra->request()->post()->get("_method")]); - } + [$uri, $params] = $this->handlePattern($route, $uriSegments); - if (in_array($requestMethod, ["PUT", "PATCH", "DELETE"])) { - parse_str(file_get_contents("php://input"), $data); - $this->rudra->request()->{strtolower($requestMethod)}()->set($data); + if ($uri === $uriSegments) { + $this->setCallable($route, $params); } } - /** - * @param array $route - * @throws RouterException - * @throws ReflectionException - */ - protected function handleRequestUri(array $route): void + protected function handleRequestMethod(): void { - $this->handleRequestMethod(); + $request = $this->rudra->request(); + $requestMethod = $request->server()->get('REQUEST_METHOD'); - if ($route['method'] == $this->rudra->request()->server()->get("REQUEST_METHOD")) { - $requestString = parse_url(ltrim($this->rudra->request()->server()->get("REQUEST_URI"), '/'))["path"] ?? ""; - [$uri, $params] = $this->handlePattern($route, explode('/', $requestString)); + // Spoofing метода через _method + if ($requestMethod === 'POST' && $request->post()->has('_method')) { + $spoofedMethod = strtoupper($request->post()->get('_method')); - if (implode('/', $uri) === $requestString) { - $this->setCallable($route, $params); + if (in_array($spoofedMethod, ['PUT', 'PATCH', 'DELETE'])) { + $requestMethod = $spoofedMethod; + $request->server()->set(['REQUEST_METHOD' => $spoofedMethod]); } } - } - /** - * @param array $route - * @param $params - * @throws RouterException|ReflectionException - */ - protected function setCallable(array $route, $params): void - { - if ($route['controller'] instanceof \Closure) { - (is_array($params)) ? $route['controller'](...$params) : $route['controller']($params); - exit(); - } - - $this->directCall($route, $params); - } - - /** - * @param $params - * @param $action - * @param $controller - * @throws RouterException - */ - protected function callAction($params, $action, $controller): void - { - if (isset($params) && in_array("", $params)) { - throw new RouterException("404"); - } - - try { - // Вызываем метод контроллера с параметрами или без них - $controller->{$action}(...(empty($params) ? [] : $params)); - } catch (\ArgumentCountError $e) { - $trace = $e->getTrace()[0]; - $this->rudra()->autowire($this->rudra()->get($trace['class']), $trace['function']); - } catch (\TypeError $e) { - $trace = $e->getTrace()[0]; - $this->rudra()->autowire($this->rudra()->new($trace['class']), $trace['function'], $trace['args']); + // Обработка PUT/PATCH/DELETE + if (in_array($requestMethod, ['PUT', 'PATCH', 'DELETE'])) { + $rawInput = file_get_contents('php://input'); + parse_str($rawInput, $data); + $request->{strtolower($requestMethod)}()->set($data); } } @@ -180,6 +128,91 @@ protected function handlePattern(array $route, array $request): array return [$uri, $params]; } + /** + * @param array $route + * @param $params + * @throws RouterException|ReflectionException + */ + protected function setCallable(array $route, $params): void + { + if ($route['controller'] instanceof \Closure) { + if (is_array($params)) { + $route['controller'](...$params); + } else { + $route['controller']($params); + } + + exit(); + } + + $this->directCall($route, $params); + } + + /** + * @param array $route + * @param $params + * @return void + */ + public function directCall(array $route, $params = null): void + { + $controller = $this->rudra->get($route['controller']); + $action = $route['action']; + + if (!method_exists($controller, $action)) { + throw new RouterException("503"); + } + + // Bootstrap controller + $controller->shipInit(); + $controller->containerInit(); + $controller->init(); + + $controller->before(); + + if (isset($route['middleware']['before'])) { + $this->handleMiddleware($route['middleware']['before']); + } + + $this->callAction($params, $action, $controller); + + if (isset($route['middleware']['after'])) { + $this->handleMiddleware($route['middleware']['after']); + } + + $controller->after(); + + if ($this->rudra->config()->get('environment') !== 'test') { + exit(); + } + } + + /** + * @param $params + * @param $action + * @param $controller + * @return void + */ + protected function callAction($params, $action, $controller): void + { + if (isset($params) && in_array('', $params)) { //Проверка на пустой элемент + throw new RouterException("404"); + } + + try { + if (empty($params)) { + $controller->$action(); //Без параметров + } else { + $controller->$action(...$params); //С параметрами + } + } catch (\ArgumentCountError $e) { + $trace = $e->getTrace()[0]; + $this->rudra()->autowire($this->rudra()->get($trace['class']), $trace['function']); + } catch (\TypeError $e) { + $trace = $e->getTrace()[0]; + $this->rudra()->autowire($this->rudra()->new($trace['class']), $trace['function'], $trace['args']); + } + } + /** * @param array $chainOfMiddlewares */ @@ -188,14 +221,27 @@ public function handleMiddleware(array $chainOfMiddlewares): void if (!$chainOfMiddlewares) { return; } - + $current = array_shift($chainOfMiddlewares); - if ((is_array($current)) && count($current) === 2) { - (new $current[0]())($current[1], $chainOfMiddlewares); + if (is_array($current) && count($current) === 2 && is_string($current[0])) { + $middleware = new $current[0](); + $middleware($current[1], $chainOfMiddlewares); + return; + } + + if (is_array($current) && is_string($current[0])) { + $middleware = new $current[0](); + $middleware($chainOfMiddlewares); + return; + } + + if (is_string($current)) { + $middleware = new $current(); + $middleware($chainOfMiddlewares); return; } - (is_array($current)) ? (new $current[0]())($chainOfMiddlewares) : (new $current())($chainOfMiddlewares); + throw new \InvalidArgumentException('Invalid middleware format'); } } diff --git a/src/RouterFacade.php b/src/RouterFacade.php index dea7c09..103f7ef 100755 --- a/src/RouterFacade.php +++ b/src/RouterFacade.php @@ -3,8 +3,8 @@ declare(strict_types=1); /** - * @author : Jagepard - * @license https://mit-license.org/ MIT + * @author : Jagepard + * @license https://mit-license.org/ MIT */ namespace Rudra\Router; diff --git a/src/RouterInterface.php b/src/RouterInterface.php index 8aa6c49..0432b5c 100755 --- a/src/RouterInterface.php +++ b/src/RouterInterface.php @@ -3,8 +3,8 @@ declare(strict_types=1); /** - * @author : Jagepard - * @license https://mit-license.org/ MIT + * @author : Jagepard + * @license https://mit-license.org/ MIT */ namespace Rudra\Router; diff --git a/src/Routing.php b/src/Routing.php index 1d4983f..0f19815 100755 --- a/src/Routing.php +++ b/src/Routing.php @@ -1,5 +1,10 @@ + * @license https://mit-license.org/ MIT + */ + namespace Rudra\Router; #[\Attribute] diff --git a/src/Traits/RouterAnnotationTrait.php b/src/Traits/RouterAnnotationTrait.php index 01a9072..6582487 100755 --- a/src/Traits/RouterAnnotationTrait.php +++ b/src/Traits/RouterAnnotationTrait.php @@ -3,8 +3,8 @@ declare(strict_types=1); /** - * @author : Jagepard - * @license https://mit-license.org/ MIT + * @author : Jagepard + * @license https://mit-license.org/ MIT */ namespace Rudra\Router\Traits; @@ -20,53 +20,53 @@ trait RouterAnnotationTrait * @param boolean $attributes * @return void */ - public function annotationCollector(array $controllers, bool $getter = false, bool $attributes = false) + public function annotationCollector(array $controllers, bool $getter = false, bool $attributes = false): ?array { - $annotations = []; + $annotations = []; + $annotationService = $this->rudra->get(Annotation::class); foreach ($controllers as $controller) { - if (class_exists($controller)) { - $actions = get_class_methods($controller); - } else { + if (!class_exists($controller)) { throw new \Exception("Удалите контроллер $controller из файла routes.php"); } - foreach ($actions as $action) { - $annotation = ($attributes) - ? $this->rudra->get(Annotation::class)->getAttributes($controller, $action) - : $this->rudra->get(Annotation::class)->getAnnotations($controller, $action); + $reflection = new \ReflectionClass($controller); + $methods = $reflection->getMethods(\ReflectionMethod::IS_PUBLIC); + + foreach ($methods as $method) { + $action = $method->getName(); + $annotation = $attributes + ? $annotationService->getAttributes($controller, $action) + : $annotationService->getAnnotations($controller, $action); $middleware = []; if (isset($annotation["Middleware"])) { - $middleware = array_merge($middleware, ['before' => $this->handleAnnotationMiddleware($annotation["Middleware"])]); + $middleware['before'] = $this->handleAnnotationMiddleware($annotation["Middleware"]); } if (isset($annotation["AfterMiddleware"])) { - $middleware = array_merge($middleware, ['after' => $this->handleAnnotationMiddleware($annotation["AfterMiddleware"])]); + $middleware['after'] = $this->handleAnnotationMiddleware($annotation["AfterMiddleware"]); } if (isset($annotation["Routing"])) { foreach ($annotation["Routing"] as $route) { - - $route['controller'] = $controller; - $route['action'] = $action; - $route['middleware'] = $middleware; - $route['method'] = $route['method'] ?? "GET"; - - if ($getter) { - $annotations[] = [$route]; - } else { - $this->set($route); - } + $route += [ + 'controller' => $controller, + 'action' => $action, + 'middleware' => $middleware, + 'method' => 'GET', + ]; + + $getter + ? $annotations[] = [$route] + : $this->set($route); } } } } - if ($getter) { - return $annotations; - } + return $getter ? $annotations : null; } /** @@ -76,18 +76,15 @@ public function annotationCollector(array $controllers, bool $getter = false, bo protected function handleAnnotationMiddleware(array $annotation): array { $middleware = []; - $count = count($annotation); - - for ($i = 0; $i < $count; $i++) { - $middleware[$i][] = $annotation[$i]["name"]; - if (isset($annotation[$i]["params"])) { - $middleware[$i][] = $annotation[$i]["params"]; + foreach ($annotation as $item) { + $entry = [$item['name']]; + if (isset($item['params'])) { + $entry[] = $item['params']; } + $middleware[] = $entry; } return $middleware; } - - abstract public function rudra(): RudraInterface; } diff --git a/src/Traits/RouterRequestMethodTrait.php b/src/Traits/RouterRequestMethodTrait.php index 7cd6dec..f4323ec 100755 --- a/src/Traits/RouterRequestMethodTrait.php +++ b/src/Traits/RouterRequestMethodTrait.php @@ -3,120 +3,67 @@ declare(strict_types=1); /** - * @author : Jagepard - * @license https://mit-license.org/ MIT + * @author : Jagepard + * @license https://mit-license.org/ MIT */ namespace Rudra\Router\Traits; -use Rudra\Container\Interfaces\RudraInterface; - trait RouterRequestMethodTrait { /** - * @param array $route - * @return void + * @param array $route */ - public function get(array $route): void - { - $route['method'] = "GET"; - $this->set($route); - } + abstract public function set(array $route): void; - /** - * @param array $route - * @return void - */ - public function post(array $route): void - { - $route['method'] = "POST"; - $this->set($route); - } + public function get(array $route): void { $route['method'] = 'GET'; $this->set($route); } + public function post(array $route): void { $route['method'] = 'POST'; $this->set($route); } + public function put(array $route): void { $route['method'] = 'PUT'; $this->set($route); } + public function patch(array $route): void { $route['method'] = 'PATCH'; $this->set($route); } + public function delete(array $route): void { $route['method'] = 'DELETE'; $this->set($route); } - /** - * @param array $route - * @return void - */ - public function put(array $route): void - { - $route['method'] = "PUT"; - $this->set($route); - } - - /** - * @param array $route - * @return void - */ - public function patch(array $route): void - { - $route['method'] = "PATCH"; + public function any(array $route): void { + $route['method'] = 'GET|POST|PUT|PATCH|DELETE'; $this->set($route); } /** - * @param array $route - * @return void - */ - public function delete(array $route): void - { - $route['method'] = "DELETE"; - $this->set($route); - } - - /** - * @param array $route - * @return void - */ - public function any(array $route): void - { - $route['method'] = "GET|POST|PUT|PATCH|DELETE"; - $this->set($route); - } - - /** - * @param array $route - * @param array $actions - * @return void + * @param array $route + * @param array $actions */ - public function resource(array $route, array $actions = ["read", "create", "update", "delete"]): void + public function resource(array $route, array $actions = ['read', 'create', 'update', 'delete']): void { - switch ($this->rudra->request()->server()->get("REQUEST_METHOD")) { - case "GET": - $route['method'] = "GET"; - $route['action'] = $actions[0]; - - $this->set($route); + $request = $this->rudra->request(); + $server = $request->server(); + $post = $request->post(); + + $requestMethod = $server->get('REQUEST_METHOD'); + $httpMethod = $requestMethod === 'POST' && $post->has('_method') + ? strtoupper($post->get('_method')) + : $requestMethod; + + switch ($httpMethod) { + case 'GET': + $route['method'] = 'GET'; + $route['action'] = $actions[0]; // read break; - case "POST": - $actionKey = ["GET" => 0, "POST" => 1, "PUT" => 2, "PATCH" => 2, "DELETE" => 3]; - $httpMethod = ($this->rudra->request()->post()->has("_method")) ? $this->rudra->request()->post()->get("_method") : "POST"; - $route['method'] = $httpMethod; - $route['action'] = $actions[$actionKey[$httpMethod]]; - - $this->set($route); + case 'POST': + $route['method'] = 'POST'; + $route['action'] = $actions[1]; // create break; - case "PUT": - $route['method'] = "PUT"; - $route['action'] = $actions[2]; - - $this->set($route); + case 'PUT': + case 'PATCH': + $route['method'] = $httpMethod; + $route['action'] = $actions[2]; // update break; - case "DELETE": - $route['method'] = "DELETE"; - $route['action'] = $actions[3]; - - $this->set($route); + case 'DELETE': + $route['method'] = 'DELETE'; + $route['action'] = $actions[3]; // delete break; + default: + return; // Неизвестный метод — игнорируем } - } - - /** - * @param array $route - */ - abstract public function set(array $route): void; - /** - * @return RudraInterface - */ - abstract public function rudra(): RudraInterface; + $this->set($route); + } } diff --git a/tests/RouterAnnotationTraitTest.php b/tests/RouterAnnotationTraitTest.php index 5a95ce9..659e39c 100755 --- a/tests/RouterAnnotationTraitTest.php +++ b/tests/RouterAnnotationTraitTest.php @@ -1,8 +1,8 @@ - * @license https://mit-license.org/ MIT + * @author : Jagepard + * @license https://mit-license.org/ MIT */ namespace Rudra\Router\Tests; @@ -22,36 +22,36 @@ class RouterAnnotationTraitTest extends PHPUnit_Framework_TestCase // Rudra::set([Annotation::class, Annotation::class]); // Router::setNamespace("Rudra\\Router\\Tests\\Stub\\"); // } -// + // public function testAnnotation() // { // $_SERVER["REQUEST_URI"] = "test/123"; // $_SERVER["REQUEST_METHOD"] = "GET"; -// + // $this->setContainer(); // Router::annotation("MainController", "actionIndex"); // $this->assertEquals("actionIndex", Rudra::config()->get("actionIndex")); // } -// + // public function testAnnotationCollector() // { // $_SERVER["REQUEST_URI"] = "test/123"; // $_SERVER["REQUEST_METHOD"] = "GET"; -// + // $this->setContainer(); // Router::annotationCollector([["MainController", "actionIndex"]]); -// + // $this->assertEquals("actionIndex", Rudra::config()->get("actionIndex")); // } -// + // public function testAnnotationCollectorMultilevel() // { // $_SERVER["REQUEST_URI"] = "test/123"; // $_SERVER["REQUEST_METHOD"] = "GET"; -// + // $this->setContainer(); // Router::annotationCollector(["blog" => ["MainController", "actionIndex"]]); -// + // $this->assertEquals("actionIndex", Rudra::config()->get("actionIndex")); // } } diff --git a/tests/stub/Controllers/MainController.php b/tests/stub/Controllers/MainController.php deleted file mode 100755 index a2bbb0c..0000000 --- a/tests/stub/Controllers/MainController.php +++ /dev/null @@ -1,69 +0,0 @@ -set(["actionIndex" => "actionIndex"]); - } - - public function actionGet() - { - Rudra::config()->set(["actionGet" => "GET"]); - } - - public function actionPost() - { - Rudra::config()->set(["actionPost" => "POST"]); - } - - public function actionPut() - { - Rudra::config()->set(["actionPut" => "PUT"]); - } - - public function actionPatch() - { - Rudra::config()->set(["actionPatch" => "PATCH"]); - } - - public function actionDelete() - { - Rudra::config()->set(["actionDelete" => "DELETE"]); - } - - public function actionAny() - { - Rudra::config()->set(["actionAny" => "ANY"]); - } - - public function read($params = null) - { - Rudra::config()->set(["read" => "read"]); - } - - public function create() - { - Rudra::config()->set(["create" => "create"]); - } - - public function update($params) - { - Rudra::config()->set(["update" => "update"]); - } - - public function delete($params) - { - Rudra::config()->set(["delete" => "delete"]); - } - - public function shipInit() {} - public function containerInit() {} - public function init() {} - public function before() {} - public function after() {} -} diff --git a/tests/stub/Middleware/Middleware.php b/tests/stub/Middleware/Middleware.php deleted file mode 100755 index e95e5fe..0000000 --- a/tests/stub/Middleware/Middleware.php +++ /dev/null @@ -1,13 +0,0 @@ -set(["middleware" => Middleware::class]); - } -} diff --git a/tests/stub/route.php b/tests/stub/route.php deleted file mode 100755 index fe79957..0000000 --- a/tests/stub/route.php +++ /dev/null @@ -1,17 +0,0 @@ -setNamespace($namespace); - - // Routes - - return false; - } -}