Why belongsTo() return null? - php

I have two models and two tables. First model name Inbox with database structure:
And second model name is StudentData with database structure:
And I will return with route('/sended') all mesages using my method for get needed messages:
public function getMessages($message_type = "new")
{
$user_id = Auth::user()->id;
$inbox = new Inbox();
$paginate = 3;
switch ($message_type) {
case 'sended':
$messages = $inbox->where('sender', $user_id)
->where('trashed_in_sender', 0)
->where('show_in_sender', 0)
->orderBy('created_at', 'desc')
->paginate($paginate);
break;
default:
return abort(404);
break;
}
return $messages;
}
And I have methods in my model Inbox:
public function messageSender()
{
return $this->belongsTo("App\StudentData", 'sender');
}
public function messageRecipient()
{
return $this->belongsTo("App\StudentData", 'recipient');
}
When I call in view $message->messageSender in result given NULL. Why I can't get data using sender id from inboxes table in student_datas table

So, I have a few questions....
1) How exactly does your User and StudentData models interact? It kinda seems strange to have 2 models with what seems to be a 1:1 relationship?
Why not just use a User model?
(Do you even have a User model or am I misinterpreting things?)
2) I think your direction is wrong... if you already have a User model, try to get the sent messages from there. I will give you an example.
Let's say you have a User model and a Inbox model, where you have a "sender" and "recipient", which both have an id of the User model.
So in the Inbox model we have:
public function messageSender()
{
return $this->belongsTo("App\User", 'sender');
}
public function messageRecipient()
{
return $this->belongsTo("App\User", 'recipient');
}
But why not go from the other direction? We can write the relationships in the User model like
public function sentMessages()
{
return $this->hasMany("App\Inbox", 'sender');
}
public function receivedMessages()
{
return $this->hasMany("App\Inbox", 'recipient');
}
Now you can get all sent messages (i.e. all messages where the user is the sender) just by using
$user->sentMessages
and operate on that. Or you could even set up a special helper relation (my name sucks, find a better one... just for example)
public function unreadSentMessages()
{
return $this->hasMany("App\Inbox", 'sender')
->where('trashed_in_sender', 0)
->where('show_in_sender', 0)
->orderBy('created_at', 'desc');
}
and can either use $user->sentMessages for all his messages or $user->unreadSentMessages for only the ones you need in your case.

Related

Laravel and mySQL with relational tables

I need to create an API with laravel and PHP.
I've created api routes to GET all users and GET all devices related to the user.
I've made the following tables in mySQL:
Devices:
increments('id');
string('name');
longText('description');
Table for relations between users and devices:
increments('id');
unsignedInteger('user_id');
unsignedInteger('device_id');
foreign('user_id')->references('id')->on('users');
foreign('device_id')->references('id')->on('devices');
Variables:
increments('id');
string('type');
unsignedInteger('device_id');
longText('description');
foreign('device_id')->references('id')->on('devices');
And the models have the relationscode:
User Model:
public function deviceVariables() {
return $this->hasMany('App\DeviceVariable');
}
public function devices()
{
return $this->belongsToMany('App\Device');
}
Device Model:
public function users()
{
return $this->belongsToMany('App\User');
}
public function variables()
{
return $this->hasMany('App\DeviceVariable');
}
And finally the DeviceVariable Model:
public function device()
{
return $this->belongsTo('App\Device');
}
public function user()
{
return $this->belongsTo('App\User');
}
I am able to show all the devices related to an authenticated user, but i am unable to show all the variables related to the devices that are related to that user.
This code (index method of DeviceVariablecontroller) is the closest i've come to getting the variables:
$counter = 1;
$arrayIndex = 0;
while($counter <= 10) {
if(auth()->user()->devices()->find($counter)) {
$variables[$arrayIndex] = auth()->user()->devices()->find($counter)->variables;
$arrayIndex++;
}
$counter++;
}
Is there a way to make an array of all the user's devices' IDs and the loop through them?- or is there a smarter way to get all the variables of all the user's devices?
EDIT:
Comment got me both the devices aswell as the each device variables.
$variables = auth()->user()->devices()->with('variables')->get();
return response()->json([
'success' => true,
'data' => $variables
]);
How can i get the variables ONLY without the device info?
Maybe something like this:
$variables = auth()->user()->devices()->with('variables')->get();
This will eager load relationships that devices had.
For accessing only users variables you can use has-many-throught relationship like mentioned in docs:
https://laravel.com/docs/5.7/eloquent-relationships#has-many-through
You can use a BelongsToMany relationship to get the variables directly:
public function variables()
{
return $this->belongsToMany('App\DeviceVariable', 'device_user',
null, 'device_id', null, 'device_id');
}
return response()->json([
'success' => true,
'data' => auth()->user()->variables
]);

