PHP 8.5

๐Ÿฅž Exercise 2 โ€” The Pancake Builder

๐Ÿ“– The setup

You run a small pancake house. Every order is an immutable Pancake โ€” once placed, you can't mutate it; you clone a new one with the change. Customers build their order step by step: add a topping, size it up, bake it, calculate the bill.

You'll use clone with to return new instances without a constructor-spaghetti, and the pipe operator to compose it all into one readable pipeline.

๐Ÿงฑ Starter code

pancake.php โ€” starter

<?php

final class Pancake
{
    public function __construct(
        public readonly string $base = 'plain',
        public readonly array $toppings = [],
        public readonly string $size = 'medium',
        public readonly bool $isBaked = false,
    ) {}

    public function describe(): string
    {
        $t = $this->toppings ? implode(' + ', $this->toppings) : 'no toppings';
        $state = $this->isBaked ? '๐Ÿ”ฅ cooked' : '๐ŸงŠ raw batter';
        return "{$this->size} {$this->base} [{$t}] โ€” {$state}";
    }
}

// Today's mission:
//   Build a large 'apple' pancake with 'syrup', 'cinnamon' and 'bacon',
//   bake it, print the description, then print the price.
//   Do it all with |> and `clone with`. No intermediate variables!

๐ŸŽฏ Your challenges

  1. Immutable modifiers. Add three methods using clone with:
    • withTopping(string $t): self โ€” returns a new pancake with $t appended to toppings.
    • withSize(string $s): self โ€” returns a new pancake at that size.
    • bake(): self โ€” returns a baked pancake.
  2. Compose with pipes. Using only the pipe operator |> and your new methods, build today's mission order. No temporary variables. End the chain with ->describe() and echo the result.
  3. Add a bill. Add a priceEuros(): int method. Rules: base โ‚ฌ6, +โ‚ฌ1 per topping, +โ‚ฌ3 for large, +โ‚ฌ2 if baked (it's real cooking!). Extend your pipeline to also print the price on a new line.
  4. ๐ŸŽ Bonus โ€” first-class callables. Use the first-class callable syntax (e.g. strtoupper(...)) somewhere in your pipe to transform the description. Hint: not every step needs an arrow function.
  5. ๐ŸŽ Group ordering. Make an array of three pancakes and use array_map() inside a pipe to print the full menu in one expression.
๐Ÿ’ก Hint โ€” clone with & pipe syntax
// clone with: pass an array of overrides as clone's second argument
public function withTopping(string $t): self
{
    return clone($this, [
        'toppings' => [...$this->toppings, $t],
    ]);
}

// pipe operator: right side is a callable, receives left side as arg
$shout = 'hello' |> strtoupper(...); // SHOUT becomes 'HELLO'

// realistic pipe โ€” note the parens around each fn(...)
$result = new Pancake('apple')
    |> (fn(Pancake $p) => $p->withTopping('syrup'))
    |> (fn(Pancake $p) => $p->bake());
โœ… Show one possible solution

Answer both questions correctly to unlock the solution.

1. Given $result = 'hello' |> strtoupper(...); โ€” what value does $result hold?

2. You write return clone($this, ['isBaked' => true]); inside a readonly class. What happens?

๐Ÿงช Think about it: the original object is never mutated. Does clone with still trigger constructor logic? What happens if a readonly property references another object โ€” is the clone deep or shallow?
Copied to clipboard!