Get two polymorphic relationship results together - laravel

From the Laravel documentation - if I have this many-to-many polymorphic relationship:
class Tag extends Eloquent {
public function posts()
{
return $this->morphedByMany('Post', 'taggable');
}
public function videos()
{
return $this->morphedByMany('Video', 'taggable');
}
}
So I can do this:
$tag = Tag::find(1);
$posts = $tag->posts;
$videos = $tag->videos;
How could I get all the tags relationships as one result? i.e. in a one-to-many polymorphic relationship you do this:
$tag = Tag::find(1);
$taggable = $tag->taggable;
But that doesnt seem to work for me in the many-to-many relationship - I get this SQL error:
SQLSTATE[42S22]: Column not found: 1054 Unknown column 'tag_groups.'
in 'where clause' (SQL: select * from tag_groups where
tag_groups.`` is null limit 1)

You'll have to merge the results yourself:
$taggable = $tag->posts->toBase()->merge($tag->videos);
This'll return an instance of Illuminate\Support\Collection.
An Eloquent collection does not make sense in this context.

You can't get all relations in one result but you may load them all eagerly using something like this:
$tag = Tag::with(['posts', 'videos'])->find(1);
$relations = $tag->getRelations();
Here $tag->getRelations() will return an array of loaded relations so you may access one using something like this:
$posts = $relations['posts']; // Collection of Post models
$videos = $relations['videos']; // Collection of Video models
You can merge them like:
$allRelations = array_merge($posts->toArray(), $videos->toArray());
Or using merge method of Illuminate\Support\Collection (As Joseph Silber mentioned)
$allRelations = $relations['posts']->toBase()->merge($relations['videos']);

Related

laravel eloquent with pivot and another table

I have 4 table categories, initiatives, a pivot table for the "Many To Many" relationship category_initiative and initiativegroup table related with initiatives table with initiatives.initiativesgroup_id with one to many relation.
With pure sql I retrive the information I need with:
SELECT categories.id, categories.description, initiatives.id, initiatives.description, initiativegroups.group
FROM categories
LEFT JOIN category_initiative ON categories.id = category_initiative.category_id
LEFT JOIN initiatives ON category_initiative.initiative_id = initiatives.id
LEFT JOIN initiativegroups ON initiatives.initiativegroup_id = initiativegroups.id
WHERE categories.id = '40'
How can I use eloquent model to achieve same results?
Since you have such a specific query touching multiple tables, one possibility is to use query builder. That would preserve the precision of the query, retrieving only the data you specifically need. That would look something like this:
$categories = DB::table('categories')
->select([
'categories.id',
'categories.description',
'initiatives.id',
'initiatives.description',
'initiativegroups.group',
])
->leftJoin('category_initiative', 'categories.id', '=', 'category_initiative.category_id')
->leftJoin('initiatives', 'category_initiative.initiative_id', '=', 'initiatives.id')
->leftJoin('initiativegroups', 'initiatives.initiativegroup_id', '=', 'initiativegroups.id')
->where('categories.id', '=', 40)
->get();
In your models define the relationships:
Category.php model
public function initiatives()
{
return $this->belongsToMany('App\Initiative');
}
Initiative.php model (If has many categories change to belongs to many)
public function category()
{
return $this->belongsTo('App\Category');
}
Then maybe change your initiativegroup -> groups table, and then create a pivot table called group_initiative. Create model for group. Group.php and define the relationship:
public function initiatives()
{
return $this->belongsToMany('App\Initiative');
}
Then you can also add the following relationship definition to the Initiative.php model
public function group()
{
return $this->belongsTo('App\Group');
}
That should get you started.
for the record..
with my original relationship, but changing table name as alex suggest, in my controller:
$inits = Category::with('initiative.group')->find($id_cat);
simple and clean

Eloquent hasMany with foreign key on joint table

