Setup Model and Migration0:00
I'm sure you have seen this error before. This happens when the code tries to call a method or read a property of something that's not an object, and that's usually null. Let's prepare an example to help us reproduce this error and fix it with different implementations of the null object pattern in Laravel. And although I'm going to use the version 7 of the framework, most of the techniques in this video can be applied from the version 5.5 on. Let's cd into the new directory, make a new UserProfile model, including its migration, and then let's edit the new migration in our code editor, to link the new user_profiles table to the users table, using a foreign key called user_id that will reference the id column on the users table. And if we delete a User, we may want to delete its profile as well, so let's add cascade and delete. Next let's add two extra columns, one for a job_title, and another one for a website.
table. And if we delete a User, we may want to delete its profile as well, so let's add cascade and delete. Next let's add two extra columns, one for a job title, and another one for a website. These columns will represent the User's profile. Imagine that here we have 10 or more columns. Now let's run the migrations, but before that we need to configure the .env file, so add your own custom configuration, and don't forget to create a new database. I have done this step previously. Back to the console, let's run php artisan migrate, and let's also install the new Laravel UI package. And once it's installed, let's generate the scaffolding for the registration and authentication with php artisan ui bootstrap --auth, although installing bootstrap is completely optional. I just want to have some default styles for this demo, but if we are installing bootstrap,
Reproduce Null Property Error1:42
with php artisan ui bootstrap --auth, although installing bootstrap is completely optional. I just want to have some default styles for this demo, but if we are installing bootstrap, we need to run npm install and npm run dev, in order to compile the css. And once that is done, let's start our server with php artisan serve, and then let's go to this url. Okay, from here let's create an account clicking register, and we are logged in. And this page that we can see here in the browser is in resources/views/home.blade.php. Let's add an additional column, in order to display the user's profile information. This column is going to have a card with a header and a body, and in the body of the card we are going to show the user's job title, and his website. Back to the browser, if we reload the page, we will finally see the error that we are so
Add Relationship and Seed Data2:42
and his website. Back to the browser, if we reload the page, we will finally see the error that we are so interested in. Here it's very important to read the name of the property or the method, and thanks to ignition, we can also see the line of the code that produced the error. In this case, we are trying to read this property jobTitle of the User's profile, but this profile doesn't exist. It doesn't exist, first of all, because we didn't define the profile relationship in the User model. So let's do that first. This is going to be a hasOne relationship, because a User may have a profile. However, back to the browser, this is not going to solve the error, because the User does not have a profile, at least not yet. If we use a tool like php artisan tinker or tinker well, we can get our first User, instantiate a new UserProfile,
Handle Null in Views3:31
because the User does not have a profile, at least not yet. If we use a tool like php artisan tinker, we can get our first User, instantiate a new UserProfile, assign a job title, and a website to this profile, and finally link this profile to the User, as you can see here. All right, back to the browser, if we reload the page, we get to see the dashboard again, this time with the User's profile information, but if there is even the slightest chance that one User in the system is not going to have a profile, we need to program defensively. One simple idea is going back to home.blade.php and add a conditional, using the if directive. So let's get the loggedInUser, and we are going to display this column, only if the profile is not null. If it's null, the column
back to home.blade.php and add a conditional, using the if directive. So let's get the login user, and we are going to display this column, only if the profile is not null. If it's null, the column is going to be hidden. So going back to the browser, as you can see here, we can see the profile's column, because the user has a profile, but if we go back to php artisan tinker well, and we delete this profile, and we reload the page, the column disappears. And although this is really simple, there are more complex views. For example, maybe we want to display a link, so the user can edit his profile. If we reload the page, we are not going to see that link, because we don't have a profile just yet, and that's bad, because maybe we want to show the user that he can create a profile with this information. So in that case, we don't have any other solution, at least not yet. Then, removing this directive, and then adding the same conditional twice,
with this information. So in that case, we don't have any other solution, at least not yet. Then, removing this directive, and then adding the same conditional twice, every time we want to read the profile information. And as I was saying before, maybe the User has 10 or more fields. So writing this view is not going to be fun anymore. But in any case, if we go back to the browser, and we reload the page, we can now see the column again. Maybe one idea here is using the ternary operator, in order to reduce a few lines of code. So if the User profile is not null, we are going to display the jobTitle. Otherwise, we are going to display nothing. But maybe we want to display none, if the User doesn't have a jobTitle. We can see none here. But if we go back to Tinkerwell, we remove the jobTitle, and we add the profile again, just this time without a jobTitle, we won't see none in the website.
Use Optional Helper7:01
the null object pattern, in order to reduce the amount of logic in our view. The first technique that Laravel offers is the optional helper. Let's remove the conditionals. And then we are going to wrap the optional helper. And with optional, I mean the object that might or might not be present. We are going to wrap this object in a call to the optional function. So basically, if the User does not have a profile, Laravel is not going to try to read this property in the profile, because the profile is going to be null. We can see this by checking the source code. If whatever we pass to the optional helper is not an object, Laravel won't try to read the property or call any method in that object. And if it happens to be an object, then Laravel will call the method or read a property. So it's going to work in a similar way as before. Back to the browser, if we reload the page, we can see the result here.
call the method or read a property. So it's going to work in a similar way as before. Back to the browser, if we reload the page, we can see the result here. Okay, in this case, the User has a Profile. And this Profile doesn't have any job title, by the way. So we can keep the default value if we use the ternary operator in this way. You can see that the amount of code and logic has been reduced by quite a lot. And if we go back to tinker, and we delete the Profile again, we are not going to get any error, even though the User does not have a Profile anymore. And in order to reduce the amount of logic even more, especially if we have 10 or more fields in our Profile, we can go to HomeController and pass the Profile from the controller to the view. In this way.
in our profile, we can go to HomeController and pass the profile from the controller to the view. In this way. All right, we can even pass the loginUser if we need to. Back to the view, we can now interact with the profile variable. And the good thing is that we don't have to worry whether the user has a profile or not. But if we want to check if the user does have a profile, notice that we cannot work with null anymore. Because in this case, the profile is not going to be null. It's going to be an optional helper. But we can still use the exists property. This property is provided by Eloquent and is going to return true if the profile exists.
But we can still use the exists property. This property is provided by Eloquent and is going to return true if the profile exists. Otherwise, it's going to return false. Back to the browser, if we reload the page, we can see new profile because the User does not have a profile. And if we add it again, we will see edit profile with the profile information. And because dealing with optional relationships is relatively common, Eloquent actually provides a feature to work with that. Let's go back to our User model. And here, after we define the relationship, we are going to call the withDefault method.
Use withDefault Relationships10:03
Let's go back to our User model. And here, after we define the relationship, we are going to call the withDefault method. Thanks to this method, Eloquent will never return null when we ask for the profile relationship. If we read the profile relationship, we can see it here. But if we delete it, we don't get null. Instead, we get an empty default user profile that has a link to the original User and the seeds. It's important to mention that this profile is not going to be persisted in the database. We can use the exists property again, and you can see here that it returns false. Therefore, we can work with these default relationships in memory until we are ready to persist them.
Therefore, we can work with these default relationships in memory until we are ready to persist them. And the best part is that we can pass default attributes to our default relationships. So let's say that the default profession is none or developer. We can see the result here in Tinkerwell. If we print the user profile information or back in our view. We can pass custom logic as well. Passing an anonymous function as the first argument that is going to receive the default profile. And in this function, let's do something really silly. Let's interact with Faker to assign a random job title to our user's profile.
And in this function, let's do something really silly. Let's interact with Faker to assign a random job title to our User's profile. Back to the browser, we can see the result here. But bear in mind that this function is going to be executed only if the User does not have a profile. If we go back to Tinkerwell and we comment these lines. Back to the browser, we are going to see none unless we add a profile to our User. So again, as you can see, the function is not being executed. And I keep seeing none instead of web developer. So let me debug this really quickly. It might be that I have been creating more than one User profile.
So let me debug this really quickly. It might be that I have been creating more than one User profile. Oh, as you can see here. So in this case, I need to fix the database to make sure this column is unique. And back to the console, let's rerun these migrations. Okay, I will need to recreate my account. And the user's profile. So I will uncomment these lines to save the profile again. So in this case, I shouldn't be able to save another User profile for the same User. As you can see here.
So in this case, I shouldn't be able to save another User profile for the same User. As you can see here. So this is very important. Okay, and back to the browser. If we reload the page, we get to see web developer as a profession. And again, notice that even if I delete this profile, I am not going to get null. Just a profile with a random profession. And since the profile is never going to be null, we can go back to HomeController and delete the call to the optional helper.
And since the profile is never going to be null, we can go back to HomeController and delete the call to the optional helper. And this is going to work just fine. So we either use optional or with default relationships. And it's also very important to notice that trying to get a property or calling a method of something that's not an object is not limited to Eloquent, but PHP in general. For example, let's comment this middleware. Let's open a new private window in our browser and visit the home URL. Ah, here we go back to the same error.
Let's open a new private window in our browser and visit the home URL. Ah, here we go back to the same error. Trying to get property profile of no object. But in this case, this is happening because now auth user is not an object, but it's just null because the user is not logged in. So using what we have already learned, we can wrap the calls to auth user with the optional helper. However, ironically, this is just going to take us back to the first error. Trying to get property jobTitle of non object. This is happening because optional is not magic.
Create CurrentUser Helper14:22
Trying to get property jobTitle of non-object. This is happening because optional is not magic. And now profile is null again. To fix this, we will need to wrap profile in another call to the optional helper. But we are going back to the problem of having code that is difficult to write and difficult to read. So maybe a nice solution will be to create a new custom helper. In our app folder, let's create a new helpers.php file. Here, first of all, let's ask if the function currentUser already exists. This is in order to avoid re-declaring this function twice,
Here, first of all, let's ask if the function currentUser already exists. This is in order to avoid re-declaring this function twice, especially if we are running unit tests. Here, let's return our authenticated user. But if there is not login user, we are just going to return a new User in this way. To register this helper, we need to go back to the composer.json file and use the autoload object to register a key called files. And in this key, let's add an array with our new helper file. Now we have to go back to the console and run composer dump-autoload.
And in this key, let's add an array with our new helper file. Now we have to go back to the console and run composer dump-autoload. And we are ready to use our helper. So let's reopen our HomeController. And let's call our currentUser function to get our user and to get the user's profile as well. And remember, if you need to know if the user exists or not, you can use the exists property. For example, maybe it doesn't make sense to show this column. So let's ask if the user exists.
For example, maybe it doesn't make sense to show this column. So let's ask if the User exists. And if the User exists, we are going to display the column. Otherwise, the column is going to be hidden again. As you can see here. Of course, we could add a custom method if you want. And remember that you can also use auth()->check(). And this is going to return true if the User is logged in, false otherwise. And if you are feeling extra lazy, you can also use the auth directly in this way. And last but not least, you can create your own implementation of this NullObject.
Custom Guest User Null Object16:39
And if you are feeling extra lazy, you can also use the auth directly in this way. And last but not least, you can create your own implementation of this null object or special case. For example, you can pass custom attributes. And back to home.blade.php, let's add a welcome message. Or you can even create a custom class, let's call it GuestUser. This class might stem from the original User and override a few attributes and methods. For example, let's add this default name. And let's call it GuestUser. And we can see the same result in the browser.
And let's call it GuestUser. And we can see the same result in the browser. So yeah, I don't think there is a right or wrong way to implement this pattern. But in any case, I hope this helps you reduce the amount of logic in your views and in the rest of the code. And if you have any questions, feel free to ask them in the comment section. And I'll see you in the next video. My name is Duelio, and I also hope you have enjoyed this video.
