eager load of relations of a specific model in laravel - 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.

Related

Laravel hasOne relation with where clause

I have a model called RealEstate, this model has a relation with another model called TokenPrice, I needed to access the oldest records of token_prices table using by a simple hasOne relation, So I did it and now my relation method is like following:
use Illuminate\Database\Eloquent\Model;
use Illuminate\Database\Eloquent\Relations\HasOne;
class RealEstate extends Model
{
public function firstTokenPrice(): HasOne
{
return $this->hasOne(TokenPrice::class)->oldestOfMany();
}
}
By far it's fine and no complexity. But now, I need to involve another relation into firstTokenPrice.
Let me explain a bit more:
As my project grown, the more complexity was added it, like changing firstTokenPrice using by a third table called opening_prices, so I added a new relation to RealEstate called lastOpeningPrice:
public function lastOpeningPrice(): HasOne
{
return $this->hasOne(OpeningPrice::class)->latestOfMany();
}
So the deal with simplicity of firstTokenPrice relation is now off the table, I want to do something like following every time a RealEstate object calls for its firstTokenPrice:
Check for lastOpeningPrice, if it was exists, then firstTokenPrice must returns a different record of token_price table, otherwise the firstTokenPrice must returns oldestOfMany of TokenPrice model.
I did something like following but it's not working:
use Illuminate\Database\Eloquent\Model;
use Illuminate\Database\Eloquent\Relations\HasOne;
class RealEstate extends Model
{
public function lastOpeningPrice(): HasOne
{
return $this->hasOne(OpeningPrice::class)->latestOfMany();
}
public function firstTokenPrice(): HasOne
{
$lop = $this->lastOpeningPrice;
if ($lop) {
TokenPriceHelper::getOrCreateFirstToken($this, $lop->amount); // this is just a helper function that inserts a new token price into `token_prices` table, if there was none exists already with selected amount
return $this->hasOne(TokenPrice::class)->where('amount', $lop->amount)->oldestOfMany();
}
return $this->hasOne(TokenPrice::class)->oldestOfMany();
}
}
I have checked the $this->hasOne(TokenPrice::class)->where('amount', $lop->amount)->oldestOfMany() using by ->toSql() method and it returns something unusual.
I need to return a HasOne object inside of firstTokenPrice method.
You can use ofMany builder for that purpose:
public function firstTokenPrice(): HasOne
{
$lop = $this->lastOpeningPrice;
if ($lop) {
TokenPriceHelper::getOrCreateFirstToken($this, $lop->amount); // this is just a helper function that inserts a new token price into `token_prices` table, if there was none exists already with selected amount
return $this->hasOne(TokenPrice::class)->ofMany([
'id' => 'min',
], function ($query) use ($lop) {
$query->where('amount', $lop->amount);
});
}
return $this->hasOne(TokenPrice::class)->oldestOfMany();
}
I used ->oldest() with a custom scope called amounted in TokenPrice model:
class TokenPrice extends Model
{
public function scopeAmounted(Builder $query, OpeningPrice $openingPrice): Builder
{
return $query->where('amount', $openingPrice->amount);
}
/....
}
And then changed my firstTokenPrice
public function firstTokenPrice(): HasOne
{
$lop = $this->lastOpeningPrice;
if ($lop) {
TokenPriceHelper::getOrCreateFirstToken($this, $lop->amount);
return $this->hasOne(TokenPrice::class)->amounted($lop)->oldest();
}
return $this->hasOne(TokenPrice::class)->oldestOfMany();
}
It's working, but I don't know if it's the best answer or not

Eager load related models in Laravel's Revisionable history

Using Laravel 5.4 and the VentureCraft/revisionable package.
I have 3 models: User, Company and Order.
The Order model implements the Revisionable trait:
namespace App\Models;
class Order extends Eloquent {
use \Venturecraft\Revisionable\RevisionableTrait;
// ...
}
The User model has a relationship with the Company model:
public function company() {
return $this->belongsTo('App\Models\Company');
}
And I would like to eager load the company of a user from an order revision. Something like this
$order = Order::with('revisionHistory.user.company')->find(1);
foreach($order->revisionHistory as $revision) {
$company = $revision->user->company;
}
I've tried overwriting various methods (e.g. revisionHistory and identifiableName) from the Revisionable trait without any luck.
You can use $company = $revision->userResponsible()->company to get company of the User that change the Order.
I think this is not possible with the current version of the package.
The reason behind is that userResponsible() is not an actual relationship, it's just a function that returns an instance of the User model.
To allow eager loading, something like this would be needed:
public function userResponsible()
{
if (class_exists($class = '\Cartalyst\Sentry\Facades\Laravel\Sentry')) {
return $this->belongsTo(Config::get('sentry::users')['model'], 'user_id');
} else if (class_exists($class = '\Cartalyst\Sentinel\Laravel\Facades\Sentinel')) {
return $this->belongsTo(Config::get('sentinel::users')['model'], 'user_id');
} else {
return $this->belongsTo(Config::get('auth.model'), 'user_id');
}
}
Then you would be able to eager load like this:
$order = Order::with('revisionHistory.userResponsible.company')->find(1);
You can view the original issue in GitHub.

