I'm working on an API endpoint with Laravel Spark.
This endpoint returns the given Team along with its users.
// in App\Team
public function users()
{
return $this->belongsToMany(
'App\User', 'team_users', 'team_id', 'user_id'
)->withPivot('role');
}
However, I wish to order those users by a method that is on the user model.
On my App\User model I have a method:
public function currentQueueLength()
{
returns an integer based upon the users current appointments,
}
Is there any way I can return the users relationship but order the users by the result of that method?
If you add current_queue_length as an attribute to the User model, you can then order by this attribute.
You can add the attribute by adding it to the $appends array and creating an accessor:
class User extends Model {
protected $appends = ['currentQueueLength'];
public function getCurrentQueueLengthAttribute()
{
return $this->currentQueueLength();
}
}
Credit to this question: Add a custom attribute to a Laravel / Eloquent model on load?
Then in Team you can add the method like so:
class Team extends Model {
public function users()
{
return $this->belongsToMany(
'App\User', 'team_users', 'team_id', 'user_id'
)->withPivot('role');
}
public function usersByCurrentQueueLength()
{
return $this->users->orderBy('current_queue_length');
}
}
As mentioned in my comment, the issue with this approach is that it sounds like currentQueueLength() is a costly operation (based on your comment) so ideally, it would be something you could do conditionally, however, I'm unsure how to do that! You may want to reconsider your approach to implementing currentQueueLength() which may open up more options to the way you structure this query.
You can acheave this by sorting the users like this :
Team::with('users')
->all();
$team->users->sort(
function ($user1, $user2) {
return $user1->currentQueueLength() - $user2->currentQueueLength();
}
);
More information about sort : To sort in ascending order, return -1 when the first item is less than the second item. So you can use :
return $user1->currentQueueLength() < $user2->currentQueueLength() ? -1 : 1;
And to sort in descending order, return +1 when the first item is less than the second item.
return $user1->currentQueueLength() < $user2->currentQueueLength() ? 1 : -1;
And if it's a field in the users model you can do it like this :
$teams = Team::with(['users' => function ($q) {
$q->orderBy('Field', 'asc'); // or desc
}])->all();
For the case of property :
// asc
$team->users->sortBy('userProperty');
// desc
$team->users->sortByDesc('userProperty');
Hope that helps :)
Related
I have 3 way relationship firstl i have get code like this in a controller
Trial::with('subjects')->where('source_id', $sourceId)->get()->toArray()
Now I want to sort subject.reactions on desc order of subject.reactions.accounts.nb_followers column. I tried to use orderby on relationship but it does not work because it sorting account indsted on reactions. I want to sort reaction based on value of "nb_followes" column present inside account table.
Trail Model
class Trial extends Model
{
use HasFactory;
public $table = 'trials';
public function subjects()
{
return $this->hasMany(Subject::class, 'trial_id')->with(['reactions', 'news'])->withCount('reactions');
}
}
Subject Model
class Subject extends Model
{
use HasFactory;
public $table = 'subjects';
public function reactions()
{
return $this->hasMany(Reaction::class, 'subject_id', 'id')->with(['accounts' => function ($q) {$q->orderBy('nb_followers', 'DESC');}])->where('twitter_error', 0)->where('active', 1)->orderby('key_reaction', 'DESC');
}
public function news()
{
return $this->hasone(News::class, 'id', 'news_item_id');
}
Reaction Model
class Reaction extends Model
{
use HasFactory;
public $table = 'reactions';
public function accounts()
{
return $this->belongsTo(Account::class, 'account_id', 'id')->where('name', '!=', '');
}
Thank you in Advance.
I want to sort reactions based on account table's column yes i cant use simple eloquent query because it will not create a structure that i want so that's why i created these relationships.
first you need to loop over trails using map method
second you need to use transform method to transform your subject in
sorted manner
as show in Subject model you need sort reaction by key_reaction
feild
$trails = ViewTrial::with('subjects')->where('source_id', $sourceId)->get();
$trails->map(function($trails){
return $trails->subjects = $trails->subjects->transform(function (&$subject) {
return [
"column" => $subject->column,
"reactions" => $subject->reactions->sortByDesc(function ($reactions) {
return $reactions['accounts']['nb_followers'];
})->sortByDesc('key_reaction')->values()->all()
]
})->values();
});
return $trails->toArray();
I have 3 table on my database :
table 1 = user (hasMany order)
table 2 = order (hasMany order_detail, belongsTo user)
table 3 = order_detail (belongsTo order)
On my order_detail model i add this function :
public function order() {
return $this->belongsTo('App\Order');
}
so i can call the order data without define it from controller, i just define order_detail on my controller
$order_detail->order->invoice_number
but how to call the user data from the order detail?
I try use this
$order_detail->order->user
but it didn't work for me..
Is there any idea to call the grand parent relation?
In order model add function for order_detail:
public function order_details() {
return $this->hasMany('order_detail');
}
and for user:
public function user() {
return $this->belongsTo('user');
}
In user model add:
public function orders() {
return $this->hasMany('order');
}
Then, you can call in the controller:
$details = Order::find(1)->order_details->where('order_detail_id', 5)->first();
$userName = $details->username;
// ^
// column name in order_detail table
More info in the docs.
I think this is the more complete way to define the relationships:
// User model
public function orders(){
// I'm telling that users has many order. The orders have an user_id field that match with the ID field of the user.
return $this->hasMany('App/Order', 'user_id' , 'id');
}
// Order model
public function order_details(){
// I'm telling that order has many order_details. The order_details have an order_id field that match with the ID field of the order.
return $this->hasMany('App/OrderDetail', 'order_id' , 'id');
}
public function user(){
// I'm telling that an order belongs to an user. The user has an ID field that match with the order_id field of the order.
return $this->belongTo('App/User', 'id', 'user_id');
}
// OrderDetail Model
public function order(){
// I'm telling that an order detail belongs to an order. The order has an ID field that match with the order_id field of the order detail.
return $this->belongTo('App/Order', 'id', 'order_id');
}
I have seen that you put just the model name as first parameter in your relation definition. I think you must put the relative path from the root to the Model. In my case, I have the models as childs of the App folder.
I am using Laravel 4.2.
I have two models: User and Video, both of these models are having one-to-many relationship i.e. User -> HasMany-> Video.
Recently, I got a requirement to display the list of users along with sum of file-size of total videos uploaded by each user and allow users to be order by the sum of file size ascending or descending.
I've made following changes in User model:
class User extends Eloquent {
protected $hidden = array('videosSum');
protected $appends = array('videos_size');
public function videosSum() {
return $this->hasOne('Video')
->selectRaw('sum(file_size) as sum, user_id')
->groupBy('user_id');
}
public function getVideosSizeAttribute()
{
// if relation is not loaded already, let's do it first
if ( ! array_key_exists('videos_size', $this->relations)){
$this->load('videosSum');
}
$related = $this->getRelation('videosSum');
return $this->attributes['videos_size'] = isset($related->sum) ? (int) $related->sum : 0;
}
}
And using like:
User::where('id', '!=', Auth::user()->id);
I am getting the desired result.
But the problem is, I don't want the videos_size attribute everywhere, where the User model gets called. I want to set it dynamically.
I tried User::$appends = ['videos_size'] but it gives protected property cannot be set outsize of class error.
I also tried to make a method in User model which set the $appends if called, but it is also not working.
Can anybody help me how to enable the appends property dynamically?
Laravel doesn't support this off the bat.
my friend and I wrote this extention:
Dynamically hide certain columns when returning an Eloquent object as JSON?
basically you have to override your models.php toArray() method as appended attributes get calculated when you ask for the model in json or array form.
you can add to the trait that's in that link and use it or just put these methods in your respective model class.
public static function getStaticAppends() {
return self::$_appends;
}
public static function setStaticAppends(array $value) {
self::$_appends = $value;
return self::$_appends;
}
public static function getDefaultAppends() {
return with(new static)->getAppends();
}
public function getAppends(){
return $this->appends;
}
public function toArray() {
if (self::getStaticAppends()) {
$this->appends = self::getStaticAppends();
}
return parent::toArray();
}
I have read a few topics about this, but they managed to solve my problem partially ...
this is my controller
class DeskController extends BaseController{
public function getDeskUsers($deskId){
$user = DeskUserList::where(function($query) use ($deskId){
$query->where('deskId', $deskId);
})->with('userName')->get(array('deskId'));
if (!$user->isEmpty())
return $user;
return 'fail';
}
this is the model
class DeskUserList extends Eloquent {
protected $table = 'desk_user_lists';
public function userName(){
return $this->belongsTo('User', 'userId')->select(array('id','userName'));
}
}
the method getDeskUsers may returns ALL the DeskUserList table records, related with the User table record (on deskUserList.userId = User.id).
practically I want each record returned is composed of:
DeskUserList.deskId
User.userName
eg. [{"deskId":"1","user_name":antonio}]
What i get is
[{"deskId":"1","user_name":null}]
As you can see the user name is a null value...
BUT
if I edit my controller code:
->with('userName')->get(array('userId')); //using userId rather than deskId
then i get
[{"userId":"2","user_name":{"id":"2","userName":"antonio"}}]
By this way I still have two problem:
the userId field is twice repeated
I miss the deskId field (that I need...)
hope be clear, thanks for your time!
You need belongsToMany, no need for a model representing that pivot table.
I assume your models are Desk and User:
// Desk model
public function users()
{
return $this->belongsToMany('User', 'desk_user_list', 'deskId', 'userId');
}
// User model
public function desks()
{
return $this->belongsToMany('Desk', 'desk_user_list', 'userId', 'deskId');
}
Then:
$desks = Desk::with('users')->get(); // collection of desks with related users
foreach ($desks as $desk)
{
$desk->users; // collection of users for particular desk
}
// or for single desk with id 5
$desk = Desk::with('users')->find(5);
$desk->users; // collection of users
$desk->users->first(); // single User model
I have a model for user and annotations along with a pivot table user_like for storing annotations liked by user. The annotation table is also associated with another model (ranges) through hasMany relationship. I am trying to return all annotations along with its user, ranges and total number of likes.
The code below works for user, ranges and even likes. But, I am only interested in returning the count of likes and not the actual values (i.e. list of users liking the annotation). Is there a way to include just the counts for one of the models from the relations?
Eloquent query:
$annotations = Annotation::with('ranges')
->with('likes')
->with('author')
->where('document_id', $docid)->get()->toArray();
The model:
class Annotation extends Eloquent {
public function ranges()
{
return $this->hasMany('Range');
}
public function author()
{
return $this->belongsTo('User', 'user_id');
}
public function likes()
{
return $this->belongsToMany('User', 'annotation_like');
}
public function countOfLikes()
{
return $this->likes()->count();
}
}
If you want to retrieve count for multiple annotations using eager loading then you need the following 'helper' relation setup:
public function countLikesRelation()
{
return $this->belongsTo('User','annonation_like')->selectRaw('annotation_like, count(*) as count')->groupBy('annotation_like');
}
// then you can access it as such:
$annotations= Annotation::with('countLikesRelation')->get();
$annotations->first()->countLikesRelation->count;
// to make it easier, we create an accessor to the count attribute
public function getLikesCountAttribute()
{
return $this->countLikesRelation->count;
}
//And then, simply use
$annotations->first()->sectionsCount;