Playing
01. Quick and Easy Nested Categories in Laravel

Episodes

0%
Your progress
  • Total: 20m
  • Played: 0m
  • Remaining: 20m
Join or sign in to track your progress

Transcript

00:00
Let's talk about a problem that comes up a lot in development. That is nesting items, and particularly nesting items for a potentially unlimited amount.
00:09
Obviously you wouldn't have an unlimited amount of items, but there are loads of challenges that we'll come across when we try and implement this solution with traditional relationships within Laravel or any other framework. So you can see here I've got a kind of example nested category list,
00:26
and it's important to note that what we're going to cover in this video works for any kind of nested data, so it's not just tied down to simple categories. It could be a list of comments on a blog or anything like that. So you can see here that we've got two root categories.
00:41
That would just be a category model, and for each of these items or for each of these models, we'd probably have some kind of parent ID. We're going to do that in the database in just a moment. Now under this, this parent or this category would have a parent ID of this one, and so on and so forth for these, and then we have categories under them as well.
01:01
So these parents IDs would be these ones, and what we want to arrive at by the end of this video is the ability to just keep creating nested items, so items further down the tree. And really importantly, we don't want this to have any kind of speed impact on our application. We're first of all going to look at a traditional way that we might do this,
01:26
and we're going to see a problem we come across with eager loading. Now we can eager load a certain amount of category children, but we're going to get to a point where we're going to have to just eager load in a hundred items, so we'll take a look at that in just a second.
01:41
So this is what we're doing. Really importantly, while you are watching this, bear in mind this can be used for any kind of data, not just categories. I'm covering categories because they tend to be the most talked about when we speak about nesting items.
01:55
Okay, so I've gone ahead, and if I just close this off, already created out a Laravel project. The only thing that I've done is just updated my database here. I haven't even run our migrations yet, so I'm going to go ahead and just run my default migrations, and I've got my database open just here, and we'll go ahead and create a model for our categories in just a second.
02:15
I've also got this open in our browser along with the package that we're going to use to achieve this, which is a really, really good package, Laravel adjacency list. Okay, let's first of all head over to routes and web. We're not going to make this look pretty. We're literally just going to grab the welcome view and just dump a load of data out in there.
02:33
So let's head over to welcome.blade.php, clear this out, and then over in our browser, we essentially end up with an empty page. Okay, let's just look at the traditional way that we might handle this. So we're going to go ahead and say make model category, and we'll generate a migration alongside
02:49
of that. We could also generate a factory, but what we're actually going to be doing is creating the data manually in the database just so we get a really good idea of what we're doing. So let's go over to that create categories table migration, and let's fill this in. So let's add some really basic data here. Let's just add a string for the title. We won't bother
03:10
with a slug or anything like that. And really importantly, what we want to include in here is either an integer or a unsigned big integer with a parent ID. So this is what I was speaking about at the start of the video where we're going to attach the parent ID to a particular category. We're going to add a scope in there so I only get the root categories and all that kind of stuff.
03:33
So really importantly, this can be nullable because if it's null, that means it's a root level category. Okay, let's go ahead and just migrate this really quickly, and then we'll create some example data and see what we can do about outputting it. So if we head over to our database, let's open categories, and let's just start with
03:50
something really simple. Let's just say top level and one. The parent ID for that will be null. And let's fill in the created and updated updates just in case. And let's create another one which is top level two. So let's add that in there. Save them out. Okay, so we've got two categories which are both going to be shown at the top level.
04:11
Let's just head over to our roots file, and let's just grab them categories out. Of course, we're just messing around here. So things are going to get a little bit messy, but that's fine. And let's pass them categories down to our views so we can iterate through them. Okay, so over in welcome.blade.php, to iterate through these, we're just going to use a standard
04:33
for each loop. And we are also going to be talking in this video about recursive blade components as well. So we can just continually iterate on all of the children, regardless of how many we add to the database. So let's say for each categories as category. And let's just fix that up. And we'll, of course, end that for each. And let's just put a div in here and just output
04:54
the category title. And it's pretty obvious at this point what we're going to see. So let's just go for category title. And I'm actually going to stick the ID next to this as well, just so we can keep an eye on which parents we need to add. Okay, so if we just give this a refresh, sure enough, we have top level one with an ID of one and top level two with an ID of two.
05:16
So how would we traditionally go ahead and look at children of this category? Well, we know that we've got this parent ID. So if I create another item in here, let's say child one, and I want that to be under top level one. So imagine if this was some sort of e-commerce store, and you had shoes and something else, shoes might have a category
05:38
Nike or Adidas or anything like that. So let's go ahead and add that in again with just a created update. So we have it. And what we should expect to see now is if we define a relationship in here, we should see child one under top level one. Now at the moment, because there's no distinction between any of these categories, we see child output at the root level. So inside of category,
06:01
the first thing that we'd need to do is add some kind of scope. So I'm going to add a scope called root. What this will mean is we'll get our query builder in, and we can say query where null parent ID. So if we head over to our web routes really quickly, and we say category root, that's going to exclude everything where the parent ID is filled, e.g. it's not null.
06:29
So now we've successfully got our root level categories. Now inside of the category model, we wanted to find out a relationship in here called children, which will return to us the items where the parent ID matches. So we're going to go ahead and return this and has many. And we're going to go ahead and say category. So we are referencing the same model. And in here,
06:53
we want ID and parent ID. So let's just try this out. So let's go over to our welcome page here. And we're going to go ahead and do a for each inside of here, because we want to output the children. And we're going to say category children as child. And in that for each. And in that for each. And then inside of here, we want to pretty much do exactly the same thing
07:19
here. So I'm just going to wrap that in another div. And that, of course, now becomes child. So let's just take a look at what we get. Okay, so I think the relationships probably not set up properly. Let's say parent ID. I think that should be okay. Let's go over. There we go. So we get the child under the top level. Now just while we're messing around, we don't have any
07:40
styling in here. I'm just going to go ahead and add in a inline style. Not great, but let's go ahead and do that anyway. And I'll just set the margin left to 20 pixels, just so we see that bumping inwards, so we actually know what we're doing. So we've got top level one, child one. Now, if we wanted to create a child under child one, so I'm going to say child two,
08:02
then the parent ID would reference ID of three. So we just nest down as you would expect. Now, if we come up under here and we give this a refresh, we're not seeing anything because what we would technically need to do is now do another for each loop inside of here, which is one of the problems we're going to discuss in this video.
08:20
So we could say child children as, and at this point, because we're going to eventually use recursive nesting of blade components, we won't come up with this problem, but we're going to have to come up with a name of this child. So I'm just going to call this sub child, like you could just call it anything. So let's change this over to sub child, and let's go over and give this a
08:40
refresh. And there we go. So we have another child under here. Now, this might seem like a good solution, but it is not, and it is not a good solution for a couple of reasons. The first one is we need to eager load in all of these children, and we don't know how many children there are. So there might be 10 different levels of nesting, in which case we would have to account for 10
09:03
different levels of nesting in our eager loads, and we're going to take a look at that in just a second. The second problem is we're doing this in our templates, which means we want to recursively iterate through and potentially allow for hundreds, thousands of different nested levels. Let's tackle our queries first. We're going to come over to Laravel debug bar and install this,
09:23
just so we can see how many queries we're actually executing here. So very, very easy to install. We just do a compose require. Laravel's auto loading takes care of everything, and if we come back over to the browser, you can see that this has already been pulled in for us. Now, if we open up our queries tab, sure enough, for every single level of nesting,
09:42
we're executing a query here, because the query has to find each of the children underneath here. Now, we can get rid of this, like I said, with eager loading, but as soon as we start to add more children, we're going to have to add another layer of eager loading. Let's take a look at this eager loading now. So for category, we're going to say with children, that's going to get rid or
10:04
at least eager load one level of this. So you can see parent ID in one, two, but we have a third level of nesting here. So we're going to have to say children, and then the children of them children. If we give that a refresh, sure enough, we get more or less the same problem. And we could keep going and doing this for as many nests as we potentially had. Now, this is a little bit more
10:26
eager loading than we need. So we're actually creating the same amount of queries. And this is where this kind of comes in to be a problem. We have to know how many nests there are in our code based on what's in the database. So it's really, really difficult to do this, and it's not a great long-term solution. We can eager load as much as we want, but then the more categories we add,
10:50
the more queries we are going to create. So this solution is not great. We're going to take a look now at using that package here, which is Laravel adjacency list to solve this. So let's go and just install this package. So it's pulled in. So we'll just grab the command to install this, and then we will get rid of what we have done so far. And then we'll talk about how to use this package. So I'm
11:13
going to go ahead and just get rid of the eager load here because we're not going to need that. And we're also not going to need the root scope that we have created because this package is going to handle this for us. What I'm also going to do is get rid of the inner for each loop just here because we're not going to need that either. And we'll just stick to two levels for
11:32
now and just see how this package works. And then we'll look at the recursive blade component that we're going to create to handle a potentially unlimited amount of queries. Now, this package comes with a huge amount of functionality. So if we go to the included relationships, you can see here that once we set this all up, we can actually access the children. So that
11:52
children relationship is in there for us already. Now, the difference between the solution that we came up with and this package is this package works with common table expressions. We won't dive too much into what common table expressions are. A quick Google search will give you all the information you need. But essentially, a common table expression lets you, within SQL, define a
12:13
temporary set of data in the execution of any SQL statement. So basically, what we can do is grab all of the nested data we need within one query and then kind of put it together. That's a really simplistic way of telling you how this works behind the scenes. But because we've got Laravel debug bar installed, we'll actually see the query that this package puts together for us.
12:37
Now, we're going to look at a very specific subset of this package, which is building up a tree, which means we don't need to egoload anything. So that's where that common table expression comes in nicely, builds everything up for us, gives us a collection back with all the data that we need, and we can just iterate through it. So let's go back over and
12:56
let's just take a look at what we've got so far. OK, so at the moment, we're in a bit of a mess. We've got way too many queries. We've got the children being output where the root level should be, which is not what we want. So let's go over and take a look at using this package. So if we come over to the category model, we can get rid of that root scope now. We can actually get rid
13:14
of this relationship as well. And let's just go ahead and pull in the, if we just come up to installation, has recursive relationships trait that this package comes with. So we can go ahead and just re-index our workspace really quickly and go ahead and pull this in at the top. So that comes from that package. That's pretty much all we need to do. So if we come over now and give
13:36
this a refresh, we get exactly the same result again. OK, so now what we're going to do is build up this tree within our root or controller, which you would probably be using. So for this category, we're going to say tree and then we're going to say get. After that, we're going to say to tree. Now let's just look at what this has given us. So let's die dump on these categories
14:00
and let's take a look at the result directly from this dump. So you can see that this is a collection. It's a specific collection from this package. And the items that we have at the top are, of course, our root level categories. So the way that we set our database up originally with this parent ID is exactly what this package needs to work. You can change the name of that,
14:20
but all of that is in the documentation for this package, which I highly recommend you read through. So that's working. And we have, if we just check this out, our top level one. And if we just come down to this one, our top level two. Great. So we've got them two top levels. Now, the difference is if we look at top level one and we look at the relations here,
14:39
you can see that we've got another collection with the children already in there. And that's what's been built up when we used the tree get to tree functionality. So if we go and look at the items in here, you can see that we've got another category and we have child one. Now, inside of here, we've got more relations. So essentially, we've got all of the data inside of our database,
15:02
how we would expect to see it already built up for us. So we've just got a huge collection of every single item that we need to get this working. So now what we can do, get rid of this die dump, go over, give this a refresh. And there we go. You can see that we've got our top level one. We've got our first child there and our top level two. Notice there's only one query. So you
15:25
can see that we've got a recursive common table expression here, which is selecting that specific depth of everything. And then the package is putting out all of this data that it receives via this query into a collection for us. So we've got one query now. Now, let's go over to welcome.blade.php and let's actually bring back that other child iteration. Give that a refresh.
15:50
There we go. So we've got another child in there as well. And if we had more children, we would still only end up with one query. So in terms of queries, this is very, very quick. We've got one query. We've not had to egoload anything and we're getting everything we need back. The last thing that we need to focus on is how we deal with all of this. Because if I go over
16:11
to the database and I add another child under, say, child four, we're not going to see that because we're going to need to iterate over that again. So we don't see any items in here. So let's leave things as they are. And we could add more to top level two, but you kind of get the idea. And let's take a look at recursive blade component to deal with outputting a potentially unlimited
16:33
amount of children. So let's think about this. We will get rid of this for each loop in here. What we essentially want to do is create a component which outputs the title and any other information, in our case, our ID. And inside of that component, that blade component needs to call itself to re-render the same component for that category's children. And this will end in a kind
16:56
of recursive loop of rendering out that blade component. If this doesn't make sense, don't worry. Once we've got it all in place, it should do. OK, let's get started on this blade component. So we're going to go ahead and use PHP artisan make component. And I'm just going to call this category item. Of course, you can call it whatever you want. Now, I want this to be anonymous. So I'm
17:15
going to go up to app and view. I'm going to get rid of category item here. And I'm going to go ahead and just run PHP artisan view clear. And now essentially what we end up with if we just open this up and go over to views and components is a category item that we can just start to use and call on our page or render on our page. So we still want this top level for each because
17:40
that's what's going to kick the process off of rendering out all of these category items. What I want to do, though, is grab the inner section of this and replace that with that blade component. So that is category and item. And of course, into this, we're going to need to pass the category so we can access it within this component. So let's go ahead and pass
18:01
the category that we are currently wanting to show into that as a prop. So if we open up category item, we can go ahead and explicitly define up here that we're accepting in a category prop. And we can even add a default to it if we wanted to. And then we're just going to replace all of this out with what we removed from the welcome page. So now that we've done
18:24
this, let's just go over and give this a refresh. And you can see that we pretty much get the same result just without that additional for each loop that we manually added in here. Now what we need to do is call for all children this entire component again. And what that means is it's going to constantly go through and find the children for every single category we ever
18:44
pass into it. So we're going to go and just do this pretty roughly. Of course, we're not styling this up properly. But we're just going to say category item. And we're going to pass in the category. Now the category that we want to render in this case is the child. So let's go ahead and say child. So now what's happening is if we just go back to welcome.blade.php, we're rendering out
19:08
two top level category items. Inside of that, we are displaying that data. We're looking at the children for each of them. And in our case, the children for the top level one is this child here. That will render that out. Then this component will go ahead and do the same thing. So it will display that child. But then it will look for that child's children as well. And it will go into here.
19:30
And then it will do the same thing. It will render out that information. And it will look at the children of that child's child. So again, it just goes down and down and down, recursively rendering out this component for every single nest item. So let's go over, give this a refresh. And there we go. We ended up with one query to grab all of this data. And we now have a recursive blade
19:51
component, which will iterate through for however many children we create. So let's just go over to the database and play around with this. I'm going to create another child in here, four. And that's going to be parent ID of two. So that's going to come on the top level of two. I'm actually going to go ahead and add another one under one of these. So let's just add it to child three.
20:13
And we'll just see how this changes. We go over, give this a refresh. There we go. We just get another child under there. This child's rendered out. And everything will always be kept up to date, regardless of how many children we add in the database. So like I said before, that Laravel adjacency list package contains a huge amount of functionality, not just for building up trees
20:34
like we've seen here. But that tree functionality that it provides is perfect for building up trees like this within categories, or pretty much any other data that you need to be nested. We've looked at a custom solution by building a category item blade component, which recursively iterates through for every single node in that tree list.
1 episode 20 mins

Overview

To solve a potentially unlimited amount of nested items like categories, you need more than recursive Eloquent relationships. Let's figure out how to easily build a tree of items with a single query, and use recursive Blade components to output the result.

Alex Garrett-Smith
Alex Garrett-Smith
Hey, I'm the founder of Codecourse!

Episode discussion

No comments, yet. Be the first!