Update an array of objects in Laravel 5.4 - laravel

I use Laravel as an API on an Angular app. One of the controllers has to update an array of objects.
This array, coming from Angular, might have
The same objects but with different attributes
One of the objects might have been deleted
New objects
So, I cannot just update the objects I have, since I need to delete the records that are not on the array any more and also create the new records.
At the moment, I have a not-so-nice solution to delete all the previous records and create new based on the array. Like this:
Sample::where('contest_id', $request->get('contest_id'))
->where('type', '0')
->delete();
$samples = $request->get('samples');
foreach ( $samples as $sample ) {
Sample::create($sample);
}
However, I want to add an activity logger to keep track of changes, but the above solution doesn't help. The activity logger works like this:
activity()
->causedBy($user)
->performedOn($sample)
->withProperties($properties)
->log('update'); //or new or delete
Where $properties is this:
$properties = [
'property' => [
'old' => $old_sample, // empty on creating new record
'new' => $sample // empty on deleting old record
],
];
Anything you could suggest?

Can't you have your frontend also send the sample_id when you need to update the records? If you're able to do that, you could play with Collections and make something like this:
// Retrieves all samples and turn them into a Collection
$samples = collect($request->get('samples'));
// Gets only the sample_ids that are not null and greater than zero
$changed_ids = $samples->pluck('sample_id')->filter()->all();
// These samples are new - create them
$new_samples = $samples->whereStrict('sample_id', null);
// These samples were changed - update them
$changed_samples = $samples->whereIn('sample_id', $changed_ids);
// These samples were deleted - remove them
$deleted_samples = Sample::whereNotIn('sample_id', $changed_ids);

Your solution seems like the most efficient, can you not retrieve the records to be deleted, log them as deleted and then actually delete them, then log the creation of the new ones?
In that scenario, your log is actually a better reflection of the activity that's occured.

Related

laravel - update the model(!) without selecting first

I have the following ResearchModel (eloquent):
$research = new ResearchModel();
$research->id = 2;
$research->name = "test";
$research->save();
I am expecting Laravel to run an update statement (because I set the id), but it runs an insert statement instead.
$research->update(); wont do anything.
I don't want to use array in this case because I need the eloquent model events to be triggered.
I also don't want to run ResearchModel::find(2); before, this will cause significant performance problems in my use-case.
Is there any way to tell Laravel to update by the id?
Thanks
If you don't want to "select" first. Then you can update directly. This will run only one query.
$research = ResearchModel::where('id',2)->update([
'name' => 'test',
]);
A dirty solution:
// Create an instance without insert to database.
$instance = new ResearchModel;
$instance->id = 2;
$instance->name = "test";
// Set the instance status directly.
$instance->exists = true;
// Update database without model.
ResearchModel::where('id', $instance->id)
->update(['name' => $instance->name]);
// Manually trigger event.
event('eloquent.updating: App\ResearchModel', $instance);
The premise of this method is that you already have the required data. If the data required for the event must be read from the DB, then this method is useless.
Find by id
$research = ResearchModel::find(2);
Then you can run update function
$research->update([ 'name' => "test"]);
and if u don't want to use
ResearchModel::find(2);
then u have to use route binding
https://laravel.com/docs/6.x/routing#implicit-binding

Laravel - Eloquent multiple delete vs destroy array

I was wondering what's the best way to destroy multiples database entries with ELOQUENT and I don't find a way to determine that.
So I have 3 array of id's (2 with ints, 1 with strings).
Is it better to go with a foreach and ->delete() every entry or destroy the array ?
When I look at the destroy function, it states the following :
We will actually pull the models from the database table and call
delete on each of them individually so that their events get fired
properly with a correct set of attributes in case the developers
wants to check these.
And the code clearly shows :
$key = $instance->getKeyName();
foreach ($instance->whereIn($key, $ids)->get() as $model) {
if ($model->delete()) {
$count++;
}
}
So I guess there's no real difference and the destroy function is just to avoid the use of a foreach. Can anyone confirm or inform and explain ?
Thanks :)
At first you need to know the difference between destroy and delete, destroy is think to be used for removing an entity (object/model) and delete for being used on a query builder.
Both are different ways but they have the same purpose you can do like:
Model::destroy(array(1, 2, 3));
or
$ids = explode(",", [1,2,3]);
$model->find($ids)->each(function ($model, $key) {
//Do things before deleting
$model->delete();
});
But as you can see the first one is just more direct, on the second one you can do custom things before deleting.

select certain columns from eloquent collection after the query has executed

