Laravel - why is a Model's relations not being loaded? - laravel

I have a Laravel 5.3 site and I think maybe I have some weird things going on due to some actions happening in API controllers and some happening in the regular controllers.
Or maybe an issue where at some times I am dealing with a Model, and sometimes with an Eloquent Collection.
The issue is, I am trying to retrieve relations on a Model and am getting null.
For instance, I have course Model that relates to week Model.
In course Model I get week items as
public function weeks()
{
return $this->hasMany(Week::class, 'course_id');
}
In backend, these relations get sent in this way:
$course->load('weeks')
All is good.
But when course item gets deleted and I try and take action in the week controller as
static::deleting(function($course) {
$course->weeks->delete();
});
$course->weeks is null. At that time I see in the database that all is good and this course items does indeed have a week item related, but $course shows 0 relations.
So something odd is happening where $course->webinars is not grabbing the week items related.
Is there something that I am fundamentally doing wrong? Maybe it is because in the models I have these sorts of statements:
protected $table = 'Week';
Are these preventing the relations from being pulled? I always thought that is I had some function in a model that returns relations that those relations would always be available when I use syntax $course->weeks.
Ideas?
Thanks again,

You can simply setup migrations to automatically delete from weeks if you delete a course, provided you have foreign key relationship.
If you have a column course_id in weeks table then add this into your migration
$table->foreign('course_id')
->references('id')->on('courses')
->onDelete('cascade')

I think you can use Observers. In your AppServiceProvider, first register the observer.
public function boot()
{
Course::observe(CourseObserver::class);
}
Now, add an Observer class.
class CourseObserver
{
public function deleting(Course $course)
{
$course->weeks()->delete();
}
}

Related

n-n relation in Backpackforlaravel: doesn't delete corresponding items

I'm having troubles with a n-n relationship in my Backpackforlaravel app. I have Registrations and Sessions, the setup in the model looks like this:
Registration:
public function sessions()
{
return $this->belongsToMany(\App\Models\Session::class);
}
Session:
public function registrations()
{
return $this->belongsToMany(\App\Models\Registration::class);
}
So the setup seems to be fine, and I also have a registration_session table in my database.
What I want to achieve is, that whenever a Session gets deleted, I want to delete all the entries in the registration_session table, but also all the Registrations. I thought the deletion of the entries in the registration_session table maybe is a standard, but it didn't delete them when I deleted a session. In order to achieve both, I did the following in the destroy function of my SessionCrudController:
public function destroy($id)
{
$this->crud->hasAccessOrFail('delete');
foreach ($this->crud->getCurrentEntry()->registrations as $registration) {
$registration->delete();
}
DB::table('registration_session')->where('session_id', $id)->delete(); //really required?
return $this->crud->delete($id);
}
I have the feeling that I'm doing things way more complicated than I should, so I would appreciate any recommendations.
Update: this is how I store the data initially in the table...the information is coming from a form, so it's not added in a CRUD panel:
DB::table('registration_session')->insert([
'registration_id' => $reg->id,
'session_id' => $sesId
]);
By default, Backpack or Laravel don't concern themselves with deleting related entries, because they assume your Database layer will handle it. With most DBMSs you can specify such rules, and Laravel makes it easy to do so in their migrations. Take a look at foreign key constraints in the Laravel docs, it's pretty easy to build your migration in such a way that any time one item gets deleted, the related items will to, using "cascade":
$table->foreignId('user_id')
->constrained()
->onUpdate('cascade')
->onDelete('cascade');

Laravel Eloquent relationship efficency

