How to delete multiple records using Laravel Eloquent - php

Now this, from what I can see, should have been simple.
I want to be able to delete multiple records from the database. I have the id's of all the records I wish to delete. I call the resource.destroy route using comma separated list of ids (id is of postgres type uuid), like so:
Request URL:http://foo.app/products/62100dd6-7ecf-4870-aa79-4b132e60c904,c4b369f1-d1ef-4aa2-b4df-b9bc300a4ff5
Request Method:DELETE
On the other end, my controller action looks like so:
public function destroy($id)
{
try {
$ids = explode(",", $id);
$org->products()->find($ids)->delete();
}
catch(...) {
}
}
This gives me the following error:
BadMethodCallException in Macroable.php line 81:
Method delete does not exist.
in Macroable.php line 81
at Collection->__call('delete', array()) in ProductsController.php line 251
at Collection->delete() in ProductsController.php line 251
at ProductsController->destroy('62100dd6-7ecf-4870-aa79-4b132e60c904,c4b369f1-d1ef-4aa2-b4df-b9bc300a4ff5')
I have verified that find() is returning a collection of products matching the specified ids.
What am I missing?
PS:
1. The model Product has several belongsTo relationships with other models.
2. The product.destroy code works fine if I pass it a single id
EDIT
I guess, I'm also trying to understand what the difference between:
$org->products()->find($ids)->delete()
and
$org->products()->whereIn('id', $ids)->get()->delete()
is? From what I see, both find and get are returning Collections

The issue is that you're calling delete() on a Collection, which does not have that method.
You have a couple options here.
Model Events
If you have event listeners for the deleting/deleted model events, you will need to make sure the deletion happens in a way that each model is loaded and then deleted.
In this case, you can use the destroy method on the model that takes a list of ids. It will load a new model for each id, and then call delete() on it. As you mention in a comment, it won't restrict the deletion to only those products in the organization, so you would need to filter out those ids before passing the list into the destroy() method.
public function destroy($id)
{
try {
$ids = explode(",", $id);
// intersect the product ids for the org with those passed in
$orgIds = array_intersect($org->products()->lists('id'), $ids);
// now this will only destroy ids associated with the org
\App\Product::destroy($orgIds);
}
catch(...) {
}
}
If you don't particularly like that approach, you will need to iterate your collection of organization products and call delete() on them individually. You can use a standard foreach, or you can use the each method on the collection:
public function destroy($id)
{
try {
$ids = explode(",", $id);
$org->products()->find($ids)->each(function ($product, $key) {
$product->delete();
});
}
catch(...) {
}
}
No Model Events
Now, if you don't have any model events that you need to listen for, things are a little easier. In this case, you can just call delete() on the query builder, and it will go straight to deleting the records without loading any model objects. So, you get cleaner code with better performance:
public function destroy($id)
{
try {
$ids = explode(",", $id);
// call delete on the query builder (no get())
$org->products()->whereIn('id', $ids)->delete();
}
catch(...) {
}
}

If you create a model of your products, it will help you with these types of operations.
For example:
the model Products.php
<?php
namespace App\Http\Models;
use Illuminate\Database\Eloquent\Model;
class Products extends Model
{
/**
* The table associated with the model.
*
* #var string
*/
protected $table = 'products';
protected $primaryKey = 'id';
protected $fillable = ['name', 'price', 'description'];
}
The controller Products.php
You can use the destroy method and pass one or more primary keys to it as arguments.
<?php
namespace App\Http\Controllers;
use App\Http\Models\Products;
class Products
{
public function destroy($id)
{
try {
$ids = explode(",", $id);
//$ids is a Array with the primary keys
Products::destroy($ids);
}
catch(...) {
}
}
}
You can also use this option to remove query results with a custom parameter
$deletedRows = Products::where('name', 'phones')->delete();
You can check the Laravel documentation https://laravel.com/docs/8.x/eloquent#soft-deleting

When you use the find method, it will find only a single ID. You should use a whereIn to match multiple ids
public function destroy($id)
{
try {
$ids = explode(",", $id);
$org->products()->whereIn('id', $ids)->get()->delete();
}
catch(...) {
}
}
This way you will find all the products with the given IDs and delete them all.

I also faced this problem. Let $orgs contains some records as a collection. Now you can easily delete these records using a loop like this-
foreach($orgs as $org)
{
$org->delete();
}

Related

How to get only one note from relation belongsToMany?

