Sorts
sorts() lists the sorts a request may use. sort=name,-created_at sorts by the listed names, - for descending:
public function sorts(): array
{
return [
'name',
Sort::field('created')->column('created_at'),
Sort::relation('profile.country.name')->as('country'),
Sort::count('posts'),
Sort::max('posts', 'views')->clientNulls(),
Sort::callback('popularity', fn ($query, bool $descending) => ...),
];
}
public function defaultSorts(): array
{
return ['-created'];
}defaultSorts() applies when the request has no sort.
Kinds
- A string or
Sort::field('created')->column('created_at'): a column of the model. Sort::callback('popularity', fn ($query, bool $descending) => ...).Sort::relation('profile.country.name'): a column of a to-one related model, through any chain of to-one relations. It is requested by the path unless renamed with->as('country'). Rows without the related model sort asNULL. A path through amorphTotakes->morphTypes(...)like a filter.Sort::count('posts'),exists,min,max,sum,avg: an aggregate of a relation, named like the aggregate include (postsCount,postsMaxViews), without including it.
NULL values
->nullsFirst() / ->nullsLast() put NULL values first or last in either direction.
Without them an aggregate sort (min, max, sum, avg: NULL for a parent without related rows) takes NULL for the smallest value on every database, as MySQL and SQLite do: first ascending, last descending. On PostgreSQL the package adds the ordering.
->clientNulls() lets the request choose where NULL values go (any sort but a callback). The client adds :nulls-first or :nulls-last after the name, in sort and in sort_included:
Sort::field('nickname')->nullsLast()->clientNulls(),
Sort::max('posts', 'views')->clientNulls(),GET /users?sort=-postsMaxViews:nulls-last,nickname:nulls-firstnullsFirst()/nullsLast() stay the default when the request gives no suffix (sort=nickname keeps NULL values last above), and the suffix overrides them. A suffix on a sort without clientNulls() is rejected with nulls_not_allowed, an unknown one (:nulls-middle) with invalid_value. Default sorts take no suffix: declare the sort with nullsFirst()/nullsLast() instead.
Sorts of nested definitions
Relationship::to('posts', PostQuery::class)->withSorts() publishes the sorts of PostQuery. They do two different jobs, each with its own parameter:
GET /users?include=posts,latestPost&sort=-latestPost.views&sort_included[posts]=-views,titlesortalways orders the main list. Through a to-one relation (latestPost,author,profile) the field and relation sorts of the nested definition are available as<path>.<name>:sort=-latestPost.viewsorders the users by the views of their latest post. A to-many relation has no single row to order by, sosort=posts.viewsis rejected with a hint: sort by an aggregate (Sort::max('posts', 'views')) instead.sort_included[<include path>]orders the included rows:sort_included[posts]=-viewslists the posts of every user by views. Every sort of the nested definition can be used, aggregates and callbacks included, and nested paths work too (sort_included[posts.comments]=-created_at). The path must be included (or be a prefix of an include), otherwise the request is rejected withunknown_include.
Nested definitions marked the same way publish their sorts under the full path, as long as every relationship on the way is marked. The default sorts of a nested definition are not applied to the included rows.