The |u Filter & String Component

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 $12.00

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

Login Subscribe

The questions on this page are way too long. We need to shorten them!

But before we do, this answer.question.question thing is bothering me: it looks... kind of confusing. Let's make this more clear by adding a custom method to our Answer class.

<li class="mb-4">
{% if showQuestion|default(false) %}
<a
... lines 4 - 7
>
... line 9
{{ answer.question.question }}
</a>
{% endif %}
... lines 13 - 46
</li>

Adding Answer::getQuestionText() for Clarity

Open src/Entity/Answer.php. It doesn't matter where... but right by getQuestion() makes sense, add a new method: public function getQuestionText(), which will return a string.

... lines 1 - 11
class Answer
{
... lines 14 - 98
public function getQuestionText(): string
{
... lines 101 - 105
}
... lines 107 - 134
}

On a high level, this method makes me happy! If I have an Answer object, there's a good chance that I might want to easily get the question text related to this answer. Inside, I'll start by coding defensively: if not $this->getQuestion() - so if there is no related Question object, return empty quotes.

... lines 1 - 11
class Answer
{
... lines 14 - 98
public function getQuestionText(): string
{
if (!$this->getQuestion()) {
return '';
}
... lines 104 - 105
}
... lines 107 - 134
}

Now, you might be screaming:

Hey Ryan! I thought the question property was required in the database! How could we not have a question?

And... that's mostly right! We can't save an Answer to the database without a Question. But, in theory, we could create a new Answer object and call getQuestionText() on it before even trying to save it. To avoid an error if we did that, I'm coding defensively.

At the bottom, return $this->getQuestion->getQuestion()... but cast that to a string, just in case it's null... which, again, isn't likely since that property is required in the database, but it is technically possible.

... lines 1 - 11
class Answer
{
... lines 14 - 98
public function getQuestionText(): string
{
if (!$this->getQuestion()) {
return '';
}
return (string) $this->getQuestion()->getQuestion();
}
... lines 107 - 134
}

Thanks to the new method, over in _answer.html.twig, we can change this to {{ answer.questionText }}.

<li class="mb-4">
{% if showQuestion|default(false) %}
<a
... lines 4 - 7
>
<strong>Question:</strong>
{{ answer.questionText }}
</a>
{% endif %}
... lines 13 - 46
</li>

So much nicer. But... the front-end still looks weird. So let's shorten the question string!

Twig's "u" Filter & the String Component

In Twig, we have a special filter called |u. This filter leverages Symfony's string component to give you what's called a UnicodeString object. It's basically an object that wraps this string... and gives you access to a bunch of useful methods. One of those methods is called truncate(). This means we can say .truncate(). Pass this 80 and '....

<li class="mb-4">
{% if showQuestion|default(false) %}
<a
... lines 4 - 7
>
<strong>Question:</strong>
{{ answer.questionText|u.truncate(80, '...') }}
</a>
{% endif %}
... lines 13 - 46
</li>

So if the string is longer than 80 characters, truncate it and add a ... to the end. I love it!

Before we try this, search for "Symfony string component" to find its documentation. If you scroll down... you'll see a bunch of examples of what you can do - in PHP - with the string component. This u() function in PHP creates the same thing as our |u filter. Down here, you can see a ton of examples of what you can do - like lower-casing, title-casing, camel-casing... and a lot more... including a truncate() method. So if you ever need to mess around with strings - in Twig or PHP - don't forget about this component!

But... if we try this... it doesn't actually work! It says:

the u filter is part of the StringExtension... try running composer require twig/string-extra.

No problem! Find your terminal and run that:

composer require twig/string-extra

When it finishes... we can now refresh and see... awesome! We have truncated questions!

But look down at the web debug toolbar. This page made 8 queries... which seems like a lot just to render 10 answers. This is because we're suffering from the the N+1 query problem.

Next, let's learn more about this and see how we can join across a relationship to solve it.

Leave a comment!

What PHP libraries does this tutorial use?

// composer.json
{
    "require": {
        "php": "^7.4.1 || ^8.0.0",
        "ext-ctype": "*",
        "ext-iconv": "*",
        "babdev/pagerfanta-bundle": "^3.3", // v3.3.0
        "composer/package-versions-deprecated": "^1.11", // 1.11.99.3
        "doctrine/doctrine-bundle": "^2.1", // 2.4.2
        "doctrine/doctrine-migrations-bundle": "^3.0", // 3.1.1
        "doctrine/orm": "^2.7", // 2.9.5
        "knplabs/knp-markdown-bundle": "^1.8", // 1.9.0
        "knplabs/knp-time-bundle": "^1.11", // v1.16.1
        "pagerfanta/doctrine-orm-adapter": "^3.3", // v3.3.0
        "pagerfanta/twig": "^3.3", // v3.3.0
        "sensio/framework-extra-bundle": "^6.0", // v6.1.5
        "stof/doctrine-extensions-bundle": "^1.4", // v1.6.0
        "symfony/asset": "5.3.*", // v5.3.4
        "symfony/console": "5.3.*", // v5.3.7
        "symfony/dotenv": "5.3.*", // v5.3.7
        "symfony/flex": "^1.3.1", // v1.15.1
        "symfony/framework-bundle": "5.3.*", // v5.3.7
        "symfony/monolog-bundle": "^3.0", // v3.7.0
        "symfony/runtime": "5.3.*", // v5.3.4
        "symfony/stopwatch": "5.3.*", // v5.3.4
        "symfony/twig-bundle": "5.3.*", // v5.3.4
        "symfony/webpack-encore-bundle": "^1.7", // v1.12.0
        "symfony/yaml": "5.3.*", // v5.3.6
        "twig/extra-bundle": "^2.12|^3.0", // v3.3.1
        "twig/string-extra": "^3.3", // v3.3.1
        "twig/twig": "^2.12|^3.0" // v3.3.2
    },
    "require-dev": {
        "doctrine/doctrine-fixtures-bundle": "^3.3", // 3.4.0
        "symfony/debug-bundle": "5.3.*", // v5.3.4
        "symfony/maker-bundle": "^1.15", // v1.33.0
        "symfony/var-dumper": "5.3.*", // v5.3.7
        "symfony/web-profiler-bundle": "5.3.*", // v5.3.5
        "zenstruck/foundry": "^1.1" // v1.13.1
    }
}