Eloquent all records relationship

I'm trying to build an alternative relationship that returns all records instead of only related records. I have tried returning a query builder, but that doesn't work, it must be a relationship. What should I return to make this work?
public function devices()
{
if ($this->admin) {
// return all devices relationship instead
} else {
return $this->belongsToMany('Device', 'permissions');
}
}
Fiddle: https://implode.io/XXLGG8
Edit: I'd like to continue building the query in most cases, not just get the devices.
The devices() function in your model is expected to return a relation, you shouldn't add the if statement there. Make your devices() function like this:
public function devices()
{
return $this->belongsToMany('Device', 'permissions');
}
In your User model add a new function:
public function getDevices() {
if($this->admin === true) {
return Device::all();
}
return $this->devices();
}
Now you can do:
$admin->getDevices(); // will return all devices
$user->getDevices(); // will return only relations
I actually went a slightly different way and used a scope:
protected function scopeHasAccess($query, User $user)
{
if ($user->admin) {
return $query;
}
return $query->join('permissions', 'permissions.device_id', "devices.id")
->where('permissions.user_id', $user->user_id);
}
Add devices accessor method to the User model and implement your logic there.
public function getDevicesAttribute() {
if ($this->admin) {
return Device::all();
}
return $this->getRelationValue('devices');
}
See updated "fiddle".

Laravel eloquent query right data

I am fairly new to laravel and eloquent.
I have 2 tables threads and messages you can see the structure in the links below:
threads
messages
Now my goals is to query only the threads from table threads that have the same user_id in the messages table as the logged in user .
Is there a way to do this via eloquent or do I have to write a query for this?
I currently get all the threads like this:
$thread = Thread::findOrFail($id);
But this gives security issues since you can go to any thread if you change the id in my route.
EDIT
My current show function:
public function show($id)
{
$currentUserId = Auth::user()->id;
$threads = Thread::forUser($currentUserId)->latest('updated_at')->get();
try {
$thread = Thread::findOrFail($id);
} catch (ModelNotFoundException $e) {
Session::flash('error_message', 'Oops, not found.');
return redirect('messages');
}
if(array_has($threads, $thread)){
$users = User::whereNotIn('id', $thread->participantsUserIds($currentUserId))->get();
$thread->markAsRead($currentUserId);
return view('messenger.show', compact('thread', 'users'));
}else{
Session::flash('error_message', 'Oops, not found.');
return redirect('messages');
}
}
I need a way to check if $thread is inside $threads.
If you want to use eloquent you must first define a relationship.
One message belongs to a thread and a user. Here is how to define the relationships:
Inside the Message model:
public function user()
{
return $this->belongsTo('App/User'); //User model
}
public function thread()
{
return $this->belongsTo('App/Thread'); //Thread model
}
To define the inverse you do the following:
Inside User model:
public function threads()
{
return $this->hasMany('App/Thread');
}
Inside the Thread model:
public function messages()
{
return $this->hasMany('App/Message');
}
Now you can do the following in your controller:
$threads = Auth::user()->threads;
Now you have all threads by the currently logged in user.
I am not sure if I got the question right so ask away.
Edit:
You could check like so:
$thread = Thread::find($id);
$isCurrentUserThread = false;
foreach(Auth::user()->threads as $currentUserThread) {
if($currentUserThread->id == $thread->id) {
$isCurrentUserThread = true;
//$thread belongs to the current user
}
}
if($isCurrentUserThread) {
//the thread belongs to the current user
} else {
//it doesn't belong to the current user
}
There is a way to get the current user's id by calling:
$logged_in_user = Auth::user()->id
Just make sure to include this portion somewhere on top:
use Illuminate\Support\Facades\Auth;
Then, you could just get all Messages where the user_id equals to the logged in user...
$messages = App\Message::where('user_id', '=', $logged_in_user)->get();
// the get() method will get all messages, not just one
From there, you could extract the $messages variable and grab all the thread_ids, which then, you could use the find method on the Thread model, like the following:
$threads = App\Thread::find([1, 2, 3, 4, ...]);

