Laravel: Retrieve relational data - laravel

Commodity:
public function variations() {
return $this->hasMany( CommodityVariation::class )->with( 'unit' );
}
Recipe:
public function commodity_variations() {
return $this->belongsToMany( CommodityVariation::class, 'recipe_commodity_variations' )->with( 'commodity' )->withTimestamps();
}
CommodityVariation:
public function commodity() {
return $this->belongsTo( Commodity::class )->with( 'variations', 'allergens' );
}
public function recipes() {
return $this->belongsToMany( Recipe::class, 'recipe_commodity_variations' )->withTimestamps();
}
What I'm looking for:
I would like to get all recipes that use/have this specific commodity, but I've to go through my commodity_variations to find the relation between recipe and commodity. This is because recipe is linked to commodity variations and commodity variations is linked to commodity.
So what I've build so far, is this, but it just returns 50 recipes regardleess of what commodity_id I enter. For instance if I write 100000 instead of $commodity->id this returns 50 recipes aswell.
$recipe = $department->recipes()->with(["commodity_variations" => function($q) use ( $commodity ) {
$q->where('commodity_variations.commodity_id', '=', $commodity->id);
}])->get();
I really can't figure out what I'm doing wrong. I might be misunderstanding some basic stuff on how these relations work.
Edit:
here is another example
$commodity_variations = $recipe->commodity_variations()->with( [
'commodity' => function ( $query ) use ( $commodity ) {
$query->where( 'commodities.id', $commodity->id );
}
] )->get();
Should return only the commodity variations that have commodity->id = 11, but it returns all. Like it's completely ignoring the where part of my query.

Try this once.
$commodity_variations = $recipe->whereHas('commodity_variations' ,
function ( $query ) use ( $commodity ) {
return $query->where( 'commodity_variations.commodity_id', $commodity->id );
}
)->get();
Also refer this: http://laravel.com/docs/4.2/eloquent#querying-relations

Here is a working solution. Made a new model taking care of the many to many relation between recipe and commodityvariation.
class RecipeCommodityVariations extends Model {
/**
* The database table used by the model.
*
* #var string
*/
protected $table = 'recipe_commodity_variations';
}
Query:
$recipeCommodityVariations = RecipeCommodityVariations::whereIn( 'recipe_id', $department->recipes()->pluck( 'recipe_id' ) )->whereIn( 'commodity_variation_id', $commodity->variations()->pluck( 'id' ) )->pluck( 'recipe_id' )
What troubles me, is that I feel like I'm doing it wrong... It's pretty annoying.

Related

Laravel: pass an argument to the relationship function (not the query callback function) when using whereHas

