Laravel filtering hasMany results - php

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

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

how to make consultation with three tables in Eloquent Laravel

I have three tables that are the following users, mapas, marcadores
A user has several mapas
a mapa has several marcadores
What I'm trying to do is show the marcadores that belong to the mapas of the user who has logged in.
this are the tables and relationship
This is the function in the controller that I am working on:
public function index()
{
$mapas = Mapa::orderBy('id', 'DESC')->where('user_id', auth()->user()->id);
$marcadores = Marcador::orderBy('id', 'DESC')->where('mapa_id');
return view('user.marcadores.index', compact('marcadores'));
}
thanks for your help
You are trying to get all the mapas id first and then filter marcadores according to those id's. Try using the code below for that:
public function index()
{
$mapas = Mapa::orderBy('id', 'DESC')->where('user_id', auth()->user()->id)->pluck('id')->toArray();
$marcadores = Marcador::orderBy('id', 'DESC')->whereIn('mapa_id', $mapas)->get();
return view('user.marcadores.index', compact('marcadores'));
}
You can use JOINS for this. Try the next code (maybe the syntax is not the correct one, but take the idea):
public function index()
{
$marks = DB::table('mapas')
->join('marcadores', 'marcadores.mapa_id', '=', 'mapas.id')
->where('mapas.user_id', auth()->user()->id)
->select('marcadores.*')
->orderBy('marcadores.id', 'DESC')
->get();
return view('user.marcadores.index', compact('marks'));
}
The easiest and Laravel standard way to do this is create a hasManyThrough relationship from User to Marcadores
UserModel
class User extends Model
{
public function marcadores()
{
return $this->hasManyThrough(Marcadores::class, Mapas::class);
}
}
Controller
public function index()
{
$marcadores = auth()->user()->marcadores;
return view('user.marcadores.index', ['marcadores' => $marcadores]);
}

How to make a query when have array

I have three tables: client, project and client_project.
I want to show in a view all the projects of one client.
Here is the explained code:
<?php
public function editClient($id)
{
$client = Client::find($id);
// I get an array with client_id and project_id of the clients
// which is the same I pass in the URL.
$client_project = DB::table('client_project')->where('client_id',$id)->get();
// return view('cms.public.views.clients.editclient')->withClient($client);
}
Now I want to show the name of projects when the field id of table projects have the same value of project_id in $client_project array.
Here is the example of how to do it if it's only one value, maybe can help.
public function editClient($id)
{
$client = Client::find($id);
$client_project = DB::table('client_project')->where('client_id',$id)->first()->project_id;
$project = DB::table('projects')->where('id',$client_project)->first();
return $project;
}
Use One to Many relationship.
In Client.php Model
public function projects()
{
return $this->belongsTo('App\Project', 'client_id');
}
And in your Project.php Model
public function client()
{
return $this->hasMany('App\Client', 'client_id');
}
Now call
$client_project = Client::find($id)->projects;
You will get details here https://laravel.com/docs/5.5/eloquent-relationships#one-to-many
Instead of taking the first() client project, take them all and use the whereIn() method in project selection.
$projects = DB::table('projects')->whereIn('id', function ($query) {
$query->select('project_id')->from('client_project')->where('client_id', $id);
})->get();
Here's the laravel documentation link: Laravel 5.5 Documentation - Queries#where-clauses
I'm answering on what I've understood about your request which is not very clear.

Laravel 5.2 - Selecting only articles that has comments

I built a commenting system, and I'm working on a page that shows all the comments that are waiting for approval.
The relationship:
Article.php
public function comments()
{
return $this->hasMany('App\ArticleComment');
}
ArticleComment.php
public function article()
{
return $this->belongsTo('App\Article');
}
Now, I want to select only the articles that have comments that are waiting for approval (status column on article_comments table equals 0).
Any easy way of doing it? (Of course I can get all articles and check on each one if it has comments)
The other answer will work but you asked for an easy (also re-usable) approach to use so I would suggest to create a scope method in your ArticleComment model using something like the following:
In your Article model:
use App\ArticleComment;
use Illuminate\Database\Eloquent\Model;
class Article extends Model {
// Relation for comments
public function comments()
{
return $this->hasMany(ArticleComment::class);
}
// Relation for pending comments
public function pendingComments()
{
return $this->comments()->pending();
}
}
In your ArticleComment model:
// Query scope for pending comments
public function scopePending($query)
{
$query->whereStatus(0);
}
So, you can use something like this:
$posts = Post::has('pendingComments')->get();
Also, you may chain with like:
$posts = Post::has('pendingComments')->with('pendingComments')->get();
$articles = Article::whereHas('comments', function($query) {
$query->where('status', 0);
})->get();

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

Categories