How to get data from 3 models connected with Has-One and Have-Many relationship?

I have a module of friendship request in my project. Below 3 tables are being used in it:-
Users
User_profile
Friendship
Users :- Id,slug,Name,Email, Password
UserProfile :- Id, user_slug, Profile_pic, DOB..etc.
Friendship :- Id, User_slug, Friend_slug, Status
Relationships:-
User Model:-
public function Profile(){
return $this->hasOne('UserProfile','user_slug','slug')->first();
}
public function sentFriendshipRequests(){
return $this->hasMany('Friendship','user_slug','slug');
}
public function receivedFriendshipRequests(){
return $this->hasMany('Friendship','friend_slug','slug');
}
UserProfile Model:-
public function User(){
return $this->belongsTo('User','user_slug','slug');
}
Friendship Model:-
public function receiver(){
return $this->belongsTo('User','friend_slug','slug');
}
public function sender(){
return $this->belongsTo('User','user_slug','slug');
}
Goal:- I want to display list of pending friendship request received by an user.
Data Required:-
All friendship request with pending status for current logged user & Name,Slug,Profile_pic of friendship request sender.
My Approach:-
$friendship_requests= Auth::user()->receivedFriendshipRequests();
foreach($friendship_requests as $frnd_req)
{
$sender_user=User::where('slug',$frnd_req->user_slug());
}
Is there any other proper way to get this data by using Eloquent Relationship approach,without using join. I means how to get data using HasOne and HasMany relationship in one single query.
Any help or advice is greatly appreciated.
Thanks
This is a self referencing many-to-many relationship, so you don't need those hasMany/belongsTo relations at all.
You can simply use one belongsToMany for own requests and another one for received requests.
Read this first: https://stackoverflow.com/a/25057320/784588
Then add these relationships:
// pending requests of mine
function pendingFriendsOfMine()
{
return $this->belongsToMany('User', 'friendship', 'user_slug', 'friend_slug')
->wherePivot('accepted', '=', 0)
->withPivot('accepted');
}
// pending received requests
function pendingFriendOf()
{
return $this->belongsToMany('User', 'friendship', 'friend_slug', 'user_slug')
->wherePivot('accepted', '=', 0)
->withPivot('accepted');
}
// accessor allowing you call $user->friends
public function getPendingFriendsAttribute()
{
if ( ! array_key_exists('pendingFriends', $this->relations)) $this->loadPendingFriends();
return $this->getRelation('pendingFriends');
}
protected function loadPendingFriends()
{
if ( ! array_key_exists('pendingFriends', $this->relations))
{
$pending = $this->mergePendingFriends();
$this->setRelation('pendingFriends', $pending);
}
}
protected function mergePendingFriends()
{
return $this->pendingFriendsOfMine->merge($this->pendingFriendOf);
}
then yuou simply load it using nested relations:
$user = Auth::user();
$user->load('pendingFriendsOfMine.profile', 'pendingFriendOf.profile');
// the above will execute 4 queries - 2 for requests, 2 for related profiles
$pendingFriends = $user->pendingFriends; // for all pending requests
// or
// $user->load('pendingFriendOf.profile'); // 2 queries in this case
// $pendingRequests = $user()->pendingFriendOf; // for received requests only
foreach ($pendingFriends as $user) {
$user->profile; // eager loaded profie model
}
Also, here a few errors you have in your code:
// there can't be first() in the relation definition
// and it is not needed anyway
public function Profile(){
return $this->hasOne('UserProfile','user_slug','slug')->first();
}
// You never want to run this User::where() ...
// in a foreach loop, for it will result in n+1 queries issue
// You need eager loading instead.
foreach($friendship_requests as $frnd_req)
{
$sender_user=User::where('slug',$frnd_req->user_slug());
}

