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

Fixing Broken Tests0:00

Alright, first things first, in adding validation for our posts, we actually broke some of the tests we created before. So if I go ahead and run the tests in the storeTest.php file, if we're storing a post or redirecting, both of those tests are showing as failing, and you'll see that the following errors occurred, the body field must be at least 100 characters. So we need to fix that, and thankfully, because we've extracted all of our valid data to a single location, it's going to be fairly straightforward. Our title is actually fine, it's already greater than 10 characters and less than 120, it's the body that's causing the issue. So I think once again, I'm going to reach for the Alorem Ipsum generator, generate a paragraph of it, and paste it in like so, and then let's run these tests again, and yes, they're now all passing, the data is valid. So make sure that when you finish working on a feature,

Adding Create Link0:48

and paste it in like so, and then let's run these tests again, and yes, they're now all passing, the data is valid. So make sure that when you finish working on a feature, you run all of the tests to ensure you've not actually broken anything in the process of building. The next thing we need to tackle is the fact that even though we have a create endpoint, we have no button to click to create a Post, the user would have to know that this URL existed. And this is such an important feature, such an important part of the application, I think it should actually go right here in our navigation bar. So I'm going to jump into the app layout, and inside our app layout, at the top, we added this little array right near the beginning of the series. So let's go ahead and create a new entry. The name will be create a post, and then we can have the URL, which is going to be our route helper,

near the beginning of the series. So let's go ahead and create a new entry. The name will be create a post, and then we can have the URL, which is going to be our root helper, posts.create, and then finally, we need our root, which will again be posts.create as a string. Okay, so here we go, create a post, and we can click between them to load our create form. Of course, there is a problem with this. If I go to framerate.test/posts, then a guest can see the create a post button. When they click it, they're going to be taken to the login page, but they shouldn't even see this link. Well, we already have a solution for that. We can use that little when check. So here's the when check, and we can drop it right down here. And now, if we once again go to framerate.test/posts, a guest does not see that link in the navigation bar. At the moment, as soon

Creating Post Policy2:13

and we can drop it right down here. And now, if we once again go to framerate.test/posts, a guest does not see that link in the navigation bar. At the moment, as soon as a User signs up for framerate, as soon as they create an account, they can begin adding posts. But what if we want to change that? For example, in order to help prevent spam, we might say that you have to have your account at least a week before you can create a post. You can add comments. You can interact using comments, but if you want to create a post, then you have to wait for a week. Now, we're going to implement that later down the line when we come to spam prevention, but we need to start thinking about how we would implement that. And of course, when it comes to authorization, we already know we have to set up a policy. So let's tackle the policy first, and then we'll think about

would implement that. And of course, when it comes to authorization, we already know we have to set up a policy. So let's tackle the policy first, and then we'll think about how we can tell the navigation bar about the policy in just a moment. So this should be a simple php artisan make:policy. We'll call it the PostPolicy, and we want to link it to the Post model. Okay, let's jump into that PostPolicy class, and we can already start filling some of these out. So a guest or a User can view any Post. So in order to allow a guest to view it as well, we'll make the User type hint optional and we'll return true. The same is true of viewing an individual Post, at least at the moment. So we'll make it optional, and we'll return true. Then we come to create, and this is the method that we're interested in. Can you, as a User, create a new Post? Now, we don't want to make

it optional, and we'll return true. Then we come to create, and this is the method that we're interested in. Can you, as a User, create a new Post? Now, we don't want to make it an optional type hint, you have to be a User. And for now, we're going to return true, but this is what we'll play with when we want to edit this down the line. I'm going to leave the rest of these methods for now, and it should be enough to go into the PostController, override the constructor, and as we did before, we can say this->authorize resource, and we can add the Post fully qualified class name. And with that in place, we should now have the ability to toggle whether or not you're actually allowed to access this page. So, whatever is set to true, yes, I can access the page. If I change this to false, now I get a 403 whenever I reload. But if we go back to the post endpoint, yeah, you can still

Sharing Global Permissions4:20

whatever is set to true, yes, I can access the page. If I change this to false, now I get a 403 whenever I reload. But if we go back to the post endpoint, yeah, you can still see this create a post link, even though clicking it will give you a 403. So, how do we inform the navigation bar, which is available on every single page, whether or not you're allowed to create a post as a User? Well, this is where we have to turn to shared data. We already have an example of shared data if we go into the Vue DevTools, and inside Vue DevTools, I'm on the index. I'm going to go down to this attribute section, and we have authUser. So, authUser is the object for the currently authenticated user. You can see all of this output here. And we didn't add that, did we? We've never output the current user, so where's that coming from? Well, Jetstream is adding that information. Let me show you. We'll need

output here. And we didn't add that, did we? We've never output the current user, so where's that coming from? Well, Jetstream is adding that information. Let me show you. We'll need to find a sheer Inertia data middleware class that's actually buried deep inside the vendor file of Jetstream. You can't change this, but it shows us how it's possible. So, this piece of middleware is going to be executed on every Inertia request, and Inertia is going to share the data passed down. So, ignore this Jetstream key. I'm going to scroll down past that, and here we have authUser. And inside authUser, at the bottom of the method, well, it's returning the user using the toArray method, which is where all of this information is coming from. Just to play with this a second, why don't we instead return an empty array like so, and we'll say foo is equal to bar, and refresh the page. Well, we're going to

is coming from. Just to play with this a second, why don't we instead return an empty array like so, and we'll say foo is equal to bar, and refresh the page. Well, we're going to have some things break on us, but now the User object is nothing more than foo and bar. So, that's how Jetstream is sharing information with the front end, but as a user, as the end user, can we share information with Inertia on every request? Well, of course, the answer is yes. If you open your file browser and you go to app, HTTP, middleware, inside that directory is a piece of middleware called HandleInertiaRequests, and the share method inside HandleInertiaRequests is where we would put any data that we want to share with the front end. So, for example, if here I output foo bar, and then let's refresh the page, come down to attributes, here is foo bar, and every single Inertia page will include

