Laravel Projectable Aggregates is a package that allows you to easily store aggregate values like counts, sums, averages, etc. in your models eliminating the need to calculate these values on the fly (with withCount
, withSum
, withAvg
, etc.).
- Speed up database queries by storing aggregate values in the database.
- Automatically updates aggregate values with Model Events.
- Option to calculate the aggregate values periodically in bulk.
composer require romanzipp/laravel-projectable-aggregates
Consumers hold the projectable aggregate database field. This is the model which otherwise would calculate the relationship fields via withCount
, withSum
, withAvg
, etc.
Providing models provide (duh) the aggregate values for the consumer. Think of the provider to exist many times for one consumer.
Let's continue with the example of a Car
model with Door
models. We want to store the Doors count in the Car's project_doors_count
field.
new class() extends Migration
{
public function up()
{
Schema::create('cars', function (Blueprint $table) {
$table->id();
$table->unsignedInteger('project_doors_count')->default(0);
});
}
}
The consumer model will attach the ConsumesProjectableAggregate
attribute to the provider relation.
use romanzipp\ProjectableAggregates\Attributes\ConsumesProjectableAggregate;
use romanzipp\ProjectableAggregates\ProjectionAggregateType;
class Car extends Model
{
#[ConsumesProjectableAggregate(
projectionAttribute: 'project_doors_count', // <- Name of the projection field in the database
projectionType: ProjectionAggregateType::TYPE_COUNT
)]
public function doors(): HasMany
{
return $this->hasMany(Door::class);
}
}
The provider model will attach the ProvidesProjectableAggregate
attribute to the consumer relation.
use romanzipp\ProjectableAggregates\Attributes\ProvidesProjectableAggregate;
use romanzipp\ProjectableAggregates\ProjectionAggregateType;
class Door extends Model
{
#[ProvidesProjectableAggregate(
projectionAttribute: 'project_doors_count', // <- Name of the FOREIGN projection field in the database
projectionType: ProjectionAggregateType::TYPE_COUNT
)]
public function car(): BelongsTo
{
return $this->belongsTo(Car::class);
}
}
In order to listen to model events issued by the provider models, you need to register the consumer models in the boot
method of your AppServiceProvider
.
use romanzipp\ProjectableAggregates\ProjectableAggregateRegistry;
class AppServiceProvider extends ServiceProvider
{
public function boot(ProjectableAggregateRegistry $registry)
{
$registry->registerConsumers([
Car::class,
]);
$registry->registerProviders([
Door::class,
]);
}
}
Important
Calculating aggregate values (without bulk) relies on Elouent model events which are only dispatched when working with Eloquent model itself. Using the DB
facade will not trigger the library to update the aggregate values.
There are three types of aggregates that can be calculated:
ProjectionAggregateType::TYPE_COUNT
: Counts the number of related models.ProjectionAggregateType::TYPE_SUM
: Sums the related models' values.ProjectionAggregateType::TYPE_AVG
: Averages the related models' values.
Important
In order to use the aggregate types TYPE_SUM
and TYPE_AVG
, you need to specify the target attribute of the relationship.
#[ProvidesProjectableAggregate(
projectionAttribute: 'project_price_average',
projectionType: ProjectionAggregateType::TYPE_AVG,
targetAttribute: 'price', // <- Attribute of the related model to average/sum up
)]
You can decide if you would only like to rely on models events or if you want to calculate the aggregate values periodically in bulk.
This will automatically work if your've attached the ProvidesProjectableAggregate
attribute to your provider relations. Once a provider model has been created/deleted the according consumer aggregate attribute will be incremented/decremented.
If you don't want to or can't rely on model events, you can use the bulk-aggregate
command to calculate the aggregate values periodically in bulk.
php artisan aggregates:bulk-aggregate {--queued} {--queue=} {--class=}
--queued
: Dispatch a job to the worker queue.--queue=
: Specify the queue to run the command in.--class=
: Limit the bulk calculation to a specific consumer class.
The following relationships are supported and tested:
- β Model Events
- β Bulk Aggregation
Provider::hasOneThrough()
<-> Pivot
<-> Consumer::hasManyThrough()
β οΈ WIP
- β Model Events
- β Bulk Aggregation
- β Model Events
- β Bulk Aggregation
This repository contains a Lando configuration file that can be used to run the tests on your local machine.
lando start
lando phpunit
The MIT License (MIT). Please see License File for more information.