Форум программистов, компьютерный форум, киберфорум
PHP: ООП
Войти
Регистрация
Восстановить пароль
Блоги Сообщество Поиск Заказать работу  
 
Рейтинг 4.82/11: Рейтинг темы: голосов - 11, средняя оценка - 4.82
0 / 0 / 0
Регистрация: 21.05.2022
Сообщений: 15

Реализация динамического роутинга

04.06.2022, 23:08. Показов 2351. Ответов 4
Метки нет (Все метки)

Студворк — интернет-сервис помощи студентам
Привет, пытаюсь написать фреймворк mvc, нужно реализовать динамический роутинг когда обрабатываются паттерны "/users/:userId/file/:fileId", в результате url "/users/4/file/7" должен быть обработан а в екшн контроллера который привязан к роуту эти значения должны прийти в качестве параметров function index(int $userId, int $fileId))
Помогите реализовать это или кто может знает какие-то статьи в которых есть пример данной реализации

index.php
PHP
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
<?php 
 
include 'vendor/autoload.php';
 
use App\Routing\Router;
use App\Routing\Route;
use App\Application;
use App\Controllers\HomeController;
use App\Controllers\RegisterController;
 
$router = new Router();
$router->addRoute(new Route('/login', HomeController::class, 'login'));
$router->addRoute(new Route('/register', RegisterController::class, 'register'));
$app = new Application($router);
$app->run();
app.php
PHP
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
<?php 
namespace App;
 
use App\Routing\Router;
 
class Application {
 
    private string $url;
 
    public function __construct(
        private Router $router,
    )
    {
        $this->url = $_SERVER['REQUEST_URI'];
    }
    public function run() {
        foreach ($this->router->getRoute() as $route) {
            if ($route->pattern === $this->url) {
                $controller = new $route->controller();
                $result = $controller->{$route->action}();
                print_r($result);
            }
        }
 
    }
}
route.php
PHP
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
<?php
 
namespace App\Routing;
 
class Route {
 
    public function __construct(
        public string $pattern,
        public string $controller,
        public string $action,
    )
    {
        
    }
}
router.php
PHP
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
<?php 
namespace App\Routing;
 
class Router {
 
    private array $routes = [];
 
    public function addRoute(Route $route): void{
        $this->routes[] = $route;
    }
 
    public function getRoute(): array {
        return $this->routes;
    }
}
0
cpp_developer
Эксперт
20123 / 5690 / 1417
Регистрация: 09.04.2010
Сообщений: 22,546
Блог
04.06.2022, 23:08
Ответы с готовыми решениями:

Правило роутинга на сайте
Здравствуйте, извиняюсь если такая тема уже была. Есть код, который возвращает массив с правилами для роутинга. /* Языки*/ ...

Реализация роутинга через патернматчинг
Привет народ, нужно сделать роутинг, наподобие как в rabbitmq, т.е. мне даётся строка-маска (например &quot;system.log.*&quot;) и 3 строки,...

Реализация динамического SELECTа
Доброе время суток! Изучая тему динамического SELECT, нашел в инете следующую реализацию: Исходники: index.html &lt;html&gt; ...

4
 Аватар для sad67man
2598 / 1502 / 689
Регистрация: 23.08.2015
Сообщений: 3,804
06.06.2022, 02:18
neosoznan5622, Это все делается регулярками. Можно найти и готовое решение. Но в первую очередь мы пишем свой MVC фреймворк для развития ООП мышления и основных принципов.

Давайте для начала пройдемся по текущему коду и сделаем пошагово рефакторинг. И первое к чему мы обратимся это к первому принципу GRASP - информационному эксперту, который гласит "информация должна обрабатываться там, где она содержится". Сейчас вы достаете роуты из роутера и проделываете некие манипуляции снаружи, логика может усложняться, давайте вынесем ее в сам роутер, чтоб получилось примерно так

PHP
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
class Application {
 
