Laravel models to return null relation? - php

I am writing a website for photo posts and I have these functions relating likes (they determine if the user is liking the specific post or not)
Post Model:
public function likes()
{
return $this->hasMany('Like');
}
public function isLiked()
{
return $this->likes()->where('user_id', Auth::user()->id);
}
Post Controller function for example:
public function postsByType($type)
{
if($this->user){
$posts = Post::with('isLiked')->where('type', '=', $type)->paginate(12);
} else {
$posts = Post::where('type', '=', $type)->paginate(12);
}
return $posts;
}
Is there any way to return null in MODEL function when user is not logged in, without running a query?
I want to avoid writing that if in post controller
I thought about the following solution but it's not working...
public function isFollowing()
{
return $this->setRelation('isFollowing', null);
}
getting this error:
Call to undefined method Illuminate\Database\Query \Builder::addEagerConstraints()

Since you probably always want to fetch the relation (except if there's no user logged in) I suggest you do something like this in your model:
(I also renamed the relationship to liked, you'll see later why)
public function newQuery(){
$query = parent::newQuery();
if(Auth::check()){
$query->with('liked');
}
return $query;
}
Now every time a query is run with the model with('isLiked') will be added if the user is logged in.
One problem remains though. If you access isLiked the query will be run anyways. And even for every post because it's not eager loaded. You can fix that by adding an attribute accessor:
public function getIsLikedAttribute(){
if(Auth::guest) return false;
return ! $this->liked->isEmpty();
}
So in your view you can just do this:
#if($post->isLiked)
Note: It would be nicer to move the things inside newQuery() to a global scope. Make sure to check out how to do that in the documentation if you're interested.
Here's an example with a scope. Create a class, let's call it LikedScope:
class LikedScope implements Illuminate\Database\Eloquent\ScopeInterface {
public function apply(Builder $builder, Model $model){
if(Auth::check()){
$builder->with('liked');
}
}
public function remove(Builder $builder, Model $model){
}
}
And then add it to your model:
public static function boot(){
parent::boot();
static::addGlobalScope(new LikedScope);
}

Related

Laravel get user data with profile

I have user and profile models with followers/following. What I want is to get the User data with the Profile data merged when calling Profile::followers. Now I only retrieve the profile data.
So I added the user() to Profile.php so I can call Profile::followers->user()... but no result. Can someone explain how to merge the User with Profile when calling the following() function?
User.php model
public function following()
{
return $this->belongsToMany(Profile::class);
}
public function profile()
{
return $this->hasOne(Profile::class);
}
Profile.php model
public function followers()
{
return $this->belongsToMany(User::class);
}
public function user()
{
return $this->belongsTo(User::class);
}
I don't know if I came late but the correct way to get a user profile would be
$profile = Auth::user()->profile;
I don't know if this would help, using Auth::user() you get to fetch the current user and since you have a relationship with the profile you can call the profile() public function without the braces.
I hope this helps anyone.

how to fix relationship error between models?

I have set up two models and made the relationship between them. I want to pass the attributes of the user as well as the user_detail.
I have used a similar code somewhere and it worked perfectly. But it is not working here.
//This is the function in "User.php" model.
public function user_detail(){
return $this->hasOne('App\Profile');
}
//This is the function in "Profile.php" model.
public function user(){
return $this->belongsTo('App\User');
}
//edit function in ProfileController
public function edit($id)
{
$user=User::find($id);
return view('profile.edit')->with('data',$user->user_detail);
}
When I click the edit button in the view, I expect the extract all the details from user table as well as from user_detail table.
I think you should edit your this code a little bit
public function edit($id)
{
$user=User::findOrFail($id);
return view('profile.edit')->with('data',$user);
}
And in your blade file (profile.edit), You can get all details from User and Profile Model.
{{ $data->id }}
{{ $data->user_detail->YOURPARAMETERS }}
The problem is with the relationship naming. Make it camelCase like,
//This is the function in "User.php" model.
public function userDetail(){
return $this->hasOne('App\Profile');
}
//edit function in ProfileController
public function edit($id)
{
$user=User::find($id);
return view('profile.edit')->with('data',$user->userDetail);
}
Reference: https://github.com/laravel/framework/issues/4307#issuecomment-42037712
try using where instead of find and then use with:
$user = User::where('id', $id)->with('user_detail')->first();
return view('profile.edit')->with('data', $user);
In your model:
public function user_detail(){
return $this->hasOne('App\Profile', 'student_no');
}

Laravel 5.7 not returning new record when global scope applied

I'm sure I'm missing something simple here but I am completely at a loss so any input would be greatly appreciated.
I have two models, User and Account with a many to many relationship with the model Channel. Accounts can be associated with multiple channels and users can also be associated with multiple channels. This has been created so that users can only access accounts that are associated with channels they are also associated with.
In order to do the filtering, I have applied a global scope to the account model so when I perform a query, it only returns accounts associated with the channels that the user is associated with. This works as intended for everything except newly created accounts.
If I call $account = Account::find($id) on a newly created account it returns null. If I drop the global scope it returns the account.
The only way I have found to fix the problem is if I sync the pivot table for the channel_user table and only include a single channel that is also associated with the account.
It feels like something is being cached somewhere but I'm not sure where to go from here. Please let me know what else you need to know
Account Model:
protected static function boot()
{
parent::boot();
static::addGlobalScope(new ChannelScope);
}
public function channels()
{
return $this->belongsToMany('App\Channel');
}
public function user()
{
return $this->belongsTo('App\User');
}
User Model:
public function accounts() {
return $this->hasMany('App\Account');
}
public function channels(){
return $this->belongsToMany( 'App\Channel' );
}
Channel Model:
public function accounts()
{
return $this->belongsToMany('App\Account');
}
public function users(){
return $this->belongsToMany('App\User');
}
Channel Scope:
public function apply(Builder $builder, Model $model)
{
$channels_ob = Auth::user()->channels;
$channels = array();
foreach ($channels_ob as $channel){
array_push($channels,$channel->id);
}
$builder->whereHas('channels', function ($q) use ($channels){
$q->where('channels.id','=', $channels);});
}
AccountController.php Store:
$account->save();
if (isset($request->chk_channels)){
foreach($request->chk_channels as $channel){
$ch = Channel::where('name',$channel)->get();
$ch_array[] = $ch[0]->id;
}
}
$account->channels()->sync($ch_array);
UserController.php update_channels:
public function update_channels(Request $request, $id)
{
$user = User::find($id);
if ($user->hasPermission('view_all_accounts')){
if (isset($request->chk_channels)){
foreach($request->chk_channels as $channel){
$ch = Channel::where('name',$channel)->get();
$ch_array[] = $ch[0]->id;
}
$user->channels()->sync($ch_array);
}
}
You can't have a column value equivalent to an array. You're building up an array of channels in your scope and then checking equivalency:
$q->where('channels.id','=', $channels);
Perhaps, you want whereIn:
$q->whereIn('channels.id', $channels);

Using a polymorphic relation in a child model causes an infinite loop?

This question was already asked here but it received no answer. Now I face the same problem but in laravel 5.4. I have a model Book, a model ReadingSession and a model Comment. A book has many reading sessions and has many comments but the reading session can also have comments. So I have my relations defined like this:
Book.php
protected $with = [
'author',
'readingSessions',
'userRating',
'ratings',
'comments'
];
public function users()
{
return $this->belongsToMany(User::class, 'user_book');
}
public function author()
{
return $this->belongsTo(Author::class);
}
public function allReadingSessions()
{
return $this->hasMany(ReadingSession::class);
}
public function readingSessions()
{
return $this->hasMany(ReadingSession::class)
->where('user_id', Auth::user()->id);
}
public function ratings()
{
return $this->hasMany(Rating::class);
}
public function userRating()
{
return $this->hasMany(Rating::class)
->where('user_id', Auth::user()->id);
}
public function comments()
{
return $this->morphMany('App\Models\Comment', 'commentable');
}
ReadingSession.php
protected $with = ['comments'];
public function user()
{
return $this->belongsTo(User::class);
}
public function book()
{
return $this->belongsTo(Book::class);
}
public function comments()
{
return $this->morphMany('App\Models\Comment', 'commentable');
}
Comment.php
public function commentable()
{
return $this->morphTo();
}
These seems to create an infinite loop. Can anyone hint me on what I'm doing wrong?
The main reason you might have an infinite loop there is if you are trying to load automatically a relationship that in turn tries to do the same with the previous model.
Putting it into an example:
Book.php
protected $with = [
'author',
];
public function author()
{
return $this->belongsTo(Author::class);
}
Author.php
protected $with = [
'books',
];
public function books()
{
return $this->hasMany(Book::class);
}
In this case, every time you fetch an author it will fetch automatically his books that in turn will try to fetch the author and on and on...
One other thing that might happen and it's harder to realize is when using the $appends property on some accessors. If you are trying automatically had a variable into a model through the $appends and if that accessor fetches a relation or uses a relation in some way you might get an infinite loop again.
Example:
Author.php
protected $appends = [
'AllBooks',
];
public function books()
{
return $this->hasMany(Book::class);
}
public function getAllBooksAttribute() {
return $this->books->something...
}
In this case, every time the app tries to resolve your Author model it will fetch the books, that in turn will fetch the Author, that in turn will fetch the books again and on and on...
From your snippets, is not clear what is causing the problem but this answer might give some leads where to search for it.
To solve it, you might remove the relation from the $with and load it manually: $author->load('books') or Author::with('books')->where...
You can also load a relation of a relation in this way, for example: $author->load('books', 'books.comments') or Author::with('books', 'books.comments')->where...
It all comes down what you are trying to achieve. So you have to evaluate what and what not you should auto-load.
Be careful when loading automatically relations on your models and when adding accessors to $appends, especially if they use relations. It is an awesome feature but can bite hard sometimes.

Distant HasManyThrough

I have four Models:
User
Client
Store
Opportunity
The relationships are defined as such:
User hasMany Client
Client hasMany Store
Store hasMany Opportunity
User hasManyThrough Store, Client (this works)
The problem is that I'm attempting to access the User->Opportunity relationship via built-in Laravel relationships, but it doesn't seem as if I can do it without a custom Query or an additional user_id column on the opportunities table to allow direct access (even though one can be inferred from the Store->Client relationship). I'm also not a fan of nested foreach loops if they can be avoided.
My question:
Is there a way to go one level deeper and directly access a User's Opportunities in this scenario? The actual Model code and all relevant relationships are as follows:
User
class User extends Eloquent{
public function clients(){
return $this->hasMany('Client');
}
public function stores(){
return $this->hasManyThrough('Store', 'Client');
}
public function proposals(){
return $this->hasMany('Proposal');
}
public function opportunities(){ //This does the job, but I feel like it could be better
return Opportunity::join('stores', 'stores.id', '=', 'opportunities.store_id')->
join('clients', 'clients.id', '=', 'stores.client_id')->
join('users', 'users.id', '=', 'clients.user_id')->
select('opportunities.*')->
where('users.id', $this->id);
}
public function getOpportunitiesAttribute(){ //This just helps mimic the hasManyThrough shorthand
return $this->opportunities()->get();
}
}
Client
class Client extends Eloquent{
public function stores(){
return $this->hasMany('Store');
}
public function user(){
return $this->belongsTo('User');
}
public function opportunities(){
return $this->hasManyThrough('Opportunity', 'Store');
}
}
Store
class Store extends Eloquent {
public function client(){
return $this->belongsTo('Client');
}
public function opportunities(){
return $this->hasMany('Opportunity');
}
}
Opportunity
class Opportunity extends Eloquent {
public function store(){
return $this->belongsTo('Store');
}
}
I don't think there is such method in Laravel. You have to create your custom query. This custom query can be very expensive since multiple queries will be performed. Thus, the optimum solution for this, according to me, is to relate User and Opportunity with a foreign key.
However, if you don't desire to link User and Opportunity with a foreign key, then you can create a custom query to handle this. Simply add a "hasManyThrough" relation between Opportunity and Client model like,
<?php
class Client extends Eloquent{
public function store(){
return $this->hasMany('Store');
}
public function user(){
return $this->belongsTo('User');
}
public function opportunity(){
return $this->hasManyThrough('Opportunity', 'Store');
}
}
Then create a static function in User model.
<?php
class User extends Eloquent implements UserInterface, RemindableInterface {
use UserTrait, RemindableTrait;
public function client(){
return $this->hasMany('Client');
}
public function store(){
return $this->hasManyThrough('Store', 'Client');
}
public static function getOpportunityOfUser($userId)
{
$clients = User::find($userId)->client;
foreach ($clients as $client) {
$opportunities[] = Client::find($client->id)->opportunity;
}
return $opportunities;
}
}
Now you can access Opportunity realted to a User in one go like,
Route::get('/', function()
{
return $usersOpportunities = User::getOpportunityOfUser(1);
});
This will return all opportunity of all clients related to User with id '1'.
I created a HasManyThrough relationship with unlimited levels: Repository on GitHub
After the installation, you can use it like this:
class User extends Model {
use \Staudenmeir\EloquentHasManyDeep\HasRelationships;
public function opportunities() {
return $this->hasManyDeep(Opportunity::class, [Client::class, Store::class]);
}
}

Categories