简体   繁体   中英

Symfony2: dynamic routing parameter

I'm turning to this forum because I can't find a valid solution to my problem.

I have taken over the management of a Symfony2 application which processes orders, invoices... inside a company and the problem is that there isn't archiving functions on it. So, the manager asked me to add archiving 'by year' functionalities to the application (simply display data depending on a chosen year).

So, I decided to prefix all application routes by /{year}/, parameter which will match the year the manager want to see and, as all the documents are dated, I just have to update Doctrine requests for picking those that match the chosen year. So far no problems.

routes.yml

mes_routes:
    resource: "mes_routes.yml"
    prefix:   /{year}
    defaults: {'year': %current_year%}

With this, I have created a Symfony Extension which fills the 'current_year' var by default in my route definition, with the actual year if no year is provided.

MyAppExtension.php

class MyAppExtension extends Extension
{
    public function load(array $configs, ContainerBuilder $container)
    {
        $configuration = new Configuration();
        $config = $this->processConfiguration($configuration, $configs);

        $loader = new Loader\YamlFileLoader($container, new FileLocator(__DIR__.'/../Resources/config'));
        $loader->load('services.yml');

        // Fill parameter with the current year
        $container->setParameter('current_year', date("Y"));
    }
}

Next, I have created a RouteListener that stores a previous route and its parameters inside a session var, when a user displays a new page (in order to display a same page but with a different year next)

LastRouteListener.php

class LastRouteListener
{
    public function onKernelRequest(GetResponseEvent $event)
    {
        // Don't store subrequests
        if ($event->getRequestType() !== HttpKernel::MASTER_REQUEST) {
            return;
        }

        $request = $event->getRequest();
        $session = $request->getSession();

        $routeName = $request->get('_route');
        $routeParams = $request->get('_route_params');
        if ($routeName[0] == "_") {
            return;
        }
        $routeData = ['name' => $routeName, 'params' => $routeParams];

        // Don't store the same route twice
        $thisRoute = $session->get('this_route', []);
        if ($thisRoute == $routeData) {
            return;
        }
        $session->set('last_route', $thisRoute);
        $session->set('this_route', $routeData);
    }
 }

services.yml

myapp.last_route_event_listener:
    class:  MyApp\EventListener\LastRouteListener
    tags:
        - { name: kernel.event_listener, event: kernel.request, method: onKernelRequest, priority: 30 }

And finally, I have added a new controller which, via a dropdown menu in the application navbar, displays the current page the user is viewing, but with a different year

ArchiveController.php

class ArchiveController extends Controller
{
    public function switchYearAction(Request $request, $year)
    {
        $session = $request->getSession();
        $lastRoute = $session->get('last_route');
        $route = $lastRoute["name"];
        $routeParams = $lastRoute["params"];

        if (array_key_exists("year", $routeParams)) {
            $routeParams["year"] = $year;
            $session->set("current_year", $year);
        }

        return $this->redirect($this->generateUrl($route, $routeParams));
    }
}

Arrived here, everything work. If a user chose an other date, the application will display the same page but with the new date chosen.

However , and there is my problem, if, from a previous year, the user clicks on a link in the page, we come back to the actual year. Quite normal, because Twig paths in the application doesn't fill the 'year' routing parameter, and the router provide the current year by default.

So, my question is : How can I keep the chosen year in memory, and use it as a route parameter ?

First, I had thought about setting the local var 'current_year' when the application uses the switchYearAction(), but Symfony returns an error ('Frozen variable')

Next, I had thought about using a session var to store the chosen year, but I can't access to the session within my extension MyAppExtension.

There might be a third solution which consists in update all Twig paths and Controller redirect(), but it represents some much line to edit...

Or maybe with a routeEventListener... but I don't know how to proceed.

Thanks you in advance.

You can access the application session in Twig using {{ app.session }} . So something like this is possible:

{{ url('myapp.whatever', {year: app.session.get('current_year')}) }}

Since you have a bit of logic around your current year stuff (if it's not set in the session fallback to the current year, etc), a twig extension that provides a funtion to fetch the current year may be a better way to go. Quick, untested example:

<?php
use Symfony\Component\HttpFoundation\Session\SessionInterface;

class CurrentYearExtension extends \Twig_Extension
{
    private $session;
    private $defaultYear;

    public function __construct(SessionInterface $session, $defaultYear)
    {
        $this->session = $session;
        $this->defaultYear = $defaultYear;
    }

    public function getFunctions()
    {
        return [
            new \Twig_SimpleFunction('current_year', [$this, 'currentYear']),
        ];
    }

    public function currentYear()
    {
        return $this->session->get('current_year') ?: $this->defaultYear;
    }
}

Then add it to your container and tag it with the twig.extension tag.

<service id="myapp.currentyear_extension" class="CurrentYearExtension" public="false">
    <argument type="service" id="session" />
    <argument>%current_year%</argument>
    <tag name="twig.extension" />
</service>

And use it in your route generation:

{{ url('myapp.whatever', {year: current_year()}) }}

If you need the current year other places than twig, then pull a re-usable object out of the twig extension and use that both with the extension and elsewhere.

Thanks to the answer given by @chrisguitarguy : https://stackoverflow.com/a/13495302/1031898 I found a way to resolve my problem.

In fact, I could use my routeListener to do the job. I just needed to implement the Router Interface.

LastRouteListener.php (updated)

class LastRouteListener
{
    private $router;

    public function __construct(RouterInterface $router)
    {
        $this->router = $router;
    }

    public function onKernelRequest(GetResponseEvent $event)
    {
        // Don't store subrequests
        if ($event->getRequestType() !== HttpKernel::MASTER_REQUEST) {
            return;
        }

        $request = $event->getRequest();
        $session = $request->getSession();
        $context = $this->router->getContext();

        // If current_year exists in session, replace route parameter with it
        if ($session->has('current_year')) {
            $context->setParameter('year', $session->get('current_year'));
        }
        // Else, we set the current year by default 
        else {
            $context->setParameter('year', date('Y'));
        }

        $routeName = $request->get('_route');
        $routeParams = $request->get('_route_params');
        if ($routeName[0] == "_") {
            return;
        }
        $routeData = ['name' => $routeName, 'params' => $routeParams];

        // On ne sauvegarde pas la même route plusieurs fois
        $thisRoute = $session->get('this_route', []);
        if ($thisRoute == $routeData) {
            return;
        }
        $session->set('last_route', $thisRoute);
        $session->set('this_route', $routeData);
    }
}

Don't forget to inject the @router argument in services.yml

myapp.last_route_event_listener:
    class:  MyApp\EventListener\LastRouteListener
    tags:
        - { name: kernel.event_listener, event: kernel.request, method: onKernelRequest, priority: 30 }
    arguments: ['@router']

And then, no need to use 'current_year' default parameter in route config anymore.

routes.yml

mes_routes:
    resource: "mes_routes.yml"
    prefix:   /{year}

MyAppExtension.php

class MyAppExtension extends Extension
{
    public function load(array $configs, ContainerBuilder $container)
    {
        $configuration = new Configuration();
        $config = $this->processConfiguration($configuration, $configs);

        $loader = new Loader\YamlFileLoader($container, new FileLocator(__DIR__.'/../Resources/config'));
        $loader->load('services.yml');
}

}

The technical post webpages of this site follow the CC BY-SA 4.0 protocol. If you need to reprint, please indicate the site URL or the original address.Any question please contact:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM