تماشای این درس نیاز به اشتراک حرفه‌ای دارد.

Problem: Controller Bloat0:00

One thing that I've been meaning to research and implement for a while now is a more widget-like approach and structure. For example, have you ever had a controller action that grows and grows each time you add a new section? And you'll often see this on a home page, or a dashboard, or a stats page, or as if you think of a typical WordPress blog, all of those widgets in the sidebar. That would be another example. So maybe you start out with trending articles, and you pass that to your view. All right, but later you add a section for recent news, and then you add another one for popular users. You keep adding these little widgets to the page, and every time you do it, you have to return to the controller action to build up the data it requires. Maybe another one for latest discussion threads, and again it keeps growing and growing. It gets messy very quickly. And worse, this only covers the data portion. Once you get to your view, how you structure things,

Proposed Widget Pattern0:45

for latest discussion threads, and again it keeps growing and growing. It gets messy very quickly. And worse, this only covers the data portion. Once you get to your view, how you structure things, again, is entirely up to you and often is not consistent. Maybe the way you did it a year ago is not the way you do it now, so there's no convention really being followed. For example, maybe you have, if each of these divs represents like a little widget or a little section of the site, each of these will have all sorts of HTML that depends upon data that's being fetched and declared in your controller action. So again, it gets messy pretty quickly. So I was thinking it might be nice to instead adopt something like this. What if we had a widget Blade directive we created, and we'd have something like this. So now, if we took this approach, we'd have a companion class called TrendingArticles. That class would declare its associated view, it would

we created, and we'd have something like this. So now, if we took this approach, we'd have a companion class called TrendingArticles. That class would declare its associated view, it would declare its data, and it would run any queries it needed to. And at least in my eyes, that seems fairly clean. Because if you think about it, there's no shortage of solutions to this problem. For example, if your project uses the Vue framework or React, whatever you want, you could always just create a view component here. This view component would declare its own template. It would fire an AJAX request to your server to fetch the data it needs. But not every project uses Vue or JavaScript at all. Or maybe your backend doesn't really have an API that you can easily expose to provide the data the view component needs. So it would be nice if we could do this all on the PHP end, which is what this allows for. Now, in researching this, I came across a few solutions.

Evaluating Alternatives2:20

to provide the data the view component needs. So it would be nice if we could do this all on the php end, which is what this allows for. Now, in researching this, I came across a few solutions. So of course, we could use Laravel's view composers, which are available out of the box with the framework. We could probably make that work. But in general, I don't love composers. And that's because, for example, a year from now, after you implement it, if you come across a variable like this, it's not immediately clear where it came from. So for example, maybe you go to the controller action, and you don't, in this example, you don't see it. So you think, well, where is it? Now you have to hunt around. Well, is there some view composer I don't know about that's providing data to this view? It requires intimate knowledge of the framework, which I don't love, or at least of the application. And I don't love that. I want it to be very clear. And that's why with this

data to this view? It requires intimate knowledge of the framework, which I don't love, or at least of the application. And I don't love that. I want it to be very clear. And that's why with this approach, I immediately know, okay, there's going to be a TrendingArticles class. That class will declare any data it needs. It'll declare its associated view. And again, if we're following a convention, maybe I just know the view for this will be the kebab case version of the class name. Done. I don't even have to think about it. But yeah, view composers, you could probably get that to work. Freak from Spady, he told me about Laravel BladeX. And that seems to offer, it almost looks like a view component, but this works with Laravel and Blade. My understanding is you can make it work with this approach as well. That's worth considering. And then I also came across a GitHub project called Laravel Widgets, which seems to be the closest to what I was

Implement Widget Class3:54

you can make it work with this approach as well. That's worth considering. And then I also came across a GitHub project called Laravel Widgets, which seems to be the closest to what I was originally going for. So if you'd like some out of the box solutions, definitely consider those. I don't imagine for my needs, it'll take long to implement at all. So let's get started. Now, ultimately we will have a Blade directive, but until then, let's just do it in long form. And then at the very end of the video, we will turn that all into a Blade directive to keep it nice and clean. So I think what we would do is resolve the associated widget. So for example, if we have this trending articles, that would be our way of saying, all right, well, we need to resolve, look in a widgets directory. We're going to find a TrendingArticles class and resolve it. That way it can fetch any dependencies it requires. And then maybe to start, we call a view method and

look in a widgets directory. We're going to find a TrendingArticles class and resolve it. That way it can fetch any dependencies it requires. And then maybe to start, we call a view method and we echo that. All right, let's see. So if I come to Firefox, of course, it's going to fail because at the moment that class doesn't exist. So that's our first step. In the HTTP directory, I'm going to add a new directory called widgets. And then within there, we'll add a class called TrendingArticles. So yeah, if you want to think of this as a view composer or a view model, you can. I don't really care how you think of it. And then we'll have a view method here. And this would return its associated view. How about widgets.trending_articles? Now, again, we're going to hard code this here. But when we're done, I want to set it up so that I don't even have to create this method because we're following a convention. And that convention will likely be, look in the widgets

this here. But when we're done, I want to set it up so that I don't even have to create this method because we're following a convention. And that convention will likely be, look in the widgets directory and look for a kebab case version of this class. And that way, every time I create a new widget, I don't have to repeat myself. But until then, we'll write it out. So now in the views directory, we'll add a new directory, widgets. And within there, we'll call it trending-articles. Now, if you want, some people will precede it with an underscore just as a hint that this isn't a full view. It's more like a partial. I don't think it's necessary. And then here, we'll add trending-articles. Okay, so if I come back to Firefox and I give it a refresh, this will in fact work. And further, we could do something like this. Let's imagine you have some articles. And maybe it's just a simple array for now. So we'll do something like that.

Auto-Pass Widget Data6:14

this will in fact work. And further, we could do something like this. Let's imagine you have some articles. And maybe it's just a simple array for now. So we'll do something like that. Let's see if we can get that to work. Now, if I come back to Firefox, of course, it fails because the articles variable is not available to it. All right. So of course, you know when you call the view function, you could pass an array or you could also do with. So you could say with articles is this one, two, three. And of course, that's going to work. But it would be nice if instead, and this is something Laravel's mailable classes offer, so we're going to follow that approach. What if we made any public property available to the view automatically? So if we have something like this, I'd love to be able to write this code here and have that work. Any public property is available within this view. Let's see if we can get that to work. If we run it, of course,

like this, I'd love to be able to write this code here and have that work. Any public property is available within this view. Let's see if we can get that to work. If we run it, of course, no, it doesn't work. We have some work to do. Okay. So, you know, there might be something Laravel offers to make this work because it is used in a number of places in the framework. But to my knowledge, I don't know how. So educate me in the comments below if you know an easy way to do this. Until then, I'm just going to reach for the Reflection API. So maybe we'll have a method buildViewData. But notice as you add these little helper methods, it makes your main widgets less clean. I don't want to add all this crap every time I create a widget. So instead, we're going to have each of our widgets extend a parent abstract class. Okay. So let's do this. In widgets, we'll add a new class here, Widget. It's going to be abstract because you'll never

we're going to have each of our widgets extend a parent abstract class. Okay. So let's do this. In widgets, we'll add a new class here, Widget. It's going to be abstract because you'll never instantiate this directly on your own. But now we can take data like this and move it up. Like so. So now your Widget remains nice and clean, but any of that extra work can be done up here where likely you're not visiting this class all the time. And that's an important thing. Take the code that you're not going to be looking at all the time and tuck it away when you can. So then here, again, this isn't quite what we want, but to give you an idea, one, two, this method would need to be called or passed along with the view like this, with this buildViewData. And that would work. So come back and refresh. And yeah, that works. But yeah, again, we're back to this situation where each individual Widget needs to know

with this buildViewData. And that would work. So come back and refresh. And yeah, that works. But yeah, again, we're back to this situation where each individual widget needs to know to call buildViewData. And I just don't like that. So instead, what if we had a second method? Maybe something like loadView. And this method would call your view method and then pass through the view data. We're kind of working on this on the fly. So we'll see how this works. Now, if we took that approach and we come back, once again, I keep this nice and clean, which is most important to me. But it does seem like now from our Blade directive, ultimately, we would need to call this loadView method. So let's see. Let's go back to welcome.blade.php. And we would have to do this. And if we come back to Firefox, forgot to return it properly. Yep. There we go. And that would work. But next, we're not going to hard code the articles. Instead, we're going to

have to do this. And if we come back to Firefox, forgot to return it properly. Yep. There we go. And that would work. But next, we're not going to hard code the articles. Instead, we're going to read all of the public properties here. So yeah, again, I'm going to reach for the Reflection API, but maybe and hopefully there's a simpler way we could do it. Maybe something Taylor has available that does it automatically. Until then, let's do this. I'm going to instantiate the ReflectionClass. So we will reflect into the current object. And then we're going to call method getProperties. So take a look at this. If we die and dump these properties, and I come back to Firefox, sure enough, we do get the one that includes that public property. The only problem, though, is if I switch back, it's going to include all properties. So if I have some kind of private variable, and I come back, that will be included as well. And we don't care about that. I only want

though, is if I switch back, it's going to include all properties. So if I have some kind of private $variable, and I come back, that will be included as well. And we don't care about that. I only want the public properties. So I can fix this by passing a method to get properties, a filter. And the filter will be I only care about public properties. I'm sorry, public properties. So now if I come back and refresh, we're back to just the one. Great. So let's come back and get rid of that. And we end up with something like that. Let's do a cleanup here. Now, let's see, we're going to have our viewData. So we'll just build up an array that we will then pass to the with method. So let's see. For each $properties as $property, how do we do this? So right now, this is an array of the reflected properties. So I could just say, all right, build up our viewData. We want the key to be the name of the property. So I could say $property, give me the name. So in this particular

of the reflected properties. So I could just say, all right, build up our view data. We want the key to be the name of the property. So I could say property, give me the name. So in this particular case, the name is articles. And the value should be property, getValue. Get the value associated with it. So now I could return the view data. And in fact, first, let me just show you what this looks like. Come back and refresh. getValue expects, oh yeah, of course. It wants to know the current instance so it can grab that property. And now if I give it a refresh, we have an array of everything for the view. So that's the way I'm doing it. I think and I would hope there might be kind of a more streamlined way to do it, but I just don't know at the moment. So this is what we get. And if I come back and refresh, everything works, which means this widget is looking good to me. You can append to it, and it instantly works. We could do things like this. Maybe we

get. And if I come back and refresh, everything works, which means this widget is looking good to me. You can append to it, and it instantly works. We could do things like this. Maybe we have title, and that would be trending articles. So you just create a new property, and that's immediately available in the view, like so. Still works. Okay. But next, here's the next problem. What about situations where, of course, articles will usually be a database query? So at the moment, we would have to do something like this. Maybe create a constructor, and there you could assign to articles. And I actually have an example article table. I could say take three and give me those. And then assign it to this public property. So yeah, that would work. And if I come back and refresh, in this case, we're getting a whole collection. So I need to spit out whatever we want, the title. Yeah. And that would work, but I don't love this. Maybe instead, we just have a

refresh, in this case, we're getting a whole collection. So I need to spit out whatever we want, the title. Yeah. And that would work, but I don't love this. Maybe instead, we just have a public method called articles, and that would perform this query. And whatever gets returned here, just like public properties, would instantly be available in the view. I kind of like that. And again, this is stuff I've seen Laravel do. So we're following a good convention there as well. Okay. But of course, it's going to fail. So we need to make that work. Let's go back to our parents. And yeah, do we do the same thing? I'm working this out on the fly with you. So chime in the comments below if you have some ideas. But maybe we just have to do it like this. The Reflection API has a getMethods class. So we're going to do the exact same thing. ReflectionMethod. So reflect in and give me all public methods. So let me show you what that looks like. If we run it, give it a

API has a get methods class. So we're going to do the exact same thing. Reflection method. So reflect in and give me all public methods. So let me show you what that looks like. If we run it, give it a refresh, we now have three. Yeah. So we want this to be included, but we have these other methods, and those should not be passed to the view. That's going to break things at the least. So do we... I mean, of course, we could just say exclude. If we don't have a big surface area, we could just say, all right, read all the public properties, but forget the view method and forget load view. That would be one way just ignoring it. I'm not sure if it's the best way. Let's see. Hmm. What if we said here, let's say for each methods as method view data. So create a new key for the method and its value. We wouldn't do anything like getClosure. I think in these cases, we actually want to call the method and make it equal to whatever is returned there.

for the method and its value. We wouldn't do anything like getClosure. I think in these cases, we actually want to call the method and make it equal to whatever is returned there. Um, so we need the name. Can we do that? And then we would say this->name, and then we call it as a method. So let's see viewData. You know what? I feel like this isn't going to work though. Yeah, we're going to use up all this memory. Yeah. And that's because at the moment, it's trying to, it's probably getting into some recursive thing where it keeps calling the loadView method, which calls view, but then we call it again. Those need to be ignored. So maybe we could say if in_array, the needle will be, let's do this method, getName. See if that is in this array, uh, of loadView and view. And if it is continue, because we don't care in that case, otherwise assign it. Yeah. This is feeling a little messy, but I bet that fixes it. Yeah,

Um, we're only using this properties array in one place. So maybe I can inline that and then we'll do the same thing here. And then maybe I could say I could reverse it. If it's not in the array, then it's safe to add. And we just removed that extra line or two. And that's what we end up with. So yeah, do let me know if you have a better idea for how to do this, especially that section, which I don't love. Okay. Anyways, moving on. If I come back, we did say at one point that we want this method to be optional. If you, if you are storing the view in a special place, then override it. Otherwise we should be able to automate that. So if I come back and refresh now it fails because, um, there is no method view. Okay. So we go back to our widget and we will declare it up here. But of course the child class can override it if it needs to. Okay. So let's figure out how we can create the view path.

So we go back to our widget and we will declare it up here. But of course the child class can override it if it needs to. Okay. So let's figure out how we can create the view path. So for example, if we dine dump static class, that's going to give us the full path to the child class. But in our case, we just want the class name itself. So Laravel offers a helper called class_basename, or you could also use the reflection API for that. But if we took a look at this, there you go. Okay. So next we want to translate this into the view name. So it would be something like trending-articles. So in that case, we can use a kebab_case helper. Kebab kind of makes it look like the words are on a kebab and it uses the dividers as the capital letters. So if I give that a refresh, there we go. That's going to be the default view name. Okay. So let's save that. And then let's just return a view and the convention will be it's

Create Blade Widget Directive18:01

letters. So if I give that a refresh, there we go. That's going to be the default view name. Okay. So let's save that. And then let's just return a view and the convention will be it's in a widgets directory and the name will be the class name as a kebab case. Okay. Let's see if that works. Refresh. And it does. Okay. So now, um, your, your widgets, if they need to, they can override their default view. Otherwise we follow a convention, which I like. I like knowing there's always going to be a system for where this is located or where that is located. And if I need to, uh, I have the tools to override it, but otherwise I stick with the default conventions. So this is feeling good to me. Uh, the next step, if I go back to our welcome view is we're still hard coding it here. Let's turn this into a widget directive. Okay. We'll store this in AppServiceProvider to start. And we can go to the boot method and register a custom directive like this.

hard coding it here. Let's turn this into a widget directive. Okay. We'll store this in AppServiceProvider to start. And we can go to the boot method and register a custom directive like this. It's going to be called widget, and it's going to accept an expression. Now this can actually get a little confusing. This expression will be equal to everything within the parentheses here. So let's do this. Let's get rid of all this like so. And again, everything within those parentheses will be passed to the expression. So expression will be that including the quotes. Now we already know how to do this. We have to resolve it out of the container. So we want something like that. Uh, but yeah, I can't quite do that. Uh, and that's because that would actually give us the equivalent of this trending article. So notice those quotes. So let's change the name and just trim that off because we don't need them in this case. So trim off any single quotes from the

And you can try bringing it back by clearing the view cache. Yeah, but notice we end up with all this, this weird stuff, right? Generally, this isn't how you want to do it. What we really want to do is return this as a string. So we're going to echo. Let's clean this up. Yeah, it always takes me a minute. I think that's right. So now, um, we'll get around the caching issues. We're just returning a string that will then be executed later. So let's clear that view cache one more time. And if I come back to Firefox, yeah, that's working again. Okay, so now if we go back to trending articles, we can change this however we want. And it's instantly reflected. So now think about it. If we go back to our welcome view, if you add another widget, maybe this would be popularUsers. All right, if we run that, it immediately lets us know there is no popularUsers widget. So we create it.

maybe this would be popularUsers. All right, if we run that, it immediately lets us know there is no popularUsers widget. So we create it. And in fact, things like this, you might add an artisan command, like make:widget. And that way, it'll do all of this for you. And then here, just add whatever you need. Now, if we run it, again, it's going to try to load that view. So again, we're following conventions. It's not something you have to think about every single time. So we add that here. popularUsers. And yet again, let's see if I can steal some of this. In these cases, we're just iterating over an array. Like so. Okay, so now if we go back to our welcome view, we have two different widgets.

In these cases, we're just iterating over an array. Like so. Okay, so now if we go back to our welcome view, we have two different widgets. If we come back and give it a refresh, everything is working like we expect. And again, to my eyes, the nicest part is our controller action no longer has to build up all this data over and over every time you had a new little section or a new widget. Instead, it's responsible for grabbing, of course, the most important pieces of the puzzle. But anything else, like these little widgets we've been talking about, they don't need to be declared there. We can come down here, and if we need to modify things, again, it's not confusing. Like with a view composer, you're thinking, where is that data coming from? Not here. I know. All right, let's check out that trending articles.

again, it's not confusing. Like with a view composer, you're thinking, where is that data coming from? Not here. I know. All right, let's check out that trending articles. Ah, there's the data it needs. And I also know, okay, let's look at the view. We're following the convention. It's always in the widgets directory. So a lot of convenience here. And again, I like how simple and clean this is. So that'll do it for this lesson. I'm going to implement some form of this into the Laravel codebase. If you want to take it a step further, if you're working along, you might think about things like, what about passing data that will then be available in the widget? You know, things like this. You could update the blade directive to make this an option as well. Anyways, that does it, and I'll see you next time.

دوست دارید گاهی خبرهای Laracasts را ایمیل کنیم؟