0

I have a node node/1 with the path alias /path. What I want is for /path/* to always behave like you went to /path, but not change the url. So going to /path/test?something=1 should behave identically to going to /path but the url will still be /path/test?something=1 in the user's browser.

Right now, I'm getting a redirect loop when I return the same url in the processOutbound method. If I just return $path in this method, the user gets redirected to /path?something=1.

My use case is a vue app that has it's own routing. But we need a node to allow the client to edit the content around the app.

Code below. Thanks!

<?php

namespace Drupal\gm_custom;

use Drupal\Core\PathProcessor\OutboundPathProcessorInterface;
use Drupal\Core\Render\BubbleableMetadata;
use Symfony\Component\HttpFoundation\Request;
use Drupal\Core\PathProcessor\InboundPathProcessorInterface;

class PathSubscriber implements InboundPathProcessorInterface, OutboundPathProcessorInterface
{
    /**
     * Processes the inbound path.
     *
     * @param string $path
     *   The path to process, with a leading slash.
     * @param \Symfony\Component\HttpFoundation\Request $request
     *   The HttpRequest object representing the current request.
     *
     * @return string
     *   The processed path.
     */
    public function processInbound($path, Request $request)
    {
        if ($this->stringStartsWith('/plan/', strtolower($path))) {
            return '/node/1';
        }

        return $path;
    }

    public function processOutbound(
        $path,
        &$options = [],
        Request $request = null,
        BubbleableMetadata $bubbleable_metadata = null
    ) {
        $url = strtok($_SERVER[ "REQUEST_URI" ], '?');
        if ($this->stringStartsWith('/plan/', $_SERVER[ 'REQUEST_URI' ])) {
            return $url;
        }

        return $path;
    }


    protected function stringStartsWith($needle, $haystack)
    {
        return (substr($haystack, 0, strlen($needle)) === $needle);
    }
}

Edit I've confirmed it's the redirect module is the issue. Simply installing it creates the above problem. Still looking for a solution.

2
  • Path processing doesn't redirect, do you have the Redirect module installed? Also, only use the request object to get the URI and query parameters. The returned path should not contain the query string. Put the query parameters in $options['query'].
    – 4uk4
    Commented Jan 29, 2020 at 19:53
  • Confirmed it's the redirect module! Just simply installing it without any additional config causes this problem. Thanks for the tips. Still looking into it, will report back if I find anything.
    – John_911
    Commented Jan 30, 2020 at 17:54

1 Answer 1

4

Alright got it working! The redirect module hooks into the kernel.request event and will redirect if you're doing wildcard routing like I am below.

You may want to split this up into two services since we're hooking into two events, but they're directly related so I chose to keep it simple. Basic explanation of methods in here is:

PathSubscriber::processInbound Check the incoming url and map it to a single node

PathSubscriber::getSubscribedEvents Register our callback for the kernel.request aka KernelEvents::REQUEST event so we can stop the redirect module from redirecting.

PathSubscriber::stopRedirectIfOurRoute Check the path and if it matches against our wildcard routing from PathSubscriber::processInbound then set the _disable_route_normalizer to true to disable the redirect module from redirecting to the node's alias. Leave our urls alone!


mymodule/src/PathSubscriber.php

namespace Drupal\mymodule;

use Symfony\Component\HttpFoundation\Request;
use Symfony\Component\EventDispatcher\EventSubscriberInterface;
use Symfony\Component\HttpKernel\Event\GetResponseEvent;
use Drupal\Core\PathProcessor\InboundPathProcessorInterface;
use Symfony\Component\HttpKernel\KernelEvents;

class PathSubscriber implements InboundPathProcessorInterface, EventSubscriberInterface
{
    /**
     * Processes the inbound path.
     *
     * @param string $path
     *   The path to process, with a leading slash.
     * @param \Symfony\Component\HttpFoundation\Request $request
     *   The HttpRequest object representing the current request.
     *
     * @return string
     *   The processed path.
     */
    public function processInbound($path, Request $request)
    {
        if (self::stringStartsWith('/plan/', strtolower($path))) {
            return '/node/1';
        }

        return $path;
    }

    /**
     * {@inheritdoc}
     */
    public static function getSubscribedEvents()
    {
        // register our event right before the redirect module redirects
        $events[ KernelEvents::REQUEST ][] = ['stopRedirectIfOurRoute', 31];

        return $events;
    }

    /**
     * ------------------------------------
     * From the redirect module (redirect/src/EventSubscriber/RouteNormalizerRequestSubscriber.php):
     *
     * The normalization can be disabled by setting the "_disable_route_normalizer"
     * request parameter to TRUE. However, this should be done before
     * onKernelRequestRedirect() method is executed.
     * ------------------------------------
     *
     * @param \Symfony\Component\HttpKernel\Event\GetResponseEvent $event
     *   The event to process.
     */
    public static function stopRedirectIfOurRoute(GetResponseEvent $event) {
        $path = $event->getRequest()->getPathInfo();

        if (self::stringStartsWith('/plan/', strtolower($path))) {
            $event->getRequest()->attributes->set('_disable_route_normalizer', true);
        }
    }

    public static function stringStartsWith($needle, $haystack)
    {
        return (substr($haystack, 0, strlen($needle)) === $needle);
    }
}

mymodule.services.yml

services:
  mymodule.path_subscriber:
    class: Drupal\mymodule\PathSubscriber
    tags:
     - { name: path_processor_inbound }
     - { name: event_subscriber }

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service and acknowledge you have read our privacy policy.

Not the answer you're looking for? Browse other questions tagged or ask your own question.