SoftDeletes on model breaks dynamic properties - php

TLDR: When the SoftDeletes trait is included in my parent model, I no longer get soft deleted instances of the parent model as a dynamic property of the child. How can this be done?
I have defined a couple of basic models, like this:
<?php
namespace App;
use Illuminate\Database\Eloquent\Model;
use Illuminate\Database\Eloquent\SoftDeletes;
use Illuminate\Database\Eloquent\Builder;
class User extends Model
{
use SoftDeletes;
public function posts()
{
return $this->hasMany("App\Post");
}
}
class Post extends Model
{
public function user()
{
return $this->belongsTo("App\User");
}
public function scopePending(Builder $query)
{
return $query->whereNull("pending");
}
}
In my controller, I want to list pending posts, so I do this:
<?php
namespace App\Controllers;
use App\Post;
class PostController extends Controller
{
public function index()
{
$posts = Post::pending()->get();
return view("post.index", ["pending"=>$posts]);
}
}
And finally, in my view:
#foreach($pending as $post)
{{ $post->title }}<br/>
{{ $post->user->name }}<br/>
#endforeach
This results in an exception being thrown, "Trying to get property of non-object" with the line number corresponding to where I try to output $post->user->name for users which have been soft deleted.
How can I have these dynamic properties include soft deleted items?

Apparently the related user model has been soft-deleted, that's why the related user is not loaded.
Define the relation like in the code below and you'll be always able to fetch a user regardless if they have been soft-deleted or not:
public function user()
{
return $this->belongsTo("App\User")->withTrashed();
}

Unfortunately if you do so
public function user()
{
return $this->belongsTo("App\User")->withTrashed();
}
you will no longer available to use dynamic access to user relation, because dynamic access expects that relation method returns BelongsTo instance, which returns by belongsTo method of Eloquent class. But withTrashed returns Builder instance.
EDIT
I was wrong, thanks #patricius for being guided on the right path (in comments).

Related

Lavarel - Error in relation: Call to undefined relationship [nota] on model [App\Nota]

I want to make the following query with Eloquent:
$nota=DB::table('notas')
->join('users', 'users.id', '=', 'notas.id_user')
->select('notas.id','notas.nombre', 'notas.descripcion', 'users.name AS user_name', 'users.email')
->first();
I try to make the relation in the models and called like this, in my controller:
public function show($id)
{
$nota = Nota::with(['user','nota'])->first();
print_r($nota);
return view("notas.detalle", compact("nota"));
}
But i get the follow error:
Illuminate\Database\Eloquent\RelationNotFoundException
Call to undefined relationship [nota] on model [App\Nota].
My models looks like this:
Nota.php:
<?php
namespace App;
use Illuminate\Database\Eloquent\Model;
class Nota extends Model
{
public function user()
{
return $this->belongsTo('App\User');
}
}
User.php:
<?php
namespace App;
use Illuminate\Contracts\Auth\MustVerifyEmail;
use Illuminate\Foundation\Auth\User as Authenticatable;
use Illuminate\Notifications\Notifiable;
class User extends Authenticatable
{
use Notifiable;
public function notas()
{
return $this->hasMany('App\Nota');
}
}
The problem is with your understanding of relationship. You don't make any relationship named nota but you are using it and ridiculously with the same model. So firstly make the relationship right using naming convention.
In Nota model
public function user()
{
return $this->belongsTo('App\User','id_user');
}
In User model
public function notas()
{
return $this->hasMany('App\Nota','id_user');
}
And now in controller
$nota = Nota::with('user')->first();
return view("notas.detalle", compact("nota"));
Look this is an eager loading. You can lazy load the relationship too.
In view now you can access object property like
{{ $nota->nombre }}
And relationship object like
{{ $nota->user->email }}
The problem is with this function Nota::with(['user','nota'])->first()
Why do you want nota with nota. it's sounds ridiculous isn't it?
So just remove it, then everything will work fine.
$nota = Nota::with(['user'])->first();
return view("notas.detalle", compact("nota"));

Laravel Eloquent: hasManyThrough Many to Many relationship

I have the following classes (simplified here) in my Laravel 5.7 model:
VEHICLE.PHP
<?php
namespace App;
use Illuminate\Database\Eloquent\Model;
class Vehicle extends Model
{
public function journeys()
{
return $this->hasMany('App\Journey');
}
}
JOURNEY PHP
<?php
namespace App;
use Illuminate\Database\Eloquent\Model;
class Journey extends Model
{
public function vehicle()
{
return $this->belongsTo('App\Vehicle');
}
public function users()
{
return $this->belongsToMany('App\User');
}
}
USER.PHP
<?php
namespace App;
use Illuminate\Database\Eloquent\Model;
class User extends Model
{
public function journeys()
{
return $this->belongsToMany('App\Journey');
}
}
I have an intermediate table (journey_user) between users and journeys (see schema attached).
I can easily get all journeys made by a particular user. But how can I get all vehicles used by a particular user? The standard hasManyThrough method does not appear to work because of the Many to Many relationship between users and journeys.
Thanks for your help!
I haven't tested this out. However, it should be possible to get all vehicles by looping through all the user's journeys, creating an array of vehicles and return this as a collection.
This can be added to your User.php model controller:
/**
* Get all vehicles which user has used
*
*/
public function vehicles()
{
$vehicles = [];
$this->journeys()
->each(function ($journey, $key) use (&$vehicles) {
$vehicles[] = $journey->vehicle;
});
return collect($vehicles);
}
Here, we create an empty array. Then we loop through all the journeys of the users (passing the $vehicles array as a reference to update it).
We use the each() collection method to loop through each journey. We create a new entry to the $vehicles array, adding the vehicle.
Finally, we return all vehicles as a collection.
We can use this in our application like so:
User::find($id)->vehicles();
Note: You could return this as an accessor attribute by changing the function name to setVehiclesAttribute(). This will allow you to access the vehicle field like User::find($id)->vehicle.