Assume this:
class List extends Model
{
public function items(){
return $this->hasMany(Items::class, 'c.class_id', 'class_id')
->rightjoin('items_classes as c', 'c.items_id', '=', 'items.id');
}
}
The problem is that Eloquent prepends items to foreign key field and the final query is:
SELECT * FROM items
RIGHT JOIN items_classes as c ON c.items_id = items.id
// here it is
WHERE items.c.class_id = 10
Even using DB::raw('c.class_id') didn't solve the problem.
If you notice the signature of hasMany relation method :
return $this->hasMany(Model::class, 'foreign_key', 'local_key');
Which means when Laravel will make the query, it will consider second argument foreign_key as a column of table defined in Model::class.
To simplify in your case :
return $this->hasMany(Items::class, 'c.class_id', 'class_id')->...
Leaving the rightjoin aside for a moment, Laravel is considering c.class_id as a foreign key of Item::class table which is indeed items table.
So the resultant query is :
SELECT * FROM items WHERE items.c.class_id = 10
Then when you add the right join, laravel just adds into the main query and makes it :
SELECT * FROM items
RIGHT JOIN items_classes as c ON c.items_id = items.id
WHERE items.c.class_id = 10
Laravel will not refer items_classes in the relation because you are relating List Model to Item::class and not ItemClass::class.
I am not sure about the data you need but see if you can use with like below :
class List extends Model
{
public function items(){
return $this->hasMany(Items::class, 'c.class_id', 'class_id');
}
}
List::with(['items', function($q){
return $q->->rightjoin('items_classes as c', 'c.items_id', '=', 'items.id');
}])->get();
Hope this gives you an idea how you can update your relationships to get desired query. If you add your table structure and data you want, I can update the answer with relationships for you.

Complicated Eloquent relationship using `Model::query`

I have a complicated relationship I'm trying to establish between two models.
The goal is to use $supplier->supply_orders to access the orders where the user supplies an item.
This throws: LogicException: Relationship method must return an object of type Illuminate\Database\Eloquent\Relations\Relation.
With the code I've got I can use $supplier->supply_orders()->get(), however, when I try to use it as a relationship it throws. Since this is a relationship I should be able to wrap it in a relationship, but how would I go about doing that?
Supplier Model:
class Supplier extends Model {
public function supply_orders() {
return Order::query()
->select('order.*')
->join('item_order', 'order.id', '=', 'item_order.order_id')
->join('item', 'item_order.item_id', '=', 'item.id')
->where('item.supplier_id', '=', $this->id);
}
}
~~~ A whole lot of back info that I don't think you need but might ~~~
sql tables:
supplier
- id
items:
- id
- supplier_id
item_order:
- id
- order_id
- item_id
orders:
- id
The other Eloquent Models:
class Item extends Model {
public function orders() {
return $this->belongsToMany('Order');
}
}
class Order extends Model {}
Example of how this should work:
$supplier = factory(Supplier::class)->create();
$item = factory(Item::class)->create([
'supplier_id' => $supplier->id,
]);
$order = factory(Order::class)->create();
$order->items()->attach($item);
$orders = $supplier->supply_orders // Throws LogicException
This throws: LogicException: Relationship method must return an object of type Illuminate\Database\Eloquent\Relations\Relation
Sounds like a hasManyThrough with a many to many relationship. Laravel has no inbuilt support for this but you can always go ahead and write your own relationship like this: https://laravel.io/forum/03-04-2014-hasmanythrough-with-many-to-many
If you dont want relationships you can always do something like:
Order::whereHas('items.supplier', function($query) use($supplier) {
$query->where('id', $supplier->id);
});
For this to work, you need to have a relationship function items in your Order model and a relationship function supplier in your item model
I believe the reason it throws a relationship error is that you haven't created an Eloquent relation for
$supplier->supply_orders.
Instead, Laravel looks at your supply_orders() as a method in the class, and thus can't figure out which table to use as the pivot. To get the base relationship to work within Eloquent, you'd need to create a new pivot table for the relationship between suppliers and orders something like:
suppliers
-id
orders
-id
order_supplier
-id
-order_id
-supplier_id
From here, Laravel will accept a simple many to many relationship between the two (this would not cause a failure):
Supplier Class:
/**
* Get all orders associated with this supplier via order_supplier table
*
* #return \Illuminate\Database\Eloquent\Relations\BelongsToMany
*/
public function orders(){
return $this->belongsToMany("\App\Order");
}
Now that the relationship is solid both between the suppliers and orders, as well as the orders and items, you can eager load the relationship in all directions. Where it gets complicated for your particular need with the current DB setup is that you have a 3rd parameter from the items table that is not a direct pivot. Without having to re-structure the DB, I think the easiest would be to load your suppliers and the relationships like normal:
$suppliers = Supplier::with('orders', function($query) {
$query->with('items');
});
From here you've got all the relationships loaded and can draw down the ones with the right item->ids in a follow-up to the $suppliers collection. There are quite a few ways to skin the cat (even including all in one query) now that you have the Eloquent relationship... but I tend to keep it a little more simple by breaking it into a few readable bits.
Hope this helps.

