Get relationship with eloquent - laravel

I am using laravel 6.16.0 and I have two migrations:
person
Schema::create('persons', function (Blueprint $table) {
$table->bigIncrements('id');
$table->string('full_name')->nullable($value = true);
$table->date('date_of_birth')->nullable($value = true);
$table->string('phone')->nullable($value = true);
$table->string('office')->nullable($value = true);
$table->timestamps();
});
senator_attributes
Schema::create('senator_attributes', function (Blueprint $table) {
$table->bigIncrements('id');
$table->integer('persons_id')->default('999999')->nullable($value = true);
$table->string('party')->nullable($value = true);
// ...
$table->timestamps();
});
My models look like the following:
class Person extends Model
{
protected $table = 'persons';
protected $guarded = ['id'];
}
class SenatorAttributes extends Model
{
protected $table = 'senator_attributes';
protected $guarded = ['id'];
public function person()
{
return $this->belongsTo(Person::class, 'persons_id');
}
}
I was wondering, how can I query all Persons and get for each person their attributes?
I appreciate your replies!

For pretty straight forward solutions you can directly do:
First, you need to add the SenatorAttributes model inside the Person model. You can do so like the following:
class Person extends Model
{
protected $table = 'persons';
protected $guarded = ['id'];
public function senator_attributes()
{
return $this->hasMany(SenatorAttributes::class, 'persons_id'); // or you can use hasOne if person model is supposed to only have one single senator attribute.
}
}
Now to load senator attributes for each person.
Solution 1, with Eager(early/desperate) Loading:
$persons = Person::with('senator_attributes')->get();
Solution 2, on-demand loading:
$persons = Person::all();
foreach($persons as $person) {
$senator_attribures = $person->senator_attributes;
// write your logic here...
}
Solution 1 is fine as long as you don't load all of the rows at the runtime. If you are planning to load hundreds/thousands of models, consider chunking the solution and use eager loading with it.
Learn more about Laravel Eloquent Relationships here.
Now, read the following if you are interested in best practices.
I don't want to sound nosey, but I personally follow the following ground rules while working on any project.
Table name should always be in plural and model name to be in singular form. For example, persons table will have Person model.
Foreign keys should always be in singular form of referenced table + referenced column of the referenced table. For example, if you wish to add a person reference to senator_attributes table, you should add person_id column to senator_attributes table.
Fact, Laravel supports this naming convention, above all, you save yourself time and maintain the consistency and practice across the teams and projects.

Related

Property [countries] does not exist on this collection instance

I am trying to create a drop down menu by with eloquent from where I can go to subcontinent with drop down and subcontinent to countries with sub-dropdown. The relationship is Subcontinent has many countries.
Models
Subcontinent
class Subcontinent extends Model
{
protected $guarded = [];
public function countries()
{
return $this->hasMany(Division::class, 'country_name', 'id');
}
}
Country
class Division extends Model
{
protected $table = 'divisions';
protected $fillable = [
'country_name', 'subcontinent_id'
];
public function subcontinent()
{
return $this->belongsTo(Subcontinent::class, 'country_name', 'id');
}
}
The table name of country is divisions and the model name is also Division.
Table
country/division
Schema::create('divisions', function (Blueprint $table) {
$table->id();
$table->string('country_name');
$table->bigInteger('subcontinent_id');
$table->timestamps();
});
Database formation
$subcontinents = Subcontinent::orderBy('id', 'DESC')->get();
But when I try to call dd($subcontinents->countries) it gives me property does not exist error.
"Property [countries] does not exist on this collection instance."
with $subcontinents = Subcontinent::find(1);
the dd still gives null value. How can I call subcontinents to countries!
you have misconception about second and third option in relationship method. for belongsTo relationship, the second argument is the foreign key of the child table and the third argument is the primary key or the reference key of the parent table.
your Division model relationship should be
public function subcontinent()
{
return $this->belongsTo(Subcontinent::class, 'subcontinent_id', 'id');
}
and for hasMany relationship the second argument is the foreign key in the child table. For SubContinent model the relationship would be
public function countries()
{
return $this->hasMany(Division::class, 'subcontinent_id', 'id');
}
and when you use $subcontinents = Subcontinent::orderBy('id', 'DESC')->get(); you get a collection, not an object. you have to loop over to get values and relationship data from this.
foreach($subcontinents as $subcontinent) {
$subcontinent->$subcontinent_name;
$subcontinent->countries;
}
and when you use $subcontinents = Subcontinent::find(1); you get an object. you can directly access its values. just update the relationship method. and you will get values by $subcontinents->countries then.