I would like to know how to pass an argument to a model relationship function. Just to be clear, I'm NOT talking about the query callback.
Consider a model like so:
class UserRelationships extends Model
{
// other stuff
// dynamic scope:
/**
* Scope a query to only include users of a given type.
*
* #param \Illuminate\Database\Eloquent\Builder $query
* #param mixed $type
* #return \Illuminate\Database\Eloquent\Builder
*/
// $relationships = UserRelationships::at( Carbon::parse('2022-10-10') )->get();
public function scopeAt($query, Carbon $date)
{
return $query->where('superseded_at', '>', $date )
->where('created_at', '<=', $date );
}
}
And a related model featuring the following relationships:
class User extends Authenticatable
{
public function progenial_relation(Carbon $date=null) // returns this user record in the userRelationships table, which can be used to retrieve this users parent (directly lookup the sponsor_id)
// when eager loading, this is useful for getting all users whose parent is x, hence the name
{
return $this->hasOne(UserRelationships::class, 'user_id', 'id')
->at( #$date ?: Carbon::now() ) // local dynamic scope in userRelationships
->orderByDesc('created_at')
->limit(1);
}
public function parental_relation(Carbon $date=null) // returns records from the userRelationships table, of all the users which refer to this user as their sponsor
// when eager loading, this is useful for getting the user whose child is x, hence the name
{
return $this->hasMany(UserRelationships::class, 'sponsor_id', 'id')
->at( #$date ?: Carbon::now() ); // local dynamic scope in userRelationships
}
}
As you can see my relationships accept an argument (the date).
Now, if you wanted to use those relationships straightforwardly like so, there's no issues:
$date = Carbon\Carbon::parse('2022-06-01');
$relations_at_date = User::find(1)->parental_relation( $date )->get();
But what happens if you need to use eager-loading methods such as has(), whereHas(), doesntHave(), whereDoesntHave()?
How do you pass an argument to the relationship? For example, I wanted to add other relationships to my User model.
public function children(Carbon $date=null)
{
$date = #$date ?: Carbon::now();
return self::whereHas('progenial_relation', function($q) {
$q->where('sponsor_id', $this->id);
}, $date); // not working
}
I tried with these syntax, but it doesn't work:
whereHas( 'relationship_name', $callback, $argument )
whereHas( 'relationship_name', $argument, $callback )
whereHas( 'relationship_name', [$argument], $callback )
whereHas( 'relationship_name', $callback, [$argument] )
Is it somehow possible?
Are there any alternatives?
For completeness I'm going to add what happens if I use a normal closure:
public function children(Carbon $date=null)
{
$date = #$date ?: Carbon::now();
return self::whereHas('progenial_relation', function($q) use ($date) {
$q->at($date)->where('sponsor_id', $this->id);
});
}
This is the resulting SQL. As you can see the constraints are applied twice. Once by the query callback and once by the relationship. But since I cannot pass the correct argument to the relationship, it gets the default one. The 2 constraints collide and the query does not work.
"select * from `users`
where exists (
select *
from `user_relationships`
where `users`.`id` = `user_relationships`.`user_id`
and `user_relationships`.`superseded_at` > ?
and `user_relationships`.`created_at` <= ?
and `sponsor_id` = ?
and `user_relationships`.`superseded_at` > ?
and `user_relationships`.`created_at` <= ?
)
and `users`.`deleted_at` is null"
I don't think that its possible to pass variables to relationship methods when eager-loading like this.
But you can apply a sub-query to the wherehas:
$date = #$date ?: Carbon::now();
return self::whereHas('progenial_relation', function($q) use ($date) {
$q
->where('sponsor_id', $this->id)
->at( #$date ?: Carbon::now() );
}, $date);
Although I'm not sure what the ->at method/scope you added does.

query scope with conditional constraint

When building a query, if one of the constraints does not return result only then run another constraint
public function scopeLatestLocationSchedule($query, $location)
{
return $query
->where('location_id', $location)
->whereNull('service_id')
->whereNull('end_date') //If not found null end_date then return latest first end_date
}
How to write this query with a conditional check on the end_date constraint
Write below code in your model
use Illuminate\Database\Eloquent\Builder;
class YourModelName extends Model
{
public function scopeEndDateNullLocationSchedule(Builder $builder, $locationId)
{
return $builder->where('location_id', $locationId)
->whereNull('service_id')
->whereNull('end_date');
}
public function scopeEndDateNotNullLocationSchedule(Builder $builder, $locationId)
{
return $builder->where('location_id', $locationId)
->whereNull('service_id')
->whereNotNull('end_date');
}
Write this below code in your controller
$locationId = 1; // put your location id here;
$query = YourModelName::endDateNullLocationSchedule($locationId);
if (!count($query->get())) {
$query = YourModelName::endDateNotNullLocationSchedule($locationId);
}
$locationSchedule = $query->orderBy('end_date', 'DESC')->first();
dd($locationSchedule);
When I have many conditions, I find ver useful to use this snippet before the Controller declaration (just under "use...")
Builder::macro('if', function ($condition, $column, $operator, $value){
if ($condition) {
return $this->where($column, $operator, $value);
}
return $this;
});
Then you can build your queries adding this kind of conditions:
->if($request->service_id, 'id', '=', $request->service_id)
the line above will trigger only if there is a value for service_id, in that case the select query will search where the id is like $request->service_id.
Hope it helps!

Get values from relationship Laravel

I have a query where I get values from 3 tables, for first 2 I use leftJoin, and is Ok, but for third one I try to get an array of objects, and I am not sure how.
In relationship table a have multiple rows for each ID from People table. HasMany type.
$q = Person::leftJoin('registers', 'people.register_id', '=', 'registers.id')
->leftJoin('relationships', 'people.id', '=', 'relationships.person_id') //if I comment this it works for first 2 tables
->find($id);
return response()->json($q);
Person
public function relationship()
{
return $this->hasMany(Relationship::class);
}
public function register()
{
return $this->belongsTo(Register::class);
}
Relationship
public function person()
{
return $this->belongsTo(Person::class, 'person_id');
}
Register
public function people(){
return $this->hasOne(Person::class);
}
UPDATE -> this works,but is kind of ugly, I think that should be a better way in Laravel
$q = Person::leftJoin('registers', 'people.register_id', '=', 'registers.id')
->find($id);
$q2 = Person::find($id)->relationship;
return response()->json([
'values' => $q,
'relationship' => $q2,
]);
You can just use with like this:
Person::with(['register', 'relationship'])->find($id);

I have multiplied users on joined request

In laravel 6 app I have multiplied users getting users with related spatie/persmissions,
as I need filter on selected permission and to show names of user's permissions :
I make as :
$users = User
::getByName($this->filter_name)
->getByStatus($this->filter_status)
->leftJoin('model_has_permissions', 'model_has_permissions.model_id', '=', 'users.id')
->getByUserPermission($this->filter_user_permission, 'model_has_permissions')
->getByUserPermissionModelType( 'model_has_permissions')
->orderBy($this->order_by, $this->order_direction)
->offset($limit_start)
->take($backend_items_per_page)
->distinct()
->paginate($backend_items_per_page);
$users->getCollection()->transform(function ($user) {
$permissions_text= '';
$permissions = $user->permissions;
foreach( $permissions as $nextPermission ) {
$permissions_text.= $nextPermission->name . ', ';
}
$user->permission_text = MyFuncsClass::trimRightSubString( $permissions_text, ', ' );
return $user;
});
I expected that using of
->distinct()
would salve it m but I failed.
Why?
UPDATED BLOCK :
Code with whereHas :
->whereHas('permissions', function ($query): void {
$query->where('id', $this->filter_user_permission);
})
works ok if $this->filter_user_permission is not empty. In case it is empty. It does not work.
That is backend listing form, so $this->filter_user_permission) can be filled or empty.
To salve it I used next scope:
public function scopeGetByUserPermission($query, $permission_id= null, $table_name)
{
if (!empty($permission_id)) {
if ( is_array($permission_id) ) {
$query->whereIn($table_name.'.permission_id', $permission_id);
} else {
$query->where($table_name.'.permission_id', $permission_id);
}
}
return $query;
}
It was used in my original post.
I tried to use it as :
->whereHas('permissions', function ($query): void {
$query->getByUserPermission($this->filter_user_permission, 'model_has_permissions');
})
but got error :
Call to undefined method
Illuminate\Database\Eloquent\Builder::getByUserPermission()
If there is a valid way ?
Thanks!
If you're using "spatie/laravel-permission" package, You may do it like so:
$users = User
::getByName($this->filter_name)
->getByStatus($this->filter_status)
/** ->leftJoin('model_has_permissions', 'model_has_permissions.model_id', '=', 'users.id') */
/** I don't really know what this 2 methods below does
->getByUserPermission($this->filter_user_permission, 'model_has_permissions')
->getByUserPermissionModelType( 'model_has_permissions') */
->whereHas('permissions', function ($query): void {
/** Change "permission_field" here to column which you use (example: id,name) from the "permissions" table of your database */
$query->where('permission_field', $this->filter_user_permission);
})
->orderBy($this->order_by, $this->order_direction)
->offset($limit_start)
->take($backend_items_per_page)
->distinct()
->paginate($backend_items_per_page);
I hope this help for You
Your scope method "scopeGetByUserPermission" defined in your User model, but in the "whereHas" method $query variable related to Permission model.
If You want to use some scope in your Model, try this.
public function scopeGetByUserPermission($query, $permission_id=null/**, $table_name */)
{
if (!empty($permission_id)) {
$query->whereHas('permissions', function ($query) use ($permission_id): void {
/** Cast your $permission_id variable to array and use "whereIn" method */
$query->whereIn('permission_id', (array)$permission_id);
});
}
return $query;
}
Also you can use php7 features for arguments in your scope method
public function scopeGetByUserPermission($query, array $permission_id=[])
{
/** Your scope logic from the code above */
}
And calling this scope will be looking like so
$users = User
::getByName($this->filter_name)
->getByStatus($this->filter_status)
->getByUserPermission((array)$this->filter_user_permission)
->orderBy($this->order_by, $this->order_direction)
->offset($limit_start)
->take($backend_items_per_page)
->distinct()
->paginate($backend_items_per_page);

Laravel/Lumen - defining a relationship with parameters

Consider a model Employee and a model Project
The employees table has a property type that can be assigned the following values "1", "2", "3", etc.
Project hasMany Employees
public function programmers() {
return $this->hasMany( 'App\Employee' )
->where( 'type', '1' );
} // hasMany programmers
public function testers() {
return $this->hasMany( 'App\Employee' )
->where( 'type', '2' );
} // hasMany testers
public function managers() {
return $this->hasMany( 'App\Employee' )
->where( 'type', '3' );
} // hasMany managers
Instead of these relationships, I would want to have only one:
public function employees( $type_id ) {
return $this->hasMany( 'App\Employee' )
->where( 'type', $type_id );
} // hasMany employees
It would work like this:
$app->get( '/employee', function() {
$project = App\Employee::find( 1 );
return $project->employees( "1" );
} );
However, I am getting the following exception:
ErrorException in Response.php line 402:
Object of class Illuminate\Database\Eloquent\Relations\HasMany could not be converted to string
Look at the contents of the error message:
ErrorException in Response.php line 402:
Object of class Illuminate\Database\Eloquent\Relations\HasMany could not be converted to string
The error is occurring in the Response class. The reason this error is coming up is because you're returning a relationship in your route definition rather than a response:
$app->get( '/employee', function() {
$project = App\Employee::find( 1 );
return $project->employees( "1" );
} );
A Relationship object cannot be converted to a string, and therefore the Response class doesn't know what to do with it.
If you want to examine the result of your relationship query in the browser, you'll need to return a valid response.
Try changing your route to something like this:
$app->get( '/employee', function() {
$project = App\Employee::find( 1 );
return response()->json($project->employees( "1" )->get());
} );
This will output the results of your query to JSON. Notice also the use of get(). This makes sure the relationship query is actually executed.

Resources