    public function __construct(
        private Router $router,
    )
    {
 
    }
    public function run() 
    {
        $route = $this->router->match($_SERVER['REQUEST_URI']);
        $controller = new ($route->controller);
        $result = $controller->{$route->action}();
        print_r($result);
    }
}
Сразу оговорюсь, что когда вы делаете публичные свойства - то это означает, что вы даете возможность из изменять снаружи, что может привести ко многим побочным эффектам. Поэтому лучше их сделать приватными и добавить getter-ы

PHP
1
2
3
$route = $this->router->match($_SERVER['REQUEST_URI']);
$controller = new ($route->getController());
$result = $controller->{$route->getAction()}();
Теперь на мне нужно давать доступ в самом роутере к роутам) И вся логика будет находиться внутри.

PHP
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
class Router {
 
    /**
     * @var Route[]
     */
    private array $routes = [];
 
    public function add(Route $route): void
    {
        $this->routes[] = $route;
    }
 
    public function match($uri): Route
    {
        foreach ($this->routes as $route) {
            if ($route->pattern === $uri) {
                return $route;
            }
        }
 
        throw new \Exception('Роут не найден');
    }
}
Теперь тоже самое проделаем с классом Route. Тут мы тоже можем вынести некую логику внутрь.

PHP
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
class Router {
 
    /**
     * @var Route[]
     */
    private array $routes = [];
 
    public function add(Route $route): void
    {
        $this->routes[] = $route;
    }
 
    public function match($uri): Route
    {
        foreach ($this->routes as $route) {
            if ($route->match($uri)) {
                return $route;
            }
        }
 
        throw new \Exception('Роут не найден');
    }
}
Теперь вернемся к вашему вопросу. Дело в том, что получение самого роута нам не достаточно, нам необходимы еще некие параметры, чтоб их передать в контроллер.

PHP
1
2
3
4
[$route, $attributes] = $this->router->match($_SERVER['REQUEST_URI']);
$controller = new ($route->getController());
$result = $controller->{$route->getAction()}(...$attributes);
print_r($result);
Чтоб не возвращать таким образом. сделаем отдельный объект. Как говорилось, что в программировании самое сложное - это придумывать названия. Нам нужен некий готовый объект для получения контроллера, экшена и аттрибутов. А то каким уже образом вы все это будете использовать - уже другое дело. Ну давайте пока назовем этот объект RouteMatchResult - не стоит сильно пока над этим закапываться - может потом мы и вовсе откажемся от этого объекта.

PHP
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
class RouteMatchResult
{
    private Route $route;
    private array $attributes;
 
    public function __construct(Route $route, array $attributes)
    {
        $this->route = $route;
        $this->attributes = $attributes;
    }
 
    public function getController()
    {
        return $this->route->getController();
    }
 
    public function getAction()
    {
        return $this->route->getAction();
    }
 
    public function getAttributes()
    {
        return $this->attributes;
    }
}
PHP
1
2
3
$result = $this->router->match($_SERVER['REQUEST_URI']);
$controller = new ($result->getController());
$result = $controller->{$result->getAction()}(...$result->getAttributes());
Создание этого объекта можно перенести в сам роут. Сделаем так, что Route::match будет возвращать этот объект либо null, если этот роут не подходдит.

PHP
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
class Router {
 
    /**
     * @var Route[]
     */
    private array $routes = [];
 
    public function add(Route $route): void
    {
        $this->routes[] = $route;
    }
 
    public function match($uri): RouteMatchResult
    {
        foreach ($this->routes as $route) {
            if ($result = $route->match($uri)) {
                return $result;
            }
        }
 
        throw new \Exception('Роут не найден');
    }
}
Осталось реализовать сам метод match роута. Я накидал некий пример - еще не проверял, главное, чтоб понятна была сама идея - а там сами допилете)

PHP
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
class Route {
 
    public function __construct(
        private string $pattern,
        private string $controller,
        private string $action,
    )
    {
 
    }
 
