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
php
<?php class NameParser {
Parsing a full name into components in PHP
string-parsing
arrays
normalization
Intermediate
8 steps
php
<?php namespace App\Services\Checkout;
Validating coupons with Laravel's Pipeline
pipeline
chain of responsibility
transactions
Intermediate
7 steps
php
<?php namespace App\Services;
How a password strength validator works in PHP
validation
regular-expressions
data-driven
Intermediate
8 steps
rust
use std::f64::consts::PI; #[derive(Debug, Clone, Copy)] pub struct LatLng {
Building geographic bounding boxes in Rust
geospatial
value-types
option
Intermediate
7 steps
php
<?php namespace App\Services;
Building a cached daily leaderboard in Laravel
caching
aggregation
eager-loading
Intermediate
9 steps
rust
use chrono::{Duration, NaiveDate}; #[derive(Debug)] pub struct DateRange {
Parsing and iterating date ranges in Rust
error-handling
iterators
parsing
Intermediate
7 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.