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.
Related
I have 3 tables
User (id,name,mail,mobile)
Contest (id,contest_name,contest_description)
Contest_user (id,user_id,contest_id)
I want to write a has many contest user method in the contest model also I need the user details from the method.
Kindly tell me how can I get the desired result.
Model Screenshot
<?php
namespace App\Model\Contest;
use App\Model\Project\ContestUsers;
use Illuminate\Database\Eloquent\Model;
class Contest extends Model
{
protected $table = 'contest';
protected $fillable = ['name','description'];
public $dates = [
'created_at',
'updated_at',
'deleted_at',
];
//I want to get user details from this method
public function project_contest_users()
{
return $this->hasMany(ContestUsers::class, 'contest_id', 'id')->whereNull('deleted_at');
}
}
contest_user is a pivot table for users and contests and should not have a model class.
The nature of the relation between User and Contest is a many to many, so you need to use belongsToMany() relation in both sides
Contest.php
public function users()
{
return $this->belongsToMany(User::class);
}
User.php
public function contests()
{
return $this->belongsToMany(Contest::class);
}
From what you have done, based on my experience, it's already a good thing that you normalize the many to many relationship. But what N69S said is also correct, that pivot table should not have a Model class, so you need to make your own convention to not use the Model class to create/update/delete (read is fine by me) and if you still want to stick to this method, what you need to do now is just defining the relationships for each of the Models.
User.php:
public function contest_user()
{
return $this->hasMany(ContestUser::class);
}
ContestUser.php:
public function user()
{
return $this->belongsTo(User::class);
}
public function contest()
{
return $this->belongsTo(Contest::class);
}
Contest.php:
public function contest_user()
{
return $this->hasMany(ContestUser::class);
}
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.
How do I create a column just in a pivot (intermediate) table in Laravel (5.4) and then filter results on it?
I have two models, Films and CastAndCrew. CastAndCrew are the various directors, producers, actors who work on a film. The pivot table should define the type of relationship between a CastAndCrew member and a Film. Obviously it's possible for someone to be e.g. an actor in one film and a producer on another, so I can't define this in their entry in the CastAndCrew table because it'll only be true for one film, and may be different for other films they worked on. So I assume I have to define the relationship in a pivot table, but I'm not sure how to do this exactly. What I've got so far:
class Film extends Model
{
protected $fillable = array('filmtitle', 'description');
public function List_Directors()
{
return $this->belongsToMany('App\CastAndCrew')->withPivot('type')->wherePivot('type', 'director');
}
public function List_Actors()
{
return $this->belongsToMany('App\CastAndCrew')->withPivot('type')->wherePivot('type', 'actor');
}
}
and
class CastAndCrew extends Model
{
protected $fillable = array('firstname', 'lastname');
public function List_Films_With_Director()
{
return $this->belongsToMany('App\Film')->withPivot('type')->wherePivot('type', 'director');
}
public function List_Films_With_Actor()
{
return $this->belongsToMany('App\Film')->withPivot('type')->wherePivot('type', 'actor');
}
}
When new CastAndCrew members get added to the site, I'm intending to use the attach method, e.g. to add a new director:
$newcastcrew->CastAndCrew::create(['firstname' => Request::get('firstname'), 'lastname' => Request::get('lastname')]);
$newcastcrew->List_Films_With_Director()->attach($filmID, ['type' => 'director']);
1.) Is that right?
2.) Does the ->withPivot('type') create the 'type' column in the Pivot table? If not, where/how do I define it?
2.) Presumably the ->wherePivot('type', 'director') clause in Film->List_Directors() then returns CastAndCrew members who are directors of that film? (which is what I want)
Corrections much appreciated!
Thanks
Your idea and logic is perfectly fine. You might want to add a relationship without the type condition to fetch all the films of user and all the cast and crew of a film. You also need to name your methods and relationships better. I've cleaned up the code for you. Feel free to use this if you prefer.
class Film extends Model
{
protected $fillable = array('filmtitle', 'description');
public function castAndCrew()
{
return $this->belongsToMany('App\CastAndCrew')->withPivot('type');
}
public function directors()
{
return $this->castAndCrew()->wherePivot('type', 'director');
}
public function actors()
{
return $this->castAndCrew()->wherePivot('type', 'actor');
}
}
class CastAndCrew extends Model
{
protected $fillable = array('firstname', 'lastname');
public function films()
{
return $this->belongsToMany('App\Film')->withPivot('type');
}
public function filmsAsDirector()
{
return $this->films()->wherePivot('type', 'director');
}
public function filmsAsActor()
{
return $this->films()->wherePivot('type', 'actor');
}
}
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).
I have read a few topics about this, but they managed to solve my problem partially ...
this is my controller
class DeskController extends BaseController{
public function getDeskUsers($deskId){
$user = DeskUserList::where(function($query) use ($deskId){
$query->where('deskId', $deskId);
})->with('userName')->get(array('deskId'));
if (!$user->isEmpty())
return $user;
return 'fail';
}
this is the model
class DeskUserList extends Eloquent {
protected $table = 'desk_user_lists';
public function userName(){
return $this->belongsTo('User', 'userId')->select(array('id','userName'));
}
}
the method getDeskUsers may returns ALL the DeskUserList table records, related with the User table record (on deskUserList.userId = User.id).
practically I want each record returned is composed of:
DeskUserList.deskId
User.userName
eg. [{"deskId":"1","user_name":antonio}]
What i get is
[{"deskId":"1","user_name":null}]
As you can see the user name is a null value...
BUT
if I edit my controller code:
->with('userName')->get(array('userId')); //using userId rather than deskId
then i get
[{"userId":"2","user_name":{"id":"2","userName":"antonio"}}]
By this way I still have two problem:
the userId field is twice repeated
I miss the deskId field (that I need...)
hope be clear, thanks for your time!
You need belongsToMany, no need for a model representing that pivot table.
I assume your models are Desk and User:
// Desk model
public function users()
{
return $this->belongsToMany('User', 'desk_user_list', 'deskId', 'userId');
}
// User model
public function desks()
{
return $this->belongsToMany('Desk', 'desk_user_list', 'userId', 'deskId');
}
Then:
$desks = Desk::with('users')->get(); // collection of desks with related users
foreach ($desks as $desk)
{
$desk->users; // collection of users for particular desk
}
// or for single desk with id 5
$desk = Desk::with('users')->find(5);
$desk->users; // collection of users
$desk->users->first(); // single User model