Custom method not work on HasMany relation - laravel

I trying to call a custom method from a model by relation.
User Model:
class User extends Model
{
public function files()
{
return $this->hasMany(Files::class, 'file_id', 'id');
}
}
File Model:
class Files extends Model
{
public function cover()
{
dd('blah blah');
}
}
In my controller I said:
$user = User::find(1);
$user->files()->cover();
But I will get this error:
Call to undefined method Illuminate\Database\Eloquent\Relations\HasMany::cover()
What is the problem in my code?

Basically you are calling your cover() method over the collection. That's why that is not working.
You are using hasMany Laravel relationship. And this hasMany returns collection of related records(items). And yo can't call any model function on that directly.
But if you wan to call function on this. you need to firstly loop the items, like below example:-
$user = User::find(1);
foreach($user->files() as $file) {
$file->cover();
}
Above code will provide you output. Try this.

If you want to get all the covers of you files, you can do this :
$user = User::with('files.cover')->find(1);
$covers = $user->files->pluck('cover')->flatten();
I want to use this method to check and then store the cover. because I need to check the cover before insert I couldn't use create method, it will be an alias to create. so I couldn't overwrite to create method?
From this, you can do the following:
foreach($user->files as $file){
$cover = $file->cover()->firstOrCreate(['attribute' => $value]);
// If you want to check if you just created the cover
if($cover->wasRecentlyCreated){
// Do stuff
}
}

Related

How to disable loading of relationships when not needed in Laravel

