We have the models: Question and Answer.
In the answers table, we have a boolean column with the name accepted and we have just one accepted Answer. We want to get question with its answers and accept answer separately.
I use two approaches, but I don't know which is better:
1). check if the relation is loaded if loaded filter relations and get an accepted answer like this:
if(!$this->relationLoaded('answers')){
$this->load('answers');
}
return $this->answers->firstWhere('accepted',true);
2). We can use this approach too:
function accepted_answer(){
return $this->answer()->where('accepted',true);
}
The problem of the second approach is we get an array, but we expect a single response or null.
Is there any better approach to this or we can use neither 1 or 2?
Create an Accessor on Question model.
class Question extends Model
{
public function getSelectedAnswerAttribute()
{
return $this->answers->where('accepted', true)->first();
}
}
now in every Answer instance has an attribute called selected_answer.
You can access it like,
$answer->selected_answer;
Note
this 'selected_answer' is not fetching from database, it filters from already fetched answers list. so its more efficient than your 2nd approach.
and, If you want this attribute automatically bind to the Question model always. add this accessor to appends array.
Model
protected $appends = ['selected_answer'];
create accepted scope at your answer model.
public function scopeAccepted($query){
return $query->where("accepted",true)->first();
}
then you can easly retreive first accepted answer.
$question->answer()->accepted();
for more further check out query scopes;
You should go with the second approach as you will have a single source to change later on if something changes (instead of digging through a lot of controllers)
To get the result, use ->get(); at the end of builder query.
P.S: Scopes are generally always better than writing queries manually inside the controller
Related
I have many to many relationship But I want to get only one record not a collection of records. I know it's possible to get only one record for a one to many relationship using the latestOfMany() function but it doesn't exist for a many to many one.
I ended up implementing it like this and I was wondering if there is a better way to have an actual relation function and not a getter function.
public function animal(): Attribute
{
return Attribute::make(
fn () => $this->morphToMany(
Animal::class,
'follow_up',
'animal_follow_ups',
'follow_up_id',
'animal_id'
)->first()
);
}
i think the purpose itself doesn't make any sense even when we use raw query to get a single result from many to many relationship, we should apply where condition to get it, or programatically we could apply iteration do get what we need... at least that what the documentation said here
https://laravel.com/docs/9.x/eloquent-relationships#many-to-many
hope it helps
I have a question maybe a little silly. I'm trying to replicate and store a model instance in a variable to call the loadMissing method. The idea is to call the loadMissing method without being affected by the original model. Let me explain.
I have the following method in my User model:
class User extends Authenticatable
{
...
public function myCustomMethod()
{
$cloned = $this->replicate()
->loadMissing(['roles', 'roles.permissions'])
->getAttribute('roles')
->flatMap(function ($role) {
return $role->permissions
});
return $cloned;
}
The problem is that in this way the original model instance is affected, and it loads in the original instance all the relations that I am loading only in this method.
My question is: is there a way to load and isolate relationships in a replica of the model instance without affecting the original model?
Thanks in advance.
EDIT 1
I have also tried cloning the instance and trying to use the loadMissing method on the cloned instance, but the original instance is also affected. For example:
class User extends Authenticatable
{
...
public function myCustomMethod()
{
$original = $this;
$cloned = clone $original;
$cloned->loadMissing('roles.permissions');
dump($original);
dump($cloned);
die();
}
But in this way, I get the following results. Notice that both the original and the cloned instance are loading the relationships when I'm just trying to load the relationships in the cloned instance:
$original
$cloned
I was wondering if this is normal behavior. If it is a normal behavior, which I suppose it is not, I was wondering if there is any way to use the loadMissing method on a copy, replica or clone of an instance without modifying the original instance. Thank you very much in advance.
EDIT 2
My controller looks like simple like this:
class HomeController extends Controller
{
public function index()
{
dd(User::find(1)->myCustomMethod());
}
}
SOLUTION
After several days looking for documentation, doing tests and pulling my hair a little, I have found the solution to my problem and share it in case someone comes here looking for similar information in the future.
Basically the answer that helped me find the right way was this:
https://stackoverflow.com/a/29787693/13066106
Which is documented in the official PHP documentation:
https://www.php.net/manual/en/language.oop5.cloning.php
I copy and paste verbatim the most relevant here:
When an object is cloned, PHP will perform a shallow copy of all of the object's properties. Any properties that are references to other variables will remain references.
When I got to this point, I understood why it wasn't working ... I was cloning a collection of type Illuminate\Database\Eloquent\Collection and this collection contained multiple objects of type Role, but according to the documentation, it is normal behavior that when I modify any property in the cloned instance, the changes will be reflected back to me in the original instance.
From here, I looked for more documentation about it and I found an article that definitely helped me solve my problem:
Don't clone your php objects, DeepCopy them
The article is quite descriptive and basically what it does is advise the use of the package DeepCopy
Fortunately, the package is already a Laravel dependency by default, so I didn't have to install it with composer. I just used the deep_copy method to clone the instance.
Thank you very much to everyone who helped me in some way. I hope that the links to the documentation that I have shared will be of use to those who come here looking for similar information.
Why don't you just get the permissions collection (outside of model):
$userId = $user->id;
$premissions = Permissions::whereHas('users', function($userQuery) use ($userID) {
$userQuery->where('id',$userId);
})
->get();
Or do the flatmap:
$result = Roles::with('permissions')
->whereHas('users', function($userQuery) use ($userID) {
$userQuery->where('id',$userId);
})
->get()
->flatMap(function ($role) {
return $role->permissions
});
I am trying to use the method updateOrCreate on my polymorphic relation, which is called Taggable. This is the method I use to create a new tag.
public function attachTag($attributes)
{
return $this->tags()->create($attributes);
}
If I change the create method to the update, it also works fine when it comes to updating the model. But I want to use updateOrCreate method, which is problem because I don't pass the ID in the $attributes and therefore it just doesn't know, if there's some record in the DB or not.
I made some googling and found very little about this particular case, only something like I should pass second parameter. But I'm confused which parameter should I pass as second to make it working.
Thanks in advance for any help.
2 possible solutions
1- https://laracasts.com/discuss/channels/eloquent/updateorcreate-on-polymorph-relation
in this link, you should detect which one you want by selecting the second parameter.
2- Laravel: UpdateOrCreate on Relation?
I think it is also addressing your problem.
I used to use solution #1.
Hope it helps
I have a guitar lessons site using Laravel 5.3. It has lessons that are then broken down into exercises.
I decided today to add in the backend the ability to mark lessons or exercises as published. I simply added a 'published' column in the respective tables which contains 0 or 1.
There are many places in my site where exercises or lessons are accessed in some shape or form, including a lesson view, exercise view, lessons partial view (a tabular listing), exercises partial view (also a tabular listing), search/query views. There are also routes that perform functions such as incrementing hit count for a lesson or exercise, adding a lesson or exercise to one's favorites.
Given above, I am not sure how to prevent access to unpublished lessons or exercises using minimal code edits. I started looking at the controller files but then realized the controller might not be a catch all, such as on query result pages. Also controller files contain multiple methods and I don't want to edit them separately. I suspect there is a "laravel way" to do what I need globally, without me having to modify a bunch of queries in multiple files. Maybe it amounts to middleware or something?
In any case, I am new to laravel and if there is a tried and true way to control routes for content that is marked as unpublished, I would be very interested to know about it.
thanks
Take a look at global scopes, laravelest way to do this
As an aside, I prefer to use timestamps for things like published states on models. It means I can set a future timestamp for that content to become available, i.e. if I’m going on holiday.
In terms of your actual problem, a query scope would be the way to go:
class Lesson extends Model
{
public function scopePublished($query)
{
return $query->wherePublished(true);
}
}
Alternatively, if you did use a timestamp instead, you could do:
class Lesson extends Model
{
public function scopePublished($query)
{
return $query->where('published', '<=', $this->freshTimestamp());
}
}
You will then need to update your front-end code to use this scope:
public function LessonController extends Controller
{
public function index()
{
$lessons = Lesson::published()->latest()->paginate();
return view('lesson.index', compact('lessons'));
}
}
I've a User and Post model. Both of them has a Global scope to get only published or unblocked user.
Now, I need to get the list of all posts even if the author is blocked.
This is the query that I used.
$posts = Post::withoutGlobalScopes()->paginate(10);
But since I've the author relationship with it appended, I get the author as null (due to the global scope if the user is blocked).
How can I remove the global scope of the author in the above query and get all posts (published/unpublished) along with the author (blocked/unblocked).
How can I do this?
I had a similar case and I changed the definition of single relationships (especially ->belongsTo) to explicitly turn off the usually-global scope. If I had a reference to a Post and asked for its Author, I always wanted to receive the Author object, even if the Author was blocked.
class Post extends Model{
public function author(){
return $this->belongsTo('User')->withBlocked();
}
}
But in my case I didn't do the reverse for one-to-many relationships (if I had a reference to an Author, and asked for Posts I usually didn't mean unpublished, and I'd ask for them if I wanted them).