перенаправление на следующее/действие маршрута, а не последнее после входа/регистрации пользователя symfony security


У меня есть такая форма в представлении:

{{ form_start(form, {'action': path('process_route'), 'method': 'POST'}) }}
  {#  some other fields #}
  {{ form_row(form.placeOrder) }}
  {{ form_row(form.saveOrder) }}
{{ form_end(form) }}

Как вы можете видеть, у меня есть две кнопки отправки, но это не проблема, я использую метод symfony:

if ($form->isValid()) {
    // ... do something

    // the save_and_add button was clicked
    if ($form->get('placeOrder')->isClicked()) {
        // probably redirect to the add page again
    }

    // redirect to the show page for the just submitted item
}

Как вы можете видеть, в моей форме есть конкретный маршрут процесса действий с именем 'process_route'. Когда я отправляю форму, независимо от того, нажал ли пользователь кнопку "Отправить", я проверил, подключен ли пользователь к security.context.

Если пользователь подключен, я перенаправляю на маршрут, который он назвал, при нажатии на один из двух отправленных кнопки.

Но если пользователь не подключен, я сначала перенаправляю на user registration (если он еще не зарегистрирован) или форму входа пользователя.

После того, как пользователь зарегистрировался или вошел в систему, мне нужно перенаправить на маршрут, который он задал первым, когда нажал на одну из двух кнопок отправки.

На самом деле перенаправление работает хорошо, за исключением того, что перенаправление выполняется при последнем запрошенном действии , , e-g при действии формы маршрута процесса (здесь 'process_route'), и мне нужно перенаправить к следующему шагу, а не к последнему.

В моем файле security.yml у меня есть это:

firewalls:
    main:
        pattern: ^/
            form_login:
                provider: custom
                csrf_provider: security.csrf.token_manager # Use form.csrf_provider instead for Symfony <2.4
                use_referer : true

Как я могу приступить к управлению перенаправлением после выполнения требуемого пользователем действия, нажав на одну из двух форм кнопки "Отправить", а не на последнее действие?

Author: french_dev, 2016-02-16

2 answers

Установите referer вручную в запросе.

Попробуйте сделать это напрямую с вашего контроллера, например:

if ($form->get('placeOrder')->isClicked()) {
    $request = $this->getRequest();
    $request->headers->set('referer', $this->generateUrl('expected_route'));
}

Я сомневаюсь, что запрос не будет обновлен в следующем.
В этом случае ищите RequestListener и найдите способ установить хорошее referer в зависимости от условий.

Обновление

Для входа в систему переопределите форму входа по умолчанию, добавив следующее поле:

<input type="hidden" name="_target_path" value="{{ app.request.headers.get('referer') }}" />

Не забудьте установить референт вручную с вашего контроллера во всех случаи.

Обновление 2

Поскольку referer неправильно обновлен в форме входа, я постараюсь дать вам быструю альтернативу.

Если я правильно понял, вы вручную отображаете форму входа с вашего контроллера.

В момент отображения формы передайте дополнительный параметр в ваше представление:

return $this->render('UserBundle:Security:login.html.twig, array(
    'form' => $form->createView(),
    // ...
    '_target_path' => $this->generateUrl('expected_referer'), // Defined from your condition depending on button clicked
);
 2
Author: chalasr, 2016-02-16 16:40:31

Я нашел решение, но я хотел бы быть уверенным, что это лучшая практика.

Обратите внимание, что это перенаправление должно выполняться только в определенный момент в приложении, а не на всем веб-сайте. Поэтому я решил создать сервис-прослушиватель.

На самом деле перенаправление должно выполняться, когда в сеансе есть определенный объект сущности, который я назвал entity.

Кулак, это декларация службы в app/config/services.yml:

# apply the redirect response for connected user when and only where $isEntitySession !=null
    # to recover the current request in a service we need to set the scope argument, then call the symfony service container where $request is stored
    # just understand that it is not recommanded (bad structure and mad practice) to apply directly the request in service without service container
    my_bundle.login_listener:
        class: MySpace\MyBundle\EventListener\LoginListener
        tags:
            - { name: kernel.event_listener, event: security.interactive_login, method: onSecurityInteractiveLogin  }
        scope: request
        arguments: ["@router","@security.context","@event_dispatcher","@service_container"]

Тогда это LoginListener.php в MySpace/MyBundle/EventListener/LoginListener.php:

<?php

namespace MySpace\MyBundle\EventListener;

use Symfony\Component\Routing\Router;
use Symfony\Component\Security\Core\SecurityContext;
use Symfony\Component\EventDispatcher\EventDispatcherInterface;
use Symfony\Component\Security\Http\Event\InteractiveLoginEvent;
use Symfony\Component\HttpKernel\KernelEvents;
use Symfony\Component\HttpKernel\Event\FilterResponseEvent;
use Symfony\Component\HttpFoundation\RedirectResponse;
use Symfony\Component\HttpFoundation\Request;
use Symfony\Component\DependencyInjection\ContainerInterface;

class LoginListener
{
  protected $router;
  protected $security;
  protected $dispatcher;
  protected $container;

  public function __construct(Router $router, SecurityContext $security, EventDispatcherInterface $dispatcher, ContainerInterface $container)
  {
      $this->router = $router;
      $this->security = $security;
      $this->dispatcher = $dispatcher;
      $this->container = $container;
  }

  public function onSecurityInteractiveLogin(InteractiveLoginEvent $event)
  {
      $this->dispatcher->addListener(KernelEvents::RESPONSE, array($this, 'onKernelResponse'));
  }

  public function onKernelResponse(FilterResponseEvent $event)
  {
    $request = $this->container->get('request');
    $session = $this->container->get('session');

    $isEntitySession = $session->get('entity_form') != null;

    if($isEntitySession && $request->isMethod('POST') ) {
      $entity = $session->get('entity_form');
    }

    if(isset($entity)) {

      if ($entity->getState() == 'true') { //here the state is a bool, don't search anymore about this, it's just an entity object relation set to true or false. Following the state, I return a different view.

        $response = new RedirectResponse($this->router->generate('place_order_route'));

      } else {

        $response = new RedirectResponse($this->router->generate('save_order_route'));
      }

      $event->setResponse($response);
    }
  }
}

Все комментарии в services.yml могут помочь вам понять применение контейнера службы. Документ symfony говорит вам не использовать запрос напрямую в объявлении службы. Не стесняйтесь сказать мне, правильно ли вы поступаете или нет.

Обратите внимание, что я использовал security.context, на самом деле я хотел бы использовать его для восстановления пользовательских данных, но в качестве цели здесь нет необходимости его использовать, поэтому мы могли бы удалить это argument и переменную в __construct().

К выясните, какая кнопка была нажата, я играю с методом сущности getState(). Действительно, после этого состояния перенаправление не является тем же самым.

 1
Author: french_dev, 2016-02-16 16:45:15