When I declare a relationship in a model, for example:
class Post extends Model
{
public function comments()
{
return $this->hasMany(Comment::class);
}
}
Are comments retrieved from the database the moment I retrieve the Post instance or the moment I write
$post->comments;
?
The answers so far solve this problem in pieces, but not very clearly so allow me to help. To answer your question bluntly, creating a Post instance does not also load associated comments.
Here is why:
When you define an Eloquent relationship, you are basically attaching a whole new 'query' method to your object and so it won't actually be executed unless you call it.
As a simple example we have Car:
class Car {
public $color;
public function __construct() {
$this->color = 'blue';
}
public function makeRed() {
$this->color = 'red';
return $this;
}
}
In this example, the instantiated Car will only have one property, color. This car will be blue unless you call the makeRed() method and change it. It does not compute both options simultaneously expecting that you may decide to change it's color.
So to relate that back to the Eloquent relationship, the comments method returns a relationship object, but only if the method is called on the Post object. Up until that point, your Post object will not automatically call it's own methods. Basically, don't worry about an object becoming large with a ton of methods as these methods only contribute to object size significantly if they are actually called.
If you wish for comments to be loaded with your Post immediately, eager loading the initial query will allow this by:
$post = Post::with('comments')->findOrFail('post_id');
Otherwise, the following would give you the comments for a given post:
$post = Post::findOrFail('post_id');
$post->comments;
Please see the Laravel documentation on Eager Loading for more information.
Hope this helps!
The answer its simple:
$post->comments() returns the relationship object
$post->comments returns the result of the relationship
So the moment you do $post->comments that means fetch relationship and execute query, therfore returns relational database results.
They are retrieved when you ask for it, i.e $post->comments. If you want to eager load them, you can write Post::with('comments')->get(). Check out documentation. It explains eager loading and the N+1 problem.
From the docs:
When accessing Eloquent relationships as properties, the relationship data is "lazy loaded". This means the relationship data is not actually loaded until you first access the property. However, Eloquent can "eager load" relationships at the time you query the parent model. Eager loading alleviates the N + 1 query problem.

Laravel Method [hasMany] does not exist

I have 2 controllers and models:
User Controller & Model
Hero Controller & Model
Each user can have unlimited heroes - it means that the relationship between them is one to many.
In my UserController I created the following method:
/**
* Get the heroes of the user.
*/
public function heroes()
{
return $this->hasMany(Hero::Class);
}
while in my HeroController I created this method:
/**
* Get the user that owns the hero.
*/
public function user()
{
return $this->belongsTo(User::class)
}
Added this to my routes file:
Route::get('userHeroes', 'UserController#heroes');
and it returns this error:
{"error":{"message":"Method [hasMany] does not exist.","status_code":500}}
What could have gone wrong.. ?
The controller is just a delegate between the request and the return data - you tell it that you want something, it figures out what you want, and then it calls the appropriate places to get something to return.
The hasMany() and belongsTo() methods, on the other hand, are logic specifically related to the Hero and User models, on the other hand.
What you need is to move the heroes() method to your User model, because a user can have many heroes. Also need the user() method to your Hero model, because a hero belongs to a user.
Then you put an action call in a controller. Let's say, for instance, that you have a UserController which has an getHeroes() method. That might look like this:
public function getHeroes() {
$user = auth()->user();
$heroes = $user->heroes;
return $heroes;
}
And that will format it to JSON. Just an example.
But you might want to read a tutorial or two about this, since it's fairly basic stuff and it's good to get a good handle on early on. Please don't take that the wrong way - we're happy to help if you run into problems, I just think you might need a stronger foundation. The screencasts at Laracasts are highly recommended for this purpose.
it must be declared in models, not in controllers, hasMany() is a method in eloquent models.
hasMany and belongsTo methods are eloquent class methods.
And we inherit eloquent in our model so that we can use the eloquent methods functionality.
In order to use the relation, you have to define the relation method in respective model class and then you can call from controller.
Please refer the documentation Eloquent relationship documentation
Hope i have cleared your doubt.
Thanks

Laravel - latest record from relationship in whereHas()

