Laravel 4 eager loading - php

I have a User model, a Recipe model, and a Cookbook model.
A User can 'own' many recipes. (User -OneToMany- Recipe)
A user has 'only one' Cookbook. (User -OneToOne- Cookbook)
Recipes that a user owns belong to his Cookbook.
One Recipe can belong to many Cookbook. (Recipe sharing) (Cookbook -ManyToMany- Recipe)
Many Users may LIKE Many Recipe. (User -ManyToMany- Recipe)
Many Users can follow Many Cookbook. (User -ManyToMany- Cookbook)
One User can have many friends who are Users (User -OneToMany- User)
What I want to do is load recipes for the currently logged in user.
I want to load the recipes owned by his friends and the recipes of all the cookbooks the logged in user is following.
class User extends Eloquent {
public function friends() {
return $this->belongsToMany('User', 'user_friends', 'user_id', 'friend_id');
}
public function cookbook() {
return $this->hasOne('Cookbook', 'owner_id', 'id');
}
public function recipes() {
return $this->hasMany('Recipe', 'owner_id', 'id');
}
public function followedCookbooks() {
return $this->belongsToMany('Cookbook','cb_followers', 'follower_id', 'cb_id');
}
}
class Recipe extends Eloquent() {
public function owner() {
return $this->belongsTo('User', 'owner_id', 'id');
}
public function cookbooks() {
return $this->belongsToMany('Cookbook', 'cb_recipes', 'recipe_id', 'cb_id');
}
}
class Cookbook extends Eloquent() {
public function owner() {
return $this->belongsTo('User', 'owner_id', 'id');
}
public function recipes() {
return $this->belongsToMany('Recipe', 'cookbooks_recipes', 'cookbook_id', 'recipe_id');
}
public function followers() {
return $this->belongsToMany('User', 'cb_followers', 'cb_id', 'follower_id');
}
}
What I did is this:
$user = Auth::user();
$friends = $user->friends();
$cookbooks = $user->followedCookbooks();
$recipes = array();
foreach($friends as $friend) {
$recipe = $friend->recipes();
array_push($recipes, $recipe);
}
foreach($cookbooks as $cookbook) {
$cbRecipes = $cookbook->recipes()
foreach($cbRecipes as $cbRecipe) {
array_push($recipes, $cbRecipe);
}
}
But this method would run a lot of SQL queries. How can I use eager loading to reduce the number of queries?

How about
$user = Auth::user();
$recipesFriends = $user->friends()->with('friends.recipes')->get();
$recipesCookbooks = $user->with('followedCookbooks')->with('followedCookbooks.recipes')->get();
If you want to go a bit further you can take a look at this answer: How to list out all items in a nested table in Laravel

You may try this:
$userData = Auth::user()->load('cookbook.recipes')
->load('friends.cookbook.recipes');
If you want to load friends recipes in a different variable then you may try this:
$userData = Auth::user()->load('cookbook.recipes')
->load('friends.cookbook.recipes');
$friendsData = $userData->friends;

Related

Cannot access Intermediate table in HasManyThrough relationship

My DB schema looks like this.
Now, in artisan tinker mode, When I try to query Details table from user Model, it shows me the records of the details table but I cannot access the the Cases Model for some reason, it always returns NULL in tinker.
This is my User Model
public function details()
{
return $this->hasManyThrough('App\Models\Detail', 'App\Models\Cases', 'user_id', 'case_id', 'id', 'id');
}
What am I doing wrong?
If for convenience you want to access Details directly from the User model then you can define relations as - (may seem like a little duplication but worth if it results in ease)
class User extends Model
{
public function cases()
{
return $this->hasMany(Case::class);
}
public function details()
{
return $this->hasManyThrough(Detail::class, Case::class);
}
}
class Case extends Model
{
public function details()
{
return $this->hasMany(Detail::class);
}
public function user()
{
return $this->belongsTo(User::class);
}
}
class Detail extends Model
{
public function case()
{
return $this->belongsTo(Case::class);
}
}
Now both cases and details can be directly accessed via User record
$user->cases;
$user->details;
The idea of hasManyThrough is to skip the intermediate table. If you need to look at the cases and the details maybe you should define other relations for it.
// User model
public function cases()
{
return $this->hasMany(Cases::class, 'user_id');
}
// Cases model
public function details()
{
return $this->hasMany(Detail::class, 'user_id');
}
$users = User::with('cases.details')->get();
foreach ($users as $user) {
// an user
foreach ($user->cases as case) {
// a case
foreach ($case->details as $detail) {
// the details of a case
}
}
}

Polymorphic, get user liked activities

I have a polymorphic one-to-many realtionship defined between Activity and Like.
A User can like the activity but
how can I get all activities liked by a user?
class Activity extends Model
{
public function likes(){
return $this->morphMany('App\Like', 'likeable');
}
public function like($user = 0){
$this->likes()->updateOrCreate([
'user_id' => $user ? $user->id : Auth::id(),
], [
'liked' => true
]);
}
}
class Like extends Model
{
public function likeable(){
return $this->morphTo();
}
}
class User extends Authenticatable
{
public function likes(){
// Get all liked activities by a user.
}
}
Updated
In User model:
public function likes(){
return $this->hasMany('App\Like');
}
And retrieving likes would be; $user->likes->where('liked', true);
My first answer was wrong.
Or you can use package for likes:
https://github.com/overtrue/laravel-like
I came up with:
public function likedActivities(){
$collection = collect();
foreach ($this->likes as $like) {
$collection = $collection->add($like->likeable);
}
return $collection;
}
This returns all Activities liked by a user.
Is there maybe a more elegant way to do so?

