๐ŸŽ“ A1Learner
๐Ÿ”
0 of 27 lessons completed

PHP Project: Report Engine

The Report Engine is a small program that builds a sales report from fixed rows. It pulls together the tools from this course in one place.

The report settings and the region labels

A readonly class holds the title and the currency. A backed enum gives each region its label through a match.

<?php
enum Region: string {
  case North = "north";
  case South = "south";
  public function label(): string {
    return match ($this) {
      self::North => "North region",
      self::South => "South region",
    };
  }
}
readonly class Report {
  public function __construct(public string $title, public string $currency) {
  }
}
$report = new Report("Quarterly Sales", "USD");
echo $report->title . " in " . $report->currency . "\n";
echo Region::North->label() . "\n";
echo Region::South->label() . "\n";

Rows arrive from a generator

The rows function yields one row at a time. The loop prints each row with its region label and builds the total as it walks.

<?php
enum Region: string {
  case North = "north";
  case South = "south";
  public function label(): string {
    return match ($this) {
      self::North => "North region",
      self::South => "South region",
    };
  }
}
function rows() {
  yield ["Ada", Region::North, 120];
  yield ["Ben", Region::South, 80];
  yield ["Cid", Region::North, 50];
}
$total = 0;
foreach (rows() as $row) {
  $total = $total + $row[2];
  echo $row[0] . " - " . $row[1]->label() . " - " . $row[2] . "\n";
}
echo "Total " . $total . "\n";

The heading rides in an attribute

A Label attribute sits on the Report class. Reflection finds the attribute and builds it, and its text prints.

<?php
#[Attribute]
class Label {
  public string $text;
  public function __construct(string $text) {
    $this->text = $text;
  }
}
#[Label("Sales by Region")]
class Report {
  public string $title = "Quarterly Sales";
}
$ref = new ReflectionClass(Report::class);
$attrs = $ref->getAttributes(Label::class);
$label = $attrs[0]->newInstance();
echo $label->text . "\n";
echo $ref->getName() . "\n";

Example

<?php
#[Attribute]
class Label {
  public string $text;
  public function __construct(string $text) {
    $this->text = $text;
  }
}
enum Region: string {
  case North = "north";
  case South = "south";
  public function label(): string {
    return match ($this) {
      self::North => "North region",
      self::South => "South region",
    };
  }
}
#[Label("Sales by Region")]
readonly class Report {
  public function __construct(public string $title, public string $currency) {
  }
}
function rows() {
  yield ["Ada", Region::North, 120];
  yield ["Ben", Region::South, 80];
  yield ["Cid", Region::North, 50];
}
$report = new Report("Quarterly Sales", "USD");
$ref = new ReflectionClass(Report::class);
$attrs = $ref->getAttributes(Label::class);
$label = $attrs[0]->newInstance();
echo $label->text . "\n";
echo $report->title . " in " . $report->currency . "\n";
$total = 0;
foreach (rows() as $row) {
  $total = $total + $row[2];
  echo $row[0] . " - " . $row[1]->label() . " - " . $row[2] . "\n";
}
echo "Total " . $total . " " . $report->currency . "\n";

Key points

  • A readonly class holds the report title and currency, set once at the start.
  • A string-backed enum names each region and gives it a label through a match.
  • A generator hands the report its rows one at a time.
  • An attribute on the report class carries the heading text.
  • Reflection reads that attribute back and prints the heading.
  • Totals are built during the walk over the rows, not stored in advance.

In this example, reflection reads the Label attribute on the Report class and prints its heading. The readonly report then prints its title and currency. The generator yields three rows, each row prints with the label its region enum builds through a match, and the walk builds the grand total.

Note

Every part of this program was taught earlier in this course.

Try it Yourself

Edit the code, then press Run.

Running is simulated

Exercise

Which part of the Report Engine hands rows to the loop one at a time?