Be part of JetBrains PHPverse 2026 on June 9 – a free online event bringing PHP devs worldwide together.

ignasbernotas's avatar

Report system architecture

Hi there,

I'm building a reporting system which should consist of reusable classes in order to allow the same report data to be displayed on the app pages and via an API.

There are a few models that are used for reports.

App\CollectedJob App\CollectedFailedJob App\CollectedRequest App\CollectedQuery

There are 3 report types:

Counts (an integer displaying a number of rows) Charts (daily or hourly data) Tables (list of rows)

Each model has application_id and environment_id columns, so there are App\Application and App\Environment parent models.

All reports are on a date range basis. I've got a DateRange class which holds the start/end Carbon date instances.

Basically each report needs an Application model instance, Environment model instance and a DateRange.

This seems pretty straightforward until some reports need additional filters. Also have in mind that a single app page may need to generate around 15 different types of reports (counts + charts + tables).

Some reports need to be able to be filtered by the url parameters:

  • from / to (DateRange) - relevant for all
  • response code (relevant to CollectedRequest)
  • table name (relevant to CollectedQuery)

I like the QueryFilter approach as in https://laracasts.com/series/eloquent-techniques/episodes/4 .

However since different reports may need the same model, but different parameters I'm a bit confused on whether this is a good approach in this case.

I'd like the system to be quite extendable, so each report could implement a ReportableInterface with a getReportData() method, and each sub-type could implement a Chartable/etc interfaces so that each report would have a consistent API.

Also how would you build the report instances?

I'm thinking about something like:

<?php

namespace App\Client\Reports;

use App\Models\Application;
use App\Models\ApplicationEnvironment;

class ReportBuilder
{
    protected $reports = [];

    /**
     * ReportBuilder constructor.
     *
     * @param array $reports
     */
    public function __construct(array $reports)
    {
        $this->reports = $reports;
    }

    /**
     * @param Application            $application
     * @param ApplicationEnvironment $environment
     *
     * @return AbstractReport[]
     */
    public function build(Application $application, ApplicationEnvironment $environment)
    {
        $built = [];

        foreach ($this->reports as $group => $reports) {
            foreach ($reports as $name => $report) {
                /** @var AbstractReport $instance */
                $instance = app()->makeWith($report, ['application' => $application, 'environment' => $environment]);

                $built[$group][$name] = $instance->getReportData();
            }
        }

        return $built;
    }

}

and then:

$builder = new ReportBuilder(['count' => [RequestCount::class], 'chart' => [RequestChart::class]]));
$reportData = $builder->build(...);

So two main questions:

  • How do I make each report depend on DateRange, Application and Environment classes, plus extra filters?
  • What would be the best way to build the instances of the reports?

Thanks!

0 likes
0 replies

Please or to participate in this conversation.