I will try to simplify the problem as much as I can.
I want to disable a relation load from a trait on a resource that happens at the retrieved event.
There is a model we will name Post that uses a trait named HasComments.
class Post extends Model
{
use HasComments;
...
}
The trait listenes for the retrieved event on the model and loads the comments relation.
trait HasComment
{
public static function bootHasComment(): void
{
self::retrieved(function ($model) {
$model->load('comments');
});
}
public function comments(): BelongsTo
{
return $this->belongsTo(Comment::class);
}
}
I want to be able to check if the comments relation was eager loaded and NOT load the relation again.
I tried to check if the relation is loaded but failed.
ex:
self::retrieved(function ($model) {
if (!isset($model->relations['comments'])) {
dd('still loads!');
$model->load('comments');
}
});
or
self::retrieved(function ($model) {
if (!$model->relationLoaded('comments')) {
dd('still loads!');
$model->load('comments');
}
});
I was also thinking of maybe there is a way to disable this behavior when constructin the query but failed again.
ex:
trait HasComment
{
public bool $load = true;
public static function bootHasComment(): void
{
self::retrieved(function ($model) {
if (!$this->load) {
dd('still loads!');
$model->load('comments');
}
});
}
public function comments(): BelongsTo
{
return $this->belongsTo(Comment::class);
}
public function disableRetrievedLoad()
{
$this->load = false;
}
}
Has someone encountered something similar and can give me some help?
There is a method named relationLoaded that returns a boolean. Seems like the right fit for your needs.
Alternatively, if you want to load a relationship if it's not been loaded yet, there's loadMissing.
$post = Post::first();
$post->relationLoaded('comments'); // false
$post->loadMissing('comments'); // makes the queries to load the relationship
$post->relationLoaded('comments'); // true
$post->loadMissing('comments'); // does nothing. comments is already loaded
I am designing an app where user can define traits/attributes for objects.
I was planning to go like this
class AttributeName extends Model
{
//
}
class Attribute extends Model
{
public function attributeName()
{
return $this->belongsTo('AttributeName');
}
public function job()
{
return $this->belongsTo('Job');
}
}
class Job extends Model
{
public function attributes()
{
return $this->hasMany('Attribute');
}
public function getAttributeValue($attributeNameId)
{
return $this->attributes->where('attribute_name_id', $attributeNameId)->first()->value ?? ;;
}
}
Unfourtunately, this gives me hard time as $this->attributes has another meaning for Laravel models. So Attribute is not a very good name for these things.
Naming it trait won't go well in PHP either. So what should I name this? Is there any common name that reflects semantics as well as attribute or trait does but doesn't conflict with other uses in Laravel and PHP?
I have a model named Test like this:
class Test extends Model
{
public $primaryKey = 'test_id';
public function questions ()
{
return $this->belongsToMany('App\Question', 'question_test', 'test_id', 'question_id');
}
}
And a Question model like this:
class Question extends Model
{
public function tests ()
{
return $this->belongsToMany('App\Test', 'question_test', 'question_id', 'test_id');
}
}
As you see there is a ManyToMany relation between this two model.
Now in a controller function, I want to get an specific Test(by id) and send it to a view. then eager load all it's questions related models and send it to another view. like this :
public function beginTest ($course_id, $lesson_id, $test_id)
{
$test = Test::find($test_id);
if ($test->onebyone) {
return view('main/pages/test/test-onebyone', compact('test'));
} else {
$test = $test->with('questions.options')->get();
return view('main/pages/test/test-onepage', compact('test', 'done_test_id'));
}
}
}
Problem is that when I use with() laravel method to eager load relations, it return all Test models with their Question relations while I want to get relations of selected Test model only.
what is your solution to solve that?
You can use 'lazy eager loading'.
$test->load('questions.options');
Using with off the model instance will make it use a new builder and cause a new query to be executed.
I have a RepairRequest model, which is associated with a Vehicle.
class RepairRequest extends \Eloquent {
public function vehicle() {
return $this->belongsTo('Vehicle');
}
}
class Vehicle extends \Eloquent {
public function requests() {
return $this->hasMany('RepairRequest');
}
}
I would like to get all RepairRequests for the vehicle associated with a given RepairRequest, so I do
return RepairRequests::find($id)->vehicle->requests;
This works fine.
However, RepairRequests have RepairItems:
// RepairRequest class
public function repairItems() {
return $this->hasMany('RepairItem', 'request_id');
}
// RepairItem class
public function request() {
return $this->belongsTo('RepairRequest', 'request_id');
}
which I would like to return too, so I do
return RepairRequests::find($id)->vehicle->requests->with('repairItems');
but I get the following exception:
Call to undefined method Illuminate\Database\Eloquent\Collection::with()
How can I write this so that the returned json includes the RepairItems in the RepairRequest json?
Load related models using load method on the Collection:
return RepairRequests::find($id)->vehicle->requests->load('repairItems');
which is basically the same as:
$repairRequest = RepairRequests::with('vehicle.requests.repairItems')->find($id);
return $repairRequest->vehicle->requests;
I'd suggest eager loading everything.
return RepairRequests::with('vehicle.requests.repaireItems')->find($id);
I have a simple database setup: Users, Groups, Pages - each are many to many.
See diagram: http://i.imgur.com/oFVsniH.png
Now I have a variable user id ($id), and with this I want to get back a list of the pages the user has access to, distinctly, since it's many-to-many on all tables.
I've setup my main models like so:
class User extends Eloquent {
protected $table = 'ssms_users';
public function groups()
{
return $this->belongsToMany('Group', 'ssms_groups_users', 'user_id','group_id');
}
}
class Group extends Eloquent {
protected $table = 'ssms_groups';
public function users()
{
return $this->belongsToMany('User', 'ssms_groups_users', 'user_id','group_id');
}
public function pages()
{
return $this->belongsToMany('Page', 'ssms_groups_pages', 'group_id','page_id');
}
}
class Page extends Eloquent {
protected $table = 'ssms_pages';
public function groups()
{
return $this->belongsToMany('Group', 'ssms_groups_pages', 'group_id','page_id');
}
}
I can get the groups the user belongs to by simply doing:
User::with('groups')->first(); // just the first user for now
However I'm totally lost on how to get the pages the user has access to (distinctly) with one query?
I believe the SQL would be something like:
select DISTINCT GP.page_id
from GroupUser GU
join GroupPage GP on GU.group_id = GP.group_id
where GU.user_id = $id
Can anyone help?
Thanks
TL;DR:
The fetchAll method below, in the MyCollection class, does the work. Simply call fetchAll($user->groups, 'pages');
Ok, assuming you managed to load the data (which should be done by eager-loading it, as mentioned in the other answer), you should loop through the Groups the User has, then loop through its Pages and add it to a new collection. Since I've had this problem already, I figured it would be easier to simply extend Laravel's own Collection class and add a generic method to do that.
To keep it simple, simply create a app/libraries folder and add it to your composer.json, under autoload -> classmap, which will take care of loading the class for us. Then put your extended Collection class in the folder.
app/libraries/MyCollection.php
use Illuminate\Database\Eloquent\Collection as IlluminateCollection;
class MyCollection extends IlluminateCollection {
public function fetchAll($allProps, &$newCollection = null) {
$allProps = explode('.', $allProps);
$curProp = array_shift($allProps);
// If this is the initial call, $newCollection should most likely be
// null and we'll have to instantiate it here
if ($newCollection === null) {
$newCollection = new self();
}
if (count($allProps) === 0) {
// If this is the last property we want, then do gather it, checking
// for duplicates using the model's key
foreach ($this as $item) {
foreach ($item->$curProp as $prop) {
if (! $newCollection->contains($prop->getKey())) {
$newCollection->push($prop);
}
}
}
} else {
// If we do have nested properties to gather, then pass we do it
// recursively, passing the $newCollection object by reference
foreach ($this as $item) {
foreach ($item->$curProp as $prop) {
static::make($prop)->fetchAll(implode('.', $allProps), $newCollection);
}
}
}
return $newCollection;
}
}
But then, to make sure your models will be using this class, and not the original Illuminate\Database\Eloquent\Collection, you'll have to create a base model from which you'll extend all your models, and overwrite the newCollection method.
app/models/BaseModel.php
abstract class BaseModel extends Eloquent {
public function newCollection(array $models = array()) {
return new MyCollection($models);
}
}
Don't forget that your models should now extend BaseModel, instead of Eloquent. After all that is done, to get all your User's Pages, having only its ID, do:
$user = User::with(array('groups', 'groups.pages'))
->find($id);
$pages = $user->groups->fetchAll('pages');
Have you tried something like this before?
$pages = User::with(array('groups', 'groups.pages'))->get();
Eager loading might be the solution to your problem: eager loading