the front end. So, for example, if here I output fooBar, and then let's refresh the page, come down to attributes, here is fooBar, and every single Inertia page will include that attribute. So, this is a perfect place to output global permissions for our User. So, inside the array here, let's have a key called permissions, and then inside permissions, which itself is an array, we could have createPosts. Are you allowed to createPosts? And the answer is, well, take the current request, grab the User, and say, can the User create, and then we'll use the Post fully qualified class name, which is going to defer to calling that policy method on the PostPolicy. So, let's see if this works. We'll come back to the browser, refresh. In view dev tools, we should now have a permissions object, and currently createPosts is false, but if we go to that create method on the policy, and

the browser, refresh. In view dev tools, we should now have a permissions object, and currently createPosts is false, but if we go to that create method on the policy, and instead return true, refresh the page, come back down to the permissions object, now it's set to true. That is working perfectly, and we can wire that permissions object up to the navigation menu. Let's jump into the app layout, and where we use when here to currently check for a User object, well, we can get rid of auth.user, and instead, we'll check for permissions.createPosts. Let's refresh the page. We can still see the link. If we click it, we can access the page, but if we now go to the PostPolicy, and instead return false here, the link disappears, and of course, it disappears because the create route is now no longer accessible to us. So, this is a great way to share data on each and every

false here, the link disappears, and of course, it disappears because the create route is now no longer accessible to us. So, this is a great way to share data on each and every request. There are a couple of considerations. First of all, because it's called on every request, this method is going to be called frequently. So, make sure that the logic you perform inside this policy method is very bare bones. Don't do anything that will take a long time, because if you do, your application is going to feel very slow and sluggish. The second thing to keep in mind is that this share method is going to be called regardless of whether or not you're authenticated. So, if you forget to use optional checks on your user method, you're going to cause issues for guest users. Just to show you that that's the case, let's once more open private browsing. I'll go to framework.test/posts,

User method, you're going to cause issues for guest users. Just to show you that that's the case, let's once more open private browsing. I'll go to framework.test/posts, and yeah, I have hit a 500 error because I'm trying to call can on null. Easy to prevent by either adding an optional check as we have here, or otherwise ensuring that this object is actually a User object and not null. Let's go back to our PostPolicy, and since we're not implementing any of our spam prevention checks just yet, I'll return this to true so that we can once again see the create a post link. And there's one last thing I'd like to do here. If I enter a title, let's say, hello world, how are you? And then let's go ahead and use our Raycast lorem ipsum generator to create a few paragraphs and hit createPost. You'll see that it sticks as lower case. I would much prefer this to be title.

Title Case Mutator9:46

go ahead and use our Raycast lorem ipsum generator to create a few paragraphs and hit create post. You'll see that it sticks as lower case. I would much prefer this to be title case. Now, how can we handle this? Well, one way we could handle it is enforcing it as a validation rule, but can you imagine how annoying that is? Like you've got everything right and then you click okay to submit your post and it says, oh no, you've got to make sure you use proper title case. We can automate that for the user. We don't need the user to do that. So let's automate it using model attributes. We're going to write the test first for this and it will come under the feature directory, but I'm not going to write a controller test. I'm going to create a model test. So I'll create a new directory called models there, and then inside we'll have a PHP file called PostTest. And we'll have

a controller test. I'm going to create a model test. So I'll create a new directory called models there, and then inside we'll have a PHP file called PostTest.php. And we'll have our first test method. Let's say it uses title case for titles. And how do we actually test this? Well, let's just create a Post object. So PostFactory::create, but when I call create, I'll set the title manually to "hello. How are you?" Okay. So almost the same as we had before. And then I would expect that, well, when I access the post->title, it's been altered and it's been altered to "hello. How are you" in title case? And there we go. So if we run this, obviously at the moment it fails, but we can add an attribute to our model in order to fix that. So let's jump into our Post and down at the bottom here, let's create a new public function and you'll need to name this function or this method, the same as the column

to fix that. So let's jump into our Post and down at the bottom here, let's create a new public function and you'll need to name this function or this method, the same as the column on the database that you're wanting to alter. In our case, it will be title. We need to type into it with attribute and it's this attribute here. So Illuminate\Database\Eloquent\Casts\Attribute, and it's vital that you add this type hint so that Laravel is able to actually understand what it needs to do with this method. Once we have that in place, we can return attribute and we're going to call the static set method because we want to alter the value. Anytime you set it on the model, it will accept a closure that receives the original value and we can do whatever we want to this original value. In our case, we'll say string title and I'll pass the value in, rerun the test and now it passes.

the original value and we can do whatever we want to this original value. In our case, we'll say string title and I'll pass the value in, rerun the test and now it passes. So it's that simple to create a mutator using attributes on your models. Again, remember you could be calling this from anywhere in the code base. So make sure that what you're doing inside your attributes, inside your mutators is very small. Don't do anything that will take a long time because you'll notice the slowdown in your application code. Let's just test if this works in the browser. So I'll go ahead and create a post. I'll give it the same title. Hello world, how are you? And again, we need to generate some paragraphs of Lorem Ipsum to fill here. We can go ahead, hit create post and yes, it's been converted to title case. So just a few little tweaks there, but already the process of creating

of Laura Mipson to fill here. We can go ahead, hit create post and yes, it's been converted to title case. So just a few little tweaks there, but already the process of creating a post is much nicer for our users. Let's carry on.

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