Call model from within itself - php

I have a model in laravel and need to call it within itself to check if there already are entries. It's for a generic (polymorphic) case where I don't want to do it in the controller.
This works just fine:
$check = self
::where('foreign_id',$this->attributes['foreign_id'])
->where('foreign_type', $this->attributes['foreign_type'])
->where('category', $this->attributes['category'])
->orderBy('created_at','desc')
->first()
;
Now I can access the properties with $check->id for example.
But I can't do something like this:
$check = self
::where('foreign_id',$this->attributes['foreign_id'])
->where('foreign_type', $this->attributes['foreign_type'])
->where('category', $this->attributes['category'])
->orderBy('created_at','desc')
;
if($this->isUser()) $check->where('user_id',$this->attributes['user_id']);
else $check->where('guest_ip',$this->attributes['guest_ip']);
$check->first();
It's not adding the new wheres within the if statement to the query.
I thought self is the way to go according to this answer: https://stackoverflow.com/a/15282754/1233206
In my case however it seems to be not working. What am I doing wrong?

$check->first() will run a query and return the result. The $check variable itself will not be changed. So if you try this:
$check->first();
if($check->id == 'something'){}
It won't work because you're interacting with the query builder instance. You have to do this:
$check = $check->first();
if($check->id == 'something'){}

Related

Assign a query to variable to then perform two separate queries?

Is it possible to start an eloquent query, assign it to a variable then continue using the variable for two separate queries without them conflicting with one another. A simple example:
$students = $this->student
// more query stuff
->where('is_active', 1);
$bachelorStudents = $students
->where('course_id', 3)
->get();
$masterStudents = $students
->where('course_id', 4)
->get();
or would I need to do:
$bachelorStudents = $this->student
->where('course_id', 3)
->get();
$masterStudents = $this->student
->where('course_id', 4)
->get();
I always thought I could do the former, but some of my results appear to show I can't but I am open to believe that if you can do it then perhaps I'm doing something wrong.
When you're calling
$students = $this->student->where('is_active', 1);
you're creating a query builder object. Calling where*() on this object updates the object by adding given criteria. Therefore it's not possible to achieve what you want in your first code snippet, because when you call
$masterStudents = $students
->where('course_id', 4)
->get();
the query builder already contains where('course_id', 3) constraint added when you bachelorStudents.
Once you do that:
$students = $this->student->where('is_active', 1);
$stundents will contain a query builder with your where clause
If you do:
$bachelorStudents = $students->where('course_id', 3)->get();
You'll add another where clasuse to the $students builder, and this should work as you expect
But, when you do:
$masterStudents = $students->where('course_id', 4)->get();
You are adding another where clasuse to the same $students builder, thus resulting the query builder to be something like this:
$students->where('is_active', 1)
->where('course_id', 3)
->where('course_id', 4)
->get();
That probably isn't what you expect, because you have 2 where clauses with different course_id values
Think of $student as an object you modify everytime you add a clause, so you can use it for progressive query building, but remember that once you've added a clause to the query builder, the object is modified and the clause will be keept in the builder, so when you re-use the builder it will contain all the clasuses you previously added
Also, Rembember that when you need to apply some pre-defined filters to your query, in Laravel you should use query scopes
While everyone is explaining query builder and how it works, here's your answer.
1) Start off your query builder
$studentsQuery = $this->student
//Start a new query builder (optional)
->newQuery()
->where('is_active', 1);
2) Clone the initial query builder to our separate queries
$bachelorStudentsQuery = clone $studentsQuery;
$masterStudentsQuery = clone $studentsQuery;
3) Assign your where conditions and get the results
$bachelorStudentsResult = $bachelorStudentsQuery->where('course_id', 3)
->get();
$masterStudentsResult = $masterStudentsQuery->where('course_id',4)
->get();
Your use case is too simple for cloning.
It might help you DRY your code when lots of method chaining has been performed, especially when applying filters to queries.

How to write WHEN THEN statement in Eloquent, or filter the result before get()?

I have the following query:
$results = Product::where("active", "=", true)->leftJoin(/** something **/)->leftJoin(/** something **/);
The above array is then process in the view file:
foreach($results->get() as $item)
{
// do something
}
Now, this view is too generic and shared between almost 50 pages. I cannot change the view in any possible way. Now, I need a way to change the query result in the controller, I am unable to filter result since it is get() in the view. I need to know how to inject WHEN statement of MySQL into my Eloquent Query (above query using Product model).
How should I do that? I need something like this:
$results = Product::select("is_special WHEN price > 500 THEN 'true' ELSE 'false' ")->where("active", "=", true)->leftJoin(/** something **/)->leftJoin(/** something **/);
I you have any other way for filtering the result before get(), I welcome that!
Could you just pre-process some of the results before? Such as
$preSelected = Product::select(/** something **/)->where(/** x **/);
and in another Controller function
$preSelected = Product::select(/** something else **/)->where(/** y **/);
And then later it would be the same
$results = $preSelected->where("active", "=", true)
->leftJoin(/** something **/)->leftJoin(/** something **/);
Or another option would be to use DB::raw
$results = Product::select(DB::raw('is_special WHEN price > 500 THEN true ELSE false'))
->where("active", "=", true)->leftJoin(/** something **/)
->leftJoin(/** something **/);
though I try to avoid DB::raw whenever possible to increase security and reduce the possibility of e.g. SQL injection.