is it possible to disable the loading of relationships, but only in some cases?
Here are my models:
class League extends Model
{
...
public function country()
{
return $this->belongsTo(Country::class)->with('translations');
}
}
class Country extends Model
{
...
public function translations()
{
return $this->hasMany(CountryTranslation::class, 'country_id');
}
}
class CountryTranslation extends Model
{
...
}
In many places, I need to load the translations relationship for Country, but on some pages, I want to display information about the League and its Country only. There I don't want to show the CountryTranslation collection.
Here is the code for that page:
$country = $league->country;
Is it possible only for this line to disable the relations?
So, you're currently finding out one of the reasons for not defining the eager loading inside of the relationship. The first suggestion would be to remove the with() from the relationship definition, and add it in where needed. If desired, you can create another relationship that has the eager loading enabled, and it can use the base relationship to keep it DRY:
public function country()
{
return $this->belongsTo(Country::class);
}
public function countryWithTranslations()
{
return $this->country()->with('translations');
}
If this code change is not feasible, you will need to change how you're accessing the country relationship. When you access the relationship attribute, it lazy loads the relationship, and you don't have the ability to modify the relationship query. So, instead of accessing the relationship attribute, you'd need to call the relationship query so you can modify it.
Therefore, you won't be able to do $country = $league->country;, but you can do:
$country = $league->country()->without('translations')->first();
he with() simply eager loads the translations to avoid additional queries, but you should be able to load the translations with and without it, without with( adds additional queries. https://laravel.com/docs/9.x/eloquent-relationships#eager-loading
You will want to change:
public function country()
{
return $this->belongsTo(Country::class)->with('translations');
}
to
public function country()
{
return $this->belongsTo(Country::class);
}
If you want to load translations, you can do it in the controllers
// if you want translations at some point do this:
$league = League::with('country.translations')
$country = $league->country->translations
// if you do not want translations
$league = League::with('country')
$country = $league->country;
If you do not want to touch:
public function country()
{
return $this->belongsTo(Country::class)->with('translations');
}
you can create another method
public function countryClean()
{
return $this->belongsTo(Country::class);
}
$country = $league->countryClean;

Using an Eloquent relationship within an Eloquent accessor?

I am writing a Laravel application that manages training courses.
Each course is represented by a Course model.
A course can have many dates - these are represented by a CourseDate model, with a hasMany relationship between the two:
Each course also has a single "date template", which is a CourseDate, but with an "is_template" boolean set.
I want to create an accessor on the Course model that retrieves its date template.
The (relevant) code for each model is:
class Course extends Model {
public function getDateTemplateAttribute() {
$dates = $this->dates;
$filtered = $dates->where('is_template', true);
$template = $filtered->first();
return $template;
}
public function dates() {
$result = $this->hasMany( CourseDate::class );
return $result;
}
}
class CourseDate extends Model {
public function course() {
return $this->belongsTo( Course::class );
}
}
Then, in my controller, I have this:
// this block works absolutely perfectly
$course = Course::find(1);
$dates = $course->dates;
$working_date_template = $dates->where('is_template', true)->first();
// this one doesn't work at all and says "call to a member function first() on array"
$broken_date_template = $course->date_template;
Stepping through with xdebug in the broken code, the line $dates = $this->dates returns an empty array so everything else afterwards breaks.
Is this a limitation with the Laravel accessor/relationship system? Or am I just being dense and doing something wrong.
I worked this out just now.
I needed to use $this->dates() within the model itself as this returns the relationship and I can then filter it out accordingly using the where() method and other query builder methods.
This was, of course, mentioned in the Laravel documentation - I just didn't spot it.

Simple Laravel Relationship

I have two models, one is LeadHistory and the other one is Leads.
Leads:
class Leads extends Model
{
public function lead_history()
{
return $this->hasMany('App\LeadHistory');
}
}
LeadHistory:
class LeadHistory extends Model
{
public function lead()
{
return $this->belongsTo('App\Leads', 'lead_id', 'id');
}
}
When I go into php tinker, get the first Lead ($lead = App\Leads::first();), create a new LeadHistory ($leadHistory = new App\LeadHistory;) and ($leadHistory->message = 'second one';) and ($leadHistory->status_id = 11;) then try to save the leadHistory ($leadHistory->lead()->save($lead);). I get this error message:
BadMethodCallException with message 'Call to undefined method Illuminate\Database\Query\Builder::save()'
Can someone point me in the right direction, I feel like I have been following the instructions given in Laracasts but can't seem to get the LeadHistory to save with the associated Lead ID.
You’re trying to call save() on a relation rather than a model I think.
Instead, “attach” your LeadHistory model to your Lead model:
$lead = Lead::create($leadAttributes);
$history = new LeadHistory($leadHistoryAttributes);
$lead->history()->attach($history);
You’ll need to rename your relation if you copy-and-paste the above code:
class Lead extends Model
{
public function history()
{
return $this->hasMany(LeadHistory::class);
}
}
I feel the name “lead history” is superfluous when you’re already working with a Lead model.
Try to save $leadHistory first:
$leadHistory->save();
And then:
$lead->lead_history()->save($leadHistory)
Correct me if I'm wrong, but since you already have a model instance of your target App\Leads, I think you should be able to simply access the id of that instance and inject it into a static create call:
$lead = App\Leads::first();
$leadHistory = App\LeadHistory::create([
'message' => 'second one',
'status_id' => 11,
'lead_id' => $lead->id
]);
Before being able to use the create method you'd have to make the properties you want to assign 'mass assignable', by defining a protected property called $fillable in your model:
class LeadHistory extends Model
{
protected $fillable = [
'message',
'status_id',
'lead_id'
];
public function lead()
{
return $this->belongsTo('App\Leads', 'lead_id', 'id');
}
}
This will effectively associate your new record with that lead, since the only thing the Eloquent model does in this regard is providing another way to describe the same relationships your database exercises.
Some other answers mention the attach() method of an Eloquent model. This method is used to attach two models with a many to many relationship (relationships defined with belongsToMany).

Laravel 5.2 How to delete parent record from belongsTo relationship?

My Pricing model
protected $fillable = [
'zone_id',
'is_short_time',
'is_service_feeder',
'route_id',
'value',
];
public function route()
{
return $this->belongsTo(Route::class);
}
My Route Model
protected $fillable = [
'zone_id',
'from_city_id',
'to_city_id',
'is_in_the_city',
];
public function pricing(){
return $this->hasOne(Pricing::class);
}
Here is my controller
public function feeder_destroy($zoneid, $pricingfeederid)
{
$pricing_feeder = Pricing::find($pricingfeederid)->where('zone_id', $zoneid)->where('is_service_feeder', 1);
$pricing_feeder->route()->delete();
$pricing_feeder->delete();
}
The error says
Call to undefined method Illuminate\Database\Query\Builder::route()
I want to delete pricing record and route record too.
What wrong with my code? How should it's look like?
Any help appreciated.
Your controller should
$pricing_feeder = Pricing::find($pricingfeederid)->where('zone_id', $zoneid)->where('is_service_feeder', 1)->first();
Dont forget first() at the end.
Then use like so $pricing_feeder->route->delete();
Try this... $pricing_feeder->route->delete(); Removing () from route()
Your error is on the relation, not the Parent.
not the fanciest but you can delete the route that comes with pricing by adding this method to your route model
public function delete()
{
// delete all related pricing
$this->pricing()->delete();
// delete the route as well
return parent::delete();
}
then just call $pricing_feeder->route->delete();
Ok so I have this model relationships
In Post model
public function user(){
return $this->belongsTo(User::class);
}
In User model
public function post(){
return $this->hasMany(Post::class);
}
and the following code deleted all the posts and the user
$user = \App\User::findOrFail(1);
$user->post[1]->delete();
$user->delete();
Your code does not work, becouse you need to add a first() method call:
$pricing_feeder = Pricing::find($pricingfeederid)->where('zone_id', $zoneid)->where('is_service_feeder', 1)->first();
Than I'd check the returned object if is null. If is not, than you can delete the related relationship and the model as well.
Also, when calling the delete() methods use the relationships as follows:
$pricing_feeder->route->delete();

Laravel 4.1 eloquent model set appends dynamically

I am using Laravel 4.2.
I have two models: User and Video, both of these models are having one-to-many relationship i.e. User -> HasMany-> Video.
Recently, I got a requirement to display the list of users along with sum of file-size of total videos uploaded by each user and allow users to be order by the sum of file size ascending or descending.
I've made following changes in User model:
class User extends Eloquent {
protected $hidden = array('videosSum');
protected $appends = array('videos_size');
public function videosSum() {
return $this->hasOne('Video')
->selectRaw('sum(file_size) as sum, user_id')
->groupBy('user_id');
}
public function getVideosSizeAttribute()
{
// if relation is not loaded already, let's do it first
if ( ! array_key_exists('videos_size', $this->relations)){
$this->load('videosSum');
}
$related = $this->getRelation('videosSum');
return $this->attributes['videos_size'] = isset($related->sum) ? (int) $related->sum : 0;
}
}
And using like:
User::where('id', '!=', Auth::user()->id);
I am getting the desired result.
But the problem is, I don't want the videos_size attribute everywhere, where the User model gets called. I want to set it dynamically.
I tried User::$appends = ['videos_size'] but it gives protected property cannot be set outsize of class error.
I also tried to make a method in User model which set the $appends if called, but it is also not working.
Can anybody help me how to enable the appends property dynamically?
Laravel doesn't support this off the bat.
my friend and I wrote this extention:
Dynamically hide certain columns when returning an Eloquent object as JSON?
basically you have to override your models.php toArray() method as appended attributes get calculated when you ask for the model in json or array form.
you can add to the trait that's in that link and use it or just put these methods in your respective model class.
public static function getStaticAppends() {
return self::$_appends;
}
public static function setStaticAppends(array $value) {
self::$_appends = $value;
return self::$_appends;
}
public static function getDefaultAppends() {
return with(new static)->getAppends();
}
public function getAppends(){
return $this->appends;
}
public function toArray() {
if (self::getStaticAppends()) {
$this->appends = self::getStaticAppends();
}
return parent::toArray();
}

Resources