    public function match(string $uri): ?RouteMatchResult
    {
        $pattern  = preg_replace('~(:[^/]*)~', '([^\/]*)', $this->pattern);
 
        if (!preg_match('~' . $pattern . '~i', $uri, $matches)) {
            return null;
        }
 
        preg_match_all('~:[a-zA-Z]*~i', $this->pattern, $attributeNames);
        array_shift($matches);
        $attributes = array_combine(reset($attributeNames), $matches);
 
        return new RouteMatchResult($this, $attributes);
    }
 
    public function getAction(): string
    {
        return $this->action;
    }
 
    public function getController(): string
    {
        return $this->controller;
    }
}
Добавлено через 17 минут
neosoznan5622, Далее может быть вы захотите создавать роуты, чтоб они были доступны только определенными методами POST, или GET. Поэтому возможно нам будет недостаточно передавть $_SERVER['REQUEST_URI']. Если подумать, то задача самого приложения - это принять некий запрос Request, и отдать некий ответ Response

PHP
1
2
3
4
5
$app = new Application(...);
$response = $app->run(new Request($_GET, $_POST, $_SERVER, $_FILES, ...));
 
$response->sendHeaders();
echo $response->getBody();
Можно сделать фабричный метод для создания объекта Request

PHP
1
Request::createFromGlobals();
Теперь вместо передачи $_SERVER['REQUEST_URI']; Мы можем передавать Request

PHP
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
class Application {
 
    public function __construct(
        private Router $router,
    )
    {
 
    }
    public function run(Request $request): Response
    {
        $result = $this->router->match($request);
        $controller = new ($result->getController());
        return $controller->{$result->getAction()}(...$result->getAttributes());
    }
}
PHP
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
class Route {
 
    private $attributes = [];
 
    public function __construct(
        private string $pattern,
        private string $controller,
        private string $action,
    )
    {
 
    }
 
    public function match(Request $request): ?RouteMatchResult
    {
        $pattern  = preg_replace('~(:[^/]*)~', '([^\/]*)', $this->pattern);
 
        if (!preg_match('~' . $pattern . '~i', $request->getUri(), $matches)) {
            return null;
        }
 
        preg_match_all('~:[a-zA-Z]*~i', $this->pattern, $attributeNames);
        array_shift($matches);
        $attributes = array_combine(reset($attributeNames), $matches);
 
        return new RouteMatchResult($this, $attributes);
    }
 
    public function getAction(): string
    {
        return $this->action;
    }
 
    public function getController(): string
    {
        return $this->controller;
    }
}
Так же, чтоб внутри контроллера не обращаться к суперглобальным переменным $_GET и $_POST, мы можем передавать сам объект Request, и вместе с ним передавать наши аттрибуты

PHP
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
class Request
{
    private array $attributes;
 
    public function __construct(
        private array $get,
        private array $post,
        private array $server,
        ...
    )
    {
 
    }
 
    public static function createFromGlobals(): self
    {
        return new self($_GET, $_POST, $_SERVER, ...);
    }
    
    public function getUri()
    {
        return $this->server['REQUEST_URI'];
    }
 
    public function withAttributes($attributes): self
    {
        $clone = clone $this;
        $clone->attributes = $attributes;
        return $clone;
    }
 
    public function getAttribute($name, $default = null): mixed
    {
        return $this->attributes[$name] ?? $default;
    }
}
PHP
1
2
3
$result = $this->router->match($request);
$controller = new ($result->getController());
return $controller->{$result->getAction()}($request->withAttributes($result->getAttributes()));
И уже внутри контроллера можно дергать Request

PHP
1
2
3
4
5
6
7
8
9
10
11
12
13
class UserController
{
    public function file(Request $request): Response
    {
        $userId = $request->getAttribute('userId');
        $fileId = $request->getAttribute('fileId');
        
        return new JsonResponse([
            'userId' => $userId,
            'fileId' => $fileId
        ]);
    }
}
Т.е. еще раз хочу обратить внимание, что аттрибуты можно использовать по разному - это уже не относится к роутеру. И наша задача спроектировать объекты таким образом, чтоб они не зависели от других компонентов нашей системы.

