Laravel eloquent in a loop - php

I have an Eloquent query in a foreach loop.
My League model is correct, when I echo values, I get correct $match->league_id inside the loop.
I have relative records in my database with that $match->league_id.
foreach ($matchesRaw as $k=>$match) {
$lg= League::find($match->league_id)->first();
echo $lg->name;
}
My problem is my code displays only the first row ($lg->name) in the database. It means my query all the times gets the first row. No matter what is $match->league_id comes in the loop. How can I solve this?

Your problem is that you're calling first like this:
League::first();
So yes, it gets always the first row in that table.
This is because you do just the same as:
$leagueModel = League::find($someId); // returns model
$leagueModel->first() == League::first(); // returns the same model
Instead you simply call League::find($someId), no first at all.

I think you should use join in instead of calling another eloquent object in a loop ..You understand

Related

Replicating multiple models at once

I am using Laravel 4.2.
If I want to duplicate a model I can use the following:
$newModel = $currentModel->replicate();
$newModel->save();
However I have this inside a loop, like so:
foreach ($this->models as $currentModel) {
$newModel = $currentModel->replicate();
$newModel->save();
}
Which obviously causes a several DB calls. I want something more efficient, so I can loop through my models and then outside of the loop use one DB call to write them all in one go.
In Laravel is there a way to replicate multiple models in one go?
You can use the insert statment of DB query builder like this :
foreach ($this->models as $currentModel) {
$newModel = $currentModel->replicate()
$newModels[] = $newModel->toArray();
}
DB::table('table_name')->insert($newModels);
No it not possible duplicate more than one model, you can see in the api documentation:
https://laravel.com/api/4.2/Illuminate/Database/Eloquent/Model.html#method_replicate
So if you would duplicate X models you need loop them and you can speific (with array parameter) wich columns you would like to not copy

Yii2 - Calling model functions on entire activeRecords?

What I am trying to do
I want to query a specific set of records using active model like so
$jobModel = Jobs::find()->select('JOB_CODE')->distinct()->where(['DEPT_ID'=>$dept_id])->all();
Then I want to assign a flag attribute to the records in this activerecord based on whether they appear in a relationship table
What I have tried
So in my job model, I have declared a new attribute inAccount. Then I added this function in the job model that sets the inAccount flag to -1 or 0 based on whether a record is found in the relationship table with the specified account_id
public function assignInAccount($account_id){
if(JobCodeAccounts::find()->where(['JOB_CODE'=>$this->JOB_CODE])->andWhere(['ACCOUNT_ID'=>$account_id])->one() == null){
$this->inAccount=0;
}
else{
$this->inAccount = -1;
}
}
What I have been doing is assigning each value individually using foreach like so
foreach($jobModel as $job){
$job->assignInAccount($account_id);
}
However, this is obviously very slow because if I have a large number of records in $jobModel, and each one makes a db query in assignInAccount() this could obviously take some time if the db is slow.
What I am looking for
I am wondering if there is a more efficient way to do this, so that I can assign inAccount to all job records at once. I considered using afterFind() but I don't think this would work as I need to specify a specific parameter. I am wondering if there is a way I can pass in an entire model (or at least array of models/model-attributes and then do all the assignations running only a single query.
I should mention that I do need to end up with the original $jobModel activerecord as well
Thanks to scaisEdge's answer I was able to come up with an alternative solution, first finding the array of jobs that need to be flagged like so:
$inAccountJobs = array_column(Yii::$app->db->createCommand('Select * from job_code_accounts where ACCOUNT_ID = :account_id')
->bindValues([':account_id' => $account_id])->queryAll(), 'JOB_CODE');
and then checking each job record to see if it appears in this array like so
foreach($jobModel as $job){
if(in_array($job->JOB_CODE, $inAccountJobs))
$job->inAccount = -1;
else
$job->inAccount = 0;
}
Does seem to be noticeably faster as it requires only a single query.

Collection addFieldToFilter does not filtering

I have a NON-EAV model, and I want to filter on its collection like below
$td_trans_collection = Mage::getModel('tichdiem/scoretransaction')->getCollection();
$td_trans_collection->addFieldToFilter('increment_id', $incrementId)
->addFieldToFilter('action', self::TICHDIEM_ADD)
->load();
The query produce by
$td_trans_collection->getSelect()->__toString();
return
SELECT `main_table`.* FROM `fhs_td_score_transaction` AS `main_table` WHERE (increment_id = '100010565') AND (action = '0')
which is a correct query, should return only 1 answer. I ran this query on the terminal as well. However, when I loop through the collection like
foreach($td_trans_collection as $item){
echo $item;
}
I got every entries inside my table, why is this the case? I am using Magento 1.9.1
First, test that select query directly in your database to see what it returns. If it's returning only one row as you want then that means somewhere in Magento, something is modifying your collection perhaps.
Alternatively, you can try this method to limit your collection to 1 result:
$td_trans_collection->getSelect()->limit(1);
You can also try:
$td_trans_collection->getFirstItem()->getData();
If that reliably returns the result you want, then problem solved.
If the above fails, go to: lib/Varien/Db/Adapter/Pdo/Mysql.php and locate the following properties and change them as shown below:
protected $_debug = true;
protected $_logAllQueries = true;
After you set those values to true, run your collection again. Then go to: var/debug/pdo_mysql.log and take a careful look at the final queries that were executed. Hopefully, this will give you a better clue as to what's happening.
Additionally, remove the ->load();
Don't call load(), you will retrieve everything again, when you already have that in the collection object. The collection is an associative array, add
If you claim that your query returns 1 record.
The correct way to loop through the collection is
foreach($td_trans_collection as $item){
var_dump($item->getData());
}

