Simple Laravel Relationship - laravel

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).

Related

Is there a way to silently abort creating a model in Laravel?

I have a ProductTags model that is in a one to many relationships to my product. I need to prevent the same model id and tag from being duplicated in multiple records. In the migration I have $table->unique(['product_uuid', 'tag']); but that means if I attempt to create a model that is a duplicate of an existing model I get an integrity constraint violation.
So when a ProductTags model is created, before it gets saved it needs to check the database for an existing version then abort its own creation. I would prefer to do this in the model so I only have to implement it once instead of both the controllers and factory.
I tried to override the create method to intercept it, but when I seeded, it doesn't seem to be called at all.
class ProductTag extends Model
{
protected $fillable = [
'product_uuid', 'tag',
];
public function create($data){
if(
ProductTag::where('product_uuid', '=', $data['product_uuid'])->
where('tag','=', $data['tag'])
){
return false;
}
return parent::create($data);
}
}
You can use the ->exists() function on the query builder to check if a record exists for that query:
public function create($data){
$productTag = self::where('product_uuid', $data['product_uuid'])
->where('tag', $data['tag']);
if ($productTag->exists()) {
return false;
}
return self::create($data);
}
What about using firstOrCreate
public function create($data)
{
return self::firstOrCreate( // self or use ProductTag
[
'product_uuid' => $data['product_uuid'],
'tag' => $data['tag']
],
$data
);
}
I am just not sure if you can invoke it on self, but you can do it on the model for sure.
This will return an instance of ProductTag back in any case so you can chain other methods

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.

Custom method not work on HasMany relation

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
}
}

Laravel Eloquent "siblings" as a relationship?

class PageRelation extends Eloquent
{
public $incrementing = false;
public $timestamps = false;
protected $table = 'page_relation';
protected $casts = [
'parent' => 'int', // FK to page
'child' => 'int', // FK to page
'lpc' => 'int',
];
protected $fillable = [
'lpc',
];
public function children()
{
return $this->hasMany(Page::class, 'category_id', 'child');
}
public function parents()
{
return $this->hasMany(Page::class, 'category_id', 'parent');
}
public function siblings()
{
// ... return $this->hasMany(Page::class ...
// how do I define this relationship?
}
}
In my design a sibling is (as you might expect) a record that shares the same parent but not itself (exclude current child). How can I achieve this?
This is not a duplicate of Laravel Eloquent Relationships for Siblings because 1) the structure is different, 2) I would like to return a relationship, not a query result, I know how to query this, but I want the power of eager loader.
I don't think you can do that with Laravel's in-built relations. What I would suggest doing is creating your own relation type that extends HasMany and use that.
use Illuminate\Database\Eloquent\Builder;
use Illuminate\Database\Eloquent\Model;
use Illuminate\Database\Eloquent\Relations\HasMany;
class HasManySiblings extends HasMany
{
public function addConstraints()
{
if (static::$constraints) {
if (is_null($foreignKeyValue = $this->getParentKey())) {
$this->query->whereNull($this->foreignKey);
} else {
$this->query->where($this->foreignKey, '=', $foreignKeyValue);
$this->query->whereNotNull($this->foreignKey);
}
$this->query->where($this->localKey, '!=', $this->parent->getAttribute($this->localKey));
}
}
public function getParentKey()
{
return $this->parent->getAttribute($this->foreignKey);
}
}
By extending the HasMany class and providing your own implementation of addConstraints you are able to control what gets added to the query for related models. Usually, what Laravel would do here is add where parent_id = <your model ID> but I've changed it here to add where parent_id = <your model PARENT ID> (if your model's parent_id is null it will instead add where parent_id is null). I've also added an extra clause to ensure that the calling model is not included in the resulting collection: and id != <your model ID>.
You can use it like this in your Page model:
class Page extends Model
{
public function siblings()
{
return new HasManySiblings(
$this->newRelatedInstance(Page::class)->newQuery(), $this, 'parent_id', 'id'
);
}
}
Now you should be able to load the siblings like this:
$page = Page::find(1);
dd($page->siblings);
Please note though, I have only tested this for retrieving related models and it may not work when using the relation for other purposes such as saving related models etc.
Also, please note that in my examples above I've used parent_id instead of parent as in your question. Should be straight swap though.
I am not sure if it works with your model which is kinda marginal because you are relating same objects with a middle table. But,
hasManyThrough()
could be a solution for this.
"... has many siblings through parent."
https://laravel.com/docs/5.6/eloquent-relationships#has-many-through
This is off-topic but bare me with this. I have this suggestion for the way you are handling these relations. You don't need PageRelation model, you can define belongsToMany relation on Page model directly. Moreover, you dont need extra attribute parent, this is kind of inconsistent, defining parent and child both, only children are enough to determine parents. So Instead of two seperate columns, You can reverse the keys when you retrieve the relation. Let me show you with an example what I mean:
pages:
keep this table intact
pages_relation:
- id
- page_id (foreign key to id on page)
- child_id (foreign key to id on page)
And then define two relations in your model:
class Page extends Model
{
public function children()
{
return $this->belongsToMany('App\Page', 'pages_relation', 'page_id', 'child_id');
}
public function parents()
{
return $this->belongsToMany('App\Page', 'pages_relation', 'child_id', 'page_id');
}
}
You can stick to whatever feels good to you. But, I feel this is more consistent. As, there is only single source of truth.
If A is a child of B, then B has to be a parent of A, its obvious, only "A is child of B" is enough to state "B is a parent of A".
I have tested this, it works very well.
EDIT
You can extend BelongsToMany relation to get BelongsToManySiblings realtionship, and just override the addWhereConstraints method.
class BelongsToManySiblings extends BelongsToMany
{
protected function addWhereConstraints()
{
$parentIds = \DB::table($this->table)
->select($this->foreignPivotKey)
->where($this->relatedPivotKey, '=', $this->parent->{$this->parentKey})
->get()->pluck($this->foreignPivotKey)->toArray();
$this->query->whereIn(
$this->getQualifiedForeignPivotKeyName(),
$parentIds
)->where(
$this->getQualifiedRelatedPivotKeyName(),
'<>',
$this->parent->{$this->parentKey}
)->groupBy($this->getQualifiedRelatedPivotKeyName());
return $this;
}
}
Then you can add siblings relationship method on your Page model:
public function siblings()
{
return new BelongsToManySiblings(
$this->newRelatedInstance(Page::class)->newQuery(),
$this,
'pages_relation',
'parent_id',
'child_id',
'id',
'id',
$this->guessBelongsToManyRelation()
);
}
Note: This case does not work for eager loads, eager load needs overriding match and addEagerContraints methods on the BelongsToManySiblings class. You can peek the BelongsToMany class on laravel source to see an example how it eager loads the relations.

