Try null object pattern where you either return new Product instead of null but make sure you do not call save() on that object, or you can create a null object representation of that Product and return new NullProduct which will have empty save() method in it so you do not have to worry if save() is called on it.
What to return if no record was found
Hi,
I have a scenario where I have a helper function that looks up latest product and returns it. As this function is called in few places I have placed it in its own file and class. Normally that function will return a instance (record) which is the latest product. And in few places I use the with function to load up some relationships. However, currently when there are no products, the function returns null, and when I can with on null it error out. This could be easily mitigated by just checking if the value is null, and don't perform the with command if it's, however that will require me to change the function call in every place the with command is used in congustion with my helper function, which is not ideal.
So what I am asking is, what is the Eloquent way to deal with such a scenario, if any.

Thanks!
I use this pattern in my project
In my view
@if($book->inCollection() > 0)
In my model.
public function inCollection()
{
return( BookCollection::whereBookId($this->id)->where('user_id', Auth::user()->id)->count());
}
Isn't it better to use withCount()? And I think that is not what @aem99 asked. Also your code will fail if used in a command or something like that where there is no request. Harder to test also. Too many hidden dependencies.
Eloquent has findOrFail methods that throw an exception for things like this. May be better throwing an exception that you can handle than returning null as the fact that you return a null may not be clear to future people using the class api. If you are using nulls, you probably want to make that clear in your method return type by specifying that it has a nullable return type e.g. ?Product. At least with exceptions it's pretty clear what is expected and your IDE will pick up that your method can throw exceptions and that the client code should probably handle them.
In your case, unfortunately, you would have to update all your existing calling code to handle exceptions. Guess that would be the same for handling null. Just think exceptions are a bit clearer.
Of course, if the null object suggested by others works for you, then you probably won't have to bother with the above.
@goldtaste people use NullObject pattern to cut down on if statements and try catch blocks. What you suggest will just increase the count.
@bugsysha As mentioned this is how laravel handle findOrFail. They don't return a null object.
The NullObject Pattern is a valid pattern, but you don't want to over use it. It's not a catch all. It's good if you have a null logger for example.
In this case, the poster will want to handle a not found featured product and display an appropriate error page or display a message on the page saying that there is no featured product. How would you do that with a null object in this case? Just print blank values? Somehow I doubt he would want to do that.
Also, you don't always need try catch blocks. In laravel - maybe not in this case - you can let the error propagate up and let the framework handle the exception. For example, findOrFail and 404 exceptions.
@goldtaste for example I do following. On model I have some method like getUrlShow() and for object which is found in the database that will return something like products/1, while on NullProduct I can have that same method but hardcode products/not-found. And that is it. Lot cleaner.
@bugsysha . What if there is a featured product page? Your method won't work on that page.
@goldtaste ofc it will work and it improves encapsulation.
@bugsysha Will it? Give it a bit of thought and get back to me.
@goldtaste I know it will work.
I'm guessing he has a controller method like:
public function show()
{
$featuredProduct = Product::getFeaturedProduct();
return view('featured-product', compact('featuredProduct'));
}
Can you explain how your null object would work for that.
@goldtaste you’ve already decided which view to return and passed variable to it. And on top of that you already got Product instance so there is no collision with null object.
@bugsysha . Sorry, I don't understand what you mean. If you have a dedicated featured product page like above, how would you handle this with a null object? It's a pretty common scenario. You would need to add some sort of if to check if the null object was empty in the view. Or if in the controller add a check to see if the product url is not-found and redirect instead of loading the view.
As mentioned, the null object pattern shouldn't just replace exceptions.
@goldtaste no problem friend. Just tell me the logic behind getFeaturedProduct().
@bugsysha It's a made up method for this example. I assume the poster has a similar method that get's the site's featured product. In my example, it would simply return a product - could be any of the products - that is flagged as the website's featured product. I'm guessing it will be flagged in the db.
So it simply returns a product (that is somehow flagged as featured.)
Sorry, just realise the original poster was talking about latest and not featured products, but my logic still applies.
So controller method has hardcoded Product::getFeaturedProduct() and it returns only one product. Now everything depends on the flow you want to take. But if you want to gracefully handle the case when there is no featured product in the database you will have to have some kind of check in the controller method.
So in that case we go from what you've wrote to:
public function show()
{
$featuredProduct = Product::getFeaturedProduct();
if (!$featuredProduct) {
return redirect('products/not-found');
}
return view('featured-product', compact('featuredProduct'));
}
But if that getFeaturedProduct() method returns Model or NullProduct then you can have something like:
public function show()
{
$featuredProduct = Product::getFeaturedProduct();
if ($featuredProduct->notFound()) {
return redirect($featuredProduct->getShowUrl());
}
return view('featured-product', compact('featuredProduct'));
}
This is all due to having hardcoded return view('featured-product', compact('featuredProduct'));. But if you have dynamic view resolution then you can avoid if statement with something like:
public function show()
{
$featuredProduct = Product::getFeaturedProduct();
return view($featuredProduct->getShowView(), compact('featuredProduct'));
}
It would even have more sense if you are not hardcoding the retrieving featured product:
public function show(int $product)
{
$product = Product::findOrNull($product);
return view($product->getShowView(), compact('product'));
}
And within getShowView() you can have
public function getShowView(): string
{
if ($this->isFeatured()) {
return 'featured-product';
}
return 'normal-product';
}
And in NullProduct
public function getShowView(): string
{
return 'no-product';
}
Best part is that featured field will not leak out of that object and therefor you have better encapsulation.
There is a bunch of implementation routes that you can go with, but while holding a baby I can not write all of them.
Hope this helps a bit and that is not too confusing since I'm writing with one hand and in a rush.
Lol.
if ($featuredProduct->notFound()) {
return redirect($featuredProduct->getShowUrl());
}
I thought you were getting rid of if statements.
It is pretty common to have a featured products page. I wouldn't call it hard coding.
Instead of using an eloquent model - which is a very common thing to do in laravel - you could inject a Product repository if you were wanting to improve flexibility. Obviously using an interface in the method arguments.
But I think you are over complicating things. Should a product know about which views to use? What if the view name changes? You would need to update the class. Most people specify views directly in the controller. Is that really a bad thing to do?
Also, what if a summary of the featured product needs to be added to the home page? A null object isn't going to be any good there - unless you put an if in the view.
Also, what happens if you now needed to add both a latest and featured product. Your class would break the open/closed principle.
Lol.
What?
You've picked one example which was caused by your imaginary example about which you haven't though about at all?
It is pretty common to have a featured products page. I wouldn't call it hard coding.
OK, let me see you return something else with code you've wrote? Here is the code below. No changes in the controller.
public function show()
{
$featuredProduct = Product::getFeaturedProduct();
return view('featured-product', compact('featuredProduct'));
}
Instead of using an eloquent model - which is a very common thing to do in laravel - you could inject a Product repository if you were wanting to improve flexibility.
And you say I'm overcomplicating when I was trying to keep it simple and according to your example. I'm using repositories in my projects.
Should a product know about which views to use? What if the view name changes?
If a product can know to return it's route then there is no reason not to know which view to use. That can always be extracted to some other object and called from the model itself while passing that model to that object. Also if the name of the file changes you will have only one place to change it.
Most people specify views directly in the controller. Is that really a bad thing to do?
I never said that, but you can do more that way. If you are not using that approach then you have to either use if statement or handle that in another controller method or new controller.
Also, what if a summary of the featured product needs to be added to the home page? A null object isn't going to be any good there - unless you put an if in the view.
Not correct since it will be redirected to appropriate view where you will not have that issue.
I would want to see details of the featured product on the home page or a message saying that there isn't one, not get redirected. Or see featured product name: blank. Also with repositories that was just an example because you were complaining about hard coding.
You can have a logic for a partial which will be used. That can all go under presentation logic for that model which can be extracted to some other class like a model presenter in order to keep everything encapsulated. Any other situation you want to come up with?
Yupp, over complicated. All these extra classes because you don't want to do a fairly reasonable if check for a null or do a bit of error handling. Good luck following that logic. Also, I think we better call it a day or this thread will go on forever.
Yupp, over complicated. All these extra classes because you don't want to do a fairly reasonable if check for a null or do a bit of error handling. Good luck following that logic. Also, I think we better call it a day or this thread will go on forever.
Jeffrey says that beginners are reluctant to create classes. Good luck following your ego.
It sure won't end by you giving a smart suggestion or example or constructive argument.
I'm happy to create classes when they are needed and certainly not reluctant to create them to improve flexibility. But there has to be a limit. Like creating a bizillion classes because I want to use a null object when I probably don't need one. Possibly 5 website if statements vs 1000000 classes.
You are thinking about only one if statement in controller or one in blade file. But real world app will have that if statement repeated over and over.
This is exactly why I quoted you since you are changing your replies.
And no one was saying that if statements are bad, but the question was what can be used instead of that null as a return value.
Your quote:
@goldtaste people use NullObject pattern to cut down on if statements and try catch blocks. What you suggest will just increase the count.
My point was that he should just do the null check or throw and exception. And I'm only updating my replies to correct the english.
Please or to participate in this conversation.