Get the morphMany relationship to a model from the user

Let say i have the following;
User Model;
class User extends Authenticatable
{
public function posts()
{
return $this->hasMany('App\Models\Socials\Post');
}
}
Post Model;
class Post extends Model
{
public function comments()
{
return $this->morphMany('App\Models\Socials\Comment', 'commentable');
}
Comment model;
class Comment extends Model
{
public function commentable()
{
return $this->morphTo();
}
When i used $user = User::find($id); and $user->posts(), it returns all the post of the user, but if i used this method $user->posts()->comments() It return this message Method Illuminate\Database\Query\Builder::comments does not exist.
The question is how can i get all the comments of the user on the said post?
Change:
$user->posts()->comments();
to:
$user->posts->pluck('comments')->collapse();
The method itself returns an instance of Eloquent's query builder allowing you to add to or edit the query if you want. However, if you don't want to edit the query you can access the relationships as properties and Laravel will handle to execution of the query.
Essentially, $user->posts is actually turning into $user->posts()->get() in the background.
Credit to #JonasStaudenmeir.

Laravel 5.5 MassAssignmentException

I'm following the Laravel From Scratch tutorial series, I'm currently at the part that you are creating a comment system for your articles system. But I'm having a problem, I don't really know what the error is saying at this point.
The error:
Illuminate\Database\Eloquent\MassAssignmentException
body
The comment model:
<?php
namespace App;
use Illuminate\Database\Eloquent\Model;
class Comment extends Model
{
public function post()
{
return $this->belongsTo(Post::class);
}
}
The post model:
<?php
namespace App;
class Post extends Model
{
public function comments()
{
return $this->hasMany(Comment::class);
}
public function addComment($body)
{
$this->comments()->create(compact('body'));
}
}
The route I made:
Route::post('/posts/{post}/comments', 'CommentsController#store');
The comments controller:
<?php
namespace App\Http\Controllers;
use App\Post;
class CommentsController extends Controller
{
public function store(Post $post)
{
$post->addComment(request('body'));
return back();
}
}
Thanks in advance!
Explanation of this error
This is a security feature of Laravel. It is designed to protect you against form manipulation when using mass assignments.
For example on a sign-up form: When you have an is_admin column in your database, a user simply could manipulate your form to set is_admin to true on your server, and therefore in your database. This security feature prevents that by using a whitelist to define safe fields.
How to fix that
You need to set a $fillable property on your model. It's value must be an array containing all fields that are safe to mass assignable (like username, email address, ...).
<?php
namespace App;
use Illuminate\Database\Eloquent\Model;
class Comment extends Model
{
# This property!
protected $fillable = ['body'];
// ...
}
See "Mass assignment" in the docs:
https://laravel.com/docs/5.5/eloquent#mass-assignment
Mass assignment is when you send an array to the model creation, basically setting a bunch of fields on the model in a single go, rather than one by one, something like what you did here:
public function addComment($body)
{
$this->comments()->create(compact('body'));
}
You need to add the field you are populating to the fillable array in Comments.php model:
<?php
namespace App;
use Illuminate\Database\Eloquent\Model;
class Comment extends Model
{
protected $fillable = ['body'];
public function post()
{
return $this->belongsTo(Post::class);
}
}
As the documentation states:
You may also use the create method to save a new model in a single
line. The inserted model instance will be returned to you from the
method. However, before doing so, you will need to specify either a
fillable or guarded attribute on the model, as all Eloquent models
protect against mass-assignment by default.
Hope this helps you.

Accessing a property on a method which returns a Model in Laravel

I have a Post model which has a function as follows:
namespace App;
use App\Category;
class Post extends Model
{
public function category()
{
return $this->hasOne(Category::class);
}
}
And my Category Model looks like this
namespace App;
use App\Post;
class Category extends Model
{
public function posts()
{
return $this->hasMany(Post::class);
}
}
In my view, I want to get access to the name field on the category for each post.
I assumed I would have access to that model and could therefore get it by doing this in my blade file:
{{ $post->category()->name }}
$post is correct, I have access to other properties but this throws the error:
Undefined property: Illuminate\Database\Eloquent\Relations\HasOne::$name
Any ideas?
You should access it as an attribute:
{{ $post->category->name }}
The function category() should be defined in Post model as:
public function category()
{
return $this->belongsTo(Category::class, 'category_id');
}
If category_id has another name, just change it in the parameter.
you can easily do this:
$category=$post->category;
$cat_name=$category->name;
also if you only want the name field of the category you can use :
$cat_name=$post->category()->get(['name']);

Categories