Sub Requests & Request Data

Keep on Learning!

If you liked what you've learned so far, dive in!
Subscribe to get access to this tutorial plus
video, code and script downloads.

Start your All-Access Pass
Buy just this tutorial for $10.00

With a Subscription, click any sentence in the script to jump to that part of the video!

Login Subscribe

Remember that cool trick that we did a few minutes ago with the argument value resolver that allowed us to have an $isMac argument to any controller in our system? Does that also work in a controller that's called by sub request? Of course! Because there's nothing special about this controller: it was called thanks to a complete cycle through HttpKernel::handle(). All the same listeners and all the same argument value resolvers are called.

So... cool! Let's use that! Add an $isMac argument... then pass it into the template.

... lines 1 - 6
class PartialController extends AbstractController
{
public function trendingQuotes($isMac)
{
... lines 11 - 12
return $this->render('partial/trendingQuotes.html.twig', [
... line 14
'isMac' => $isMac
]);
}
... lines 18 - 38
}

Inside trendingQuotes.html.twig, near the bottom, add {% if isMac %}{% endif %} and inside, put an <hr>, a <small> tag, and then say:

<div class="quote-space pb-2 pt-2">
... lines 2 - 10
{% if isMac %}
<hr>
<small>BTW, you're using a Mac!</small>
{% endif %}
</div>

BTW, you're using a Mac!

Easy enough! Find your browser... navigate back to the homepage... and refresh. On the right, there it is! We're using a Mac.

Just for the heck of it, let's add that same logic to the sidebar above this. This lives in the homepage template, so find ArticleController::homepage... add an $isMac argument and pass this into the template.

... lines 1 - 13
class ArticleController extends AbstractController
{
... lines 16 - 32
public function homepage(ArticleRepository $repository, LoggerInterface $logger, $isMac)
{
... lines 35 - 37
return $this->render('article/homepage.html.twig', [
... line 39
'isMac' => $isMac,
]);
}
... lines 43 - 71
}

Steal the isMac logic from the trending quotes template, open homepage.html.twig and... right below the "Buy Now!" button, paste.

... lines 1 - 2
{% block body %}
<div class="container">
<div class="row">
... lines 6 - 45
<div class="col-sm-12 col-md-4 text-center">
<div class="ad-space mx-auto mt-1 pb-2 pt-2">
... lines 48 - 51
{% if isMac %}
<hr>
<small>BTW, you're using a Mac!</small>
{% endif %}
</div>
... lines 57 - 58
</div>
</div>
</div>
{% endblock %}

When we try the page now, no surprise: both places show the message.

Adding the ?mac Override

Since I am using a Mac, it's kind of hard to test whether or not this feature correctly hides for people who are not on a Mac. To make testing easier, let's add a way to override the real logic. I want to be able to add a ?mac=true or ?mac=false to the URL to have full control.

The code for setting the argument is in IsMacArgumentValueResolver. So, if we want to "short-circuit" the real logic, it's no problem. Before we read the User-Agent, add if $request->query->has('mac'), then yield $request->query->getBoolean('mac). getBoolean() is a cool function that grabs the mac query parameter but runs it through PHP's filter_var() function with the FILTER_VALIDATE_BOOLEAN flag. That means a value like a false string will turn into a false boolean. Kinda fun. Anyways, after this, return so the function doesn't continue.

... lines 1 - 8
class IsMacArgumentValueResolver implements ArgumentValueResolverInterface
{
... lines 11 - 15
public function resolve(Request $request, ArgumentMetadata $argument)
{
if ($request->query->has('mac')) {
yield $request->query->getBoolean('mac');
return;
}
... lines 23 - 26
}
}

Ok: if I refresh without changing the URL, it still reads my User-Agent and everything looks right. Now add ?mac=false. And... it works! The message is gone. Oh wait! The first message is gone, but the one coming from the sub-request controller is still there! What the heck?

If you're thinking that somehow the argument value resolver isn't called on a sub request, that's not it. A sub request is handled exactly like the main request. This function is being called twice on this page: once for the main request and again for the sub request. So why do those two calls produce a different result?

The Request in the Sub Request is not the Same

Click into the profiler and go to the Performance section. The Request object that's being processed on top is not the same as the Request object that's being processed down here for the sub-request. Symfony creates two, distinct Request objects. The first Request object represents the data for the real HTTP request that's coming into our app. And so, it contains the query parameter info. But that second Request is kind of a "fake" request. It mainly exists so that the _controller attribute can be set on it. It's not really a representation of the "real" request. And so, it may not have all the same data. It doesn't have the query parameters, for example.

