Weave Code
Weaver
Helps Laravel developers discover, compare, and choose open-source packages. See popularity, security, maintainers, and scores at a glance to make better decisions.
Feedback
Share your thoughts, report bugs, or suggest improvements.
Subject
Message

Nova Sort Relations Laravel Package

lifeonscreen/nova-sort-relations

A Laravel Nova package for sorting related models. It adds drag-and-drop reordering of hasMany and belongsToMany relationships directly on resource detail pages, saving sort order automatically. Ideal for managing ordered lists like menu items, gallery images, or featured posts without custom code.

View on GitHub
Deep Wiki
Context7

Getting Started

v0.0.2 adds a single extension point: a static sortableRelations() method on the sortable model (trait-level) with a default implementation returning an empty list. Nothing is required to upgrade — it's purely additive — but the release is the moment to decide whether relation-based sorting is part of your feature surface.

First steps:

  1. composer update the package and confirm the installed version with composer show <vendor>/<package>. No migrations, config publishes, or cache clears are implied by the changelog.
  2. Open the trait that owns sortableRelations() and read the default return shape. The release note only says "with default," so verify whether the default is a plain array of relation names or a alias => relation.column map before you write code against it.
  3. Find the one place where sort keys from the request are validated (typically a ?sort= / ?orderBy= handler or a query scope). That is where sortableRelations() is consumed, and it's the file you'll actually be editing.
  4. Apply it to your first use case: an index endpoint that currently sorts only by own-table columns but where users keep asking to sort a list by a parent record (e.g. orders by customer name). Override sortableRelations() in that model, add the relation, and verify the generated SQL uses a join or subquery rather than an unqualified column.

If no model overrides the method, behaviour is unchanged from v0.0.1 — the default is deliberately permissive-of-nothing, so relation sorts are simply not offered.

Implementation Patterns

Declare relations per model, not globally. The static method is meant to be overridden on each sortable model. Keep it declarative and side-effect free:

public static function sortableRelations(): array
{
    return [
        'customer' => 'customers.name',
        'status'   => 'statuses.label',
    ];
}

Treat it as an allow-list. Every relation that appears here becomes user-reachable input. Only expose relations whose joins are indexed and whose tables are small enough to join on a paged query. Never build the list from $this->getRelations() or from request input.

Call parent::sortableRelations() if you want inheritance. If you have a base model (or a shared trait) that seeds a common set — e.g. tenant, owner — merge rather than replace so future package additions in the default survive your override:

return array_merge(parent::sortableRelations(), [
    'customer' => 'customers.name',
]);

Keep the mapping aligned with your validation layer. If you validate sort against Rule::in(...), derive that rule from Model::sortableRelations() instead of hardcoding a parallel list. One source of truth means adding a relation in v0.0.3 doesn't require touching the controller.

Pair it with eager loading for output, not for sorting. Relation sorting is a SQL concern (join/subquery) and has nothing to do with with(). If you add a join for sorting, remember to select('orders.*') (or the model's qualified columns) so the joined table's id/name don't overwrite your attributes — a very common symptom is models coming back with the customer's id.

Feature-flag new entries when rolling out. Because this is a static method, changing the list is a code deploy. If the relation join is expensive on your largest tenant, gate the entry behind a config value so you can disable it without a hotfix.

Test the sort surface, not just the method. A short Pest/PHPUnit test asserting that each key in sortableRelations() produces a 200 and that ?sort=__bad__ produces a 422 catches the two failure modes that matter: broken joins and unvalidated passthrough.

Gotchas and Tips

  • Name collisions are fatal, not soft. PHP forbids a class from declaring the same method name twice regardless of static-ness. If any model already defines an instance method sortableRelations(), or includes two traits that each declare it, you'll get a fatal Cannot redeclare on class load. Grep your app/Models before upgrading, and add insteadof if two traits collide.
  • The default returning nothing is the intended behaviour, not a bug. Relation sorts will silently fall back to a default order. If a user reports "sorting by name does nothing," check the model override before you check the query builder — the sort key is most likely absent from the allow-list.
  • Static means no $this. You cannot reference $this->getTable() or instance state inside the override without (new static). Prefer literal strings or static::TABLE_CONSTANT to keep it clean and cache-safe.
  • Watch for SELECT * with joins. Any query that joins for sorting must qualify its select list. Also double-check count() on paginated endpoints — a join (as opposed to leftJoin) on a has-many relation will silently inflate the total and produce duplicated rows; only expose hasOne/belongsTo relations here unless you know the sort implementation uses a subquery.
  • Caching. If your codebase caches per-model metadata (e.g. an admin filter list built from sortableRelations()), the static method won't bust it. Key any such cache on the package version or add a manual flush to your deploy script.
  • Debugging tip: dd(Model::sortableRelations()) inside a tinker session is the fastest way to confirm which class actually wins the resolution — especially in hierarchies where a child override shadows a parent list.
  • Extension point to watch: because the method is static and was introduced with a default, future releases can add relations (or change the expected return shape) to that default. Pin the package version in composer.json with a caret on the minor if you rely on the default's contents, and re-run the sort-surface test after every bump.
Weaver

How can I help you explore Laravel packages today?

Conversation history is not saved when not logged in.
Prompt
Add packages to context
No packages found.
skyraptor/chatter
edlugz/sasapay
abbasudo/artisan-route
mirvan/ippanel
ignibyte/aic-framework
glavweb/data-schema-bundle
glavweb/datagrid-bundle
tacticmedia/scim-server
uni-method/frame
neika-ai/sdk
adachsoft/php-code-style
pharaonic/laravel-notification-dependencies
beautinow/translug
dev-brainiac/bot-cloaked
octoberrain/html
octoberrain/extension
octoberrain/exception
octoberrain/contracts
adachsoft/api-version-bundle
cgv/localized-resource