I just have a table that has relation belongsToMany, BUT it was a mistake by developer so I can not change this structure SO I need to get only first(). However, when I take only first it return empty array but I need in object
$animals = Cat::query()->with(['types' => function($query) {
$query->first(); //wrong
}])
So how I can get only first? Because I need to order by this field and I can't because it is array
you can do this in two ways:
1- using hasOne relation:
class Cat {
public function firstType() {
return $this->hasOne(Type::class, 'type_id', 'id')->latest();
}
}
2- using staudenmeir/eloquent-eager-limit
after installing it you can write:
class Cat extends Model
{
use \Staudenmeir\EloquentEagerLimit\HasEagerLimit;
public function firstType() {
return $this->hasMany(Type::class, 'type_id', 'id')->latest()->limit(1);
}
}
class Type extends Model
{
use \Staudenmeir\EloquentEagerLimit\HasEagerLimit;
// ......
}
the advantage of HasEagerLimit trait is that you can limit the result not only to one but any number you want ...
now you can write:
$animals = Cat::query()->with('firstType');
You can add a attribute getter and set up into appends attribute. follow bellow example:
class Cat {
protected $appends = ['type'];
public function getTypeAttribute() {
// return the first element from your array of the belongsToMany relationship if it exists
return isset($this->types[0])? $this->types[0] : null;
}
}
That's important to remember this method will bring just one type. If you want to get the same type everytime, you create a diferente table where the cat table has the type_id column.
obs: Sorry for my english, it's still in working progress.

use relationship in model accessor in laravel

