How I can count items with same foreign key and still get acces to relationship data?
I'm using PHP - Laravel - Eloquent, and have three tables: User, User_items and items.
User_items has three columns: id, user_id and item_id:
ID | user_id | item_id
0 | 5 | 2
1 | 5 | 3
2 | 5 | 8
3 | 5 | 3
Let's say items also has three columns: id, name and value:
ID | Name | Value
2 | Sword | 500
3 | Pickaxe | 250
8 | Shovel | 700
I want to count items with the same user_id and return item data from the items table. In order to get all of the user items, I'm using the following relationship:
public function user_items()
{
return $this->hasMany(User_items::class, 'user_id', 'id');
}
Next, I want to get the names of these items and count them. I don't just want:
Sword
Pickaxe
Shovel
Pickaxe
I want, somehow, to get the results like this:
2 Pickaxe
1 Sword
1 Shovel
My user_items relationship, to get the data about an item looks like :
public function item_data()
{
return $this->hasOne(Items::class, 'id', 'item_id');
}
Thats how I'm getting repeatable result:
return response()->json(["DATA" => $user->user_items->load('item_data')->toArray()], 201);
Example output
"DATA":[
{
"id":1,
"item_id":5,
"created_at":"2019-06-04 08:44:08",
"updated_at":"2019-06-04 08:44:08",
"item_data":{
"id":5,
"name":"Sword",
"rarity":"good",
"value":500,
"image":"image.jpg",
"color":"#3160ed",
"created_at":null,
"updated_at":null
}
},
}
I want to add a row like "count": 5 to the above output.
I reckon what you need is a many-to-many relationship between a user and an item.
I rename the tables like this for better naming convention: users, user_item, items.
class User
{
public function items()
{
return $this->belongsToMany(Item::class)
}
}
For each user to return the items and the number of items for each. You might do something like.
$items = $user->items()->select('items.*', 'count(items.id) AS items_count')
->groupBy('items.id')
->get()
->toArray();
// From the controller, this array will be automatically converted to json.
return ['DATA' => $items];
You will have something like this. Not exactly the format you want, but I think it's better keep it this way.
[
'DATA' => [
{
"id":5,
"name":"Sword",
"rarity":"good",
"value":500,
"image":"image.jpg",
"color":"#3160ed",
"created_at":null,
"updated_at":null,
items_count: 1
}
]
]
Related
I have the following table structure:
Products
========
id | name
------|-------
1 | A
2 | B
Stocks
========
id | product_id | color_id | size | qty
------|------------|----------|----- |-----
1 | 1 | 1 | S | 37
2 | 1 | 1 | XL | 89
3 | 1 | 2 | S | 6
4 | 1 | 2 | L | 8
Colors
========
id | name | hex
------|-------|-------
1 | Red | #ff0000
2 | Green | #00ff00
What I want is to get the list of products with each of its available colors and sizes, so:
Product {
id: string
name: string
available_sizes: string[]
available_colors: Color[]
}
I looked up the Laravel documentation and figured that I need to use hasManyThrough (probably) to make a relation between Product <==> Color. But since my colors table doesn't have a stock_id, I am not understanding how can I connect them.
How can I achieve this?
As I understand, hasManyThrough works in a scenario that a product has many stocks and a stock has many colors. This is not the case.
You can simply eager load the relationship and present the data the way you want, using collection and higher order messages:
public function index()
{
$products = Product::with('stocks.color')->get();
return ProductResource::collection($products);
}
class ProductResource extends JsonResource
{
public function toArray($request)
{
return [
'id' => $this->id,
'name' => $this->name,
'available_sizes' => $this->stocks->map->size->unique()->toArray(),
'available_colors' => $this->stocks
->map->color
->map->name
->unique()
->toArray()
]
}
}
here you will use models
ProductModel
public function stocks()
{
return $this->belongsto(StockModel::class,'product_id');
}
StockModel
public function colors()
{
return $this->hasmany(ColorModel::class,'color_id');
}
Controller
$product=Product::with('stocks.colors')->find($id);
dd($product);
and check if the data are available.
or according to the documentation hasmanythrough
ProductModel:
public function colors(){
return $this->hasManyThrough(
Stock::class,
Color::class,
'product_id', // Foreign key on the product table...
'color_id', // Foreign key on the colors table...
'id', // Local key on the products table...
'id' // Local key on colors table...
);
}
Controller:
$product=Product::with('colors')->find($id);
I've been struggling with this for a couple of weeks and I can't seem to find the answer anywhere.
I am trying to reference three foreign keys into one primary key on another table.
Here are some information:
User Table
|---------------------|
| id |
|---------------------|
| 1 |
|---------------------|
| 2 |
|---------------------|
| 3 |
|---------------------|
Service Table
Service table with three foreign keys referencing to user_id
|---------------------|------------------|------------------|------------------|
| id | tenant | service_person | landlord |
|---------------------|------------------|------------------|------------------|
| 1 | 1 | 2 | 3 |
|---------------------|------------------|------------------|------------------|
User Model
public function service(){
return $this->hasMany(Service::class, 'id');
}
Service Model
public function tenant(){
return $this->belongsTo(User::class, 'id', 'tenant');
}
public function service_person(){
return $this->belongsTo(User::class, 'id', 'service_person');
}
public function landlord(){
return $this->belongsTo(User::class, 'id', 'landlord');
}
So when I try to query with User::find()->service with tinker, only one of my three users find results, which is the service_person. The other ones just return an empty object.
Query result:
>>> User::find(1)->service
=> Illuminate\Database\Eloquent\Collection {#3125
all: [
App\Service {#3156
id: 1,
tenant: 2,
service_person: 1,
landlord: 3
},
],
}
>>> User::find(2)->service
=> Illuminate\Database\Eloquent\Collection {#3165
all: [],
}
>>> User::find(3)->service
=> Illuminate\Database\Eloquent\Collection {#3166
all: [],
}
What I am trying to achieve is looking for all the services related to that user, doesn't matter if that user is the tenant, service_guy or landlord, so I can show it on the front end.
Any idea on how can I achieve that?
Ideally I do not want to do a many to many relationship and create a pivot table.
Thank you in advance
i think the problem is for the User model 's relations:
User Model:
public function servicesTenants(){
return $this->hasMany(Service::class, 'tenant');
}
public function servicesPerson(){
return $this->hasMany(Service::class, 'service_person');
}
public function servicesLandlord(){
return $this->hasMany(Service::class, 'landlord');
}
now, you can try:
$user=User::with(['servicesLandlord','servicesPerson','servicesTenants'])->find($user_id);
if you want to get all user 's services regardless of the relation you can try:
$userServices=Service::where('tenant',$user_id)->orWhere('service_person',$user_id)->
orWhere('landlord',$user_id)->get();
I'm having trouble relating photos to tags using an intermediate table.
In the example below, how can I select all photos that belong to tag 1 with an Eloquent relationship method in Laravel?
I have these tables:
-Photos Table
| id | name | description |
1 photo1.png ....
2 photo2.png ....
3 photo3.png ....
-Tags Table
| id | name |
1 Aesthetic
2 Dark
-Tags Relations
| id | tag_id | photo_id |
1 1 3
2 1 2
3 2 1
First of, you need to ensure that both Photos and Tags table have the relationship defined.
Under the Photos model you should have the following function:
public function tags() {
return $this->belongsToMany(
Tag::class,
"photos_tags", // the name of the pivot table
"photo_id",
"tag_id"
);
}
Under the Tags model you should have the following function:
public function photos() {
return $this->belongsToMany(
Photo::class,
"tags_photos", // the name of the pivot table
"tag_id",
"photo_id"
);
}
Now to access all the tags that are related to the Photo of id 1, you can call the following:
Photo::findOrFail(1)->tags()->get();
And the same you can do for a specific tag to get all it's photos.
Tag::findOrFail(1)->photos()->get();
Hope this will lead you to what you wish.
I'm trying to inner join a users table to itself using an eloquent model. I've looked everywhere but can't seem to find a solution to this without creating two queries which is what I am currently doing.
A users table has a many to many relationship itself through the pivot table friends
I tried and failed inner joining Users::class to itself. The best I can get at an inner join is by running two queries and seeing if there is an overlap. Thus one person has reached out to the other and vice versa.
friends | users
----------|------
send_id | id
receive_id| name
is_blocked|
sample data & expected result
users.id | name
---------|------
1 | foo
2 | bar
3 | baz
friends
send_id | receive_id | is_blocked
--------|------------|-----------
1 | 2 | 0
2 | 1 | 0
1 | 3 | 0
3 | 1 | 1
2 | 3 | 0
The user should have an eloquent relationship called friends. It should be what you expect comes out of requestedFriends or receivedFriends just joined.
foo->friends
returns `baz`
bar->friends
returns `foo`
baz->friends
returns empty collection
currently using
// User.php
public function requestedFriends()
{
$left = $this->belongsToMany(User::class, 'friends','send_id','receive_id')
->withPivot('is_blocked')
->wherePivot('is_blocked','=', 0)
->withTimestamps();
return $left;
}
public function receivedFriends()
{
$right = $this->belongsToMany(User::class, 'friends','receive_id','send_id')
->withPivot('is_blocked')
->wherePivot('is_blocked','=', 0)
->withTimestamps();
return $right;
}
public function friends()
{
$reqFriends = $this->requestedFriends()->get();
$recFriends = $this->receivedFriends()->get();
$req = explode(",",$recFriends->implode('id', ', '));
$intersect = $reqFriends->whereIn('id', $req);
return $intersect;
}
Research so far
Laravel Many to many self referencing table only works one way -> old question, but still relevant
https://github.com/laravel/framework/issues/441#issuecomment-14213883 -> yep, it works… but one way.
https://laravel.com/docs/5.8/collections#method-wherein
currently the only way I have found to do this in eloquent.
https://laravel.com/docs/5.7/queries#joins -> Ideally I would find a solution using an innerjoin onto itself, but no matter which way I put the id's I couldn't get a solution to work.
A solution would
A solution would inner join a self referencing table using eloquent in laravel 5.7 or 5.8, where a relationship only exists if send_id & receive_id are present on multiple rows in the friends table.
OR
Somehow let the community know that this can't be done.
Thanks in advance!
I have not checked this solution in every detail yet, but I have written a "ManyToMany" Class extending the "BelongsToMany" Class shipped with laravel, which appears to work.
The class basically just overrides the "get" method, duplicating the original query, "inverting" it and just performing a "union" on the original query.
<?php
namespace App\Database\Eloquent\Relations;
use Illuminate\Database\Eloquent\Relations\BelongsToMany;
class ManyToMany extends BelongsToMany
{
/**
* Execute the query as a "select" statement.
*
* #param array $columns
* #return \Illuminate\Database\Eloquent\Collection
*/
public function get($columns = ['*'])
{
// duplicated from "BelongsToMany"
$builder = $this->query->applyScopes();
$columns = $builder->getQuery()->columns ? [] : $columns;
// Adjustments for "Many to Many on self": do not get the resulting models here directly, but rather
// just set the columns to select and do some adjustments to also select the "inverse" records
$builder->addSelect(
$this->shouldSelect($columns)
);
// backup order directives
$orders = $builder->getQuery()->orders;
$builder->getQuery()->orders = [];
// clone the original query
$query2 = clone($this->query);
// determine the columns to select - same as in original query, but with inverted pivot key names
$query2->select(
$this->shouldSelectInverse( $columns )
);
// remove the inner join and build a new one, this time using the "foreign" pivot key
$query2->getQuery()->joins = array();
$baseTable = $this->related->getTable();
$key = $baseTable.'.'.$this->relatedKey;
$query2->join($this->table, $key, '=', $this->getQualifiedForeignPivotKeyName());
// go through all where conditions and "invert" the one relevant for the inner join
foreach( $query2->getQuery()->wheres as &$where ) {
if(
$where['type'] == 'Basic'
&& $where['column'] == $this->getQualifiedForeignPivotKeyName()
&& $where['operator'] == '='
&& $where['value'] == $this->parent->{$this->parentKey}
) {
$where['column'] = $this->getQualifiedRelatedPivotKeyName();
break;
}
}
// add the duplicated and modified and adjusted query to the original query with union
$builder->getQuery()->union($query2);
// reapply orderings so that they are used for the "union" rather than just the individual queries
foreach($orders as $ord)
$builder->getQuery()->orderBy($ord['column'], $ord['direction']);
// back to "normal" - get the models
$models = $builder->getModels();
$this->hydratePivotRelation($models);
// If we actually found models we will also eager load any relationships that
// have been specified as needing to be eager loaded. This will solve the
// n + 1 query problem for the developer and also increase performance.
if (count($models) > 0) {
$models = $builder->eagerLoadRelations($models);
}
return $this->related->newCollection($models);
}
/**
* Get the select columns for the relation query.
*
* #param array $columns
* #return array
*/
protected function shouldSelectInverse(array $columns = ['*'])
{
if ($columns == ['*']) {
$columns = [$this->related->getTable().'.*'];
}
return array_merge($columns, $this->aliasedPivotColumnsInverse());
}
/**
* Get the pivot columns for the relation.
*
* "pivot_" is prefixed ot each column for easy removal later.
*
* #return array
*/
protected function aliasedPivotColumnsInverse()
{
$collection = collect( $this->pivotColumns )->map(function ($column) {
return $this->table.'.'.$column.' as pivot_'.$column;
});
$collection->prepend(
$this->table.'.'.$this->relatedPivotKey.' as pivot_'.$this->foreignPivotKey
);
$collection->prepend(
$this->table.'.'.$this->foreignPivotKey.' as pivot_'.$this->relatedPivotKey
);
return $collection->unique()->all();
}
}
I came across the same problem quite some time ago and have thus been following this problem closely and have made a lot of research. I have come across some of the solutions you have also found, and some more, and also have thought of other solutions that I summed here, mostly how to get both user_ids in the same column. I am afraid they will all not work well. I am also afraid that using any custom classes will stop you from using all of Laravel's handy relation features (especially eager loading). So I still thought what one could do, and, until one comes up with a hasMany-function on many columns, I think I have come up with a possible solution yesterday. I will show it first and then apply it to your project.
My project
Initial solution
In my project, one user partners with another one (= partnership) and then later will be assigned a commission. So I had the following tables:
USERS
id | name
---------|------
1 | foo
2 | bar
17 | baz
20 | Joe
48 | Jane
51 | Jim
PARTNERSHIPS
id | partner1 | partner2 | confirmed | other_columns
----|-----------|-----------|-----------|---------------
1 | 1 | 2 | 1 |
9 | 17 | 20 | 1 |
23 | 48 | 51 | 1 |
As each user should always have only one active partnership, the non-active being soft-deleted, I could have helped myself by just using the hasMany function twice:
//user.php
public function partnerships()
{
$r = $this->hasMany(Partnership::class, 'partner1');
if(! $r->count() ){
$r = $this->hasMany(Partnership::class, 'partner2');
}
return $r;
}
But if I had wanted to lookup all partnerships of a user, current and past, this of course, wouldn't have worked.
New solution
Yesterday, I came up with the solution, that is close to yours, of using a pivot table but with a little difference of using another table:
USERS
(same as above)
PARTNERSHIP_USER
user_id | partnership_id
--------|----------------
1 | 1
2 | 1
17 | 9
20 | 9
48 | 23
51 | 23
PARTNERSHIPS
id | confirmed | other_columns
----|-----------|---------------
1 | 1 |
9 | 1 |
23 | 1 |
// user.php
public function partnerships(){
return $this->belongsToMany(Partnership::class);
}
public function getPartners(){
return $this->partnerships()->with(['users' => function ($query){
$query->where('user_id', '<>', $this->id);
}])->get();
}
public function getCurrentPartner(){
return $this->partnerships()->latest()->with(['users' => function ($query){
$query->where('user_id', '<>', $this->id);
}])->get();
}
// partnership.php
public function users(){
return $this->belongsToMany(User::class);
}
Of course, this comes with the drawback that you always have to create and maintain two entrances in the pivot table but I think this occasional extra load for the database -- how often will this be altered anyway? -- is preferable to having two select queries on two columns every time (and from your example it seemed that you duplicated the entries in your friends table anyway).
Applied to your project
In your example the tables could be structured like this:
USERS
id | name
---------|------
1 | foo
2 | bar
3 | baz
FRIENDSHIP_USER
user_id | friendship_id
---------|------
1 | 1
2 | 1
3 | 2
1 | 2
FRIENDSHIPS
id |send_id* | receive_id* | is_blocked | [all the other nice stuff
--------|---------|-------------|------------|- you want to save]
1 | 1 | 2 | 0 |
2 | 3 | 1 | 0 |
[*send_id and receive_id are optional except
you really want to save who did what]
Edit: My $user->partners() looks like this:
// user.php
// PARTNERSHIPS
public function partnerships(){
// 'failed' is a custom fields in the pivot table, like the 'is_blocked' in your example
return $this->belongsToMany(Partnership::class)
->withPivot('failed');
}
// PARTNERS
public function partners(){
// this query goes forth to partnerships and then back to users.
// The subquery excludes the id of the querying user when going back
// (when I ask for "partners", I want only the second person to be returned)
return $this->partnerships()
->with(['users' => function ($query){
$query->where('user_id', '<>', $this->id);
}]);
}
There are two models: Restaurant and Category
Restaurant{
'_id' : 12345678
'name' : "xyz",
'abstract' : "awdwadawdawdawd",
'category': [1,2,3,4] //category ids
}
Category{
'_id' : 1,
'name' : 'PQR'
}
How can I assign relationship and fetch all the categories using that array field (category) of Restaurant in laravel 5.3 and Database is mongodb?
I am not quite sure about your DB structure, but I think it would be better to create a new pivot table to store the categories of each restaurant in a different row like this:
id | restaurant_id | category_id
1 | 23 | 3
2 | 23 | 5
3 | 25 | 4
As a convention the table should be named 'category_restaurant'
This would be a many to many relationship in laravel.
class Restaurant
{
public function categories()
{
return $this->belongsToMany(Category::class);
}
}
class Category
{
public function restaurants()
{
return $this->belongsToMany(Restaurant::class);
}
}
Then in your code you can get the ids of a restaurant in this way:
$restaurant = Restaurant::find(1);
$categories = $restaurant->categories;