WEBVTT

NOTE Created by CaptionSync from Automatic Sync Technologies www.automaticsync.com

00:00:01.306 --> 00:00:07.146 align:middle
We have a Recipe entity and, on the
frontend, a page that lists the recipes.

00:00:08.566 --> 00:00:11.906 align:middle
We also saw how easy it is to create a layout,

00:00:12.206 --> 00:00:15.496 align:middle
which instantly makes parts
of this page configurable.

00:00:16.106 --> 00:00:19.616 align:middle
But now, looking at the homepage, I'm wondering

00:00:19.616 --> 00:00:23.866 align:middle
if we can add more complex
blocks, beyond just text.

00:00:24.516 --> 00:00:29.526 align:middle
Could we, for example, add a block
that renders a list of recipes?

00:00:29.986 --> 00:00:32.636 align:middle
Something similar to what
we have here right now...

00:00:32.636 --> 00:00:35.886 align:middle
except instead of adding this via a Twig block,

00:00:36.016 --> 00:00:39.696 align:middle
it's added entirely via layouts
by an admin user?

00:00:40.306 --> 00:00:46.216 align:middle
And, to go further, could we even let the
admin user choose which recipes to show here?

00:00:46.956 --> 00:00:55.226 align:middle
Totally! If the first big idea of Layouts is
allowing Twig template blocks to be rearranged

00:00:55.226 --> 00:01:01.296 align:middle
and mixed with dynamic content, then
the second big idea is allowing pieces

00:01:01.296 --> 00:01:05.596 align:middle
of existing content - like
recipes from our database -

00:01:05.876 --> 00:01:09.726 align:middle
to be embedded onto our page by admin users.

00:01:10.506 --> 00:01:13.026 align:middle
How? Edit the Homepage Layout.

00:01:14.116 --> 00:01:17.766 align:middle
In the blocks on the left, check
out this one called "Grid".

00:01:18.516 --> 00:01:21.156 align:middle
Add that after our "Hero" Twig block.

00:01:22.106 --> 00:01:26.166 align:middle
The Grid allows us to add
individual items to it...

00:01:26.546 --> 00:01:28.356 align:middle
which could be anything.

00:01:28.936 --> 00:01:31.476 align:middle
But, I don't see a way to do that!

00:01:32.256 --> 00:01:37.496 align:middle
Ok, so we know that a lot of
blocks, like titles, maps, markdown,

00:01:37.496 --> 00:01:44.536 align:middle
etc can be added to our pages in layouts
out-of-the-box with no extra setup work.

00:01:45.146 --> 00:01:51.706 align:middle
But the purpose of some blocks - like
List, Grid, and the Gallery blocks

00:01:51.706 --> 00:01:57.386 align:middle
down here (which are just fancy grids that
have JavaScript behavior attached to them) -

00:01:57.776 --> 00:02:03.226 align:middle
is to render a collection of "items"
that are loaded from somewhere else,

00:02:03.646 --> 00:02:08.326 align:middle
like our local database, CMS,
or even your Sylius store.

00:02:09.076 --> 00:02:15.336 align:middle
The "things" or "items" we can add to
these blocks are called "value types".

00:02:15.906 --> 00:02:18.876 align:middle
And... we currently have zero.

00:02:18.876 --> 00:02:25.146 align:middle
If this were a Sylius project, we could
install the Sylius and Layouts integration

00:02:25.516 --> 00:02:29.116 align:middle
and instantly be able to select products.

00:02:29.116 --> 00:02:31.736 align:middle
The same is true if you're using Ibexa CMS.

00:02:32.166 --> 00:02:41.626 align:middle
So here's our next big goal: to add our Recipe
Doctrine entity as a "value type" in layouts

00:02:41.946 --> 00:02:45.606 align:middle
so that we can create lists
and grids containing recipes.

00:02:45.606 --> 00:02:50.896 align:middle
Step one to adding a value type is to
tell Layouts about it in a config file.

00:02:51.576 --> 00:02:58.096 align:middle
Over in config/packages/netgen_layouts.yaml,
very simply, say value_types,

00:02:58.596 --> 00:03:01.476 align:middle
and below that, doctrine_recipe .

00:03:02.346 --> 00:03:08.716 align:middle
This is the internal name of the value
type, and we'll refer to it in a few places.

00:03:09.536 --> 00:03:15.766 align:middle
Give it a human-friendly name - Recipe -
and for now, set manual_items to false...

00:03:16.446 --> 00:03:18.976 align:middle
and make sure that has an "s" on the end.

00:03:19.576 --> 00:03:25.336 align:middle
We'll talk about manual_items more later, but
it's easier to set this to false to start.