Laravel filtering hasMany results

I have three tables - Campaigns, Actions and Activists. A Campaign has many Actions, and an Action belongs to both an Activist and a Campaign.
Each action has a client_id (from the client_id of the campaign it belongs to), so when a client views a list of activists, they should only see those who have taken an action on one of their campaigns.
Likewise, when viewing an individual activist, they should only see those actions related to their campaigns.
Models
Campaign.php
public function actions()
{
return $this->hasMany('Action');
}
Action.php
public function campaign()
{
return $this->belongsTo('Campaign', 'campaign_id');
}
public function activist()
{
return $this->belongsTo('Activist', 'activist_id');
}
Activists.php
public function actions()
{
return $this->hasMany('Action');
}
Controllers
ActivistsController.php
public function index()
{
$activists = Activist::with('actions')->whereHas('actions', function($q) {
$user = Sentry::getUser();
$q->where('client_id', $user->client_id);
}))->get();
foreach ($activists as $activist)
{
$activist->total = $activist->actions()->count();
}
}
public function getActivist($id)
{
$activist = Activist::with('actions')->whereHas('actions', function($q) {
$user = Sentry::getUser();
$q->where('client_id', $user->client_id);
})->find($id);
$activist->total = $activist->actions()->count();
}
I'm seeing the following:
On the /activists page, I'm correctly seeing only those activists who have taken an action related to my client_id, but also every action they've taken. Likewise, count() returns a full count of all the activists' actions.
On the /activists/{id} page, it correctly returns null if the activist hasn't taken any actions related to my client_id, but where they have, I again see all of their actions and a full count.
AFL. There's something blinding obvious I'm missing, right?
Thanks.
[edit] Updated to add:
Using the client_id filter on both with and whereHas rectifies the 'all actions appearing regardless' issue, but the count issue remains (and I'm not sure this is remotely the right way to improve this):
ActivistController.php
public function index()
{
$filter = function($q) {
$user = Sentry::getUser();
$q->where('client_id', $user->client_id);
};
$activists = Activist::with(array('actions' => $filter))
->whereHas('actions', $filter)
->get();
}
public function getActivist($id)
{
$filter = function($q) {
$user = Sentry::getUser();
$q->where('client_id', $user->client_id);
};
$activist = Activist::with(array('actions' => $filter))
->whereHas('actions', $filter)
->find($id);
}
I've solved this now, but for reference:
$activist->actions()->count()
This, obviously in hindsight, ignores any of the prior queries and simply counts data returned from the actions() method as defined in the activist model.
I should have provided an alternate method in the model that includes the appropriate where function, like so:
public function actionsClient($id)
{
return $this->hasMany('Action')->where('client_id', $id);
}
Meaning the count could then be invoked with:
$activist->total = $activist->actionsClient($id)->count();
for a single campaign and
foreach ($activists as $activist)
{
$activist->total = $activist->actionsClient($activist->id)->count();
}
on the index. I'd previously tried this, but as described here - How to access model hasMany Relation with where condition? - relations must be described in camelCase (actions_client > actionsClient).
In my usage this worked for me:
$clients = Profile::select('id', 'name')->orderBy('initial')->whereHas('type', function ($query) {
$query->where('slug', 'client');
})->office()->pluck('name', 'id');
You already have the instance of $activist eager loading their actions, meaning you already know the actions of the activist beacause they are already in the instance, so why call actions() again instead of just doing this:
$activist->actions->count()

Categories