I think this is because you have a model already retrieved from DB and it doesn't have query builder methods (like where() or first()). But newQuery() method gives you another query builder ready to retrieve data from database.
Difference between $model->newQuery()->where and $model->where
Hi guys, I am currently creating my own project using Laravel 12 when I stumbled to this part.
At first, I was using this code:
abstract class Controller
{
protected function isModelExists(Model $model, string $column, string $value): bool
{
$obj = $model->where($column, $value)->first();
return $obj;
}
}
This code works but, I just found out that I should use the newQuery() before doing the where clause because it will cause an error but it didn't. And the output whether I use newQuery() before doing the where clause or not is still the same. I still get the instance of the model.
I just wanted to ask if there is any downside of not using newQuery() or will it cause any issue in the near future.
Thanks!
What is the context ? Why do you need to reference $model in this way? Don't you know what type of model it might be?
@xatnys0820 That method seems pretty pointless to me.
Why do you need to check a model exists? Eloquent models already have an exists method, and such a method certainly doesn’t belong in a controller.
The result is also needlessly assigned to a variable. Why? Just return the result:
- $obj = $model->where($column, $value)->first();
- return $obj;
+ return $model->where($column, $value)->first();
However, the first method doesn’t return a bool like the return type declares; first either returns a model instance if it exists, or null if it does not. So your types are completely wrong. Again, this is where you would use the exists method available on Eloquent models instead, which does return a bool result:
return $model->where($column, $value)->exists(); // returns bool and not Model|null
So, it would be much more helpful to us if you showed us code that used this method. Because otherwise it just looks like a method created by someone unfamiliar with the Laravel framework and its functionality, given Laravel offers features like route–model binding to look up models based on route parameters, etc.
@martinbean my apologies for providing only the partial code. My only goal here was to confirm whether there was a difference between $model->newQuery->where and $model->where.
Answering you question - there are no downsides. If you didn't call newQuery - laravel will do it for you under the hood. You can see it by digging into the code of Model class
public function __call($method, $parameters)
{
// ...
return $this->forwardCallTo($this->newQuery(), $method, $parameters);
}
If the method doesn't exist (like method where in your case) - it will just forward call to QueryBulder
Thank you so very much guys for all of your replies! I was able to find the answer that I was looking for because of it.
Thank you so very much guys for all of your replies! I was able to find the answer that I was looking for because of it.
@xatnys0820 …and yet no one knows what you were trying to do, nor what solution you ended up with, making this entire thread pointless.
So you didn't want to explain what you were doing, and you didn't want to explain what you did different as a result of this discussion.
Don't worry, your skills and confidence will increase.
Here is the whole scenario:
I have this ExepnseController (a basic Create, Read, Update, Delete, Restore, Force Delete):
<?php
namespace App\Http\Controllers;
use App\Http\Requests\Expense\StoreExpenseRequest;
use App\Http\Requests\Expense\UpdateExpenseRequest;
use App\Http\Requests\Expense\RestoreExpenseRequest;
use App\Models\Expense;
use Illuminate\Contracts\View\View;
use Illuminate\Http\JsonResponse;
use Illuminate\Http\RedirectResponse;
class ExpenseController extends Controller
{
protected Expense $expenseModel;
//
public function __construct(Expense $expenseModel)
{
$this->expenseModel = $expenseModel;
}
public function index(): View
{
$expenses = $this->expenseModel->withTrashed()->get();
return view('expense.index')->with('expenses', $expenses);
}
public function edit(Expense $expense): View
{
return view('expense.edit')->with('expense', $expense);
}
public function create(): View
{
return view('expense.create');
}
public function store(StoreExpenseRequest $storeExpenseRequest): RedirectResponse
{
if ($this->isModelExists($this->expenseModel, 'name', $storeExpenseRequest->input('name'))) {
return redirect()->back()
->with('message', 'Cannot create expense with this name as it already exist and might be just temporarily deleted.')
->with('messageColor', config('response.color.error'));
}
$expense = new Expense();
$expense->name = $storeExpenseRequest->input('name');
if ($expense->save()) {
return redirect()->back()
->with('message', responseMessage('Expense', $expense->name, 'store'))
->with('model', $expense)
->with('messageColor', config('response.color.success'));
}
return redirect()->back()->with('message', 'Something went wrong, please try again later.');
}
/**
* @param Expense $expense
* @param UpdateExpenseRequest $updateExpenseRequest
*
* @return RedirectResponse
*/
public function update(Expense $expense, UpdateExpenseRequest $updateExpenseRequest): RedirectResponse
{
$expenseName = $expense->name;
if ($this->isModelExists($this->expenseModel, 'name', $updateExpenseRequest->input('name'))) {
return redirect()->back()
->with('message', 'Cannot update expense name to this name as it already exists and was only temporarily deleted.')
->with('messageColor', config('response.color.error'));
}
$updatedExpense = $expense->update(['name' => $updateExpenseRequest->input('name')]);
if ($updatedExpense) {
return redirect()->back()
->with('model', $expense)
->with('messageColor', config('response.color.success'))
->with('message', responseMessage('Expense', $expenseName, 'update'));
}
return redirect()->back()
->with('model', $expense)
->with('message', 'An error occurred while updating "'. $expenseName .'".')
->with('messageColor', config('response.color.error'));
}
public function destroy(Expense $expense): JsonResponse
{
$expenseName = $expense->name;
if ($expense->delete()) {
return response()
->json([
'message' => responseMessage('Expense', $expenseName, 'soft_delete'),
'model' => $expense,
'icon' => 'success',
'color' => config('response.json.color.success')
]);
}
return response()->json([
'message' => 'An error occurred while deleting Expense.',
'model' => $expense,
'icon' => 'error',
'color' => config('response.json.color.error')
], 500);
}
public function restore($expenseId, RestoreExpenseRequest $request): JsonResponse
{
$expense = $this->restoreModel($this->expenseModel, $expenseId);
if ($expense instanceof $this->expenseModel) {
return response()->json([
'message' => responseMessage('Expense', $expense->name, 'restore'),
'model' => $expense,
'icon' => 'success',
'color' => config('response.json.color.success')
]);
}
return response()->json([
'message' => 'An error occurred while restoring Expense.',
'icon' => 'error',
'color' => config('response.json.color.error')
], 500);
}
public function forceDelete($expenseId): JsonResponse
{
$expense = Expense::withTrashed()->find($expenseId);
$expenseName = $expense->name;
if ($expense->forceDelete()) {
return response()->json([
'message' => responseMessage('Expense', $expenseName, 'permanent_delete'),
'icon' => 'success',
'color' => config('response.json.color.success')
]);
}
}
}
And this is the code inside my abstract Controller class:
<?php
namespace App\Http\Controllers;
use Illuminate\Database\Eloquent\Model;
use Illuminate\Database\Eloquent\ModelNotFoundException;
/**
* @TODO
*
* Remove all the functions here and create Service Containers for CRUD function
*
* Inside of those functions call the function isModelExists.
*/
abstract class Controller
{
/**
* @param Model $model Empty model instance.
* @param string $column Name of the column you want to check for unique.
* @param string $value Value that you want to check if exists
*
* @return boolean
*/
protected function isModelExists(Model $model, string $column, string $value): Model|bool
{
$obj = $model->where($column, $value)->first();
if ($obj)
return $model;
if ($obj->trashed())
return false;
}
protected function restoreModel(Model $model, int $id): Model|bool
{
$obj = $model->onlyTrashed()->find($id)->restore();
if ($obj) {
return $model->find($id);
}
return false;
}
protected function permanentlyDeleteModel(Model $model, int $id): bool
{
$obj = $model->withTrashed()->find($id);
// Explicitly declare the ID column since we don't need to assign any column.
if ($this->isModelExists($model, 'id', $id)) {
return $obj->forceDelete();
}
return false;
}
}
And this is my global helper for the responseMessage:
<?php
if (!function_exists('responseMessage')) {
function responseMessage(string $model, string $obj, string $actionType): string
{
$actionTaken = null;
switch ($actionType) {
case 'store':
$actionTaken = ' was successfully created.';
break;
case 'update':
$actionTaken = ' was successfully updated.';
break;
case 'restore':
$actionTaken = ' was successfully restored.';
break;
case 'soft_delete':
$actionTaken = ' was temporarily deleted.';
break;
case 'permanent_delete':
$actionTaken = ' was permanently deleted.';
break;
default:
return 'Failed to throw the correct action taken as it does not exists.';
break;
}
return $model . ' "' . $obj .'" ' . $actionTaken;
}
}
My first goal here is to create a reusable code on the abstract controller that is not just for ExpenseController but for other controllers as well.
As you can see on the store function of ExpenseController I am using the $this->isModelExists with 3 parameters
- Empty model
- Unique column
- User input
My thinking here is, create a reusable function where it checks if the model exists or if it is soft deleted, if the model exists it returns the model, if soft deleted or trashed, return false.
Now, the reason I created this thread is because, I posted my abstract Controller code in the ChatGPT and asking if there are any potential issues in the code and it replied with, the function isModelExists might cause an error/issue because it is not creating a new builder for the model and it suggests I should use the
$model->newQuery(),
I was confused because my system is working properly so why did it says it will throw an error. So I created this post and was looking for other resources as well, until someone posted here that the newQuery() is automatically being called even if I don't explicitly call it because of the Laravel's magic function on the Eloquent class (I think):
function __call()
So I did a deep dive on the Eloquent class and figured out how it works.
But now I am going to refactor my abstract controller class because I also found out Yesterday that Service Containers is better because it is easier to test, and can be access throughout the system, and keep abstract controller class from being bloated.
NOTE: Most of the code here might be incorrect since I am now refactoring this two controllers and the CRUD function will be placed in Service Containers.
Another note: You guys might now be able to understand what I am trying to say because I'm still in a train of thought, and for that, my apologies in advance.
@xatnys0820 If I could review;
protected function isModelExists(Model $model, string $column, string $value): Model|bool
{
$obj = $model->where($column, $value)->first();
if ($obj)
return $model;
if ($obj->trashed())
return false;
}
the if(obj->trashed()) will never be true because you only find non-trashed models.
It all feels over engineered.
Your forceDelete could be as simple as;
public function forceDelete($expenseId): JsonResponse
{
Expense::withTrashed()->where('id', $expenseId)->forceDelete();
return response()->json([
'message' => responseMessage('Expense', $expenseName, 'permanent_delete'),
'icon' => 'success',
'color' => config('response.json.color.success')
}
}
And in a stroke, eliminated two methods in your abstract class.
You also made your method idempotent (always returns the same response). If something goes wrong then an exception will be thrown, which you don't catch.
Rather than abstract classes, Traits are more commonly used, or extend the actual base controller.
Please or to participate in this conversation.