php
62 lines · 8 steps
An immutable typed collection in PHP
A final PHP class wraps Product objects in an immutable, iterable, countable collection keyed by SKU.
Explained by
highlit
1<?php
2
3declare(strict_types=1);
4
5namespace App\Collection;
6
7use App\Entity\Product;
8use ArrayIterator;
9use Countable;
10use InvalidArgumentException;
11use IteratorAggregate;
12use Traversable;
13
14final class ProductCollection implements IteratorAggregate, Countable
15{
16 private array $products = [];
17
18 public function __construct(Product ...$products)
19 {
20 foreach ($products as $product) {
21 $this->products[$product->getSku()] = $product;
22 }
23 }
24
25 public function add(Product $product): self
26 {
27 $clone = clone $this;
28 $clone->products[$product->getSku()] = $product;
29
30 return $clone;
31 }
32
33 public function get(string $sku): Product
34 {
35 return $this->products[$sku]
36 ?? throw new InvalidArgumentException("No product with SKU {$sku}.");
37 }
38
39 public function filter(callable $predicate): self
40 {
41 return new self(...array_filter($this->products, $predicate));
42 }
43
44 public function totalPrice(): int
45 {
46 return array_reduce(
47 $this->products,
48 static fn (int $carry, Product $p): int => $carry + $p->getPriceInCents(),
49 0,
50 );
51 }
52
53 public function getIterator(): Traversable
54 {
55 return new ArrayIterator(array_values($this->products));
56 }
57
58 public function count(): int
59 {
60 return count($this->products);
61 }
62}
01 / 01
STEP 01
‹ swipe to step through ›
Walkthrough
Space play
←→ step
click any line
Three takeaways
- 1Cloning before mutation keeps a collection immutable so shared references never surprise you.
- 2Implementing IteratorAggregate and Countable lets your class behave like a native array in loops and count().
- 3Keying entries by a domain identifier like SKU gives free deduplication and O(1) lookup.
Related explainers
ruby
class Order class InvalidTransition < StandardError; end TRANSITIONS = {
A state machine for order transitions in Ruby
state-machine
data-driven
error-handling
Intermediate
8 steps
php
<?php namespace App\Http\Controllers;
Streaming a filtered CSV export in Laravel
streaming
csv-export
query-builder
Intermediate
9 steps
php
<?php namespace App\Http\Middleware;
Idempotent requests with Laravel middleware
idempotency
middleware
caching
Advanced
8 steps
php
<?php namespace App\Services;
Batch image resizing with PHP GD
image-processing
constructor-promotion
match-expression
Intermediate
10 steps
java
public record FixedWidthField(String name, int start, int end) { public String extract(String line) { int from = Math.min(start, line.length());
Parsing fixed-width text in Java
records
parsing
streams
Intermediate
7 steps
php
<?php namespace App\Cache;
A content-hashed fragment cache in PHP
caching
content-hashing
atomic-writes
Intermediate
10 steps
Share this explainer
Here's the card — post it anywhere.
Made with highlit — turn any snippet into a walkthrough like this in about a minute.
Explain your code
Embed this explainer
Drop the interactive walkthrough into a blog or docs. Views never cost a credit.
<iframe src="https://highlit.co/explainers/an-immutable-typed-collection-in-php-explained-php-ff28/embed?autoplay=1" width="100%" height="520" loading="lazy" style="border:0"></iframe>
Autoplay is on by default — add ?autoplay=0 to start paused.