How to join three tables with Laravel and keep data integrity

I am using Laravel 5.7 and now I am trying setup a relationship between three tables named:
Tickets (PK - TicketID, FK - CampusID)
Campus (PK - CampusID, FK - TechID)
User (PK - TechID)
I don't think I set up my models correctly as I am showing a ticket where the CampusID doesn't belong to the TechID. I am looking for a best practice on setting up Eloquent to keep the data integrity in place so I can prevent any abnormalities. As mentioned above the foreign key for Tickets should reference the Campus primary key, and Campus foreign key should reference the User primary key.
Here are my Models:
Ticket
protected $table='tickets';
public function user() {
return $this->belongsTo(User::class);
}
Campus
protected $table='campus';
public function user() {
return $this->belongsTo(User::class);
}
User
public function campus()
{
return $this->hasMany(Campus::class, 'TechID');
}
public function ticket()
{
return $this->hasMany(Ticket::class, 'AssignedTo');
}
Here is my controller:
namespace App\Http\Controllers;
use Illuminate\Http\Request;
use Auth;
use App\Campus;
use App\Ticket;
class PagesController extends Controller
{
/**
* Create a new controller instance.
*
* #return void
*/
public function __construct()
{
$this->middleware('auth');
}
// Dashboard Page
public function index()
{
$user = Auth::user();
$campuses = Campus::where('TechID',$user->id)->pluck('CampusName');
$tickets = Ticket::all()->where('AssignedTo', $user->id);
return view('home')->with(['user' => $user,'campuses'=>$campuses,'tickets'=>$tickets]);
}
// Queue Page
public function Queue() {
return view('Pages.Queue');
}
// Reports Page
public function Reports() {
return view('Pages.Reports');
}
// Search Page
public function Search() {
return view('Pages.Search');
}
}
I think my models are fine, but my controller is probably where I made some mistakes. I've tried reading questions on here already, watching videos, and reading the Laravel docs, but nothing has really clicked with me yet. I really appreciate any and all help. Ideally it should cascade changes. So if I have a situation where I want to change what location a tech belongs to I could just make the change in the Campus table probably in the TechID column.
I would use Eager Loading.
public function index()
{
$user = User::with([
'campuses' => function($query) {
$query->select(['id', 'CampusName']);
},
'tickets'
])->where('id', Auth::id())->first();
$campuses = $user->campuses->pluck('CampusName');
$tickets = Ticket::all()->where('AssignedTo', $user->id);
return view('home')->with([
'user' => $user,
'campuses'=>$user->campuses->pluck('CampusName'),
'tickets'=>$user->tickets]);
}
EDIT
You need to update your User model.
public function campuses()
{
return $this->hasMany(Campus::class, 'TechID');
}
public function tickets()
{
return $this->hasMany(Ticket::class, 'AssignedTo');
}

Laravel - Relationships issue

I have 3 models:
class Site extends Model
{
public function users()
{
return $this->belongsToMany('App\Models\User');
}
public function stats()
{
return $this->hasMany('App\Models\Stat');
}
}
class User extends Model
{
public function sites()
{
return $this->belongsToMany('App\Models\Site');
}
public function stats()
{
return $this->belongsToMany('App\Models\Stat');
}
}
class Stat extends Model
{
public function users()
{
return $this->belongsToMany('App\Models\User');
}
public function sites()
{
return $this->belongsTo('App\Models\Site');
}
}
So there are :
a many to many relation between sites and users
a many to many relation between users and stats
a one to many relation between site and stats
A site have a list of stats and from this list, an user can have some stats.
I'm trying to get all sites and foreach site, count of stats for the connected user.
For the moment i tried :
//repository
function getAll($user_id = 0)
{
$with = [];
$with['users'] = function ($query) use ($user_id) {
$query->where('id', '=', $user_id);
};
return Site::with($with)->orderBy('name')->get();
}
//controller
$sites = getAll($user_id);
//view
foreach($sites as $site){
$count_stats = $site->users->first()->stats->where('site_id',$site->id)->count();
}
It works but it is not very elegant, it does a lot of sql requests and the page is slower.
Do you have a better solution ?
If Sites have many Users, and Sites have many Stats, then a User has many Stats through Site
Modify User class:
public function stats() {
return $this->hasManyThrough('App\Stat', 'App\Site', 'user_id', 'site_id');
}
Eager load:
$user = User::with('stats')->find($user_id);
$stats = $user->stats();
Also, I think your Stat should belongsTo Site, since Site hasMany Stat. You need to change a lot of the belongsToMany as well since they look incorrectly used.

Eloquent many-to-many-to-many - how to load distant relation easily

I have 3 tables; users, groups and permissions
In models I have the relationships set as belongsToMany
in user model:
public function groups() {
return $this->belongsToMany('Group');
}
in group model:
public function users() {
return $this->belongsToMany('User');
}
public function permissions() {
return $this->belongsToMany('Permission');
}
in permissions model:
public function groups() {
return $this->belongsToMany('Group', 'id');
}
many users - to - many groups
many groups - to - many permissions
I'm trying to get all the permissions a user has, and have no clue what the code for it should look like. Can anyone help?
This is how you can do it:
User::where('id', $id)->with(['groups.permissions' => function ($q) use (&$permissions) {
$permissions = $q->get()->unique();
}])->first();
// then
$permissions; // collection of unique permissions of the user with id = $id
It should look something like this if you are eager loading...
$user = User::where('id', $id)->with(['groups.permissions'])->first();

Categories