Skip to main content

Summary Class

A summary entity is an entity that contains pre-aggregated data from the source entity. To work with this package, you need to create one or more summary entities for each source entity that you want to analyze.

Example Source Entity

This is the example of an entity that we would like to analyze. Here, we have an Order:

use Doctrine\DBAL\Types\Types;
use Doctrine\ORM\Mapping as ORM;

#[ORM\Entity()]
class Order
{
#[ORM\Id]
#[ORM\GeneratedValue]
#[ORM\Column]
private ?int $id = null;

#[ORM\ManyToOne()]
private ?Item $item = null;

#[ORM\ManyToOne()]
private ?Customer $customer = null;

#[ORM\Column(type: Types::DATETIME_MUTABLE)]
private ?\DateTimeInterface $time = null;

// setters, getters and other logic are omitted for brevity
}

The Summary Entity

This is an example summary entity for the above Order entity. A summary table is a standard Doctrine entity with additional attributes that define how the data is rolled up from the source entity:

use Brick\Money\Money;
use Doctrine\DBAL\Types\Types;
use Doctrine\ORM\Mapping as ORM;
use Rekalogika\Analytics\AggregateFunction\Count;
use Rekalogika\Analytics\AggregateFunction\Sum;
use Rekalogika\Analytics\Attribute as Analytics;
use Rekalogika\Analytics\Model\Hierarchy\TimeDimensionHierarchy;
use Rekalogika\Analytics\Model\Partition\DefaultIntegerPartition;
use Rekalogika\Analytics\Model\Summary;
use Rekalogika\Analytics\ValueResolver\EntityValueResolver;
use Rekalogika\Analytics\ValueResolver\PropertyValueResolver;
use Symfony\Component\Translation\TranslatableMessage;

#[ORM\Entity()]
#[Analytics\Summary(
sourceClass: Order::class,
label: new TranslatableMessage('Orders'),
)]
class OrderSummary extends Summary
{
// 1. Partition

#[ORM\Embedded()]
#[Analytics\Partition(new PropertyValueResolver('id'))]
private DefaultIntegerPartition $partition;

// 2. Dimensions

#[ORM\Embedded()]
#[Analytics\Dimension(
source: new PropertyValueResolver('time'),
label: new TranslatableMessage('Time'),
sourceTimeZone: new \DateTimeZone('UTC'),
summaryTimeZone: new \DateTimeZone('Asia/Jakarta'),
)]
private TimeDimensionHierarchy $time;

#[ORM\ManyToOne()]
#[Analytics\Dimension(
source: new EntityValueResolver('customer.country'),
label: new TranslatableMessage('Customer Country'),
)]
private ?Country $customerCountry = null;

#[ORM\ManyToOne()]
#[Analytics\Dimension(
source: new EntityValueResolver('customer.country.region'),
label: new TranslatableMessage('Customer Region'),
)]
private ?Region $customerRegion = null;

#[ORM\Column(enumType: Gender::class, nullable: true)]
#[Analytics\Dimension(
source: new PropertyValueResolver('customer.gender'),
label: new TranslatableMessage('Customer Gender'),
)]
private ?Gender $customerGender = null;

// 3. Measures

#[ORM\Column(type: Types::INTEGER)]
#[Analytics\Measure(
function: new Sum('item.price'),
label: new TranslatableMessage('Price'),
)]
private ?int $price = null;

#[ORM\Column(type: Types::INTEGER)]
#[Analytics\Measure(
function: new Count('id'),
label: new TranslatableMessage('Count'),
)]
private ?int $count = null;

// 4. An example getter with business logic

public function getPrice(): ?Money
{
if ($this->price === null) {
return null;
}

return Money::ofMinor($this->price, 'EUR');
}

Sections

1. Partition

The partition attribute is used to define how the data is partitioned. The default DefaultIntegerPartition here should be sufficient for auto-incrementing primary key of the source entity.

Read more about partitions in the partitioning section.

2. Dimensions

These are the properties that have distinct, descriptive values. You will use these properties to filter and group the data. Using SQL as an analogy, these are the fields that you would use in a GROUP BY and/or WHERE clause.

All dimensions are indicated by the #[Analytics\Dimension] attribute. The most important argument is the source argument. This argument is used to resolve the value of the dimension from the source entity. A PropertyValueResolver points to the value of a property in the source entity. An EntityValueResolver points to a related entity.

A dimension can be hierarchical, like the time dimension above. A hierarchical dimension is modeled using a Doctrine embeddable. Inside the class, the time dimension is further divided into year, month, day, and more.

Read more about dimensions in the dimensions section.

3. Measures

These are the properties that you want to aggregate. Using SQL as an analogy, you would use these fields in a SUM, COUNT, and other aggregate functions.

Measures are indicated by the #[Analytics\Measure] attribute. The most important argument is the function argument. It is used to define the aggregation function.

Read more about measures in the measures section.

Getters

Just like a regular Doctrine entity, you can define getters in the summary entity. You can also have simple business logic in these getters. For example, the getPrice() getter above converts the price to a Money object.

Labels and Translations

All the items in the summary entity have a label attribute that accepts a string or a TranslatableInterface. These labels are used in the user interface to identify the item, for example in a table header or a chart legend.

If a TranslatableInterface is used, the label will be translated using the Symfony translation component.

Indexing

The framework automatically creates indexes for the summary table. You don't need to create any indexes manually.

Changing Summary Entity

A summary entity should not be changed after it is created and populated. Mainly because the summary entity has the groupings property, which relies on the entity's properties and their ordering. If you make any changes, then you will need to refresh the entire data anyway.

If you need to change the summary entity, you should create a new one, refresh the data and wait until it is completed, and then retire the old one. If you anticipate that you will have to change the summary entity, we suggest date-coding the summary entity class name, for example OrderSummary20250115.

Summary Entity is an Entity but not an Entity

A summary entity is defined as a Doctrine entity. But it is mainly for defining the structure of the summary table and the summarization behavior. You will never interact with a real instance of the summary entity. Instead, you query the summary table using the SummaryManager, and gets the result not in the form of a summary entity.

An event listener is installed to prevent you from accidentally persisting, updating, or deleting a summary entity.