Combines data from multiple models into a single unified query using SQL unions, allowing for consistent pagination and customization across diverse data sources.
austinw/laravel-union-paginator is a Laravel package for combines data from multiple models into a single unified query using sql unions, allowing for consistent pagination and customization across diverse data sources..
It currently has 78 GitHub stars and 51.431 downloads on Packagist (latest version v2.2.4).
Install it with composer require austinw/laravel-union-paginator.
Discover more Laravel packages by austinw
or browse all Laravel packages to compare alternatives.
Last updated
The UnionPaginator package enables you to paginate and unify results from multiple Eloquent models into a single dataset. By merging multiple model queries, it allows for straightforward pagination and sorting of data drawn from various sources.
Key Features:
Install via Composer:
composer require austinw/laravel-union-paginator
If you are upgrading from an earlier version of UnionPaginator, please refer to the Migration Guide for detailed instructions on updating your code to take advantage of the latest features and improvements.
Specify which Eloquent models you want to combine:
use AustinW\UnionPaginator\UnionPaginator;
$paginator = UnionPaginator::forModels([User::class, Post::class]);
All provided classes must be subclasses of Illuminate\Database\Eloquent\Model.
Call paginate to get paginated results:
$results = $paginator->paginate(15);
This returns a LengthAwarePaginator instance, seamlessly integrating with Laravel’s pagination utilities.
You can apply specific query conditions to a single model type before creating the union:
$paginator->applyScope(User::class, fn($query) => $query->where('active', true));
The UnionPaginator class allows you to customize how models are retrieved during pagination. This can be useful if you need to apply specific logic or optimizations when fetching models from the database.
You can register a custom callback for retrieving models by type using the fetchModelsUsing method. This method allows you to define how models should be fetched for a specific model type.
Now only active users are included in the final union.
use AustinW\UnionPaginator\UnionPaginator;
use App\Models\Post;
use App\Models\Comment;
// Create a new UnionPaginator instance for the specified models
$paginator = new UnionPaginator([Post::class, Comment::class]);
// Register a custom retrieval callback for the Post model
$paginator->fetchModelsUsing(Post::class, function (array $ids) {
// Custom logic to retrieve Post models
return Post::with('author')->findMany($ids);
});
// Register a custom retrieval callback for the Comment model
$paginator->fetchModelsUsing(Comment::class, function (array $ids) {
// Custom logic to retrieve Comment models
return Comment::with('post')->findMany($ids);
});
// Use the paginator as usual
$paginatedResults = $paginator->paginate();
By using custom retrieval callbacks, you can optimize and tailor the model fetching process to suit your application's specific needs.
Use transformResultsFor to alter records for a particular model type:
$paginator->transformResultsFor(User::class, fn($user) => [
'id' => $user->id,
'uppercase_name' => strtoupper($user->name),
]);
If model retrieval is active, $user is an Eloquent model. If you call preventModelRetrieval(), $user is a raw database record (stdClass).
If you don’t need Eloquent models and prefer raw records:
$paginator->preventModelRetrieval()->paginate();
Transformations still apply, but are run on raw records.
Choose specific columns for each model type to reduce overhead:
$paginator->setSelectedColumns(User::class, ['id', 'email', DB::raw("'User' as type")]);
Models using SoftDeletes are automatically filtered so that soft-deleted records do not appear.
forModels(array $modelTypes): self
Set the models to combine. Throws an exception if a non-model class is provided.
applyScope(string $modelType, Closure $callable): self
Modify queries for an individual model type.
transformResultsFor(string $modelType, Closure $callable): self
Apply transformations to either models or raw records of a particular model type.
preventModelRetrieval(): self
Skip loading actual models. Return raw database rows instead.
setSelectedColumns(string $modelType, array $columns): self
Specify which columns to fetch for each model type.
paginate($perPage = 15, $columns = ['*'], $pageName = 'page', $page = null): LengthAwarePaginator
Execute the union query, apply scopes and transformations, and return a paginator.
__call($method, $parameters)
Forward method calls to the underlying union query builder, enabling sorting and other query modifications.
use AustinW\UnionPaginator\UnionPaginator;
$paginator = UnionPaginator::forModels([User::class, Post::class])
->applyScope(User::class, fn($query) => $query->where('active', true))
->transformResultsFor(User::class, fn($user) => ['id' => $user->id, 'name' => strtoupper($user->name)])
->transformResultsFor(Post::class, fn($post) => ['title' => $post->title, 'date' => $post->created_at->toDateString()])
->paginate(10);
foreach ($paginator->items() as $item) {
// Each $item could be a transformed array or a raw record, depending on your configuration.
}
You can chain Eloquent methods before paginate():
$paginator->latest()->paginate();
or
$paginator->orderBy('created_at', 'desc')->paginate();
Applying multiple transformations for the same model type overwrites earlier ones:
$paginator->transformResultsFor(User::class, fn($user) => ['transformed' => true])
->transformResultsFor(User::class, fn($user) => ['overridden' => true]);
The latter transformation takes precedence.
If no matching records are found, the paginator returns an empty result set without errors.
UnionPaginator is well-tested across various scenarios, including: