OrderBy on Laravel polymorph one-to-one relationship - php

I have problem to sort results by polymorph's table relationship.
Tried many ways, but results does not sort.
I have looked in many threads, and final result code that I have tried looks like this:
return \App\Models\Advert::with(['advertable' => function ($query) {
$query->orderBy('rooms', 'DESC');
}])->get();
I want to sort all rows in "adverts" table by relationship's "room" column, but nothing happens.
My tables structure is:
Laravel models looks like this:
<?php
// Main "adverts" polymorph table with advertable_id and advertable_type
class Advert extends \Illuminate\Database\Eloquent\Model
{
/**
* #return \Illuminate\Database\Eloquent\Relations\MorphTo
*/
public function advertable()
{
return $this->morphTo();
}
}
// "advert_flats" table
class Flat extends \Illuminate\Database\Eloquent\Model
{
/**
* #return \Illuminate\Database\Eloquent\Relations\MorphOne
*/
public function advert()
{
return $this->morphOne(\App\Models\Advert::class, 'advertable');
}
}
// "advert_homes" table
class Home extends \Illuminate\Database\Eloquent\Model
{
/**
* #return \Illuminate\Database\Eloquent\Relations\MorphOne
*/
public function advert()
{
return $this->morphOne(\App\Models\Advert::class, 'advertable');
}
}
UPDATE
If I dump SQL, then I see that this code does not event run into relationship
select * from `adverts` where `category_id` = 2 and `adverts`.`deleted_at` is null
Also I have tried to change whereHas function to work with polymorph relationships, as Laravel does not support whereHas with polymorph relations.
I have ran this code
return $model->whereHas('advertable', function ($q) {
$q->orderBy('rooms', 'DESC');
}, '>=', 1, ['\App\Models\Flat']);
But it also does not sort results.
I had to change Laravel builder to add support for polymorph tables.
UPDATE 2
I have resolved issue and implemented code by using example provided by #Sturm answer, after applying SortBy / SortByDesc I'm manually building LengthAwarePaginator.
But could this issue be resolved using Builder class, before calling ->get() method on my query ?