How to properly implement this polymorphic relationship in Laravel?

I am trying to build an inventory for users in Laravel 5.8, however the items have their own properties, therefore I needed to set up a polymorphic relationship. When attaching items to users, it tries to add the model User to the table on itemable_type and the user's ID to itemable_id aswell as add the User's ID to user_id, something I could workaround by passing the models I need, but when I try to retrieve them it tries to find item with itemable_type = 'App\Models\User', which makes me think something's completely wrong here. Can I have some orientation on how to solve it?
class User extends Model
{
public function inventory()
{
return $this->morhpToMany(InventoryItem::class, 'itemable', 'user_inventories', null, 'itemable_id')
->withPivot('amount', 'notes');
}
}
class InventoryItem extends Model
{
public $timestamps = false;
protected $table = 'character_inventories';
protected $fillable = [
'character_id', 'itemable_type', 'amount', 'parent_id', 'notes'
];
public function cloth()
{
return $this->mophedByMany(Cloth::class, 'itemable');
}
public function food()
{
return $this->morphedByMany(Food::class, 'itemable');
}
// Other similar relations
}
// The Inventory migration:
Schema::create('user_inventories', function (Blueprint $table) {
$table->bigIncrements('id');
$table->unsignedBigInteger('user_id');
$table->unsignedInteger('itemable_id');
$table->string('itemable_type');
$table->unsignedInteger('amount')->default(0);
$table->text('notes', 65535)->nullable();
$table->foreign('character_id')->references('id')->on('characters');
});
The expected result is the User model to have different items in his inventory, but the relation is trying to query by joinning to itself and filtering by user type instead of actual items.
The error:
Syntax error or access violation: 1066 Not unique table/alias: 'user_inventories' (SQL:
select `user_inventories`.*,
`user_inventories`.`itemable_id` as `pivot_itemable_id`,
`user_inventories`.`itemable_type` as `pivot_itemable_type`,
`user_inventories`.`amount` as `pivot_amount`,
`user_inventories`.`parent_id` as `pivot_parent_id`,
`user_inventories`.`notes` as `pivot_notes`
from `user_inventories`
inner join `user_inventories` on `user_inventories`.`id` = `user_inventories`.`itemable_id`
where `user_inventories`.`itemable_id` in (4)
and `user_inventories`.`itemable_type` = App\Models\User)
I highly suspect that you have to references the user table in the inventory relation. In general it is a million times easier just following the Laravel convention for naming.
public function inventory()
{
return $this->morhpToMany(InventoryItem::class, 'itemable', 'users', null, 'itemable_id')
->withPivot('amount', 'notes');
}

Returning all manyToMany Polymorphic relations

I have a case where I have the following models:
Presentation, Slideshow, Video (not yet implemented)
These are all joined via a polymorphic many to many relationship, and everything looks fine. However I find the need to query for all "presentables" (both Slideshows and Videos) for a particular presentation, as I want to display them all in one place in a list.
How can I define this relationship, or am I doomed to create a function in my model to handle all of this?
Below is the models and DB schema:
Presentation Model
class Presentation extends \Eloquent {
protected $fillable = [
'name'
];
public function slideshows()
{
return $this->morphedByMany('Slideshow', 'presentable'); //, 'presentables', 'presentable_id', 'presentable_type');
}
public function presentables()
{
//code in here to return all presentable objects
}
}
Slideshow Model
<?php
class Slideshow extends \Eloquent {
protected $fillable = [];
public function presentation()
{
return $this->morphToMany('Presentation', 'presentable', 'presentables', 'presentable_id', 'presentable_type');
}
}
presentables table
Schema::create('presentables', function(Blueprint $table)
{
$table->increments('id');
$table->integer('presentation_id');
$table->integer('order');
$table->integer('presentable_id')->unsigned()->index();
$table->string('presentable_type')->index();
$table->timestamps();
});

working with m:m relationships in eloquent outside laravel

