Skip to content

PHP Tutorial Advanced OOP Exercise

Practice: Shapes and Payments

Lesson 221 of 369 2 min read By Deepak Paswan Updated Oct 5, 2026

Try each exercise first, then compare with the solution.

Exercise 1: shapes with an abstract class

Create an abstract class Shape with an abstract method area() and a concrete method label() that returns "<ClassName>: <area>" (area rounded to 1 decimal). Add Square and Triangle (base and height). Print the label of each in a loop.

Show a possible solution
index.php
PHP
<?php
abstract class Shape
{
    abstract public function area(): float;

    public function label(): string
    {
        return static::class . ": " . round($this->area(), 1);
    }
}

class Square extends Shape
{
    public function __construct(private float $side) {}

    public function area(): float
    {
        return $this->side ** 2;
    }
}

class Triangle extends Shape
{
    public function __construct(private float $base, private float $height) {}

    public function area(): float
    {
        return 0.5 * $this->base * $this->height;
    }
}

foreach ([new Square(3), new Triangle(4, 5)] as $shape) {
    echo $shape->label() . "\n";
}
Output
OUTPUT
Square: 9
Triangle: 10

Exercise 2: an interface for discounts

Define an interface Discount with apply(float $price): float. Implement PercentDiscount and FixedDiscount. Write a function finalPrice(float $price, Discount ...$discounts) that applies all of them in order.

Show a possible solution
index.php
PHP
<?php
interface Discount
{
    public function apply(float $price): float;
}

class PercentDiscount implements Discount
{
    public function __construct(private float $percent) {}

    public function apply(float $price): float
    {
        return $price * (1 - $this->percent / 100);
    }
}

class FixedDiscount implements Discount
{
    public function __construct(private float $amount) {}

    public function apply(float $price): float
    {
        return max(0, $price - $this->amount);
    }
}

function finalPrice(float $price, Discount ...$discounts): float
{
    foreach ($discounts as $discount) {
        $price = $discount->apply($price);
    }

    return $price;
}

echo finalPrice(1000, new PercentDiscount(10), new FixedDiscount(50));
Output
OUTPUT
850

Exercise 3: a trait for slugs

Write a trait HasSlug with a method slug() that turns the object’s title property into a lowercase, hyphenated slug. Use it in an Article class.

Show a possible solution
index.php
PHP
<?php
trait HasSlug
{
    public function slug(): string
    {
        $slug = strtolower(trim($this->title));
        $slug = preg_replace('/[^a-z0-9]+/', "-", $slug);

        return trim($slug, "-");
    }
}

class Article
{
    use HasSlug;

    public function __construct(public string $title) {}
}

echo (new Article("PHP: Traits & Slugs!"))->slug();
Output
OUTPUT
php-traits-slugs

Exercise 4: inject a logger

Create a Logger interface and an ArrayLogger that stores messages in an array. Write a Signup class that receives a Logger in its constructor and logs "Signed up: <email>". Show the stored messages.

Show a possible solution
index.php
PHP
<?php
interface Logger
{
    public function log(string $message): void;
}

class ArrayLogger implements Logger
{
    public array $messages = [];

    public function log(string $message): void
    {
        $this->messages[] = $message;
    }
}

class Signup
{
    public function __construct(private Logger $logger) {}

    public function register(string $email): void
    {
        $this->logger->log("Signed up: $email");
    }
}

$logger = new ArrayLogger();
$signup = new Signup($logger);

$signup->register("ana@example.com");
$signup->register("ben@example.com");

echo implode("\n", $logger->messages);
Output
OUTPUT
Signed up: ana@example.com
Signed up: ben@example.com

Exercise 5: choose composition

A Report can be exported as CSV or as JSON. Without inheritance, design it so that you can add a new format without changing Report.

Show a possible solution
index.php
PHP
<?php
interface Exporter
{
    public function export(array $rows): string;
}

class JsonExporter implements Exporter
{
    public function export(array $rows): string
    {
        return json_encode($rows);
    }
}

class CsvExporter implements Exporter
{
    public function export(array $rows): string
    {
        return implode("\n", array_map(fn($r) => implode(",", $r), $rows));
    }
}

class Report
{
    public function __construct(private array $rows, private Exporter $exporter) {}

    public function render(): string
    {
        return $this->exporter->export($this->rows);
    }
}

$rows = [["Pen", 12], ["Book", 250]];

echo (new Report($rows, new JsonExporter()))->render() . "\n";
echo (new Report($rows, new CsvExporter()))->render();
Output
OUTPUT
[["Pen",12],["Book",250]]
Pen,12
Book,250