Laravel: create a fake belongsToMany withPivot relationship - php

I have a belongsToMany() relationship between a User and a Group. The user has a level within any group he belongs to.
public function groups()
{
return $this->belongsToMany('App\Group', 'user_group', 'user_id', 'group_id')
->withPivot('level');
}
This works great.
However if the User is an admin, I would like the groups function to return ALL Groups with level = 3, regardless of whether that relationship exists in the pivot table or not.
I can successfully create a Collection which mirrors the data structure as follows:
\App\Group::all()->transform(function ($item, $key) use ($uid) {
$item->pivot = collect(['user_id'=>$uid,'group_id'=>$item->id,'level'=>3]);
return $item;
});
However, I cannot substitute the two outputs as one returns a belongsTo relationship instance and the other returns a Collection. This means I can call ->get() on the former but not the latter.
I thought about using the DB:: facade and creating a Builder for the latter, but I cannot add the Pivot values manually.
Any thoughts on how to achieve this?
-- UPDATE --
I am currently cheating by adding the ->get() inside the groups() method, but this is messy and I would still like to know if there is a better way to solve this problem.
public function groups()
{
if ($this->isAdmin()) {
return \App\Group::all()->transform(function ($item, $key) use ($uid) {
$item->pivot = collect(['user_id'=>$uid,'group_id'=>$item->id,'level'=>3]);
return $item;
});
} else {
return $this->belongsToMany('App\Group', 'user_group', 'user_id', 'group_id')
->withPivot('level')->get();
}
}

