We have moved our forum to GitHub Discussions. For questions about Phalcon v3/v4/v5 you can visit here and for Phalcon v6 here.

Дополнение для Url для реализации обратных ссылок

Всем привет. Появилось немного времени, сделал первую реализацию обещанных обратных ссылок для phalcon. Для тех, кто не в курсе: стандартная реализации Route в phalcon 1.* не позволяет использовать опциональные параметры и не правильно генерирует обратные ссылки для роутов. Например:

$router
  ->add('/admin(/:controller(/:action(/{id:[0-9]+}(/:params)?)?)?)?', array(
    'module'     => 'backend',
    'controller' => 2,
    'action'     => 4,
    'id'         => 6,
    'params'     => 8,
  ))
  ->setName('backend');

...

echo $this->url->get(array('for'=>'backend', 'controller'=>'users'));

В результате данного кода получим /adminusers? вместо ожидаемого /admin/users. Для многих - это нормально, они не привыкли к опциональным параметрам. Для других (таких, как я) - это плохо.

Теперь немного затравки, чтобы понимать, что дает данный класс для того-же роута:

echo $this->url->route('backend'); // /admin
echo $this->url->route('backend', array('controller' => 'users')); // /admin/users
echo $this->url->route('backend', array('controller' => 'users', 'action' => 'add')); // /admin/users/add
echo $this->url->route('backend', array('controller' => 'users', 'action' => 'edit', 'id' => 1)); // /admin/users/edit/1
echo $this->url->route('backend', array(
    'controller' => 'users',
    'action' => 'info',
    'id' => 1,
    'params' => array(
        'page' => 1,
        'limit' => 10,
    ),
)); // /admin/users/info/1/page/1/limit/10

Удобно, правда?) Реализация: https://gist.github.com/aktuba/7702229 . Есть вопросы - задавайте.



8.1k

Sorry, but I do not understand the magic of indexation

'controller' => 2,
'action'     => 4,
'params'     => 6,

What does this index?



10.2k

@Oleg, ты же вроде по-русски говоришь?) Индексы немного поправил, до этого был старый роут, скопипастил его сюда, изменил, а индексы поменять забыл. Сейчас вроде правильные.



8.1k

Конечно, говорю. Мне такая реализация не нравится. Это, конечно же, не значит, что она не имеет права на жизнь. Если это коиу-то подходит - пусть использует. Я за абсолютную однозначность в определениях. У вас же индексация идет в разрез с правилами индексации в Phalcon.



10.2k

Где индексация идет в разрез?) Встроенные классы phalcon вообще не трогаются, индексация стандартная ;)



8.1k

Почему 2 4 6 8, но не 1 2 3 4? Ну это ладно... У вас в корневом примере роутер описан, как такой, что может содержать или не содержать controller, action etc. В этом я вижу неоднозначность. При обратном преобразовании невозможно точно определить роут, так как возможны несколько исходов. Я избегаю такую практику в своей деятельности. В даном случае вижу "силовое назначение" при обратном преобразовании, и, насколько я увидел, опять происходит возврат к нормальной структуре - module/controller/action/params. Так что целесообразности не вижу. Но я за свободу в программировании (пока она не мешает другим, конечно :) ). Так что, если кому-то это принесет пользу - будет хорошо



10.2k

Почему 2 4 6 8, но не 1 2 3 4?

Потому-что в регулярке они под такими параметрами идут. Скобки видишь?

При обратном преобразовании невозможно точно определить роут, так как возможны несколько исходов.

Т.е.? Почему нельзя определить роут? о_О

В даном случае вижу "силовое назначение" при обратном преобразовании, и, насколько я увидел, опять происходит возврат к нормальной структуре - module/controller/action/params.

Ты о чем? Какой "возврат" в формировании урлов? Почему "module/controller/action/params" - "нормальная структура", а "module/controller/action/id" - нет? Можешь чуть подробней рассказать, а то не совсем понятно что ты имеешь в виду.



10.2k

По поводу целесообразности - создано для удобства (не только, но в основном). Например, если сделать такой роут

$router
  ->add('/admin/:controller/:action', array(
    'module'     => 'backend',
    'controller' => 1,
    'action'     => 2,
  ))
  ->setName('backend');

то в урлах всегда будет /admin/users/index, где /index явно лишний. А в некоторых случаях (имею в виду seo, будь оно не ладно) еще и вредным. Если сделать action в роуте опциональным - все будет отлично. Причем, такой роут (с опциональным action) даже работать будет правильно, и ссылки будут формироваться правильно. Но стоит добавить 3-4-5-... опциональных параметров - phalcon впадает в ступор. Потому и появился данный класс.