Eloquent - Updating all models in a collection

I want to set a certain attribute in all the models of a collection.
in plain SQL:
UPDATE table SET att = 'foo' WHERE id in (1,2,3)
the code i have:
$models = MyModel::findMany([1,2,3]);
$models->update(['att'=>'foo']);
taken from here
but doesn't work. I'm getting
Call to undefined method Illuminate\Database\Eloquent\Collection::update()
the only way i have found it's building a query with the query builder but i'd rather avoid that.
You are returning a collection, not keeping the query open to update. Like your example is doing.
$models = MyModel::whereIn('id',[1,2,3]);
$models->update(['att'=>'foo']);
whereIn will query a column in your case id, the second parameter is an array of the ids you want to return, but will not execute the query. The findMany you were using was executing it thus returning a Collection of models.
If you need to get the model to use for something else you can do $collection = $models->get(); and it will return a collection of the models.
If you do not just simply write it on one line like so;
MyModel::whereIn('id',[1,2,3])->update(['att'=>'foo']);
Another option which i do not recommend is using the following;
$models = MyModel::findMany([1,2,3]);
$models->each(function ($item){
$item->update(['att'=>'foo']);
});
This will loop over all the items in the collection and update them individually. But I recommend the whereIn method.
The best solution in one single query is still:
MyModel::whereIn('id',[1,2,3])->update(['att'=>'foo']);
If you already have a collection of models and you want to do a direct update you can use modelKeys() method. Consider that after making this update your $models collection remains outdated and you may need to refresh it:
MyModel::whereIn('id', $models->modelKeys())->update(['att'=>'foo']);
$models = MyModel::findMany($models->modelKeys());
The next example I will not recommend because for every item of your $models collection a new extra query is performed:
$models->each(function ($item) {
$item->update(['att'=>'foo']);
});
or simpler, from Laravel 5.4 you can do $models->each->update(['att'=>'foo']);
However, the last example (and only the last) is good when you want to trigger some model events like saving, saved, updating, updated. Other presented solutions are touching direct the database but models are not waked up.
Just use the following:
MyModel::query()->update([
"att" => "foo"
]);
Be mindful that batch updating models won't fire callback updating and updated events. If you need those to be fired, you have to execute each update separately, for example like so (assuming $models is a collection of models):
$models->each(fn($model) => $model->update(['att'=>'foo']) );

Getting a specific row other than first()

I'm having a little problem with laravel that may be easily solved. To be short, the situation is: I have two tables, one for the users and the other one for products that has a column 'user_id' so I can identify the associated user.
In Laravel, I can use
$user = Sentry::getUser(); //Or Auth::user() if you're not using Sentry
$products = DB::table('table2')->where('user_id',$user->id);
And that should give me every product that user has. Good.
Now I want to show the products individually on screen, but unfortunately that doesn't work. It seems I can't echo this information in a string because it's made of multiple rows. I get
Object of class Illuminate\Database\Query\Builder could not be converted to string
For the solution, since the maximum associated products I allowed in the system is 3, I came up with the idea of getting each row separately and echoing them. For the first one, it's simple: $products->first(); but I have no idea on how to get the other two.
And maybe I'm being a newbie here, but I don't think I can use the products' id info since $products->id returns an error.
Can anyone help me?
Thanks in advance!
You want to use take, limit the number of results to three and then print out every one with a foreach loop. Docs: Laravel Queries, see skip and take.
$products = DB::table('table2')->where('user_id',$user->id)->take(3)->get()
Then, inside your view, you can just iterate through this data:
#foreach($products as $p)
Alternatively, in your PHP you can iterate through this data using something like:
foreach ($products as $product) { var_dump($product); }
(You are getting that error because you are trying to output a result object as a whole, and not the data it contains. Using the loop actually fetches the data from the result object so you can then use the loop variable ($product) normally.)
To get data from database you can use one one those methods: all, get, or first.
Using all:
$products = DB::table('table2')->all();
you are getting all the products.
Using first you can use conditions but you will get only first record that fulfil conditions:
$products = DB::table('table2')->where('user_id',$user->id)->first();
Using get you can use conditions and you will get all the records that fulfil those conditions:
$products = DB::table('table2')->where('user_id',$user->id)->get();
So in your case you want to use get to get data from database.
When you are using
$products = DB::table('table2')->where('user_id',$user->id);
Then $products is an array and you do not have to echo an array.
To display the products you need to use a foreach loop like below
foreach ( $products as $key => $product ) {
var_dump( $product );
}
If you want to show only three products, then you can use for loop inside foreach.
You can learn more about foreach from below link
http://php.net/manual/en/control-structures.foreach.php

Categories