Laravel - Eloquent 3 Table Query - php

I know there are several questions out there on 3 table joins, but the examples are simpler than my set up.
I have three tables: Items, Attributes, Categories.
`item.item_code = attributes.item_code`
`attributes.category_id = category.id`
Using eloquent, I can access attributes no problem with:
$items = Item::with('attributes')->paginate(15);
But I can't seem to get the relationship set correctly to retrieve the category name.
With a standard MySql query I'd use something like:
SELECT category_name FROM items
JOIN attributes on items.item_code = attributes.item_code
JOIN categories on attributes.pg3_id = categories.id
WHERE items.item_code = 40992264
How do I achieve this using eloquent?
Edit - My bad - Totally messed up the SQL. Updated to reflect the correct table names and include the second join
Update
My models currently look like this:
class Attributes extends Model
{
public function category(){
return $this->belongsTo(Category::class);
}
}
class Product extends Model
{
public function item()
{
return $this->belongsTo(Item::class);
}
}
class Category extends Model
{
public function attributes()
{
return $this->belongsTo(Attributes::class);
}
}
But this still isn't returning a result. I've tried using
$items = Item::with('attributes.category')->get();
as suggested, but this still throws an error. If I update the Product model to:
class Product extends Model
{
public function item()
{
return $this->belongsTo(Item::class);
}
public function category(){
return $this->belongsTo(Category::class);
}
}
I don't get an error, but the relationship returns null.

You can do
$items = Item::with('attributes.category')->get();
So you access the category relationship inside the attributes relationship.
For example:
foreach ($items as $item) {
foreach ($item->attributes as $attribute) {
echo $attribute->category->id; // Will print the category id.
}
}

Related

How do I use Eloquent to get all relationships from an intermediate table?

I have many to many relationships, eg:
// Category Model
class Category extends Model
{
public function sections()
{
return $this->belongsToMany('App\Section');
}
}
// Section Model
class Section extends Model
{
public function categories()
{
return $this->belongsToMany('App\Category');
}
}
Let's say I have the following category / section relationships with the following slugs:
domestic
dogs
cats
birds
zoo
cats
birds
winged
birds
Is there an Eloquent or easy way to list all of the sections with their category?
To put this in context, if category and section had a slug, based on my example, I would like to get a list of sections like this:
domestic/dogs
domestic/cats
domestic/birds
zoo/cats
zoo/birds
winged/birds
I have tried to create a Pivot model by extending the Pivot class but can't work it out.
Any help greatly appreciated.
Something like this could do the trick:
// Retrieve all categories
$categories = Category::with('sections')->all();
// Define result array
$result = [];
foreach ($categories as $category) {
foreach ($category->sections as $section) {
$result[] = $category->slug . '/' . $section->slug;
}
}
// Result now holds the contents you are looking for

Create a lists of categories and their subcatgory in Laravel 5

I have three tables as category, subcategory and subcategory_category that I use to store the foreign key of other table in
I want to show a list with category and subcategory, such as:
category1
subcategory1
subcategory2
category2
subcategory3
subcategory4
I tried this but it is not OK
in my controller:
$mycategories = SubcategoriesCategories::with('parent')->with('children2')->get();
and in model:
class SubcategoriesCategories extends Model {
protected $table = 'subcategories_to_categories';
public function parent()
{
return $this->belongsTo('App\categories', 'categories_id');
}
public function children2()
{
return $this->belongsTo('App\subcategories', 'subcategories_id');
}
}
in mysubcategories_categories table i have:
For children operations you can use nested set technique, there is a library that can help you, it is called Baum https://github.com/etrepat/baum
I think you didn't configure your modeles right..
its should be like:
class Category{
public function subcategories(){
return $this->hasMany('app\subcategory')//or whatever is your relations
}
}
and than in controller:
$categories = Category::with('subcategories')->all();

How would I get a distinct list of Categories associated through two many to many relationships with Laravel 5/Eloquent?