Вот еще пример:

$this->url->get(array('for'=>'backend/default'));
$this->url->get(array('for'=>'backend/users'));
$this->url->get(array('for'=>'backend/users/new'));
$this->url->get(array('for'=>'backend/users/edit', 'id'=>$id));

сравни с

$this->url->get(array('for'=>'backend'));
$this->url->get(array('for'=>'backend', 'controller'=>'users'));
$this->url->get(array('for'=>'backend', 'controller'=>'users', 'action'=>'new'));
$this->url->get(array('for'=>'backend', 'controller'=>'users', 'action'=>'edit', 'id'=>$id));

Лично мне второе намного проще воспринимать и поддерживать: я сразу вижу модуль, контроллер, действие и параметры. А в первом случае ничего, кроме названия роута, не видно. Ни в какой модуль пойдет запрос, ни в какой контроллер, ни в какой action...



8.1k

Давайте возьмем ваш образец роута и протестируем во фреймворке :

<?php

$url = new Phalcon\Mvc\Url();

$url->setBaseUri('/');

$router = new Phalcon\Mvc\Router();

$router
  ->add('/admin(/:controller(/:action(/{id:[0-9]+}(/:params)?)?)?)?', [
    'module'     => 'backend',
    'controller' => 2,
    'action'     => 4,
    'params'     => 6,
  ])
  ->setName('backend');

$testRoutes = [
'/admin',
'/admin/users',
'/admin/users/edit/123',
'/admin/config/edit',
'/admin/pages/new',
'/admin',
'/admin/users',
'/admin/users/1',
'/admin/users/1/edit',
'/admin/pages',
'/admin/pages/contacts',
'/admin/pages/contacts/edit',
'/admin/users/info/1/page/1/limit/10',
];

foreach ($testRoutes as $testRoute) {

    $router->handle($testRoute);
    echo 'Test route : ', $testRoute, '<br>', PHP_EOL;

    if ($router->wasMatched()) {
        echo 'Controller : ', $router->getControllerName(), '<br>', PHP_EOL;
        echo 'Action : ', $router->getActionName(), '<br>', PHP_EOL;
    } else {
        echo "The route was't matched...<br>", PHP_EOL;
    }
    echo '<br>', PHP_EOL;
} 

Имеем результат :

Test route : /admin
Controller : 
Action : 

Test route : /admin/users
Controller : users
Action : 

Test route : /admin/users/edit/123
Controller : admin
Action : users

Test route : /admin/config/edit
Controller : config
Action : edit

Test route : /admin/pages/new
Controller : pages
Action : new

Test route : /admin
Controller : pages
Action : new

Test route : /admin/users
Controller : users
Action : new

Test route : /admin/users/1
Controller : users
Action : new

Test route : /admin/users/1/edit
Controller : admin
Action : users

Test route : /admin/pages
Controller : pages
Action : users

Test route : /admin/pages/contacts
Controller : pages
Action : contacts

Test route : /admin/pages/contacts/edit
Controller : admin
Action : pages

Test route : /admin/users/info/1/page/1/limit/10
Controller : admin
Action : users

Возьмем первый же роут - вопрос - куда идем после /admin? Вот это я и не понимаю. Я взял ваши выкладки для тестирования. Поэтому и говорю, что использовать такое не буду. К тому же у меня уже давненько таких проблем с роутерами и не возникает. Понравилось использовать роуты в аннотациях. И, соответственно, с обратными ссылками неурядиц не наблюдается.



10.2k

Вон вы о чем))). Уважаемый, вы будете удивлены, но... Это стандартная работа роутера phalcon. Повторюсь: это работа phalcon, а не моего класса. Не верите?) Удалите мой класс и сравните результаты). Ну а если проставите дефолтные значения - все встанет на свои места ;).

Мой класс дополняет (даже не изменяет, а только дополняет!!!) класс \Phalcon\Mvc\Url, который не имеет вообще никакого отношения к обработке входящих урлов ;). Видимо, сам роут ввел вас в некоторое заблуждение, давайте объясню. Возьмем роут попроще, например:

$router
  ->add('/:controller/:action', array(
    'controller' => 1,
    'action'     => 2,
  ))
  ->setName('default');

Проще можно, но смысла нет). Теперь представим, что есть сайт с этим роутом и 30-40 вьюх. Во вьюхах есть какие-то ссылки. Судя по тому, что вы писали выше, скорее всего они выглядят так:

<a href="/main/index">Ссылка 1</a>
<a href="/post/title">Пост</a>
<a href="/comment/view">Комментарий</a>
...