00:03:26.246 --> 00:03:30.226 align:middle
Head over, refresh our layouts
page (it's okay to reload it)...

00:03:31.476 --> 00:03:33.766 align:middle
and check out our Grid block!

00:03:34.216 --> 00:03:36.396 align:middle
There's a new "Collection type" field

00:03:36.556 --> 00:03:39.436 align:middle
and "Manual collection" is
our only option right now.

00:03:39.436 --> 00:03:42.986 align:middle
So... this still doesn't seem to be working.

00:03:43.656 --> 00:03:45.856 align:middle
I can't change this to anything else...

00:03:46.106 --> 00:03:49.006 align:middle
and I also can't select items manually.

00:03:49.746 --> 00:03:54.226 align:middle
There are two ways to add items to
one of these "collection" blocks.

00:03:54.576 --> 00:04:00.496 align:middle
The first is a dynamic collection
where we choose from a pre-made query.

00:04:00.976 --> 00:04:06.306 align:middle
We could choose a "Most Popular" query that
would query for the most popular recipes

00:04:06.686 --> 00:04:10.326 align:middle
or a "latest recipes" query,
to give two examples.

00:04:11.146 --> 00:04:14.656 align:middle
The second way to choose items is manually:

00:04:15.136 --> 00:04:19.606 align:middle
the admin user literally selects
which they want from a list.

00:04:20.336 --> 00:04:23.946 align:middle
We're going to start with the
first type: the dynamic collection.

00:04:24.596 --> 00:04:28.806 align:middle
We don't see "Dynamic collection"
as an option yet because we need

00:04:28.806 --> 00:04:32.466 align:middle
to create one of those pre-made queries first.

00:04:33.046 --> 00:04:35.966 align:middle
Those pre-made queries are called query_types.

00:04:36.556 --> 00:04:40.816 align:middle
We could, for example, create
a query type for Recipe called

00:04:40.816 --> 00:04:43.766 align:middle
"Most Popular" and another one called "Latest".

00:04:44.506 --> 00:04:46.036 align:middle
How do we create those?

00:04:46.576 --> 00:04:53.826 align:middle
Head back to the config file, add query_types
and below that, let's say latest_recipes.

00:04:54.476 --> 00:04:57.816 align:middle
Once again, this is just an "internal name".

00:04:58.346 --> 00:05:01.606 align:middle
Also give it a human-readable
name: Latest Recipes.

00:05:02.306 --> 00:05:04.296 align:middle
So... what do we do now?

00:05:05.046 --> 00:05:06.376 align:middle
If we head back and refresh...

00:05:07.706 --> 00:05:13.636 align:middle
we get a very nice error that tells
us what to do next: Query type handler

00:05:13.636 --> 00:05:17.136 align:middle
for latest_recipes query type does not exist.

00:05:17.136 --> 00:05:22.436 align:middle
It's trying to tell us that we need to build
a class that represent this query type!

00:05:22.936 --> 00:05:24.046 align:middle
Let's do it!

00:05:24.876 --> 00:05:30.626 align:middle
Over in the src/ directory, I'm going to create
a new Layouts/ directory: we'll organize a lot

00:05:30.626 --> 00:05:32.986 align:middle
of our custom Layouts stuff inside here.

00:05:33.706 --> 00:05:36.226 align:middle
Then add a new PHP class called...

00:05:36.546 --> 00:05:40.106 align:middle
how about LatestRecipeQueryTypeHandler.

00:05:41.476 --> 00:05:44.986 align:middle
Make this implement QueryTypeHandlerInterface...

00:05:45.436 --> 00:05:49.136 align:middle
then go to "Code Generate"
(or "command" + "N" on a Mac),

00:05:49.526 --> 00:05:52.906 align:middle
and select "Implement Methods"
to add the four we need.

00:05:53.646 --> 00:05:55.966 align:middle
Nice! Let's see...

00:05:56.176 --> 00:06:00.426 align:middle
I'll leave buildParameters() empty for a
minute, but we'll come back to it soon.

00:06:01.176 --> 00:06:04.296 align:middle
The most important method is getValues().

00:06:04.746 --> 00:06:08.796 align:middle
This is where we'll load and return the "items".

00:06:09.486 --> 00:06:15.756 align:middle
If our recipes were stored on an API, we
would make an API request here to fetch those.

00:06:16.316 --> 00:06:20.226 align:middle
But since they're in our local
database, we'll query for them.

00:06:20.846 --> 00:06:25.876 align:middle
To do that, go to the top of the
class, add a __construct() method

00:06:26.346 --> 00:06:29.766 align:middle
with private RecipeRepository $recipeRepository.

00:06:30.546 --> 00:06:36.106 align:middle
Then, down in getValues(),
return $this-&gt;recipeRepository...

00:06:36.586 --> 00:06:39.456 align:middle
and use a method that I already created inside

00:06:39.456 --> 00:06:44.436 align:middle
of RecipeRepository called
-&gt;createQueryBuilderOrderedByNewest().

00:06:45.366 --> 00:06:50.606 align:middle
Also add -&gt;setFirstResult($offset)
and -&gt;setMaxResults($limit).

00:06:51.546 --> 00:06:57.096 align:middle
The admin user will be able to choose how many
items to show and they can even skip some.

00:06:57.626 --> 00:07:03.076 align:middle
And so, Layouts passes us those
values as $limit and $offset...

00:07:03.076 --> 00:07:04.706 align:middle
and we use them in our query.

00:07:04.706 --> 00:07:08.806 align:middle
Finish with -&gt;getQuery() and -&gt;getResult().

00:07:10.536 --> 00:07:16.556 align:middle
Perfect! Below, for getCount(),
let's do the exact same thing...

00:07:17.516 --> 00:07:21.796 align:middle
except we don't need -&gt;setMaxResults()
or -&gt;setFirstResult().

00:07:21.796 --> 00:07:27.236 align:middle
Instead, add -&gt;select('COUNT(recipe.id)').

00:07:28.276 --> 00:07:32.326 align:middle
I'm using recipe because,
over in RecipeRepository...

00:07:32.796 --> 00:07:38.506 align:middle
if we look at the custom method, it
uses recipe as the alias in the query.

00:07:39.826 --> 00:07:44.496 align:middle
After that, update -&gt;getResult()
to be -&gt;getSingleScalarResult().

00:07:44.696 --> 00:07:49.936 align:middle
Phew! That was a bit of work,
but fairly straightforward.

00:07:50.746 --> 00:07:53.936 align:middle
Oh, and for isContextual(), return false.

00:07:54.776 --> 00:07:58.016 align:middle
We won't need it, but this method is kinda cool.

00:07:58.636 --> 00:08:03.706 align:middle
If you return true, then you can read
information from the current page

00:08:03.916 --> 00:08:09.216 align:middle
to change the query - like if you
were on a "category" page and needed

00:08:09.216 --> 00:08:11.766 align:middle
to list only products in that category.

00:08:12.646 --> 00:08:14.456 align:middle
Anyways, that's it.

00:08:14.916 --> 00:08:17.986 align:middle
This is now a functional query type handler!

00:08:18.496 --> 00:08:20.276 align:middle
But if you go over and refresh...

00:08:20.556 --> 00:08:22.416 align:middle
it still doesn't work.

00:08:22.876 --> 00:08:24.376 align:middle
We get the same error.

00:08:25.146 --> 00:08:30.186 align:middle
That's because we need to associate
this query type handler class

00:08:30.576 --> 00:08:34.726 align:middle
with the latest_recipes query
type in our config.

00:08:35.446 --> 00:08:39.826 align:middle
To do that, we need to give the service a tag...

00:08:39.826 --> 00:08:44.386 align:middle
and there's a really cool way to
do this thanks to Symfony 6.1.

00:08:44.976 --> 00:08:49.216 align:middle
Above the class, add an attribute
called #[AutoconfigureTag()].

00:08:50.186 --> 00:08:55.296 align:middle
The name of the tag we need is
netgen_layouts.query_type_handler:

00:08:55.976 --> 00:08:58.486 align:middle
this is straight out of the documentation.

00:08:59.136 --> 00:09:04.536 align:middle
We also need to pass an array with
a type key set to latest_recipes.

00:09:05.076 --> 00:09:10.946 align:middle
This type must match what we have in
our config: it ties the two together.

00:09:11.946 --> 00:09:12.836 align:middle
And now...

00:09:14.426 --> 00:09:16.076 align:middle
the page works!

00:09:16.556 --> 00:09:18.116 align:middle
If we click on our Grid block...

00:09:20.136 --> 00:09:23.156 align:middle
we can switch to "Dynamic collection".

00:09:23.616 --> 00:09:26.886 align:middle
Awesome! I'll hit apply and...

00:09:27.466 --> 00:09:30.156 align:middle
everything immediately stops loading!

00:09:30.776 --> 00:09:33.146 align:middle
When you have an error in the admin section,

00:09:33.446 --> 00:09:37.056 align:middle
there's a good chance it'll
show up via an AJAX call.

00:09:37.806 --> 00:09:41.366 align:middle
Often, Layouts will show
you the error in a modal.

00:09:41.746 --> 00:09:47.056 align:middle
But if it doesn't, no worries: just
look down here on the web debug toolbar.

00:09:47.746 --> 00:09:50.286 align:middle
Yup! We have a 400 error.

00:09:51.196 --> 00:09:54.696 align:middle
Let's fix that next by creating
a value converter.

00:09:55.216 --> 00:09:57.686 align:middle
Then we'll make our query even smarter.