Laravel retrieving eloquent nested eager loading

In my application i have 4 models that relate to each other.
Forms->categories->fields->triggers
What I am trying to do is get the Triggers that refer to the current Form.
Upon researching i found nested eager loading, which would require my code to look like this
Form::with('categories.fields.triggers')->get();
Looking through the response of this i can clearly see the relations all the way down to my desired triggers.
Now the part I'm struggling with is only getting the triggers, without looping through each model.
The code i know works:
$form = Form::findOrFail($id);
$categories = $form->categories;
foreach ($categories as $category) {
$fields = $category->fields;
foreach ($fields as $field) {
$triggers[] = $field->triggers;
}
}
I know this works, but can it be simplified? Is it possible to write:
$form = Form::with('categories.fields.triggers')->get()
$triggers = $form->categories->fields->triggers;
To get the triggers related? Doing this as of right now results in:
Undefined property: Illuminate\Database\Eloquent\Collection::$categories
Since it is trying to run the $form->categories on a collection.
How would i go about doing this? Do i need to use the HasManyThrough relation on my models?
My models
class Form extends Model
{
public function categories()
{
return $this->hasMany('App\Category');
}
}
class Category extends Model
{
public function form()
{
return $this->belongsTo('App\Form');
}
public function fields()
{
return $this->hasMany('App\Field');
}
}
class Field extends Model
{
public function category()
{
return $this->belongsTo('App\Category');
}
public function triggers()
{
return $this->belongsToMany('App\Trigger');
}
}
class Trigger extends Model
{
public function fields()
{
return $this->belongsToMany('App\Field');
}
}
The triggers run through a pivot table, but should be reachable with the same method?
I created a HasManyThrough relationship with unlimited levels and support for BelongsToMany:
Repository on GitHub
After the installation, you can use it like this:
class Form extends Model {
use \Staudenmeir\EloquentHasManyDeep\HasRelationships;
public function triggers() {
return $this->hasManyDeep(Trigger::class, [Category::class, Field::class, 'field_trigger']);
}
}
Form::with('triggers')->get();
Form::findOrFail($id)->triggers;

How to include an Eloquent relationship into JSON conversion result?

I want to return JSON data to my Angular front-end with Laravel 5 but I am stuck with this problem : I have an Alert eloquent model which has a relationship with AlertFrequency model:
class Alert extends Model
{
public function alertFrequency()
{
return $this->belongsTo('App\Models\AlertFrequency');
}
}
but when I try to return the alerts listing as a JSON...
$alerts = Auth::user()->alerts->toJson();
return $alerts;
here is what I get :
[{"id":9,"title":"fqdsgsq","place":"gdqsgq","user_id":1,"alert_frequency_id":3,"job_naming_id":3,"created_at":"2016-07-16 14:09:41","updated_at":"2016-07-16 14:09:41"}]
So I do have the "alert_frequency_id" column value but not the actual AlertFrequency object
The only workaround I found to be working so far is this :
$alerts = Auth::user()->alerts;
foreach ($alerts as $alert) {
$alert->jobNaming = $alert->jobNaming;
$alert->alertFrequency = $alert->alertFrequency;
}
return $alerts;
which is very ugly...
You need to manually load the relationship. This is called lazy eager loading a relationship.
$alerts = Auth::user()->alerts->load('alertFrequency')->toJson();
return $alerts;
When you do Auth::user()->alerts->toJson() you just fetched the data from the Alert model, but you want data from another model (another table) then you need to tell that using the load method.
Bonus
If your code is in a controller method or a router closure instead of calling toJson() you can just return the collection and Laravel will do that for you. Example:
public function controllerMethod() {
return Auth::user()->alerts->load('alertFrequency');
}
Happy coding!

Relationship method must return an object of type Relation (LogicException) Laravel 4.1

I have used Laravel 4 fair bit and it's the first time I've came across this problem.
My pager table:
class pager extends Eloquent
{
protected $table = 'pagers';
public function user()
{
return $this->belongsTo('User', 'bid');
}
public function pager_items()
{
return $this->hasMany('pager_item', 'pid');
}
}
As you can see the pager has many pager items, below is the pager item model which belongs to pager.
class pager_item extends Eloquent
{
protected $table = 'pager_items';
public function pager()
{
return $this->belongsTo('pager', 'pid');
}
}
If I try to insert new model like so:
$test = new pager_item;
$test->description = 'test';
$test->bid =1;
$test->cid =1;
$test->pid =1;
$test->save();
I receive:
LogicException
Relationship method must return an object of type Illuminate\Database\Eloquent\Relations\Relation
I haven't been able to spot any issues that will cause such error, any help is appreciated, thank you.
in a "belongs to" relation you should try to pass the object to save instead of the id.
$pager = pager::find(10);
$test->pager()->associate($pager);
btw, try to name the classes Uppercase... like
class Pager extends Eloquent
...

Categories