Laravel and mySQL with relational tables - php

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
]);

Related

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);

Laravel Eloquent Relationship Chain

I'm building a simple notification system.
I'm able to get the logged in users notifications via Auth::user()->notifications but my end goal is to also grab the users info the notification is from.
I'm hoping for an end result of
foreach( Auth::user()->notifications AS $n)
{
echo $n->from->username;
}
currently this throws a "Trying to get property of non-object" error.
Notification table;
id
user_id - notification for this user
from_user_id - notification from this user
User.php;
public function notifications()
{
return $this->hasMany('App\Notification', 'user_id', 'id')->orderBy('notifications.id','desc');
}
public function notificationsUnread()
{
return $this->hasMany('App\Notification', 'user_id', 'id')->where('notifications.seen',null)->orderBy('notifications.id','desc');
}
Notification.php;
public function to()
{
return $this->belongsTo('App\User', 'user_id');
}
public function from()
{
return $this->belongsTo('App\User', 'from_user_id');
}
First you need to have a foreign key set in the table notifications; Then a user can have a notif. and many at the same time. A notif. belongs to a user and many notif. can belong to a user. So on the Notification model you set up the relationship belongsTo like so;
Foreign key:
$table->foreign('from')->refrences('id')->on('users')->onDelete('cascade');
then the relationship:
public function users()
{
return $this->belongsTo('App\User');
}
Then from the controller you can get users info like so;
$userName= Notification::users()->name;
In your case, you're pointing it wrong inreturn you will get only the relationship type instead of data object, since you are calling from like a non method. You should do something like this:
foreach( Auth::user()->notifications AS $n)
{
echo $n->from()->username;
}

Relationships returning wrong/null data (Laravel 5.2)

Got a domain table which has a One To Many relationship with domain_hosts_table, server_hosts_table and systems_table. So far so good.
Calling the table data:
$domains = Domain::with('domain_host', 'server_host', 'system')->get();
Domain model :
public function domain_host()
{
return $this->hasOne('App\DomainHost', 'id');
}
public function server_host()
{
return $this->hasOne('App\ServerHost', 'id');
}
public function system()
{
return $this->hasOne('App\System', 'id');
}
DomainHost, ServerHost, System model :
public function domains()
{
return $this->hasMany('App\Domain');
}
Domains table :
So far so good.
Let's take a look at what this particular table returns while being foreached.
The first 2 rows should be the same (basing on their IDs), and all rows after the first 2 are just empty.
(dd of the fetched data, notice the relations being empty at 4th object, 1st object actually has data).
Had to define another parameter when defining my relationships:
public function domain_host()
{
return $this->hasOne('App\DomainHost', 'id', 'domain_host_id');
}
public function server_host()
{
return $this->hasOne('App\ServerHost', 'id', 'server_host_id');
}
public function system()
{
return $this->hasOne('App\System', 'id', 'system_id');
}
It was looking for the ID of the current row in the other table.

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