Let's see this: dump($request) inside of the resolve() method... then refresh.

... lines 1 - 15
public function resolve(Request $request, ArgumentMetadata $argument)
{
dump($request);
... lines 19 - 27
}
... lines 29 - 30

Hover over the target icon on the web debug toolbar. Yep, two dumps. If we look at the query parameters for the first Request... it's got it! mac=false. But down on the second request, it has some _path query parameter, but no mac.

The point is: there are two different requests. And the fact that they don't all contain the same data is on purpose. Because of this, whenever you're handling a sub-request, it's not a good idea to read information from the request... because you're not really reading data from the correct request!

So how can we correctly read the mac query parameter from a sub-request? To learn how, let's get crazy and make our own sub-request directly in PHP.

Leave a comment!

What PHP libraries does this tutorial use?

// composer.json
{
    "require": {
        "php": "^7.3.0",
        "ext-iconv": "*",
        "antishov/doctrine-extensions-bundle": "^1.4", // v1.4.2
        "aws/aws-sdk-php": "^3.87", // 3.133.20
        "doctrine/doctrine-bundle": "^2.0", // 2.0.7
        "doctrine/doctrine-migrations-bundle": "^1.3|^2.0", // 2.1.2
        "doctrine/orm": "^2.5.11", // v2.7.1
        "easycorp/easy-log-handler": "^1.0", // v1.0.9
        "http-interop/http-factory-guzzle": "^1.0", // 1.0.0
        "knplabs/knp-markdown-bundle": "^1.7", // 1.8.1
        "knplabs/knp-paginator-bundle": "^5.0", // v5.1.1
        "knplabs/knp-snappy-bundle": "^1.6", // v1.7.0
        "knplabs/knp-time-bundle": "^1.8", // v1.11.0
        "league/flysystem-aws-s3-v3": "^1.0", // 1.0.24
        "league/flysystem-cached-adapter": "^1.0", // 1.0.9
        "league/html-to-markdown": "^4.8", // 4.9.1
        "liip/imagine-bundle": "^2.1", // 2.3.0
        "nexylan/slack-bundle": "^2.1", // v2.2.2
        "oneup/flysystem-bundle": "^3.0", // 3.4.0
        "php-http/guzzle6-adapter": "^2.0", // v2.0.1
        "sensio/framework-extra-bundle": "^5.1", // v5.5.3
        "symfony/asset": "5.0.*", // v5.0.4
        "symfony/console": "5.0.*", // v5.0.4
        "symfony/dotenv": "5.0.*", // v5.0.4
        "symfony/flex": "^1.0", // v1.6.2
        "symfony/form": "5.0.*", // v5.0.4
        "symfony/framework-bundle": "5.0.*", // v5.0.4
        "symfony/mailer": "5.0.*", // v5.0.4
        "symfony/messenger": "5.0.*", // v5.0.4
        "symfony/monolog-bundle": "^3.5", // v3.5.0
        "symfony/security-bundle": "5.0.*", // v5.0.4
        "symfony/sendgrid-mailer": "5.0.*", // v5.0.4
        "symfony/serializer-pack": "^1.0", // v1.0.2
        "symfony/twig-bundle": "5.0.*", // v5.0.4
        "symfony/twig-pack": "^1.0", // v1.0.0
        "symfony/validator": "5.0.*", // v5.0.4
        "symfony/webpack-encore-bundle": "^1.4", // v1.7.3
        "symfony/yaml": "5.0.*", // v5.0.4
        "twig/cssinliner-extra": "^2.12", // v2.12.5
        "twig/extensions": "^1.5", // v1.5.4
        "twig/inky-extra": "^2.12" // v2.12.5
    },
    "require-dev": {
        "doctrine/doctrine-fixtures-bundle": "^3.0", // 3.3.0
        "fzaninotto/faker": "^1.7", // v1.9.1
        "symfony/browser-kit": "5.0.*", // v5.0.4
        "symfony/debug-bundle": "5.0.*", // v5.0.4
        "symfony/maker-bundle": "^1.0", // v1.14.3
        "symfony/phpunit-bridge": "5.0.*", // v5.0.4
        "symfony/profiler-pack": "^1.0", // v1.0.4
        "symfony/var-dumper": "5.0.*" // v5.0.4
    }
}