laravel5.2 delete model with all relations - php

My current model has some relations. How can I delete them too, in case of model will be deleted?
This query won't delete the related models, only the 'main model'.
I use this code to call:
$checks = Check::where('created_at','<=', Carbon::now()
->subHours(3))
->with('checks')
->with('results')
->delete();
Here's my current model of Check
protected static function boot(){
parent::boot();
static::deleting(function($check) {
$check->checks()->delete();
$check->results()->delete();
});
}
Results and checks contain more than one entry for each check. Meaning this to make things clear:
One check may have n CheckResult and may have n CheckProcedure (I'll of course delete all of them too).

Try to use deleted instead of deleting :
protected static function boot(){
parent::boot();
static::deleted(function($check)
{
$check->checks()->delete();
$check->results()->delete();
});
}
Also try to parse object by object from returned collection:
foreach($check->checks as $check_object) {
$check_object->delete();
}
Hope this helps.

Like already pointed out in the comments, you are performing the delete on a query builder instead of the actual related models. i.e.
You should have
$check->checks->delete();
$check->results->delete();
instead of what you currently have.
In addition, the right way to do this assuming you are using a relational database is to use foreign keys with cascade delete action.

Related

Get name instead of ID one to one relationship

I have a make table and post table. Make table saves make names as make_code and make_name.
Post table has a column make. While saving a post, it will save make in make_code.
While displaying in blade, I want it to display as make_name. How can I do it?
Currently {{$post->make}} gives me make_code. I need it to show make_name.
I think its a one-to-one relationship that's needed. I tried putting it in model but did not work. How can I achieve it?
MAKE MODEL
class Make extends Model
{
public function make()
{
return $this->belongsTo(App\Post::class);
}
}
POST MODEL:
class Post extends Model
{
protected $table = 'posts';
}
Update
As Tim Lewis noticed:
the relationships can't be named make, as that's a conflict.
Assuming that the your relationship work like this:
a Make has many Post
a Post belongs to a Make object.
| Note: Correct me if I'm wrong.
So, if this is correct, you should define your relationships like this:
Post.php
public function make_rel()
{
return $this->belongsTo(Make::class, 'make', 'make_code');
}
Make.php
public function posts()
{
return $this->hasMany(Post::class, 'make', 'make_code');
}
Check the One-to-Many and One-to-Many (Inverse) relationship sections of the documentation.
So, you could do in your controller (or wherever you want):
$post = Post::find(1);
dd($post->make_rel->make_name); // 'Harley Davidson'
Additionally, you could create a computed property as a shorcout to access this related property in your Post model:
Post.php
// ...
public function getMakeNameAttribute()
{
return $this->make_rel->make_name;
}
Now, you can access it like this:
$post = Post::find(1);
dd($post->make_name); // 'Harley Davidson'
Suggestion
As a suggestion, I strongly advice you to change your foreign key column from make to make_id (in your 'posts' table) to avoid conflicts. Also, you could relate the post to the make primmary key instead of a custom key given the fact that this link is almost invisible and it is handled by Laravel. This would speed up the execution of the query because primmary id's are indexed by default.

Calling Laravel relationship with aggregate

If I have a Laravel 5.5 model called User that hasMany Posts and each Post hasMany Hits, is there an aggregate function I can call to get the total number of Hits for a User across all Posts, where the Hit was created in the last week?
It seems like there may be a clever way to do it besides doing something like
$hits = $user->posts()->hits()
and then looping over those hits to check created date.
In this case it seems like raw sql would be better, but I figured there may be an Eloquent way to handle a situation like this.
I think the right solution is just to use a HasManyThrough relationship to grab all the Hit rows, joined through the posts table.
So it'd look like this on the User model (roughly):
return $this->hasManyThrough(
Hit::class,
Post::class
// if you have non-standard key names you can specify them here-- see docs
);
Then when you have your User model you can just call $user->hits to get a collection of all the associated hits through all the user's Posts
You can add the code below to your Post model.
protected static function boot()
{
parent::boot();
static::addGlobalScope('hitCount', function ($builder) {
$builder->withCount('hits');
});
}
It automatically provides a field hits_count whenever you fetch a post.
$post = Post::first();
$hits = $post->hits_count; //Count hits that belongs to this post
You can read the documentation here to customize it to your need.
Set HasManyThrough relation in the User model:
public function hits()
{
return $this->hasManyThrough('App\Models\Hits','App\Models\Posts','user_id','post_id','id');
}
then you can do this:
$reults = $user->hits()->where('hits_table_name.created_at', '>=', Carbon::today()->subWeek())->count();
HasManyThrough Link
Use DB::enableQueryLog(); and DB::getQueryLog(); to see if executed SQL Query is correct;

Laravel 5 issue with wherePivot

I am working with Laravel 5 and I am having issue getting ->wherePivot() to work on a Many-to-Many relationship. When I dd() the SQL it looks like Eloquent is looking for records in the pivot table with a `pose_state`.`pose_id` is null`.
I am hoping it is a simple error and not a bug. Any ideas are appreciated.
Database Structure
pose
id
name
type
state
id
name
machine_name
pose_state
pose_id
state_id
status
Models
Pose
<?php namespace App;
use DB;
use App\State;
use Illuminate\Database\Eloquent\Model;
class Pose extends Model {
public function states()
{
return $this->belongsToMany('App\State')
->withPivot('status_id')
->withTimestamps();
}
public function scopeWithPendingReviews()
{
return $this->states()
->wherePivot('status_id',10);
}
}
State
<?php namespace App;
use Illuminate\Database\Eloquent\Model;
class State extends Model {
public function poses()
{
return $this->belongsToMany('Pose')
->withPivot('status_id')
->withTimestamps();
}
}
PosesController function
public function listPosesForReview(){
$poses = Pose::withPendingReviews()->get();
dd($poses->toArray() );
}
SQL
select
`states`.*, `pose_state`.`pose_id` as `pivot_pose_id`,
`pose_state`.`state_id` as `pivot_state_id`,
`pose_state`.`status_id` as `pivot_status_id`,
`pose_state`.`created_at` as `pivot_created_at`,
`pose_state`.`updated_at` as `pivot_updated_at`
from
`states` inner join `pose_state` on `states`.`id` = `pose_state`.`state_id`
where
`pose_state`.`pose_id` is null and `pose_state`.`status_id` = ?
EDIT
When I updated my code to removing the scope it worked. Thanks #Deefour for putting me on the right path! Maybe scope has something else to that I am missing.
public function pendingReviews()
{
return $this->states()
->wherePivot('status_id','=', 10);
}
YET ANOTHER EDIT
I finally got this to work. The solution above was giving me duplicate entries. No idea why this works, but it does, so I will stick with it.
public function scopeWithStatusCode($query, $tag)
{
$query->with(['states' => function($q) use ($tag)
{
$q->wherePivot('status_id','=', $tag);
}])
->whereHas('states',function($q) use ($tag)
{
$q->where('status_id', $tag);
});
}
I think your implementation of scopeWithPendingReviews() is an abuse of the intended use of scopes.
A scope should be thought of as a reusable set of conditions to append to an existing query, even if that query is simply
SomeModel::newQuery()
The idea is that a pre-existing query would be further refined (read: 'scoped') by the conditions within the scope method, not to generate a new query, and definitely not to generate a new query based on an associated model.
By default, the first and only argument passed to a scope method is the query builder instance itself.
Your scope implementation on your Pose model was really a query against the states table as soon as you did this
$this->states()
This is why your SQL appears as it does. It's also a clear indicator you're misusing scopes. A scope might instead look like this
public function scopeWithPendingReviews($query) {
$query->join('pose_state', 'poses.id', '=', 'pose_state.pose.id')
->where('status_id', 10);
}
Unlike your new pendingReviews() method which is returning a query based on the State model, this scope will refine a query on the Pose model.
Now you can use your scope as you originally intended.
$poses = Pose::withPendingReviews();
which could be translated into the more verbose
$poses = Pose::newQuery()->withPendingReviews();
Notice also the scope above doesn't return a value. It's accepting the existing query builder object and adding onto it.
The other answer to this question is filled with misinformation.
You cannot use wherePivot() as is claims.
Your use of withTimestamps() is not at all related to your problem
You don't have to do any "custom work" to get timestamps working. Adding the withTimestamps() call as you did is all that is needed. Just make sure you have a created_at and updated_at column in your join table.
I think that your implementation of scopes is fine, the problem I see is just a typo. Your schema shows that the field is called status but your where condition is referring to a status_id
Try:
->wherePivot('status', 10);
Also, the withTimestamps() method is causing issues. You don't have timestamps in your schema for the pivot (as I can see) so you shouldn't be putting these in the your relation definitions as it's trying to fetch the timestamps relating to when the relation was created/updated. You can do this if you set up your pivot table schema to have the timestamp fields, but I think you'll have to do some custom work to get the timestamps to save properly.
This worked for me (Laravel 5.3):
$task = App\Models\PricingTask::find(1);
$task->products()->wherePivot('taggable_type', 'product')->get();
You can also have this problem (return no results) if the column you are using in wherePivot hasn't been added to withPivot.

Using withTrashed with relationships in Eloquent

Is there a way to use withTrashed with relationships in Eloquent.
What I need is this. I have table and model Mark and another table User. User has many Mark and Mark belongs to User. So I defined this in Eloquent models.
Now I need to get an instance of Mark that is soft deleted. This is not a problem if User isn't soft deleted, but if both Mark and User are soft deleted, I get an error Trying to get property of non-object, because
$mark->user
won't return actual user, cause it is soft deleted.
Is there a way that I can do something like
$mark->withTrashed()->user
to get this related user even if it is deleted?
Depending on your needs, you can define the relationship:
public function marks()
{
return $this->hasMany('Mark')->withTrashed();
}
// then just
$user->marks;
or use it on the fly:
$user->marks()->withTrashed()->get();
// or when lazy/eager loading
$user = User::with(['marks' => function ($q) {
$q->withTrashed();
}])->find($userId);
then your case would turn into:
$mark->user() // get relation object first
->withTrashed() // apply withTrashed on the relation query
->first(); // fetch the user
// alternatively you use getResults relation method
$mark->user()
->withTrashed()
->getResults(); // returns single model for belongsTo
$user->marks()->withTrashed()
->getResults(); // returns collection for hasMany
You can do that like this:
$mark->withTrashed()->first()->user->withTrashed()->first()

count() returning soft deleted items in laravel

I have a model Comments that uses soft deleting: it has a one-to-many relationship with my Post model.
My site will have a native mobile app associated with it and I need to send a count of the comments to it when I send the information about a post and for some reason it is returning the count WITH the soft deleted items.
I've got the Post array working and sending the comment count using
protected $appends = array('score','commentcount', 'ups', 'downs');
and
public function getCommentcountAttribute()
{
return DB::table('comments')
->where('post_id',$this->id)
->where('deleted_at','=',NULL)
->count();
}
in my post model. I've also tried
public function getCommentcountAttribute()
{
return $this->comments()->count();
}
and
public function getCommentcountAttribute()
{
return $this->comments()->whereNull('deleted_at')->count();
// also: return $this->comments()->where('deleted_at',NULL)->count();
}
also when defining the relationship I've tried adding ->whereNUll('deleted_at') to both the ->hasMany('Comment') and the ->belongsTo('Post') with no luck.
I've checked the database and ran the SQL I'm expecting Fluent and Eloquent to be generating which is
SELECT * FROM `comments` WHERE post_id=31 and deleted_at=null
(31 being the post I'm using to test). Nothing is working. Let me know if you guys need to see anymore specific functions as I'd rather not post my entire models.
I was able to make it work with ->whereRaw('deleted_at = ?',array(NULL)). That seems pretty hacky to me though. I'd gladly accept a better answer.
You have to enable Soft Deleting in your model.
class Comment extends Eloquent {
protected $softDelete = true;
}
That's it.
And you don't need to include the following where clauses in your queries:
return DB::table('comments')
->where('post_id',$this->id)
//->where('deleted_at','=',NULL) // no needed, Laravel by default will include this condition
->count();
public function getCommentcountAttribute()
{
// remove ->whereNull('deleted_at')
return $this->comments()->count();
}
Change your code to:
return \App\Comments::count();
Soft delete works only on models, not queries:
class Comment extends Eloquent
{
protected $softDelete = true;
}
Whilst this is an old post the following should hopefully be helpful with others if they come across this.
For laravel V5 and above.
Add use SoftDeletes; to your model.
If you are trying to get the count that includes soft deletes use the following:
Model::withTrashed()->'yourquery'
If you do not want soft deleted records included then you can follow the normal convection.
Model::select()->get();

Categories