$adverts = \App\Models\Advert::query()->select(
DB::raw('
adverts.*,
CASE
WHEN adverts.advertable_type like(\'%Flat\') THEN advert_flats.rooms
WHEN adverts.advertable_type like(\'%Home\') THEN advert_homes.rooms
END
as rooms
')
)->leftJoin(
"advert_flats",
function ($join) {
/** #var \Illuminate\Database\Query\JoinClause $join */
$join->on('adverts.advertable_id', '=', "advert_flats.id")
->where('adverts.advertable_type', 'like', '%Flat');
}
)->leftJoin(
"advert_homes",
function ($join) {
/** #var \Illuminate\Database\Query\JoinClause $join */
$join->on('adverts.advertable_id', '=', "advert_homes.id")
->where('adverts.advertable_type', 'like', '%Home');
}
)->orderBy('rooms', 'ASC')->get();

Here's something that will get you running, although I'll look for a moment more to see if there's a way to do it from Builder.
$sorted = $adverts->sortBy(function ($val, $key) {
return $val->advertable->rooms;
}

Related

Laravel - Getting count of nested relationship

I have an application where users can control their properties and leases.
These are the relationships defined:
//Team.php
/**
* A team can have many properties.
*/
public function properties():
{
return $this->hasMany(Property::class);
}
//Property.php
/**
* A property can have many leases.
*/
public function leases():
{
return $this->hasMany(Lease::class);
}
As you can see here, a Team can have many properties, and each property can also have many leases.
I am trying to figure out how I can get the number of leases that is associated with a Team:
$team = Auth::user()->currentTeam;
return $team->properties->leases->count();
However, the above code returns an error:
Property [leases] does not exist on this collection instance.
You could add this method to your Team.php:
public function leasesCount(){
return count(
Team::
join('properties', 'properties.team_id', '=', 'teams.id')
->join('leases', 'leases.property_id', '=', 'properties.id')
->where('teams.id', $this->id)
->get()
);
}
I ended up using a hasManyThrough relationship on the Team model, like so:
//Team.php
/**
* Get all of the leases for the team.
*/
public function leases()
{
return $this->hasManyThrough(Lease::class, Property::class);
}
Then I can simply get the number of leases created by a specific team by:
return $team->leases()->count();

Laravel Eloquent ORM whereHas with a foreach loop

Here are the relationships:
A user has many skills, there is a join table user_skills. I need to search this table to return the profiles that have the particular skill. This is part of a bigger query that is being built, so that is why there is not a ->get() on here.
User Model
/**
* A user may have many skills
*
* #return \Illuminate\Database\Eloquent\Relations\BelongsToMany
*/
public function skills()
{
return $this->hasMany('App\Core\Platform\Models\UserSkill');
}
Below is the query that isn't doing what I need it to. I need it to return the users who have the particular skill, based on the ID being passed in the search (the $this->misc['search_skills'] value).
// Skills
$this->user = $this->user->whereHas('skills', function ($q)
{
foreach ($this->misc['search_skills'] AS $skill)
{
$q->orWhere('id', $skill);
}
});
Any thoughts as to what I am doing wrong or how I could execute this in a different way?
$skills = $this->misc['search_skills']; // assuming this is an array
$this->user = $this->user->whereHas('skills', function ($q) use ($skills)
{
$q->whereIn('id', $skills);
});
Any time you end up using orWhere multiple times on the same field, you should most likely be using whereIn.
Put the search skills into a variable ($skills), import that variable into your callback with use, then use whereIn.

Laravel 5.3 get belongsToMany and count pivot

I have next models:
class Polling extends Model
{
/**
* #return \Illuminate\Database\Eloquent\Relations\BelongsToMany
*/
public function participants()
{
return $this->belongsToMany(Participant::class, 'participant_poll', 'poll_id');
}
/**
* #return \Illuminate\Database\Eloquent\Relations\BelongsToMany
*/
public function results()
{
return $this->belongsToMany(Participant::class, 'poll_results', 'poll_id');
}
}
class Participant extends Model
{
public function polls()
{
return $this->belongsToMany(Polling::class);
}
public function results()
{
return $this->belongsToMany(Polling::class);
}
}
poll_results - pivot table have structure: id, poll_id, participant_id.
I need view next table:
№|participant.name|Count vote|
1|Mike |15 |
2|................|10 |
..............................
Count vote get pivot table poll_results.
Help please, write query.
$poll = Polling::first();
$poll->participants()->get();
You may want to use withCount() method.
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
Your query would look like this one:
Participant::withCount('polls')->get();
This will add new property to results called polls_count

Yii 2: Unable to create a relation with a condition about the related table

Simplifing, I've two tables:
Product: id, name
Datasheet: id, product_id
Where product_id points to products.id. Each Product could have 0 or 1 Datasheet.
Into my Product class (wich extends ActiveQuery) I've created this relation
/**
* #return \yii\db\ActiveQuery
*/
public function getDatasheet()
{
return $this->hasOne(Datasheet::className(), ['product_id' => 'id']);
}
I'm able now to query in this way
$products_without_datasheet = Product::find()
->with('datasheet')
->all();
What I really need is to retrieve only the products without the datasheet.
I'd like to create a 'scope' (like in yii 1) to be able to reuse the resulting condition datasheet.id IS NULL because this situation has a lot of variants and will be used all around the app.
I'm not able to understand how to create a relation with an added filter, something like getWithoutDatasheet() to be used as
Product::find()->with('withoutDatasheet')->all();
or
Product::find()->withoutDatasheet()->all();
Is it possible? And how?
You need create ActiveQuery for Product. See how gii generated ActiveRecord with ActiveQuery.
In Product:
/**
* #inheritdoc
* #return ProductQuery the active query used by this AR class.
*/
public static function find()
{
return new ProductQuery(get_called_class());
}
In ProductQuery:
public function withoutDatasheet()
{
$this->with('datasheet');
return $this;
}
Usage:
Product::find()->withoutDatasheet()->all();
To retrieve only the products without the datasheet you can do it like this:
$productsWithDatasheet = Datasheet::find()
->select('product_id')
->distinct()
->asArray()
->all();
$productIdsWithDatasheet = ArrayHelper::getColumn($productsWithDatasheet, 'product_id');
$productsWithoutDatasheet = Product::find()
->where(['not in', 'id', $productIdsWithDatasheet ])
->all();

Laravel 4 querying relational relation

I've created 4 database tables:
Tool
ID | NAME | TOOLTYPE_ID
Tooltype
ID | NAME
Toolcategory
ID | NAME
Tool_Toolcategory
TOOL_ID | TOOLCATEGORY_ID
The models are as following:
class Tool extends Eloquent {
public function toolCategories()
{
return $this->belongsToMany('ToolCategory', 'tool_toolcategory', 'tool_id', 'toolcategory_id');
}
public function tooltype()
{
return $this->belongsTo('ToolType');
}
}
class ToolType extends Eloquent {
public function tools()
{
return $this->hasMany('Tool', 'tooltype_id');
}
}
class ToolCategory extends Eloquent {
public function tools()
{
return $this->belongsToMany('Tool', 'tool_toolcategory', 'tool_id', 'toolcategory_id');
}
}
Ok, my problem is getting toolcategories based on created tools with a specific tooltype.
Example I want all the categories related to the tools that has the type "Software". I am kinda lost, i have looked at trying to use scope in my toolcategory model, doing something like this:
public function scopeHasType($query, $type)
{
return $query->whereHas('tools.tooltype', function($q) use ($type)
{
$q->where('name', '=', $type);
})->exists();
}
which didn't really work :) then i tried this
$categories = ToolCategory::whereHas('tools.tooltype', function($query)
{
$query->where('tooltype_id', '=', 'Software');
})->get();
again no luck. So i was hoping that this makes sense to someone, and they could push me in the right direction. Thank you in advance.
Eloquent naming convention says:
ModelNames are StudlyCased & singular
modelMethods are camelCased (relation dynamic properties need this)
table_names are snake_cased & plural
table_columns are snake_cased
pivot_table is model1_model2 snake_cased singular (in alphabetical order)
That being said, Eloquent for a model ToolCategory looks for the table tool_categories.
So you need to specify table names whenever are not convention compliant (singular, not snake cased and so on):
// for example Tool model
protected $table = 'tool';
Also you need foreign keys to be passed to the relationship definition in order to let Eloquent know what to look for. However that you already did:
// ToolType model
public function tools()
{
// here Eloquent would look for tool_type_id (based on related model)
return $this->hasMany('Tool', 'tooltype_id');
}
// Tool model
public function tooltype()
{
// here Eloquent looks for tooltype_id by default (based on relation name)
return $this->belongsTo('ToolType');
}
Now, your relations are OK with 1 exception (keys wrong order):
class ToolCategory extends Eloquent {
public function tools()
{
return $this->belongsToMany('Tool', 'tool_toolcategory', 'toolcategory_id', 'tool_id');
}
Finally, whereHas doesn't work on nested relation (yet, check this: https://github.com/laravel/framework/pull/4954), so at the moment you need this:
// ToolCategory model
public function scopeHasType($query, $type)
{
return $query->whereHas('tools', function ($q) use ($type) {
$q->whereHas('tooltype', function($q) use ($type) {
// use table prefix, since this is going to be join
$q->where('tooltype.name', '=', $type);
});
});
}

Categories