I have kind of a complicated data model and I am trying to write my query with Laravel 5/Eloquent and not just plain ol' SQL. My SQL below works but I drank the kool-aid and I would like to get it working the 'right' way.
There is a many to many relationship between Actors and Poses and a many to many polymorphic relationship between Poses and Categories.
What I would like to do is get a list of all distinct Categories associated to an Actor through its Poses.
SQL
SELECT DISTINCT
categories.name,
categories.parent_id
FROM
actor_pose
JOIN
poses ON poses.id = actor_pose.pose_id
JOIN
categorizables ON categorizables.categorizable_id = poses.id
JOIN
categories ON categories.id = categorizables.category_id
WHERE
actor_pose.actor_id = 1 AND
categorizables.categorizable_type = "App\\Pose"
ORDER BY
parent_id
Database
actors
id
name
poses
id
name
categories
id
name
parent_id
categorizables
category_id
categorizable_id
categorizable_type
Code
CategorizableTrait.php
<?php namespace App;
trait CategorizableTrait {
public function categories()
{
return $this->morphToMany('App\Category', 'categorizable');
}
}
Pose.php
<?php namespace App;
class Pose extends Model
{
use CategorizableTrait;
public function actors()
{
return $this->belongsToMany('App\Actor')->withTimestamps();
}
}
Actor.php
<?php namespace App;
class Actor extends Model
{
public function poses()
{
return $this->belongsToMany('App\Pose')->withTimestamps();
}
The link to SoftOnSofa from Jarek Tkaczyk had a clean solution that I ended up using.
I added this method to my Actor.php file.
Actor.php
public function getCategoriesAttribute()
{
$categories = new Collection;
$this->load(['poses.categories' => function ($q) use ( &$categories ) {
$categories = $q->get()->unique();
}]);
return $categories->sortBy('order')->sortBy('parent_id');
}
The controller doesn't do anything special. In blade you access it as below.
index.blade.php
#foreach($actor->categories as $key => $category)
{{ $category->name }}
#endforeach
The Collection class has some very nice methods for handling this type of thing. Just use with() to build your relationships and get the actor you want. Loop through the relationships and add to the collection.
$categories = new \Illuminate\Database\Eloquent\Collection();
$actor = App\Actor::with('poses.categories')->find($actor_id);
foreach($actor->poses as $pose) {
foreach($pose->categories as $catgegory) {
$categories->add($catgegory);
}
}
$categories = $categories->unique();
If you want all the actors on a page each with their unique categories, it's just everything nested inside of another loop.
$actors = App\Actor::with('poses.categories')->get();
$actor_categories = [];
foreach($actors as $actor) {
$actor_categories[$actor->id] = new \Illuminate\Database\Eloquent\Collection();
foreach($actor->poses as $pose) {
foreach($pose->categories as $category) {
$actor_categories[$actor->id]->add($category);
}
}
$actor_categories[$actor->id] = $actor_categories[$actor->id]->unique();
}
You will end up with an array which you can pass to the view where the key is the actor's id and the value is a collection of unique categories. For example, if you wanted to loop through actor 1's categories, you'd do something like...
foreach($actor_categories[1] as $category) {
echo $category->name;
}

How to set Eloquent relationship belongsTo THROUGH another model in Laravel?

I have a model Listing that inherits through its belongsTo('Model') relationship should inherently belong to the Manufacturer that its corresponding Model belongs to.
Here's from my Listing model:
public function model()
{
return $this->belongsTo('Model', 'model_id');
}
public function manufacturer()
{
return $this->belongsTo('Manufacturer', 'models.manufacturer_id');
/*
$manufacturer_id = $this->model->manufacturer_id;
return Manufacturer::find($manufacturer_id)->name;*/
}
and my Manufacturer model:
public function listings()
{
return $this->hasManyThrough('Listing', 'Model', 'manufacturer_id', 'model_id');
}
public function models()
{
return $this->hasMany('Model', 'manufacturer_id');
}
I am able to echo $listing->model->name in a view, but not $listing->manufacturer->name. That throws an error. I tried the commented out 2 lines in the Listing model just to get the effect so then I could echo $listing->manufacturer() and that would work, but that doesn't properly establish their relationship. How do I do this? Thanks.
Revised Listing model (thanks to answerer):
public function model()
{
return $this->belongsTo('Model', 'model_id');
}
public function manufacturer()
{
return $this->belongsTo('Model', 'model_id')
->join('manufacturers', 'manufacturers.id', '=', 'models.manufacturer_id');
}
I found a solution, but it's not extremely straight forward. I've posted it below, but I posted what I think is the better solution first.
You shouldn't be able to access manufacturer directly from the listing, since manufacturer applies to the Model only. Though you can eager-load the manufacturer relationships from the listing object, see below.
class Listing extends Eloquent
{
public function model()
{
return $this->belongsTo('Model', 'model_id');
}
}
class Model extends Eloquent
{
public function manufacturer()
{
return $this->belongsTo('manufacturer');
}
}
class Manufacturer extends Eloquent
{
}
$listings = Listing::with('model.manufacturer')->all();
foreach($listings as $listing) {
echo $listing->model->name . ' by ' . $listing->model->manufacturer->name;
}
It took a bit of finagling, to get your requested solution working. The solution looks like this:
public function manufacturer()
{
$instance = new Manufacturer();
$instance->setTable('models');
$query = $instance->newQuery();
return (new BelongsTo($query, $this, 'model_id', $instance->getKeyName(), 'manufacturer'))
->join('manufacturers', 'manufacturers.id', '=', 'models.manufacturer_id')
->select(DB::raw('manufacturers.*'));
}
I started off by working with the query and building the response from that. The query I was looking to create was something along the lines of:
SELECT * FROM manufacturers ma
JOIN models m on m.manufacturer_id = ma.id
WHERE m.id in (?)
The query that would be normally created by doing return $this->belongsTo('Manufacturer');
select * from `manufacturers` where `manufacturers`.`id` in (?)
The ? would be replaced by the value of manufacturer_id columns from the listings table. This column doesn't exist, so a single 0 would be inserted and you'd never return a manufacturer.
In the query I wanted to recreate I was constraining by models.id. I could easily access that value in my relationship by defining the foreign key. So the relationship became
return $this->belongsTo('Manufacturer', 'model_id');
This produces the same query as it did before, but populates the ? with the model_ids. So this returns results, but generally incorrect results. Then I aimed to change the base table that I was selecting from. This value is derived from the model, so I changed the passed in model to Model.
return $this->belongsTo('Model', 'model_id');
We've now mimic the model relationship, so that's great I hadn't really got anywhere. But at least now, I could make the join to the manufacturers table. So again I updated the relationship:
return $this->belongsTo('Model', 'model_id')
->join('manufacturers', 'manufacturers.id', '=', 'models.manufacturer_id');
This got us one step closer, generating the following query:
select * from `models`
inner join `manufacturers` on `manufacturers`.`id` = `models`.`manufacturer_id`
where `models`.`id` in (?)
From here, I wanted to limit the columns I was querying for to just the manufacturer columns, to do this I added the select specification. This brought the relationship to:
return $this->belongsTo('Model', 'model_id')
->join('manufacturers', 'manufacturers.id', '=', 'models.manufacturer_id')
->select(DB::raw('manufacturers.*'));
And got the query to
select manufacturers.* from `models`
inner join `manufacturers` on `manufacturers`.`id` = `models`.`manufacturer_id`
where `models`.`id` in (?)
Now we have a 100% valid query, but the objects being returned from the relationship are of type Model not Manufacturer. And that's where the last bit of trickery came in. I needed to return a Manufacturer, but wanted it to constrain by themodelstable in the where clause. I created a new instance of Manufacturer and set the table tomodels` and manually create the relationship.
It is important to note, that saving will not work.
$listing = Listing::find(1);
$listing->manufacturer()->associate(Manufacturer::create([]));
$listing->save();
This will create a new Manufacturer and then update listings.model_id to the new manufacturer's id.
I guess that this could help, it helped me:
class Car extends Model
{
public function mechanical()
{
return $this->belongsTo(Mechanical::class);
}
}
class CarPiece extends Model
{
public function car()
{
return $this->belongsTo(Car::class);
}
public function mechanical()
{
return $this->car->mechanical();
}
}
At least, it was this need that made me think of the existence of a belongsToThrough
You can do something like this (Student Group -> Users -> Poll results):
// poll result
public function studentGroup(): HasOneDeep
{
return $this->hasOneDeepFromRelations($this->user(), (new User())->studentGroup());
}

How can I combine two tables with my Eloquent query?

We work on a database where table names are named locally.
table.product
-- id
-- picture
-- category_id
table.category
-- id
-- caterory_name
-- category_directory
There are some products in table. product.category_id should point category.id so system will understand that product belongs to desired table.
We could do this by using INNER JOIN but we can't do it with Laravel. We probably need to configure our models with has/belongs_to etc.
This is where we struggle.
//Controller
$products = Product::all();
return View::make('theme-admin.product_list')
->with('products', $products);
//View (in foreach)
{{ URL::to('uploads/categories/[NEED_CATEGORY.CATEGORY_DIRECTORY_HERE]/' . $products[$k]->picture) }}
We can't get Category.category_directory info in our views since we pass Product::all() only.
How can we make it so the $products array also contains category.category_directory for each value and we can access it like; $products[$k]->category_name?
Thanks!
Create a category relation in your Product model:
class Product extends Eloquent {
private $table = 'myProductsTableName';
public function category()
{
return $this->belongsTo('Category', 'myCategoryColumnID');
}
}
class Category extends Eloquent {
private $table = 'myCategoriesTableName';
}
Unless you need to select all products from a particular category, you don't need to create a hasMany relation on your Category model.
And you just need to use myCategoryColumnID if your products table doesn't follow the singular_table_name_id rule (product_id).
Then just use it:
//View (in foreach)
{{ URL::to('uploads/categories/'.$products[$k]->category->category_directory.'/' . $products[$k]->picture) }}
I would setup a relationship...
class Product extends Eloquent {
public function category()
{
return $this->belongsTo('Category');
}
}
class Category extends Eloquent {
public function product()
{
return $this->hasMany('Product');
}
}
The name you use in the realtionship is the Model name.....so be sure if you're using a different table name than your model name, that you set that in your model
protected $table = "yourtablename";
Then use it like so...
{{ URL::to('uploads/categories/'.$products[$k]->category->category_directory.'/'
You still end up querying the database multiple times this way... This is called the N+1 effect. Example, if you have 5 products, one query will be executed to get those products. Then in the loop we are executing a query to get the category. This results in 6 total queries.
To solve this problem, use eager loading, which reduces those 6 queries in our example down to 2.
$products = Product::with('category')->get();
Then send that to your view, where you can do your foreach loop..
foreach($products as $val){
//To output the directory
echo $val->category->category_directory;
}
Or in blade...
#foreach($products as $val)
{{URL::to('uploads/categories/'.$val->category->category_directory.'/'.$val->picture)}}
#endfor

Categories