I have been able to set up a m:m relationship in eloquent outside laravel and retrieve records but I have no idea how to add a new record as you dont create the pivot table in the code.
If these are my classes, how would i add a new instance to the M:M relationship between author and publisher?
<?php
include 'eloquent_database.php';
class Publisher extends Illuminate\Database\Eloquent\Model {
public $timestamps = false;
protected $table = 'publisher';
protected $primaryKey = 'publisher_id';
public function authors (){
return $this->belongsToMany('Author', 'author_publisher', 'publisher_id', 'author_id');
}
}
class Book extends Illuminate\Database\Eloquent\Model {
public $timestamps = false;
protected $table = 'book';
protected $primaryKey = 'book_id';
public function author() {
//related table name, pk in current table,
return $this->belongsTo('Author', 'author_id');
}
}
// Create the company model
class Author extends Illuminate\Database\Eloquent\Model {
public $timestamps = false;
protected $table = 'author';
protected $primaryKey = 'author_id';
public function books()
{
//related table, fk IN related table,
return $this->hasMany('Book', 'author_id');
}
public function publishers (){
return $this->belongsToMany('Publisher', 'author_publisher', 'author_id', 'publisher_id');
}
}
I would also need to know how to delete one too. I have seen this documentation http://laravel.com/docs/4.2/eloquent#working-with-pivot-tables but i dont really follow it
Id really appreicate an exxample of how to add and delete a new instance. Also onine there seeems to be so many different versions its hard to find which docs to follow as the code i have works but i didnt get it from the docs - just trial and error
Thanks a lot in advance
edit:
in repsonse to Rays comment i do have a pivot table in the database called author_publisher with author id and publisher id but i have no idea how to work with this pivot table. Do i have to create a class for it? I dont really understand
Here is a reference to said table in the code above
return $this->belongsToMany('Author', 'author_publisher', 'publisher_id', 'author_id');
When having a M:M relationship, make sure you have a table that translates the relationship between author and publisher. An example table could be composed of entries including both author_id and publisher_id. From what you have provided, you lack such a table.
There's another case if you do not have such a table. In order for such a M:M relationship to work, the author table must contain a column called "publisher_id" for simplicity. Likewise the publisher table must contain a column called "author_id". Then in the author model, return $this->hasMany('Publisher', 'author_id') and in the publisher model, return $this->hasMany('Author', 'publisher_id'). You should get the correct answer.

Can't use a saved instance/reference to make an association with another model

Folks i have got something strange happening with eloquent and the database i can't seem to put a finger to it.
i have 3 models User > Gp1user > Worker in an inheritance hierachy with a one to one relationship between them.
this is the code from my controller
$user = User::find(1);
$gp1user = new Gp1user;
$gp1user->user()->associate($user);
$gp1user->save();
$worker = new Worker;
$worker->gp1user()->associate($gp1user);
$worker->save();
it seems even though $gp1user is saved, i can't use that reference to make the association with $worker. i get the error
SQLSTATE[23000]: Integrity constraint violation: 1452 Cannot add or update a child row: a foreign key constraint fails (`careers`.`workers`, CONSTRAINT `workers_gp1user_id_foreign` FOREIGN KEY (`gp1user_id`) REFERENCES `gp1users` (`user_id`)) (SQL: insert into `workers` (`gp1user_id`) values (0))
But if i do check the database, the $gp1user instance does exist. so i am only left with this option.
$user = User::find(1);
$gp1user = new Gp1user;
$gp1user->user()->associate($user);
$gp1user->save();
$worker = new Worker;
$worker->gp1user()->associate(Gp1user::find(9));
$worker->save();
i have to find the particular instance in the db an create a new instance using the id. which works, but doesnt because i have to check for the id in the db first.
why won't eloquent let me use the $gp1user reference?
i suspect its something to do with the foreign keys but i can't find anything wrong.
this is what my migrations (in different files) look like
Schema::create('users', function($table)
{
//
$table->increments('id');
$table->string('first_name', 128);
$table->string('second_name', 128);
$table->string('email');
$table->string('password', 60);
$table->timestamps();
});
Schema::create('gp1users', function($table)
{
$table->integer('user_id')->unsigned();
$table->foreign('user_id')->references('id')->on('users');
$table->unique('user_id')->primary();
});
Schema::create('workers', function($table)
{
$table->integer('gp1user_id')->unsigned();
$table->foreign('gp1user_id')->references('user_id')->on('gp1users');
$table->unique('gp1user_id')->primary();
});
my models
class User extends Eloquent {
public function gp1user()
{
return $this->hasOne('Gp1user');
}
}
class Gp1user extends Eloquent {
protected $primaryKey = 'user_id';
public $timestamps = false;
public function user()
{
return $this->belongsTo('User');
}
public function worker()
{
return $this->hasOne('Worker');
}
}
class Worker extends Eloquent {
protected $primaryKey = 'gp1user_id';
public $timestamps = false;
public function gp1user()
{
return $this->belongsTo('Gp1user');
}
}
thanks in advance.
This should work for you. Unfortunately, I'm not yet sure why exactly.
$user = User::find(1);
$gp1user = new Gp1user;
$gp1user->user()->associate($user);
$worker = new Worker();
$worker->gp1user()->associate($gp1user);
$gp1user->save();
$worker->save();

Resources