Suppose I have a Course model like this :
class Course extends Model
{
public $primaryKey = 'course_id';
protected $appends = ['teacher_name'];
public function getTeacherNameAttribute ()
{
$this->attributes['teacher_name'] = $this->teacher()->first()->full_name;
}
public function teacher ()
{
return $this->belongsTo('App\User', 'teacher', 'user_id');
}
}
And in the other hand there is a User model like this :
class User extends Authenticatable
{
public $primaryKey = 'user_id';
protected $appends = ['full_name'];
public function getFullNameAttribute ()
{
return $this->name . ' ' . $this->family;
}
public function course ()
{
return $this->hasMany('App\Course', 'teacher', 'user_id');
}
}
As you can see there is a hasMany relationship between those.
There is an full_name accessor in User model.
Now I want to add a teacher_name accessor to Course model that uses it's teacher relations and gets full_name of teacher and appends to Course always.
In fact I want whenever call a Course model, it's related teacher name included like other properties.
But every time , when call a Course model , I got this error :
exception 'ErrorException' with message 'Trying to get property of non-object' in D:\wamp\www\lms-api\app\Course.php:166
That refers to this line of Course model :
$this->attributes['teacher_name'] = $this->teacher()->first()->full_name;
I do not know how can I solve that and what is problem exactly.
Yikes some interesting answers here.
FYI to those coming after me- getFooAttribute() should return the data, and not modify the internal attributes array.
If you set a new value in the attributes array (that doesnt exist in this model's db schema) and then attempt to save the model, you'll hit a query exception.
It's worth reading up the laravel docs on attribute accessors/mutators for more info.
Furthermore, if you need to access a related object from within the model (like in an accessor) you ought to call $related = $this->getRelation('foo'); - note that if the relation isnt loaded (e.g., if you didnt fetch this object/collection with eager loaded relations) then $this->getRelation() could return null, but crucially if it is loaded, it won't run the same query(ies) to fetch the data again. So couple that with if (!$this->relationLoaded('foo')) { $this->loadRelation('foo'); }. You can then interact with the related object/collection as normal.
$this->attributes['teacher_name'] = $this->teacher()->first()->full_name;
Should be
$this->attributes['teacher_name'] = $this->teacher->full_name;
First thing is that you want to reference the relationship, so loose the brackets (), and because the relationship is belongsTo, you will have one user / teacher returned. So you don't need the first().
We haven't seen your fields but probably you will have to change:
return $this->belongsTo('App\User', 'teacher', 'user_id');
to
return $this->belongsTo('App\User', 'foreign_key', 'other_key');
where foreign_key and other_key are the primary keys that you need to make the join on.
Check this link from the documentation for reference:
https://laravel.com/docs/5.4/eloquent-relationships#one-to-many-inverse
the right way to do this is:
COURSE
public function setTeacherNameAttribute ()
{
$this->attributes['teacher_name'] = $this->teacher->full_name;
}
100% working for me.
I have one to one relationship between Order and Shipment. I have to add the accessor of shipments table column from orders table.
function getOrderNoAttribute()
{
$appendText = "OR100";
if($this->orderShipment()->first()) {
$appendText = $this->orderShipment()->first()->is_shipping === 1 ? "ORE100" : "OR100";
}
return $appendText . $this->attributes['id'];
}
This error is only object data to array use or array data to object data use.
example::
$var->feild insted of $var[feild]
$var[feild] insted of $var->feild
You should use return for accessors . something like this :
public function getTeacherNameAttribute ()
{
return $this->teacher()->first()->full_name ?? '';
}
maybe a course hasn't teacher.

Why is detach not working on my Laravel model?

Here is some of my code:
class User extends Model {
public function orders() {
return $this->hasMany('App\Order');
}
public function emptyCart() {
$orders = $this->orders;
for ($i = 0; $i < count($orders); $i++) {
$order = $orders[$i];
$this->orders()->detach($order);
}
}
}
Yet I am getting an error:
Call to undefined method Jenssegers\Mongodb\Query\Builder::detach()
I also tried dissociate():
Call to undefined method Jenssegers\Mongodb\Query\Builder::dissociate()
Originally I was just going to do $this->orders()->detach() but this also failed.
The detach() method is only available for many-to-many relationships. What you have defined on your User model is a one-to-many.
If your users/orders relationship is many-to-many, you need to change the relationship definition to:
public function orders() {
return $this->belongsToMany('App\Order');
}
This also assumes you have a order_user pivot table setup. Once you do this, the detach method will work. All the detach method does is remove the entry in the pivot table that associates the two records, it does not actually delete any order records.
However, if your users/orders relationship is actually one-to-many, then the relationship is defined correctly, but your removal logic needs to be updated. If you would like to delete the orders, you can just call $this->orders()->delete();. However, if don't want to remove the order records, just remove the relationship to the user, you can use the dissociate() method, like so:
public function emptyCart() {
$orders = $this->orders;
foreach($orders as $order) {
$order->user()->dissociate();
$order->save();
}
}
The dissociate() method is a method on the belongsTo relationship. All it does is reset the foreign key, so you still need to save the model after calling the method.

Laravel ORM one to many in one array

In Laravel 4.2, I have this models
// models ticket.php
class Ticket extends Eloquent {
public function feedback()
{
return $this->hasMany('Feedback');
}
}
// models/feedback.php
class Feedback extends Eloquent {
public function ticket()
{
return $this->belongsTo('Ticket');
}
}
When I do:
$tickets = Ticket::with('feedback')->get();
It returns an array of all tickets with feedback in one array as expected.
Next I want to get one ticket with all related feedback:
$tickets = Ticket::find($id)->with('feedback')->get();
This returns also all tickets with their feedback.
I tried:
$tickets = Ticket::find($id)->with('feedback')->first();
This seems to work but ignores $id and always shows the first row/ticket in the table. $id is not empty, I checked that.
find() already runs a query. Then with()->get() runs another one without the where clause on the id. Do this instead:
$ticket = Ticket::with('feedback')->find($id);

Laravel get data from many to many relation

For a school project, I'm creating a website in the Laravel framework.
I can't work with the data from a many-to-many table in my controller.
Here are my models:
class Item extends Eloquent {
public function Tags()
{
return $this->belongsToMany('Tag', 'items_has_tags', 'items_id', 'tags_id');
}
}
class Tag extends Eloquent {
public function LearningMaterials()
{
return $this->belongsToMany('LearningMaterial', 'learning_materials_has_tags', 'tags_id', 'learning_materials_id');
}
}
I want to iterate all tags from my items in my controller:
//get all items
$all = Item::where('public', '1')->get();
foreach($allLm as $item){
$tags = $item->Tags();
var_dump('will iterate');
foreach($tags as $t){
var_dump('will not iterate');
}
}
What is wrong with my code? How can I handle the tags from my items?
FYI: I'm creating a search engine. If the user inserts an input value like "mana", all items with tag "management" should be shown.
Laravel's belongsToMany method queries related models and doesn't get them. That's because you might want to have some additional constraints in your query. What you need to do is:
$tags = $item->Tags()->get();
Or simply:
$tags = $item->Tags;
Calling the relationship function will return a relation object (in this case an instance of BelongsToMany). You can use this to run add further components to your query before running it.
For example:
$only4Tags = $item->Tags()->take(4)->get();
If you want the result of your relation, call get() or simpler, just use the magic property:
$tags = $item->Tags()->get();
$tags = $item->Tags;

Categories