Flag of Ukraine
SymfonyCasts stands united with the people of Ukraine

Lucky you! You found an early release chapter - it will be fully polished and published shortly!

Content Browser: Returning the Items

This Chapter isn't
quite ready...

Rest assured, the gnomes are hard at work
completing this video!

Browse Tutorials

Our Content Browser is sort of working. We can see our one location... we just don't have any results yet. That's because, for whatever location is selected, the Content Browser calls getSubItems(). Our job here is to return the results. In this case, all of our recipes. If we had multiple locations, like recipes divided into categories, we could use the $location variable to return the subset. But we'll query and return all recipes.

Querying in getSubItems()

To do that, go to the top of the class and create a constructor with private RecipeRepository $recipeRepository.

Then, down here in getSubItems(), say $recipes = $this->recipeRepository and use that same method from earlier: ->createQueryBuilderOrderedByNewest(). Below add ->setFirstResult($offset)... and ->setMaxResults($limit). The Content Browser comes with pagination built-in. It passes us the offset and limit for whatever page the user is on, we plug it into the query, and everyone is happy. Finish with getQuery() and getResult().

Notice that getSubItems() returns an iterable... actually it's supposed to be an iterable of something called an ItemInterface. So we can't just return these Recipe objects.

Creating the ItemInterface Wrapper Class

Instead, in src/ContentBrowser/, create another class called, how about RecipeBrowserItem. Make this implement ItemInterface - the one from Netgen\ContentBrowser - then generate the four methods it needs.

This class will be a tiny wrapper around a Recipe object. Watch: add a __construct() method with private Recipe $recipe. Now, for getValue(), this should return the "identifier", so return $this->recipe->getId(). For getName(), we just need something visual we can show, like $this->recipe->getName(). And for isVisible(), return true. That's useful if a Recipe could be published or unpublished. We have a similar situation with isSelectable(). If you had a set of rules where you wanted to show certain recipes but make them not selectable, you could return false here.

And... we're done! That was easy!

Back over in our backend class, we need to turn these Recipe objects into RecipeBrowserItem objects. We can do that with array_map(). I'll use the fancy fn() syntax again, which will receive a Recipe $recipe argument, followed by => new RecipeBrowserItem($recipe). For the second arg, pass $recipes.

This is a fancy way of saying:

Loop over all the recipes in the system, create a new RecipeBrowserItem for each one, and return that new array of items.

All right, let's see what this looks like! Refresh the layout, click on the Grid, go back to "Add Items" and... got it! We see ten items!

Implementing getSubItemsCount()

But we should have multiple pages. Ah, that's because we're still returning 0 from getSubItemsCount(). Let's fix that. Steal the query from above... paste, return this, remove setFirstResult() and setMaxResults(), add ->select('COUNT(recipe.id)'), and then call getSingleScalarResult() at the bottom.

And just like that, when we refresh... and open the Content Browser... we have pages!

Adding the Search Functionality

Ok, but could we search for recipes? Absolutely. We can leverage search() and searchCount(). This is simple. Steal all of the logic from getSubItems(), paste into search() and pass $searchText to the QueryBuilder method, which already allows this argument.

If you want to have a bit less code duplication, you could isolate this into a private method at the bottom.

Also copy the logic from the other count method... paste that into searchCount(), and pass it $searchText as well.

And just like that, if we move over here and try to search... it works. That's awesome!

Alright - select a few items, hit "Confirm" and... oh no! It breaks! It still says "Loading". If you look down on the web debug toolbar, we have a 400 error. Dang. When we open that up, we see:

value loader for "doctrine recipe" value type does not exist.

There's just one final piece we need: A very simple class called the "value loader". That's next.

Leave a comment!

Login or Register to join the conversation
Cat in space

"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
        "easycorp/easyadmin-bundle": "^4.4", // v4.4.1
        "netgen/layouts-contentful": "^1.3", // 1.3.2
        "netgen/layouts-standard": "^1.3", // 1.3.1
        "pagerfanta/doctrine-orm-adapter": "^3.6",
        "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