I need a little help with the following situation.
I am willing to saveMany based on the input value. Let me give code example.
I was experimenting with the following.
$data = [
'id' => 1,
'name' => 'example',
'number_of_slots' => 5,
'material' => 'Colo',
'equipment_status_code_id' => 1,
];
$platecontainer = PlateContainer::create($data);
foreach ($data as $key => $value) {
$platecontainer->containerSlots()->saveMany([
new ContainerSlot([
'plate_container_id' => $data['id'],
'slot' => $data['number_of_slots'],
'occuiped' => false,
])
]);
}
until $platecontainer everything works just great. What I want is when a PlateContainer is created using the data array, I want to create slots as well, but this one is based on number_of_slots
So for instance number_of_slots in the example is 5 so I want to save 5 records in (descending order) in the ContainerSlot table
so the containerslots table will end up looking something like this.
The save many method accepts an array of models, so just use a for loop for the $plateContainer's number_of_slots
$plateContainer = PlateContainer::create($data);
$containers = [];
// Need to add one to our number of slots because
// the $i count starts at one instead of zero.
$slots = $plateContainer->number_of_slots + 1;
for($i = 1; $i < $slots; $i++) {
$containers[] = new ContainerSlot([
'plate_container_id' => $plateContainer->getKey(),
'slot' => $i,
'occuiped' => false,
]);
}
$plateContainer->containerSlots()->saveMany($containers);
You can use createMany (array of arrays of attributes) instead of saveMany (array of models new or existing). Useful for API calls.
An alternative, if you want to insert data based on value, you can prepare your array and let the model insert in bulk.
$itemRebates = array_map(function ($rebateId) use ($payoutItem) {
return [
'rebate_id' => $rebateId,
'payout_item_id' => $payoutItem->id
];
}, $rebateIds);
PayoutItemRebate::insert($itemRebates);
This way you don't need to wrap your data in multiple model instances.
Additional details
I tried to use the insert method from $payoutItem->historyRebates() but it didn't take the payout_item_id from the relationship. So it's better to use the model itself.
My first attempt was to use saveMany, but even preparing the data, it doesn't work with arrays, only with model instances. I wanted to avoid that.
Related
I am using PHP 7.1.33 and Laravel Framework 5.8.36.
I am getting receiving data from a database row-by-row and I am creating a model using updateOrCreate() like the following:
foreach ($arr as $v) {
$product = new Product();
$product->name = $v['name'];
$product->url = $v['url'];
$product->price = $v['price'];
// save
$matchThese = ['name' => $name, 'url' => $url];
$product->updateOrCreate($matchThese);
}
However, nothing gets created.
Any suggestions what I am doing wrong?
I appreciate your replies!
updateOrCreate takes 2 parameters ($attributes, $values).
$attributes is an array containing key-value pairs which will basically be used for where clauses i.e. it will check the database for the attributes passed in this array.
$values is an array containing key-value pairs of what to update in the database.
If it can't find a row in the database matching the first array it will combine the values in the arrays to create a new row.
To achieve what you're after you can do:
Product::updateOrCreate(
['name' => $v['name'], 'url' => $v['url']],
['price' => $v['price']]
);
Try this way
Product::updateOrCreate(
['name' => $name, 'url' => $url],
['price' => v['price']],
)
First parameter is for search the register.
Second parameter set new values.
For more information, you can read Eloquent: Getting Started - Documentation
I'm trying to construct an array where there only strings and the array would look like this
key->key->value
To explain it I attached two screenshots below.
I start with this:
After my code below I'm 90% there, yet there is an array in value on the third level instead of simple value.
Here is some code:
$theme = ThemeHandler::with('sections.settings')->find($activeTheme);
$themeSettings = $theme->sections;
$themeSettings = collect($themeSettings->toArray());
// dd($themeSettings);
$themeSections = [];
foreach ($themeSettings as $key => $value) {
$settings = collect($value['settings']);
$settings = $settings->mapToGroups(function ($item) {
return [$item['key'] => $item['value']];
});
$themeSections[$value['key']] = $settings->toArray();
}
dd($themeSections);
I would like to end up with this structure
key->key->value
and not
key->key->single_element_array->value
I'm not sure how I end up with an array at the bottom level when I do this
return [$item['key'] => $item['value']];
inside the mapToGroups, which is a function found here: https://laravel.com/docs/5.8/collections#method-maptogroups
Maybe I misunderstand how mapToGroups work. Anybody has an idea how to get key->key->value structure output?
Use mapWithKeys() instead of mapToGroups().
You're getting an array instead of the simple value you expect because the value is a group, albeit a group with only one member.
mapToGroups() groups all the values with the same key, but mapWithKeys() will assign a single value to each key.
You can see in the examples in the collection documentation, mapToGroups() produces a result like this:
[
'Sales' => ['John Doe', 'Jane Doe'],
'Marketing' => ['Johnny Doe'],
]
And mapWithKeys() result is like this:
[
'john#example.com' => 'John',
'jane#example.com' => 'Jane',
]
I have a query that populates an array from the database. In some cases, this query returns a great amount of data, (let's say for purpose of an example, 100.000 records). Each row of the database has at least 6 or 7 columns.
$results = [
['id' => 1, 'name' => 'name', 'status' => true, 'date' => '10-01-2012'],
['id' => 2, 'name' => 'name 2', 'status' => false 'date' => '10-01-2013'],
...
]
I need to perform a substitution of some of the data inside the $results array, based on another one that give me some information about how i would change the values in the rows.
$model = [
'status' => ['function' => 'formatStatus', params => ['status']],
'date' => ['function' => 'formatDate', params => ['date']]
]
Now that i have all the data and what do i do with it i have the following routine.
foreach ($results as &$itemResult) {
$oldValues = $itemResult;
foreach ($itemResult as $attribute => &$value) {
if (isset($model[$attribute]['function'])) {
$function = $model[$attribute]['function'];
$params = $model[$attribute]['params'];
$paramsSize = count($params);
for ($i = 0; $i < $paramsSize; $i++) {
$newParams[] = $oldValues[$params[$i]];
}
$itemResult[$attribute] = call_user_func_array([$this, $function], $newParams);
$newParams = null;
}
}
}
So, for each attribute for each row of my data array, i run check for the existence of a function and params information. When the attribute in question needs to be replaced, i call the function via call_user_func_array and replace the value with the function return value.
Also notice that i am replacing the current array, not creating another, by passing the reference &$itemResult inside the loop, so in the end, i have the same array from the beginning but with all columns that needed to be replaced with its new values.
The thing is, for little arrays, this method is quite good. But for big ones, it becomes a pain.
Could you guys provide me some alternative to the problem?
Should i use another data structure instead of the PHP array?
Official Laravel documentation has this on sync() function:
$user->roles()->sync( array( 1, 2, 3 ) );
You may also associate other pivot table values with the given IDs:
$user->roles()->sync( array( 1 => array( 'expires' => true ) ) );
In the latter example only a single pivot row is being added. What I don't understand is how to associate other pivot table records if there are more than one rows to be synced?
In order to sync multiple models along with custom pivot data, you need this:
$user->roles()->sync([
1 => ['expires' => true],
2 => ['expires' => false],
...
]);
Ie.
sync([
related_id => ['pivot_field' => value],
...
]);
edit
Answering the comment:
$speakers = (array) Input::get('speakers'); // related ids
$pivotData = array_fill(0, count($speakers), ['is_speaker' => true]);
$syncData = array_combine($speakers, $pivotData);
$user->roles()->sync($syncData);
This works for me
foreach ($photos_array as $photo) {
//collect all inserted record IDs
$photo_id_array[$photo->id] = ['type' => 'Offence'];
}
//Insert into offence_photo table
$offence->photos()->sync($photo_id_array, false);//dont delete old entries = false
There is now a ->syncWithPivotValues($ids, $pivotValues) method available if you want to set the same pivot value for all synced items.
Example from the doc:
$user->roles()->syncWithPivotValues([1, 2, 3], ['active' => true]);
Attaching / Detaching
Eloquent also provides a few additional helper methods to make working with related models more convenient. For example, let's imagine a user can have many roles and a role can have many users. To attach a role to a user by inserting a record in the intermediate table that joins the models, use the attach method:
$user = App\User::find(1);
$user->roles()->attach($roleId);
When attaching a relationship to a model, you may also pass an array of additional data to be inserted into the intermediate table:
$user->roles()->attach($roleId, ['expires' => $expires]);
You can also use Sync if you want to remove old roles and only keep
the new ones you are attaching now
$user->roles()->sync([1 => ['expires' => $expires], 2 => ['expires' => $expires]);
The default behaviour can be changed by passing a 'false' as a second
argument.
This will attach the roles with ids 1,2,3 without affecting the existing
roles.
In this mode sync behaves similar to the attach method.
$user->roles()->sync([1 => ['expires' => $expires], 2 => ['expires' => $expires], false);
Reference:
https://laravel.com/docs/5.4/eloquent-relationships
Add following trait to your project and append it to your model class as a trait. This is helpful, because this adds functionality to use multiple pivots.
Probably someone can clean this up a little and improve on it ;)
namespace App\Traits;
trait AppTraits
{
/**
* Create pivot array from given values
*
* #param array $entities
* #param array $pivots
* #return array combined $pivots
*/
public function combinePivot($entities, $pivots = [])
{
// Set array
$pivotArray = [];
// Loop through all pivot attributes
foreach ($pivots as $pivot => $value) {
// Combine them to pivot array
$pivotArray += [$pivot => $value];
}
// Get the total of arrays we need to fill
$total = count($entities);
// Make filler array
$filler = array_fill(0, $total, $pivotArray);
// Combine and return filler pivot array with data
return array_combine($entities, $filler);
}
}
Model:
namespace App;
use Illuminate\Database\Eloquent\Model;
class Example extends Model
{
use Traits\AppTraits;
// ...
}
Usage:
// Get id's
$entities = [1, 2, 3];
// Create pivots
$pivots = [
'price' => 634,
'name' => 'Example name',
];
// Combine the ids and pivots
$combination = $model->combinePivot($entities, $pivots);
// Sync the combination with the related model / pivot
$model->relation()->sync($combination);
Simply just append your fields and their values to the elements:
$user->roles()->sync([
1 => ['F1' => 'F1 Updated']
]);
$data = array();
foreach ($request->planes as $plan) {
$data_plan = array($plan => array('dia' => $request->dia[$plan] ) );
array_push($data,$data_plan);
}
$user->planes()->sync($data);
Putting this here in case I forget it later and Google it again.
In my case I wanted the extra column to have the same data for each row
Where $syncData is an array of IDs:
$syncData = array_map(fn($locationSysid) => ['other_column' => 'foo'], array_flip($syncData));
or without arrow
$syncData = array_map(function($locationSysid) {
return ['ENTITY' => 'dbo.Cli_Core'];
}, array_flip($syncData));
(array_flip means we're using the IDs as the index for the array)
foreach ($request->exercise_id as $key => $exercise_id) {
$data_plan[$exercise_id] = [
'serie' => $request->serie[$key],
'observation' => $request->observation[$key],
];
}
Using the numbers from $ids, I want to pull the data from $nuts.
So for example:
$ids = [0,3,5]; // 0 calories, 3 sugar, 5 fat
$nuts = [
'calories' => 'cal',
'protein' => 'pro',
'carbohydrate' => 'car',
'sugar' => 'sug',
'fiber' => 'fib',
'fat' => 'fat',
];
$returnData = [
'calories' => 'cal',
'sugar' => 'sug',
'fat' => 'fat',
];
I could loop through each $ids number with a foreach(); but I'm curious to see if there is a better method than this?
$newNuts = array_values(array_flip($nuts));
foreach($ids as $i)
$returnData[$newNuts[$i]] = $nuts[$newNuts[$i]];
I did some work and realized, you don't need array_flip, array_values is fine.
$num_nuts = array_values ($nuts);
for ($z=0; $z<sizeof($ids); $z++) {
echo $num_nuts[$ids[$z]];
}
Just 1 more line of code, but I think it does the job. I think mine is going to be faster because the array_flip basically exchanges all keys with their associated values in an array, which is not what I am doing. It's actually one less pain.
I am simply converting the original array to a new one by index and simply looping upon it. Also, not the elegant way to use the power of PHP available to us, but works just fine. array_flip is O(n), but I think better not use it for larger data-sets.
How about a simple array_slice?
$result = array();
foreach ($ids as $i) {
$result += array_slice($nuts, $i, 1, true);
}
No need to create a copy of the array.