Laravel 4 - How to turn timestamps off to update an entire collection

I'm trying to do a mass update on an eloquent collection.
So I have my query, which looks a bit like this:
\Responder::with('details')
->where('job_number', $project->job_number)
->where('batch_id', ((int) $batch_id) - 1)
->where('updated_at', '<=', $target_time)
->whereHas('transactions', function($q) {
$q->where('status', 'success');
}, '<', 1)
->whereHas('details', function($q) {
$q->where('email', '<>', '');
});
This query object is stored as $query (because I'm re-using it - the same reason I dont want to switch how I'm doing the query), I am then performing an update on the collection, e.g.
$query->update(array('batch_id' => $batch_id));
This works great except it updates all the 'updated_at' timestamps. Now i like the timestamps, they are used extensively elsewhere, so i cant turn them off all together but I thought I could disable them temporarily but I've tried the following:
$query->timestamps = false;
$query->update(array('email_drop_off_index' => $batch_id));
and I can confirm that doesn't work, is there a way to do this?
Any help much appreciated
timestamps = false should be made on your model, but what you are doing is setting the value on the query builder. That's why it is not being picked up.
timestamps is an instance variable so you can't set it statically, and I don't think there is a built-in way to do it from the query builder. So I suggest try instantiating the model first, then create a new query from it, like this:
$responder = new \Responder;
$responder->timestamps = false;
$query = $responder->newQuery()
->with('details')
->where('job_number', $project->job_number)
...; // the rest of your wheres
$query->update(array('email_drop_off_index' => $batch_id));
Here's a possible solution: subclass your Responder model and turn off timestamps in the subclass.
class MassUpdateResponder extends Responder
{
public $timestamps = false;
}
Then use your new class to do the updates. This seems like a bit of a hack, but it should work.
BTW, doing an update like the following worked for me:
$query->timestamps = false;
$query->value = "new value";
$query->save();
The update() method may be doing something different that's causing it to ignore the value of $timestamps.

Only query "Where" if there is a value in Laravel eloquent

I have a search query that needs to be done. However, a search doesn't always have all values set, like in this case.
$aEvents = DB::table('events')
->where('client_id', '=', $client_id);
The question is, how can I make this where statement depend on the value of $client_id. So if the value is empty I don't want the Where statement to occur.
Also, I do not want to write several complete queries with if statements in PHP. To many variables. Ideally I'd like something like this:
$aEvents = DB::table('events')
->(($client_id != "") ? where('client_id', '=', $client_id) : "");
Using eloquent is (really!) nice and save, but I'm not yet up to speed with if statements in std Class objects I guess. Any help is appreciated.
You may try something like this:
$query = DB::table('events');
if(!empty($client_id)) {
$query->where('client_id', $client_id);
}
$aEvents = $query->get(); // Call this at last to get the result
If you are passing client_id to the server via a form/query string(user input) then you may try something like this:
if($client_id = Input::get('client_id')) {
$query->where('client_id', $client_id);
}
Update: For pagination try this:
$aEvents = $query->paginate(10); // For 10 per page
So you may call links() method in your view if you pass it like this:
return View::make('viewName')->with('aEvents', $aEvents);
In the view for pagination links:
$aEvents->links()
You can also use query scopes in the model for this purpose. Scopes allow you to easily re-use query logic in your models. In the model Event, you can add the following query scope:
public function scopeClientID($query, $client_id)
{
if ($client_id != '') {
return $query->where('client_id', '=', $client_id);
} else {
return $query;
}
}
Then from your controller or wherever you're calling it from, you can do the following:
$aEvents = Event::clientID($client_id);
If you want to get all the results, then you can do:
$aEvents = Event::clientID($client_id)->get();
Or if you want pagination, you can do:
$aEvents = Event::clientID($client_id)->paginate();
You can also chain it with other methods like you'd do in a eloquent query.
You can read more about model query scopes at http://laravel.com/docs/eloquent#query-scopes

Query a relationship in Laravel with WHERE method

I have a model called "User", which "belongsToMany" Items.
This relationship works fine, so I can easily do something like this:
User::find(4)->items->find(1)->name
Now, I would like to do something like this:
User::find(4)->items->where('name', '=', 'stick')->get()
I would expect the code to return all the user's items with the name "stick", but unfortunately that is not what happens. I receive this error:
"Call to undefined method Illuminate\Database\Eloquent\Collection::where()"
I also tried to build a query scope:
public function scopeName($query, $name)
{
return $query->whereName($name);
}
The query scope works when I do something like this:
Item::name('SC')->get()
but
User::find(4)->items->name('SC')->get()
still does not work.
Can you help me returning all the user's items, which have the name 'stick'?
If you're looking to just get a single user's items named "stick", this is how you would do it:
$stickItems = Item::whereUserId(4)->whereName('stick')->get();
Here we are using Eloquent's dynamic where methods, but you could rewrite it like so:
$stickItems = Item::where('user_id', '=', 4)->where('name', '=', 'stick')->get();
That should get you what you want.
You have to call the items() method, not use the magic property:
User::find(4)->items()->where('name', 'stick')->get();
// ^^

Categories