Using Laravel 5.3, I have a model with the following function
public function myData() {
return $this->hasMany(MyData::class);
}
and in my collection I have the following
$my_data = MyModel->myData()->get();
All good so far. If I return $my_data I get an eloquent collection with three items.
What I need now though is to create a duplicate of that collection but containing only three of the fields.
I have tried several different things, each of which return an error. The following is the closest I have got, but this returns an empty array - I assume because the fields are located one level deeper than the collection object.
$new_collection = $my_data->only(['field_1', 'field_2', 'field_3']);
What would be the correct way to create a new collection containing all three items, each with only the three selected fields?
Thanks for your help
You could use map:
$slimmed_down = $collection->map(function ($item, $key) {
return [
'field_1' => $item->field_1,
'field_2' => $item->field_2,
'field_3' => $item->field_3
];
});
This will return a new Collection with just the values you want. As far as I know there isn't any other method that does what you want, so iterating over every item and selecting the fields this way is one of the few solutions.
The advantage of using map instead of a standard foreach loop is that when you use map it returns a new instance of Collection.
Edit:
After some thoughts and research about this, the problem you'll have created is that the all the values in the Collection aren't instances of anything anymore. If you don't mind this effect, an even prettier and faster way would be to do this:
$slimmed_down = $collection->toArray()->only(['field_1', 'field_2', 'field_3']);
This basically has the same result.
Using Laravel 9, I just had the same issue :
$my_data->only(['field_1', 'field_2', 'field_3']);
returning an empty array.
I solved it with :
$my_data->map->only(['field_1', 'field_2', 'field_3']);

Laravel 5 session array update

I am having trouble while updating session array value in laravel 5. Here is my function,
public function postCartItemAdd()
{
$id = Request::input('id');
Session::push('items', $id);
dd(Session::all());
}
Instead of pushing a new id into the array it just replaces the existing value leaving single item. Am I doing something wrong?
The problem is the session is saved as a flash data. So, you need to save the session whenever you push the data.
$request->session()->push('user.items', 'item1');
$request->session()->push('user.items', 'item2');
$request->session()->save();
or try this
$items = Session::pull('items');
$items[] = $id;
Session::push('items', $items);
umm i think you used it wrong,
see the DOC
it says
Session::push('user.teams', 'developers');
user is the array and we gonna put a value developers to that array with teams key
so then you need to use it in your case as,
Session::push('items.id', $id);
OR if you need to maintain items as an array with default keys like 0,1,2,3... to put the ids, then items should be an array
so there should be a something like,
Session::put('items', []);
then you can use Session::push('items', $id);
if you need to push ids in to same array as you tried.

CakePHP Pagination sort() on Related Models

I have two models: Plans and PlanDetails.
Relationship is: PlanDetails hasMany Plans. Plans belongTo PlanDetails.
In the PlanDetails view.ctp, I am pulling in related Plans.
I am trying to sort the Plans by ANY field (I've tried them all), and I cannot get it working. I assume I am overlooking something very simple.
Here is my base code:
PlanDetail >> view.ctp:
...foreach ($planDetail['Plan'] as $plan_edit) :
$class = null;
if ($i++ % 2 == 0) {
$class = ' class="altrow"';
}...
<?php echo $this->Paginator->sort('Plan ID', 'Plan.id'); ?>...
...<?php echo $plan_edit['id']; ?>
plan_details_controller.php:
...function view($id = null) {
if (!$id) {
$this->Session->setFlash(__('Invalid plan detail', true));
$this->redirect(array('action' => 'index'));
}
$this->PlanDetail->recursive = 2; // To run the editable form deeper.
$this->set('planDetail', $this->PlanDetail->read(null, $id));
$this->set('plan', $this->paginate('Plan'));
}...
I should add, no errors are being thrown and the sort() arrows on the ID field are showing as expected, but the sort order DOES not change when clicked either way.
Sorry, I'm not able to comment on the question itself, but I've noticed that in your action, you set planDetail to be the PlanDetail record you read (with recursive set to 2), and then you set plan to be the result of the paginate call.
Then, in your view template, you're iterating over $planDetail's contained Plan association, like this:
foreach ($planDetail['Plan'] as $plan_edit):
But in order to get the sorting and pagination done, you need to be displaying the results of the paginate call i.e. iterate over the records contained in $plan.
Do a debug($plan) in your view template to see what results you get there and to see if the records' ordering changes when you sort by different fields.
Also, perhaps you're using syntax I'm not aware of, but if you simply call $this->paginate('Plan') in your controller, I don't know that you're going to get only the related Plan records for your particular PlanDetail record. (There's nothing tying the $id passed into your view action with the Plan records.) You might need to add some conditions to the paginate call, like so:
$this->paginate['Plan'] = array('conditions' => array('Plan.plan_detail_id' => $id));
$this->set('plans', $this->paginate('Plan'));
Here is what I did to solve this. Based on some helpful direction from johnp & tokes.
plan_details/view.ctp:
...$i = 0;
foreach ($plan as $plan_edit) : // note $plan here.
}...
In my plan_details_controller.php view action:
$conditions = array("Plan.plan_detail_id" => "$id");
$this->set('plan', $this->paginate('Plan', $conditions)); // note "plan" in the first argument of set (this is required to pass the results back to the view). Also. The $condition is set to return ONLY plans that match the plan_detail_id of id (on the plan_details table).
And in my view, in order to get my results (because I changed the array name), I had to change the way I was getting the values to:
$plan_edit['Plan']['modified'] // note I placed ['Plan'] in front of these calls as the array called for this to get the data...
Well until the next problem! SOLVED.

Resources