Laravel Polymorphic Relation, create relation for a morphOne

I'm stuck with Polymorphic relations binding in Laravel (4). Modelnames are made up to make it more clear.
<?php
class Car extends Model {
protected $fillable = ['license_plate'];
public function carable()
{
return $this->morphTo();
}
}
class Parkinglot extends Model {
protected $fillable = ['in_shadow'];
public function carable()
{
return $this->morphOne('Car', 'carable');
}
}
class Garagebox extends Model {
protected $fillable = ['can_lock'];
public function carable()
{
return $this->morphOne('Car', 'carable');
}
}
One Car can be stored in a Parkinglot Or a Garagebox. The ParkingLots and Garagebox can be created unlimited.
I have a html-Table, where all Parkingslots and Garageboxes are listed. If there is no Car stored in/on it, you can store One Car.
What I want is to make a MANAGE-button for each row. If there is a an existing Polymorphic relation, it finds that Car and you can edit it (that works already), but when there is no relation - so no Car attached - I want to save the Car Inside the row that was edited.
So I want a way to fill the carable_id and carable_type for the Car::create();, but how to achieve such a thing?
The Model-class has functions like firstOrCreate() and FirstOrNew(), but I can't figure out how to implement this.
Anyone knows how to achieve this? The Parkinglots/Garageboxes don't have to have a Car inside.
You can use firstOrNew for this. firstOrNew doesn't save the new model, it just gives you back a model ready to be saved.
$car = Car::firstOrNew([
'make' => 'BMW',
'model' => '123d',
'registration' => 'YK10HMC'
]);
Then you can check if the model is new or not and branch from that.
if ($car->exists)
{
// It was retrieved from DB as an existing car
}
else
{
// It is a new model and hasn't been saved yet
}
To save the car to a lot or garage, a lot in this case:
$storage = ParkingLot::find(1);
$storage->carable()->save($car);
Now the car has a carable_type of ParkingLot and a carable_id of 1.

Resources