Slim 3 with Eloquent ORM - Relationships not exist on collection - php

I have a problem with Eloquent relationships in my Slim 3 based application. The goal is return $data to view. When I try that:
use App\Models\Favorite as F;
$favs = F::where('user_id',$_SESSION['user_id'])->get()->offer;
foreach($favs as $offer)
{
// not important now...
}
return $data;
I am getting an error:
Property [offer] does not exist on this collection instance.
I made relations in my Offer and Favorite models:
public function offer() // in Favorite model
{
return $this->belongsTo('App\Models\Offer', 'offer_url');
}
public function favorite() // in Offer model
{
return $this->hasMany('App\Models\Favorite', 'url');
}
In database we can see tables: offers (with url column) and favorites (with offer_url column).
How to make that relationships working?
Please for help.

Get method return a Collection instance, not Favorite.
Use:
$favs = F::where('user_id', $_SESSION['user_id'])->get();
then:
foreach($favs as $favorite) {
$offer = $favorite->offer;
}
And don't forget to use eager loader: https://laravel.com/docs/5.4/eloquent-relationships#lazy-eager-loading

Related

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.

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!

eager load of relations of a specific model in laravel

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.

Eloquent Eager Load Constraints returning JSON String instead Object

I'm trying to use Laravel 4.1 and do a Eloquent Eager Load with a One-to-Many relationship. Everything seems to work fine with this:
$groups = Group::with(array('points' => function($query) {
$query->orderBy('pts', 'DESC');
}))->get();
But when I try to read the property...
foreach( $groups as $group ) {
echo var_dump($group->points);
}
It returns a JSON String of the object (with correct data in it)
What should I do to get the PHP Object instead JSON?
Edit:
Here are my models
class Group extends Eloquent
{
public $timestamps = true;
public function points() {
return $this->hasMany('Point');
}
}
.
class Point extends Eloquent
{
public function group() {
return $this->belongsTo('Group');
}
}
It seems like the code is right, and Laravel detects how are you displaying the variable an show the more pertinent format for the use case.
var_export() will show the real Object anatomy. If you need to debug it.

"Three way" many-to-many relationship using Eloquent

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

Categories