neosoznan5622, Можно пойти чуть дальше и обратить внимание, что мы можем передать название контроллера и экшена в конструктор класса RouteMatchResult, таким образом нам уже не нужны методы Route::getController и Route::getAction

PHP
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
class RouteMatchResult
{
    private string $controller;
    private string $action;
    private array $attributes;
 
    public function __construct(string $controller, string $action, array $attributes)
    {
        $this->attributes = $attributes;
        $this->controller = $controller;
        $this->action = $action;
    }
 
    public function getController()
    {
        return $this->controller;
    }
 
    public function getAction()
    {
        return $this->action;
    }
 
    public function getAttributes()
    {
        return $this->attributes;
    }
}
PHP
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
class Route {
 
    public function __construct(
        private string $pattern,
        private string $controller,
        private string $action,
    )
    {
 
    }
 
    public function match(Request $request): ?RouteMatchResult
    {
        $pattern  = preg_replace('~(:[^/]*)~', '([^\/]*)', $this->pattern);
 
        if (!preg_match('~' . $pattern . '~i', $request->getUri(), $matches)) {
            return null;
        }
 
        preg_match_all('~:[a-zA-Z]*~i', $this->pattern, $attributeNames);
        array_shift($matches);
        $attributes = array_combine(reset($attributeNames), $matches);
 
        return new RouteMatchResult($this->controller, $this->action, $attributes);
    }
}
3
 Аватар для sad67man
2598 / 1502 / 689
Регистрация: 23.08.2015
Сообщений: 3,804
07.06.2022, 00:08
Чуть доделал.

PHP
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
class Route {
 
    public function __construct(
        private string $pattern,
        private string $controller,
        private string $action,
    )
    {
 
    }
 
    public function match(Request $request): ?RouteMatchResult
    {
        if (!preg_match($this->getPattern(), $request->getUri(), $matches)) {
            return null;
        }
 
        $attributes = array_intersect_key($matches, array_flip($this->getAttributeNames()));
 
        return new RouteMatchResult($this->controller, $this->action, $attributes);
    }
 
    private function getPattern(): string
    {
        return  '#^' . str_replace(['{', '}'], ['(?P<', '>[^/]+)'], $this->pattern) . '$#i';
    }
 
    private function getAttributeNames(): array
    {
        preg_match_all('~{(.*)}~iU', $this->pattern, $attributeNames);
        return $attributeNames[1] ?? [];
    }
}
PHP
1
2
$router = new Router();
$router->add(new Route('/users/{userId}/file/{fileId}', \App\Controllers\UserController::class, 'file'));
0
Эксперт PHP
5755 / 4134 / 1508
Регистрация: 06.01.2011
Сообщений: 11,276
07.06.2022, 11:05
sad67man, мне кажется, можно избавиться от getAttributeNames, а
PHP
1
$attributes = array_intersect_key($matches, array_flip($this->getAttributeNames()));
заменить на
PHP
1
$attributes = array_filter($matches, 'is_string', ARRAY_FILTER_USE_KEY);
так как в $matches будут только числовые индексы и строковые ключи, соответствующие именованным подмаскам.

А лучше вообще убрать именованные подгруппы, раз мы атрибуты распаковываем при вызове метода контроллера:
PHP
1
...$result->getAttributes()
1
 Аватар для sad67man
2598 / 1502 / 689
Регистрация: 23.08.2015
Сообщений: 3,804
07.06.2022, 11:28
Цитата Сообщение от Para bellum Посмотреть сообщение
заменить на
Да так лучше.

Цитата Сообщение от Para bellum Посмотреть сообщение
А лучше вообще убрать именованные подгруппы, раз мы атрибуты распаковываем при вызове метода контроллера:
Хотелось именно так сделать) Вопрос был про роутер, я не опирался на другие компоненты системы.
0
Надоела реклама? Зарегистрируйтесь и она исчезнет полностью.
raxper
Эксперт
30234 / 6612 / 1498
Регистрация: 28.12.2010
Сообщений: 21,154
Блог
07.06.2022, 11:28
Помогаю со студенческими работами здесь

