I am attempting to create a user hierarchy in Laravel 8, but have been bumping up against some issues.
Our use case is a very strange one based on the sales world. One user/sales rep may come in and begin working with us, then bring in their friend as a sub-user/sub-rep/“child” for them. The original “parent” user would want to see how their “child” is doing as far as sales go and such. Then that child user may bring in a few “children” users of their own over time. Sometimes, multiple parent users may come in as a group and do this, so the relationship seems like it should be many-to-many.
There are various one-off scenarios however where a child user may start their own sales company and while their personal sales may be viewed by the original parent user, anyone that they have recruited, they might only want to be viewed by the original child user and not the original parent user. This would be some sort of toggle able thing per user. It feels like a very involved use-case, so I’m not sure how to approach it fully.
Taking baby steps, my original idea was to create a many-to-many relationship with the users model having a user_user relationship table. Here is how it currently sits:
users table columns
id
Name
user_user table columns
parent_id
child_id
practice table columns
id
user_id (foreign id column to users table id)
name
user model
public function children() {
return $this->belongsToMany(User::class, 'user_user', 'parent_id', 'child_id');
}
public function parents() {
return $this->belongsToMany(User::class, 'user_user', 'child_id', 'parent_id');
}
public function practices() {
return $this->hasMany(Practice::class);
}
practice model
public function user() {
return $this->belongsTo(User::class);
}
Here is an example of a controller view attempting to find and combine sales locations/practices that a user at the top of a hierarchy would want to view for their entire structure.
// determine if viewing user has subreps
if (Auth::user()->children->count() == 0) {
$practices = User::find(Auth::user()->id)->practices()->orderBy('name', 'asc')->get();
} else {
// get the user's own practices
$own_practices = User::find(Auth::user()->id)->practices()->orderBy('name', 'asc')->get();
// create practices collection, then
// merge the user's own practices into collection
$practices = new Collection;
$practices = $practices->merge($own_practices);
foreach (Auth::user()->children as $child) {
// get each child's practices, then
// merge each practice into the practices collection
$child_practices = User::find($child->id)->practices()->orderBy('name', 'asc')->get();
$practices = $practices->merge($child_practices);
}
// sort the final practices collection alphabetically
$practices = $practices->sortBy('name');
}
And here is an example of a hierarchy.blade.php unordered list I was trying to work on to see just how much work would be needed to dig into it all.
<ul>
#foreach ($users as $user)
#if ($user->children->count() == 0)
<li>
<a href="{{ route('users.show', $user->id) }}">
{{ $user->name }}
</a>
</li>
#else
<li>
<a href="{{ route('users.show', $user->id) }}">
{{ $user->name }}
</a>
<ul>
#foreach ($user->children->sortBy('name') as $child)
<li>
<a href="{{ route('users.show', $child->id) }}">
{{ $child->name }}
</a>
</li>
#endforeach
</ul>
</li>
#endif
#endforeach
</ul>
As you can see, this works currently for a 1-deep level with some foreach looping, but I just can’t see a way to infinitely scale this in an efficient manner, much less have situations of being able to toggle cutoffs at different levels of viewership depending on user preference. I'd appreciate being pointed in a better direction for this hierarchal issue.
Related
I´m traying to create one counter for my blog´s categories. This should appear to the right name side of my category . i´m using my model with variable appends, that after i will use in my blade for show my result in one span. But i don´t know very well how i can use count in my Model. I´m doing this in my model Blog.
my variable appends contain:
protected $appends = [
'custom_fields',
'has_media',
'restaurant',
'blog_category',
'viewer',
'postCounter',
];
i´m traying this:
return $this->blogs()->count();
i have a relation between blog and blog_category with:
public function blogCategory()
{
return $this->belongsTo(\App\Models\BlogCategory::class, 'blog_category_id', 'id');
}
i want to do in my view appear for example:
innovation (2)
in my view i´m doing this:
#foreach($categories as $category)
<li>{{ trans('web.blog_category_'.$category->name) }}<span>{{$category->postCounter}}</span></li>
#endforeach
but always returned me 0, and i have post with categories
updated
With laravel relationship withCount you can do this easily. If you want to count the number of results from a relationship without actually loading them you may use the withCount method, which will place a {relation}_count column on your resulting models.
add withCount method to your query
$categories = Category::withCount('blogCategory')->get();
You can access the count in your foreach loop
// $category->blogCategory_count
#foreach($categories as $category)
<li>
<a href="{{ url('blogs/'.$category->name) }}">
{{trans('web.blog_category_'.$category->name) }}
</a>
<span>{{$category->blogCategory_count}}</span>
</li>
#endforeach
For example, I have five tables (School, Student, Class, Session, and Enrollment) the Enrollment Table store the Primary Key of other tables, Now I like to count in Session wise that how Many students have enrolled in Session 2019-2020 and display in the dashboard.
public function index()
{
$schools=School::all();
$students=Student::all();
$sessions=Session::all();
$enrollements=Enrollement::all();
return view('dashboard',compact('schools','students','enrollements','sessions'));
}
when I write {{$sessions->latest()}} it show the following error """Method Illuminate\Database\Eloquent\Collection::latest does not exist""
and how to pass session year (String) to enrollement to count?
could anyone suggest the best method to solve the following problem?
For collections , use last() method ($sessions->last()) https://laravel.com/docs/7.x/collections#method-last
I don't know how your table is structured, but you need to group by year and then compare
$enrollementsByYear = Enrollment::selectRaw('year, count(year) AS enrollmentsByYear')
->groupBy('year')
->get();
Then in $enrollementsByYear you will have a collection where you can compare the year of the session and mount your table. Change year with the actual column name.
You can easily compare with something like:
#foreach ($sessions as $session)
#foreach ($enrollementsByYear as $y)
#if ($session->year == $y->year)
<label>{{ $session->year }}</label>: <span> {{$y->enrollmentsByYear }}</span>
#endif
#endforeach
#endforeach
With laravel's eager loading, I can do something like
$forums = Forum::with('posts', 'threads')->get();
To get the threads as its posts and forum without doing many queries.
However, I have a method that gets the last post of the forum.
This is my method in Forum.php
public function lastThread()
{
$forums = $this->arrayOfIDs(); // get array of all ids, even subforum's subforums.
return Thread::whereIn('forum_id', $forums)->orderBy('updated_at', 'desc')->first();
}
This is my partial view to get the last thread:
#if ($subforum->lastThread())
<a href="{{ viewThread($subforum->lastThread()) }}">
{{ $subforum->lastThread()->title }}
</a>
<br>
<a href="{{ viewPost($subforum->lastThread()->lastPost) }}"> {{ trans('forum/post.last_post') }}
:</a>
{{ $subforum->lastThread()->lastPost->user->info() }}
<br>
{{ $subforum->lastThread()->lastPost->created_at }}
#endif
As you can see, if I am doing $subforum->lastThread() a lot. I can store it in a variable like $lastThread = $subforum->lastThread() and then do something like $lastThread->created_at to reduce the number of queries but I figured it would be much easier if just include it with the original query to get all forums.
Something like:$forums = Forum::with('posts', 'threads', 'lastThread')->get(); perhaps? I have tried doing a hasOne relation for this method but that does not work because the last thread does not necessarily belong to that specific subforum.
Anyway, how can I best fix this N+1 problem?
Thank you.
you have endless options,
if the Cache class is right for you than I will go with this path, you can also do this:
protectd $lastThread;
public function lastThread()
{
if ($this->lastThread) return $this->lastThread;
return $this->fetchLastThread();
}
public function fetchLastThread() {
$forums = $this->arrayOfIDs(); // get array of all ids, even subforum's subforums.
$this->lastThread = Thread::whereIn('forum_id', $forums)->orderBy('updated_at', 'desc')->first();
return $this->lastThread;
}
This is what I want to do:
Display a checkbox for the tvshow field entry(from the tvshow table) only if that field entry doesn't match the entries for a given user id in the 'watchedtvshow' table
Table structure for 'tvshow':
id tvshow
Table structure for 'watchedtvshow'
id uid tvid(id of the tvshow)
Here is my controller method:
$tvshow = TVShow::with('watchedtvshow')->get();
return View::make('browse',['tvshow' => $tvshow]);
My View:
#foreach($tvshow as $show)
{{ $show->title }} {{ 'a checkbox' }}
#endforeach
What I tried:
In my controller method:
$tvshow = TVShow::with('watchedtvshow')->get();
$uid = NULL;
if(Auth::check())
$uid = Auth::user()->id;
return View::make('browse',[
'tvshow' => $tvshow,
'uid' => $uid,
]);
In my view:
#foreach($tvshow as $show)
{{ $show->title }}
#foreach($show->watchedtvshow as $watchedtvshow)
#if($watchedtvshow->uid == $uid)
{{'don't show checkbox'}}
#else
{{'show checkbox'}}
#endif
#endforeach
#endforeach
The problem:
The thing is the second foreach loop executes only for the times it finds a watched tv show, otherwise it doesn't. So it just won't show any checkboxes.
I'm not an experienced coder, haven't really encountered anything like this before, I've spent three whole days trying to solve this, using count, for loops and what not, but I can't. Does anybody know how to achieve this?
First off a quick pointer: you can get the logged-in user's ID with Auth::id() rather than having to set it to null, then check if they're logged in then get the id directly off the model.
As for your problem, you're quite right that using the code you have you won't be getting the full story. What you need to do is get a list of all TV shows (regardless of user having it) and additionally a list of all TV shows the user has seen. Now, you can do this many ways, but the best 'Laravel way' is to model the relationship between User and TVShow. Your code doesn't mention this so I won't assume you have already done it. Your database is, of course, already set up for this so all you need to do is create the relationship. In this case, the relationship you need is a belongsToMany (a user can 'have' (have watched) many shows, and a show can 'have' (have been watched by) many users:
// in User.php
public function shows()
{
return $this->belongsToMany('TVShow', 'watchedtvshow', 'uid', 'tvid');
}
// in TVShow.php
public function users()
{
return $this->belongsToMany('User', 'watchedtvshow', 'tvid', 'uid');
}
Once you have this you can get a list of all users that have watched a show with:
$show->users;
Or you can get a list of all shows a user has watched with:
$user->shows;
Now putting that all together you should use Laravel's collections to detect whether a given item if in both arrays:
// in the controller:
$shows = TVShow::all();
if (Auth::check()) {
$watched = Auth::user()->shows;
} else {
// just create an empty collection so we can assume a consistent API
$watched = new \Illuminate\Support\Collection;
}
return View::make('browse', compact('shows', 'watched'));
// in browse.blade.php
#foreach($shows as $show)
{{ $show->title }}
#if ($watched->contains($show)
<span class="glyphicon glyphicon-ok"></span>
#else
<span class="glyphicon glyphicon-remove"></span>
#endif
#endforeach
Something like that?
Hi I'm trying to query three tables from my client controller, a quick overview of my database, users clients projects tasks a user hasMany clients, projects and tasks and these projects and tasks also belongTo a client.
So I'm in the Client Controller and I want to query the logged in users clients projects, however when I try to do this I get thrown an undefined method error:
BadMethodCallException Call to undefined method Illuminate\Database\Query\Builder::user()
I'm not sure why this is occurring, I queried the clients projects separately and it works fine but when I add an additional layer it throws me the above error.
I'm a newbie on Laravel 4 so would appreciate some guidance to help rectify the error and help me understand where I'm going wrong.
My code is below:
ClientController.php
public function show($id)
{
$client = Client::find($id);
$client->load(array('projects' => function($query)
{
// With the clients for each project
$query->with('user');
}));
// Create an empty array
$associated = array();
// Loop through client projects
foreach($client->projects as $project):
// Loop through project users
foreach($project->user as $user):
// Check if the user is the same as the logged in user
if($user->id == Auth::user()->id){
// If yes add the $project to the $associated array
array_push($associated, $project);
}
endforeach;
endforeach;
// show the view
return View::make('clients.show')
->with('client', $client);
}
clients/show.blade.php
<?php $clients = $client->projects; ?>
#if (Auth::check())
#if (count($clients) > 0)
#foreach ($clients as $project)
<div class="one-third column">
<div class="projects">
<ul class="data">
<li><label>Project Name: </label><a class="btn btn-small btn-success" href="{{ URL::to('project/' . $project->id.'/show' ) }}"> {{ $project->project_name }}</a></li>
<li><label class="titletoggle">Project Brief <p>(click to toggle)</p></label><p class="brief">{{ $project->project_brief }}</p></li>
</ul>
<ul class='buttonslist'>
<li><button>Edit Project</button></li>
<li><button>Create Task</button></li>
<li><button>View Tasks</button></li>
</ul>
</div>
</div>
#endforeach
#else
<h3>You have no projects click here to create a project</h3>
#endif
#endif
The problem has to do with the way you are eager loading. Specifically this part.
$client->load(array('projects' => function($query)
{
// With the clients for each project
$query->with('user');
}));
The proper way to eager load these nested relationships would be.
$client->load(array(
'projects',
'projects.user',
));
Or more simply.
$client->load('projects.user');
Or you can set up the eager loading during the initial query.
$client = Client::with('projects.user')->find($id);
You also didn't mention that projects belongs to user. This relationship will need to be defined in the Project model.
class Project extends Eloquent {
public function user()
{
return $this->belongsTo('User');
}
}
The lack of this method is probably the cause of the error message. Eloquent will forward calls to undefined methods to it's internal query builder object. The query builder object doesn't have a user() method, so that's why you get that error.