So this solution should work(not tested), but it is not the "cleanest" it would be better to access all groups through some other mechanism but because I don't know your admin implemention it is hard to guess.
public function groups()
{
return $this->belongsToMany('App\Group', 'user_group', 'user_id', 'group_id')
->withPivot('level');
}
public function scopeSpecialGroups($query)
{
return $query->when($this->role === 'admin',function($query){
return Group::where('level', '>', 3');
})->when($this->role != 'admin',function($query){
return $query->with('groups');
});
}
Then you should be able to call User::specialGroups()->get();

Related

Laravel return model with if statement

I'm trying to create offers and assign them to parent categories, to be more specific i have an Offer model and inside the offer model i have this many to many relationship
public function category() {
return $this->belongsToMany(Category::class);
}
I want the above function to return ONLY the categories which have NULL parent_category which mean they are the parent categories. Is it possible with the above code?
Without knowing the entire scope of your project, I'd suggest one of the following: either change the name of the relation (A) or keep the relation as is and query it when you need it (B).
Option A -
public function childCategory() {
return $this->belongsToMany(Category::class)->whereNull('parent_category');
}
Option B -
public function category() {
return $this->belongsToMany(Category::class);
}
$offer = Offer::with('category')
->whereHas('category' function ($query) {
$query->whereNull('parent_category');
});
public function category() {
return $this->belongsToMany(Category::class)->where('parent_category', null);
}

Laravel 5.7: New Accessor works great but is not recognisable in Repository Class (unknown column) despite of adding $appends to Model

So I have a class Order extends Model.
I created an Accessor called requiresApproval that returns true or false.
public function getRequiresApprovalAttribute(): bool
{
if ($some_physical_column_from_db === 'does not matter') {
return true;
}
return false;
}
When I have my Order model and I call $order->requiresApproval I get my boolean value. Everything works great.
However I need this accessor to appear on the list of attributes because I want to use it in my repository class in where condition within query.
So based on the official documentation, I added:
protected $appends = [
'requires_approval',
];
but when I dd() my Order, this attribute is not on the list of attributes (while $appends property indicates the accessor is there).
Long story short:
When in my repository I call:
public function getOrdersEligibleToBeSendToOptions(): Collection
{
$columns = [
'*',
];
return $this->model
->where('status_uuid', '<>', OrderStatusesInterface::STATUS_COMPLETED)
->where('requiresApproval', '=', true) // this fails
// ->where('requires_approval', '=', true) // this fails as well
->get($columns);
}
I get:
What am I doing wrong? How can I use my accessor within repository class?
OK, this works, but the reason I don't like this solution is the fact that just half of the conditions are on the DB layer, the rest is by filtering what's already fetched.
If the query is going to return (let's say) thousand of records and filter returns just a few of them I personally see this as a huge waste of DB resource.
public function getOrdersEligibleToBeSendToOptions(): Collection
{
$columns = [
'*',
];
$results = $this->model
->where('status_uuid', '<>', OrderStatusesInterface::STATUS_COMPLETED)
->get($columns);
return $results->filter(function ($value, $key) {
return $value->requiresApproval === false;
});
}
Eloquent queries work on the database fields, but you can use your accessor after fetching a colletion from the database like this.
Here is some good article about this:
https://medium.com/#bvipul/laravel-accessors-and-mutators-learn-how-to-use-them-29a1e843ce85
return $this->model
->where('status_uuid', '<>', OrderStatusesInterface::STATUS_COMPLETED)
->get($columns)
->filter(function ($row) {
return $row->requires_approval === true;
});
The model virtual attributes cannot be used within queries. Perhaps a better approach would be to create a scope to enforce this constraint on a query:
class Order extends Model
{
public function scopeRequiresApproval($query)
{
return $query->where('some_column', '>', 100);
}
}
Then
return $this->model
->where('status_uuid', '<>', OrderStatusesInterface::STATUS_COMPLETED)
->requiresApproval()
->get($columns);

Laravel how to create empty relationship?

I have a condition inside a relationship where if the user is logged in it will return the relation and if not i want it to return empty relationship.
here is what i want :
public function dummy()
{
return (auth()->user()) ? $this->hasOne(blah::class) : emptyrelationship();
}
You should check with DD() what is being returned as you like.
If there's no data for the relationship to show, it will just return no data.
To return an empty relationship instead of null, you can try this:
public function item()
{
return $this->belongsTo(Item::class)
->withDefault(function () {
return new Item();
});
}
Try This example
public function shop(){
if(true) {
return $this->newQuery(); // or newQueryWithoutScopes()
}
return $this->belongsTo('App\Models\Shop');
}
If there is no user in the user table collection will return relationship as "relationship: user: []" as blank only if you do dd($var) on your query,then you can check though conditions in your code;
Eloquent has a method for that newModelInstance
Best to keep the eloquent model standard relationship and move logic elsewhere
public function dummy()
{
return $this->hasOne(blah::class);
}
$dummy = $model->dummy;
if (!$dummy) {
$dummy = $model->dummy()->newModelInstance();
}

Custom Laravel Relations?

Hypothetical situation: Let's say we have 3 models:
User
Role
Permission
Let's also say User has a many-to-many relation with Role, and Role has a many-to-many relation with Permission.
So their models might look something like this. (I kept them brief on purpose.)
class User
{
public function roles() {
return $this->belongsToMany(Role::class);
}
}
class Role
{
public function users() {
return $this->belongsToMany(User::class);
}
public function permissions() {
return $this->belongsToMany(Permission::class);
}
}
class Permission
{
public function roles() {
return $this->belongsToMany(Role::class);
}
}
What if you wanted to get all the Permissions for a User? There isn't a BelongsToManyThrough.
It seems as though you are sort of stuck doing something that doesn't feel quite right and doesn't work with things like User::with('permissions') or User::has('permissions').
class User
{
public function permissions() {
$permissions = [];
foreach ($this->roles as $role) {
foreach ($role->permissions as $permission) {
$permissions = array_merge($permissions, $permission);
}
}
return $permissions;
}
}
This example is, just one example, don't read too much into it. The point is, how can you define a custom relationship? Another example could be the relationship between a facebook comment and the author's mother. Weird, I know, but hopefully you get the idea. Custom Relationships. How?
In my mind, a good solution would be for that relationship to be described in a similar way to how describe any other relationship in Laravel. Something that returns an Eloquent Relation.
class User
{
public function permissions() {
return $this->customRelation(Permission::class, ...);
}
}
Does something like this already exist?
The closest thing to a solution was what #biship posted in the comments. Where you would manually modify the properties of an existing Relation. This might work well in some scenarios. Really, it may be the right solution in some cases. However, I found I was having to strip down all of the constraints added by the Relation and manually add any new constraints I needed.
My thinking is this... If you're going to be stripping down the constraints each time so that the Relation is just "bare". Why not make a custom Relation that doesn't add any constraints itself and takes a Closure to help facilitate adding constraints?
Solution
Something like this seems to be working well for me. At least, this is the basic concept:
class Custom extends Relation
{
protected $baseConstraints;
public function __construct(Builder $query, Model $parent, Closure $baseConstraints)
{
$this->baseConstraints = $baseConstraints;
parent::__construct($query, $parent);
}
public function addConstraints()
{
call_user_func($this->baseConstraints, $this);
}
public function addEagerConstraints(array $models)
{
// not implemented yet
}
public function initRelation(array $models, $relation)
{
// not implemented yet
}
public function match(array $models, Collection $results, $relation)
{
// not implemented yet
}
public function getResults()
{
return $this->get();
}
}
The methods not implemented yet are used for eager loading and must be declared as they are abstract. I haven't that far yet. :)
And a trait to make this new Custom Relation easier to use.
trait HasCustomRelations
{
public function custom($related, Closure $baseConstraints)
{
$instance = new $related;
$query = $instance->newQuery();
return new Custom($query, $this, $baseConstraints);
}
}
Usage
// app/User.php
class User
{
use HasCustomRelations;
public function permissions()
{
return $this->custom(Permission::class, function ($relation) {
$relation->getQuery()
// join the pivot table for permission and roles
->join('permission_role', 'permission_role.permission_id', '=', 'permissions.id')
// join the pivot table for users and roles
->join('role_user', 'role_user.role_id', '=', 'permission_role.role_id')
// for this user
->where('role_user.user_id', $this->id);
});
}
}
// app/Permission.php
class Permission
{
use HasCustomRelations;
public function users()
{
return $this->custom(User::class, function ($relation) {
$relation->getQuery()
// join the pivot table for users and roles
->join('role_user', 'role_user.user_id', '=', 'users.id')
// join the pivot table for permission and roles
->join('permission_role', 'permission_role.role_id', '=', 'role_user.role_id')
// for this permission
->where('permission_role.permission_id', $this->id);
});
}
}
You could now do all the normal stuff for relations without having to query in-between relations first.
Github
I went a ahead and put all this on Github just in case there are more people who are interested in something like this. This is still sort of a science experiment in my opinion. But, hey, we can figure this out together. :)
Have you looked into the hasManyThrough relationship that Laravel offers?
Laravel HasManyThrough
It should help you retrieve all the permissions for a user.
I believe this concept already exists. You may choose on using Laravel ACL Roles and Permissions or Gate, or a package known as Entrust by zizaco.
Zizaco - Entrust
Laracast - watch video 13 and 14
Good luck!

Laravel 5.2 eager loading no working

Heres what i am doing.
$question = ForumQuestion::where('link',$ques)->with('answers')->first();
$answers = $question->answers;
$answers->load('user');
//return $answers;
return view('forum.question', compact('question','answers'));
the $answers->load('user'); eager loads corresponding user of the answer.
public function user()
{
if ($this->user_type == 'student') {
return $this->belongsTo(Student::class, 'user_id');
}else{
return $this->belongsTo(Teacher::class, 'user_id');
}
}
But problem is $this->user_type gets some kind of static. If my first answer has user_type = 'teacher' then in every query it assumes as it is teacher even though it changes some time to student.
Why it is static? If I don't eager load it works well.
What if you change your eager loading into two different categories instead of putting everything together into the user function
public function student() {
return $this->belongsTo(Student::class, 'user_id');
}
public function teacher() {
return $this->belongsTo(Teacher::class, 'user_id');
}
and just if you really want to do an if statement, call it from a different function such as user. Hope that helps.

Categories