Реализация динамического массива
Здравствуйте =) подскажиет как релизовать на c# одноменый динамический массив элемент которого представлен в виде 3 полей т.е. нужно что то...

Реализация динамического типа
Уважаемые форумчане. Подскажите пожалуйста и если возможно приведите пример. Есть две переменные заранее какая будет передана в...

Реализация контейнера - динамического массива
Возникла проблема с программой. Сначала ввожу 2 интовых числа. Затем создаю динамический массив, т.е. контейнер в контейнере размером в...

Реализация метода динамического программирования
Нужна помощь с реализацией метода динамического программирования в матлаб

Реализация динамического интерфейса пользователя
Поставлена такая проблема - задача для медучреждения: Предположим, имеется некий набор видов обследования больного... Каждый вид...


Искать еще темы с ответами

Или воспользуйтесь поиском по форуму:
5
Ответ Создать тему
Новые блоги и статьи
PhpStorm 2025.3: WSL Terminal всегда стартует в ~
and_y87 14.12.2025
PhpStorm 2025. 3: WSL Terminal всегда стартует в ~ (home), игнорируя директорию проекта Симптом: После обновления до PhpStorm 2025. 3 встроенный терминал WSL открывается в домашней директории. . .
Access
VikBal 11.12.2025
Помогите пожалуйста !! Как объединить 2 одинаковые БД Access с разными данными.
Новый ноутбук
volvo 07.12.2025
Всем привет. По скидке в "черную пятницу" взял себе новый ноутбук Lenovo ThinkBook 16 G7 на Амазоне: Ryzen 5 7533HS 64 Gb DDR5 1Tb NVMe 16" Full HD Display Win11 Pro
Музыка, написанная Искусственным Интеллектом
volvo 04.12.2025
Всем привет. Некоторое время назад меня заинтересовало, что уже умеет ИИ в плане написания музыки для песен, и, собственно, исполнения этих самых песен. Стихов у нас много, уже вышли 4 книги, еще 3. . .
От async/await к виртуальным потокам в Python
IndentationError 23.11.2025
Армин Ронахер поставил под сомнение async/ await. Создатель Flask заявляет: цветные функции - провал, виртуальные потоки - решение. Не threading-динозавры, а новое поколение лёгких потоков. Откат?. . .
Поиск "дружественных имён" СОМ портов
Argus19 22.11.2025
Поиск "дружественных имён" СОМ портов На странице: https:/ / norseev. ru/ 2018/ 01/ 04/ comportlist_windows/ нашёл схожую тему. Там приведён код на С++, который показывает только имена СОМ портов, типа,. . .
Сколько Государство потратило денег на меня, обеспечивая инсулином.
Programma_Boinc 20.11.2025
Сколько Государство потратило денег на меня, обеспечивая инсулином. Вот решила сделать интересный приблизительный подсчет, сколько государство потратило на меня денег на покупку инсулинов. . . .
Ломающие изменения в C#.NStar Alpha
Etyuhibosecyu 20.11.2025
Уже можно не только тестировать, но и пользоваться C#. NStar - писать оконные приложения, содержащие надписи, кнопки, текстовые поля и даже изображения, например, моя игра "Три в ряд" написана на этом. . .
Мысли в слух
kumehtar 18.11.2025
Кстати, совсем недавно имел разговор на тему медитаций с людьми. И обнаружил, что они вообще не понимают что такое медитация и зачем она нужна. Самые базовые вещи. Для них это - когда просто люди. . .
Создание Single Page Application на фреймах
krapotkin 16.11.2025
Статья исключительно для начинающих. Подходы оригинальностью не блещут. В век Веб все очень привыкли к дизайну Single-Page-Application . Быстренько разберем подход "на фреймах". Мы делаем одну. . .
КиберФорум - форум программистов, компьютерный форум, программирование
Powered by vBulletin
Copyright ©2000 - 2025, CyberForum.ru