или так:

<a href="<?= $this->url->get('main/index') ?>">Ссылка 1</a>
<a href="<?= $this->url->get('post/title') ?>">Пост</a>
<a href="<?= $this->url->get('comment/view') ?>">Комментарий</a>
...

Есть еще вариант, когда на каждую ссылку (ну или почти) создается свой роут, но его в расчет не берем, т.к. сути это не меняет. Т.е., фактически, урлы прописаны руками. Что будете делать, когда понадобится, например, добавить к каждой ссылке окончание .html? А если в начале поставить action, а после contoller? Помимо изменения роута придется и пролопатить все 30-40 вьюх (а возможно еще и кучу других файлов), чтобы ручками поменять все ссылки. Верно?

А вот мой вариант. Меняем чуть-чуть роут:

$router
  ->add('(/:controller(/:action)?)?', array(
    'controller' => 2,
    'action'     => 4,
  ))
  ->setName('default');

И ссылки ставим вот так:

<a href="<?= $this->url->route('default', array('controller'=>'main')) ?>">Ссылка 1</a>
<a href="<?= $this->url->route('default', array('controller'=>'post', 'action'=>'title')) ?>">Пост</a>
<a href="<?= $this->url->route('default', array('controller'=>'comment', 'action'=>'view')) ?>">Комментарий</a>

Что надо сделать, чтобы поменять все эти ссылки на сайте? Всего-лишь поменять роут. Все. Надеюсь я понятно объяснил, но если есть вопросы - задавайте.

P.S.: аннотации воспринимаю очень плохо, поэтому вряд-ли буду дорабатывать класс для них (



8.1k

Да, я увидел вашу идею, но не соглашусь в ее пользе. Такое мое мнение. Есть масса способов произвести рефакторинг ссылок, и не только, в проекте. По крайней мере у меня никогда не возникало такого затруднения. Пока есть linux, vim & sed :) Ну и, к тому же, я придерживаюсь строгости (однозначности) в формировании ссылок. Динамике это не мешает. Happy weekend :)



10.2k

Ну соглашаться я никого не заставляю, как и использовать). Но вообще, довольно странно, когда рефакторинг предпочитают изначально правильному проектированию o_O. И вопрос: можете подробнее рассказать про "строгость (однозначность) в формировании ссылок"? Я считаю, что у меня все строго и однозначно, но похоже в чем-то ошибаюсь. Хотелось-бы понять в чем...



8.1k

К счастью, мне еще не приходилось заниматься переделыванием ссылок. Тем более переименование модулей и контроллеров. И index контроллер, впрочем, как и index action, у меня, как правило, отсутствуют. Как-то все решается на начальной стадии проектирования. Так что потом только добавление при необходимости. Может, везет



10.2k

Ок, это я понял. У каждого свой путь, это нормально. Но я не увидел ответ на "строгость (однозначность) в формировании ссылок". Что под этим подразумевалось? Отсутствие дефолтных контроллеров/действий? Тогда это странный критерий. И если отсутствие дефолтного контроллера я еще как-то могу понять (хоть и с большим трудом), то отсутствие дефолтного action не воспринимаю. Причина: неоднозначность ссылок ;). Получается, что ссылка выглядит (например) как /posts, но дефолтного действия нет, а значит в роутере жестко прописывается action, который может быть чем угодно... В случае наличия дефолтных параметров, они подставляются если отсутствуют в ссылке, т.е. видя /posts понимаешь сразу, что это контроллер Posts и действие index... Как-то так.



8.1k

Я даже не знаю, как вам точно объяснить этот мой принцип. Однозначное трактование... Наверное, это когда каждому модулю/контроллеру/действию соответствует один, и только один маршрут. Маршрут может передавать любые параметры, но эти параметры должны быть строго типизированы. Наверное, как-то так. Дефолтные контроллеры и действия, конечно же есть. Просто я их называю всегда более осмысленно. В URI их всегда можно скрыть.



10.2k

Ясно. Мне данный путь не подходит, я привык автоматизировать свою работу.

Хорошее и правильное решение проблемы и реализация поставленной задачи. Сможешь разместить код в Инкубаторе ( https://github.com/phalcon/incubator ) ? В пространстве \Phalcon\Mvc\Url , как вариант расширения стандартного роутера ;)



10.2k

Рано, нашел пару багов, все времени нет поправить. Как протестирую в боевом окружении - потом постараюсь добавить в инкубатор.



10.2k

Поправил класс, багов пока не нашел: https://github.com/aktuba/phalcon-url В Инкубатор не стал добавлять. Если не сложно, @boston, сделай сам plz.