This is my first post in here, so please forgive any mistakes :)
I'm currently working on the project of stock management application (Laravel). I came to the point where anything I do doesn't work, so now I beg for help with it.
I have a table with products, of which some are in the relationship with the others. Everything happens in one table. If the product has a child, the child overrides the parent.
products table view
Then, all the queries I run on them use the following logic:
If the item doesn't have any child, use it.
If the item has children, use the latest child (highest id)
Now I have the relationships created in model file:
public function childItems(){
return $this->hasMany('\App\OrderItem','parent_id');
}
public function parentItem(){
return $this->belongsTo('\App\OrderItem','parent_id');
}
public function latestChild(){
return $this->hasOne('\App\OrderItem','parent_id')->orderBy('id','desc')->limit(1);
}
The problem with latestChild() relationship is, that when you run this query:
\App\OrderItem::find(7)->latestChild()->get()
It works fine and returns only one (latest)(id 6) record in relationship - to do it I had to add orderBy and limit to hasOne().
But when I want to use this relationship in scopes, so in whereHas method, it doesn't work properly, as takes any of the children instead of the latest one.
public function scopeDue($query){
return $query->where(function($q){
$q->has('childItems','==',0)->has('parentItem','==',0)->whereDate('due_date','=', Carbon::today()->toDateString())->whereNull('return_date');
})->orWhere(function($q2){
$q2->has('childItems')->has('parentItem','==',0)->whereHas('childItems',function($q3) use($q2){
$q3->whereDate('due_date','=', Carbon::today()->toDateString())->whereNull('return_date');
});
})->with('latestChild');
}
However, with() at the end returns the right record.
I think, the reason it works so is because my relationship latestChild() returns all the children (despite hasOne()) and when i use it in whereHas it ignores the filtering functions I applied.
I know it's a little bit complex from what I describe, but to explain it better I will use an example. Executing the following in tinker
\App\OrderItem::due()->get();
Should return only record id 2, as the number seven has children, where of course id 5 is due, but the latest child is id 6 which is not due.
I hope I've explained it enough to let you help me, as I'm already going crazy with it.
If you have any ideas on how I could achieve what I need by changing exisiting one or changing the whole logic of it, please help!
Thanks,
Darek
Try this one:
->with(
[
'latestChild' => function (HasOne $query) {
return $query->latest('id')->limit(1);
}
]
);
I think the problem is in your latestChild() method where you do a limit(1). Why don't you try the last() method instead?
So:
public function latestChild(){
return $this->hasOne('\App\OrderItem','parent_id')->last();
}
EDIT:
What about returning the value like this:
public function latestChild(){
$item = App\OrderItem::all()->last();
return $item;
}

Laravel / Eloquent - custom relation method

I have a class Report which has a belongsToMany relation to Metric. Report also additionally has a belongsTo relation to Metric.
Normally, the model returned by the belongsTo relation is the same as one of the models in the belongsToMany relation. When this is true I'd like it to be the case that each of the two relations actually looks at the same object instance (this also saves an extra trip to the db).
So, in basic terms - is there a way to get one relation to check another first, to see if a model has already been loaded, and if so, point to that object rather than creating a new one.
I tried putting some code in the belongsTo relation method for Metric but I can't get round the fact it needs to return an instance of belongsTo, which needs various things passed as constructor arguments (ie. a query object), which aren't relevant in that case that the model has already been loaded in the belongsToMany relation.
I thought of ditching the belongsTo relation and adding data horizontally in the pivot table for the belongsToMany relation, but it isn't a many-to-many relation required so that seems a bit wrong.
Thanks!
Geoff
The idea here is to write a function which would check if a relationship is loaded and return that relationship, otherwise it will return the belongsToMany. This would go in your Report class. This is also for Laravel 5. If you have 4, just remove the namespaces from the model names.
public function metric()
{
return $this->belongsTo('App\Metric');
}
public function metrics()
{
return $this->belongsToMany('App\Metric');
}
public function getMetric()
{
if(array_key_exists('metric', $this->getRelations())) {
return $this->metric;
}
return $this->metrics()->first();
}
If you do decide to just go with a belongsToMany only, I'd suggest putting a unique key on your pivot table for both ID's to keep from getting any duplicates in the pivot table.

Resources