SymfonyCasts stands united with the people of Ukraine

# Deep Dive into Item Views

When it comes to customization, you can do a lot of damage by looking at which templates are rendering and using the theme system to override them. But there are a few cases where you'll need to get even more specific.

For example, suppose we want to modify the "item" template for how the skills grid renders on the homepage. If you check the web debug toolbar here and scroll down... I'll actually search for "contentful"... ah, there we go. You can see grid.html.twig... which renders item/contentful_entry.html.twig. To customize the item, we could override that template. Easy peasy.

The problem is that, in Contentful, we have multiple content types: we have Skills and Advertisements. So if we override this template, that will override it for both Skills and Advertisements... and we probably want those to look different. So, how can we solve this?

## Diving into the item_view Config

Earlier, we ran debug:config netgen_layouts view and talked about the two main sections under here - block_view (which controls how blocks render) and item_view.

php ./bin/console debug:config netgen_layouts view.item_view


As I've said a few times, some blocks, like Grid and List, render individual items. This item_view config is where we define those templates. And you'll see some familiar root keys: default for the frontend, ajax for AJAX calls, and app for the admin. Once again, this uses the match config and... hey! We see our entry in here! Remember recipes_default? We configured this inside of our config file, and it's one of the two real item templates we have right now:

 netgen_layouts: ... lines 2 - 12 view: item_view: ... lines 15 - 21 # default = frontend default: # this key is not important recipes_default: template: 'nglayouts/frontend/recipe_item.html.twig' match: item\value_type: 'doctrine_recipe' ... lines 29 - 36

There's one for recipes, and then Contentful has one for all of the Contentful items.

So again, we could just override this template via the themeing system and be done. But our goal is to override this template only when the item is a skill, like this one. So how do we do that? By adding our own item_view to this list that matches that single content type. Let's do it!

Over here... we're under item_view, default for the frontend and we have the one entry from earlier: recipes_default. Let's add another. Call it contentful_entry/skill, though this particular key doesn't make any difference. Below that, set template to @nglayouts/item/contentful_entry, followed by skill.html.twig:

 netgen_layouts: ... lines 2 - 12 view: item_view: ... lines 15 - 21 # default = frontend default: ... lines 24 - 28 contentful_entry/skill: template: '@nglayouts/item/contentful_entry/skill.html.twig' ... lines 31 - 41

Before, we were using nglayouts without the @... just because I told you that nglayouts/ was a nice directory for organizing things. Internally, Layouts uses @nglayouts in its template paths. What's the difference? By adding the @, we're hooking into the themeing system. So because we have a templates/nglayouts/ directory with themes/standard/ inside, it will use our template. Feel free to use @nglayouts or just nglayouts. I just wanted you to understand the difference because you'll see the @nglayouts syntax all over the place.

## Matching just One Content Type

The really important key here is match. We want to match only when we're working with a contentful_entry. Ok, copy match from the config... and paste.

But we need to be more specific. We also need to match only when the type of the content is a skill. But how do we do that? What matchers are even available? There is a core list... but did Contentful add any additional matchers that we could leverage?

Here's a little trick to see the true list of match items. It's a little technical, but works beautifully. Run:

php ./bin/console debug:container --tag=netgen_layouts.view_matcher


What is this doing? Well, anyone can create a custom matcher - like foo\bar. To do that, you create a class and give it this tag. By looking for all services with that tag, we can find all of the existing matchers in the system.

And... look at that list! Oh, here's an interesting one: contentful\content_type. I bet we can use that. Try it: contentful\content_type set to skill:

 netgen_layouts: ... lines 2 - 12 view: item_view: ... lines 15 - 21 # default = frontend default: ... lines 24 - 28 contentful_entry/skill: template: '@nglayouts/item/contentful_entry/skill.html.twig' match: item\value_type: 'contentful_entry' contentful\content_type: 'skill' ... lines 34 - 41

Okay, let's go create the template. Inside themes/standard/, instead of block/, this time, create a directory called item/... then contentful_entry/, and then skill.html.twig. Just put some dummy text for now:

 CONTENTFUL SKILL!

Ok, if this is working, when we refresh, these items - which are Contentful skills - should re-render using our new template. But when we try it... absolutely nothing changes. What happened?

## Wrong Config Order!

Go back to your terminal and run

php ./bin/console debug:config netgen_layouts view.item_view


again. This all looks good... except for the order. This one from Contentful is on the top of the list... and our new ones are at the bottom. And guess what? When Layouts tries to figure out which template to render, it reads the list from top to bottom and finds the first one that matches: exactly how Symfony's routing system works.

So, it first looks at contentful_entry, sees that the value_type is contentful_entry... then uses it. It never makes it to the contentful_entry/skill at the bottom.

To fix this, we're going to use a fancy prepend configuration trick. Let's do that next.

0
Login or Register to join the conversation

"Houston: no signs of life"
Start the conversation!

### What PHP libraries does this tutorial use?

// composer.json
{
"require": {
"php": ">=8.0.0",
"ext-ctype": "*",
"ext-iconv": "*",
"babdev/pagerfanta-bundle": "^3.7", // v3.7.0
"doctrine/doctrine-bundle": "^2.7", // 2.7.0
"doctrine/doctrine-migrations-bundle": "^3.2", // 3.2.2
"doctrine/orm": "^2.13", // 2.13.3
"netgen/layouts-contentful": "^1.3", // 1.3.2
"netgen/layouts-standard": "^1.3", // 1.3.1
"sensio/framework-extra-bundle": "^6.2", // v6.2.8
"stof/doctrine-extensions-bundle": "^1.7", // v1.7.0
"symfony/console": "5.4.*", // v5.4.14
"symfony/dotenv": "5.4.*", // v5.4.5
"symfony/flex": "^1.17|^2", // v2.2.3
"symfony/framework-bundle": "5.4.*", // v5.4.14
"symfony/monolog-bundle": "^3.0", // v3.8.0
"symfony/proxy-manager-bridge": "5.4.*", // v5.4.6
"symfony/runtime": "5.4.*", // v5.4.11
"symfony/security-bundle": "5.4.*", // v5.4.11
"symfony/twig-bundle": "5.4.*", // v5.4.8
"symfony/ux-live-component": "^2.x-dev", // 2.x-dev
"symfony/ux-twig-component": "^2.x-dev", // 2.x-dev
"symfony/validator": "5.4.*", // v5.4.14
"symfony/webpack-encore-bundle": "^1.15", // v1.16.0
"symfony/yaml": "5.4.*", // v5.4.14
"twig/extra-bundle": "^2.12|^3.0", // v3.4.0
"twig/twig": "^2.12|^3.0" // v3.4.3
},
"require-dev": {
"doctrine/doctrine-fixtures-bundle": "^3.4", // 3.4.2
"symfony/debug-bundle": "5.4.*", // v5.4.11
"symfony/maker-bundle": "^1.47", // v1.47.0
"symfony/stopwatch": "5.4.*", // v5.4.13
"symfony/web-profiler-bundle": "5.4.*", // v5.4.14
"zenstruck/foundry": "^1.22" // v1.22.1
}
}