Laravel 'with' and nested relation

I am retrieving some relationship:
$requirements = Requirement::with([
'countryMatch',
'applier',
'doc' ])->get();
and my model Requirements contains:
public function doc()
{
return $this->belongsTo(Doc::class);
}
Right now all fine.
Since my Doc model has its own relationship:
public function translation($language = null)
{
if ($language == null) {
$language = /*App::getLocale()*/'en';
}
return $this->hasMany('App\DocTranslation')->where('language', '=', $language);
}
I would like to retrieve this relationship direct in the first 'with' statement, something like this:
$requirements = Requirement::with([
'countryMatch',
'applier',
'doc->translation()' ])->get();
but I receive error:
Call to undefined relationship [doc->translation()] on model [App\Requirement].
That's because relation translation is in Doc model not in Requirement.
Nested with() calls use dot notation, not object notation. Change it to 'doc.translation' instead. Do note though that I'm not sure if you can eager load relations that contain application logic.
for a nested relationship use dot like announcement.advertiser
$requirements = Requirement::with([
'countryMatch',
'applier',
'doc',
'doc.translation'])->get();

Using find() in Laravel to retrieve a database object

I am working through the Laravel 4 From Scratch tutorial at https://laracasts.com/series/laravel-from-scratch. Tutorial 4: Database Access describes several methods for retrieving data from a database.
One in particular I cannot get to work:
In my routes.php, I have
Route::get('/', function()
{
$bottle = DB::table('bottle')->find(1);
dd($bottle);
});
The only output is the "Whoops, looks like something went wrong." page. In the bottle table of my database, the primary key has the name bottle_ID. I would guess this has something to do with the problem, but I cannot find any information on how to change the find() parameter. So how do I use 'find' to return an object from my database?
The following code does work:
// returns everything from bottle table
$bottles = DB::table('brewery')->get();
return $bottles;
// returns all data for the bottle with an ID of 10
$bottle = DB::table('bottle')->where('bottle_ID', '=', 10)->get();
return $bottle;
// returns all ales from the database
$bottles = DB::table('bottle')->where('beer_type', '=', 'Ale')->get();
return $bottles;
When used in the query builder (DB::table()...) the find() method has the primary key column hardcoded as id:
public function find($id, $columns = array('*'))
{
return $this->where('id', '=', $id)->first($columns);
}
What you should do instead is use where() and first():
$bottle = DB::table('bottle')->where('bottle_ID', 1)->first();
Or if you decide to use Eloquent Models you can specify the key column name:
class Bottle extends Eloquent {
protected $primaryKey = 'bottle_ID';
}
And retrieve the model like this:
$bottle = Bottle::find(1);

Resources