Checkpoint: Data & Types
You've learned enums, closures, DateTime, regex, JSON, traits, generators and type declarations — let's combine them. This checkpoint has no new syntax: it's a chance to wire several of those features into one realistic class and prove the pieces click together.
Learn Checkpoint: Data & Types in our free PHP course — a beginner-friendly interactive lesson with worked examples, a practice exercise and a quick recall.
Part of the free Php course at LearnCodingFast — hands-on lessons with examples you run in your browser, plus practice exercises and a quick quiz.
What You've Learned in This Block
- Enums — fixed, type-safe sets of named values, with methods and from() / tryFrom()
- Closures & arrow functions — functions as values, use() capture, fn() =>
- DateTime — immutable dates, format() , intervals and diff()
- Regex — preg_match , capture groups, preg_replace
- JSON — json_encode / json_decode and JsonSerializable
- Traits — reusable method bundles mixed in with use
- Generators — lazy, memory-light value streams via yield
- Type declarations — typed params, returns and properties; strict_types
🛠️ Build Challenge: A Typed, Serializable Task
Your mission is to build one small class that pulls together four features from this block:
- A backed enum Status with a label() method (uses match($this) ).
- A trait HasTimestamp supplying a $createdAt property.
- A typed Task class (with declare(strict_types=1) ) that uses the trait and a typed Status property.
- JSON : implement JsonSerializable so json_encode produces a clean shape.
Start from this scaffold — fill in each TODO , then run it and match the target output in the comments. Try it yourself first.
Reveal the Solution
Here's one clean way to wire all four features together. The key moves: the enum's label() uses an exhaustive match($this) ; the trait supplies $createdAt ; the class declares a typed Status property via constructor promotion; and jsonSerialize() converts the enum to its ->value and adds the computed label.
📝 Checkpoint Quiz
Six questions covering the whole block. Answer each in your head (or out loud) before revealing — no peeking.
1. A user submits an unknown status string. Which converter returns null instead of throwing, so you can fall back to a default?
Status::tryFrom($input) . It returns null for an invalid value, so Status::tryFrom($input) ?? Status::Todo safely supplies a default. from() would throw a ValueError .
2. What's the difference between use ($x) and use (&$x) in a closure?
use ($x) captures a copy of the value, so later changes outside don't affect the closure. use (&$x) captures by reference — the closure and outer scope share one live variable, which is how you build counters.
3. Two traits a class uses both define save() . What must you do?
Resolve the conflict explicitly in the use block: A::save insteadof B; picks the winner, and optionally B::save as saveB; keeps the other under a new name. Otherwise PHP raises a fatal error.
4. Why use a generator to read a 5 GB log file instead of file() ?
file() loads every line into an array at once — 5 GB in memory. A generator yield s one line at a time, so only a single line lives in memory, keeping usage near-constant regardless of file size.
5. With declare(strict_types=1) , what happens when you call f('9') on function f(int $n) ?
A TypeError is thrown. Strict mode disables coercion, so the string '9' is not silently converted to the int 9. (In coercive mode it would have become 9.)
6. When you json_encode an object implementing JsonSerializable , what gets encoded?
Whatever the object's jsonSerialize() method returns — typically a curated associative array — rather than the raw public properties. That's how the Task example turned a Status enum into its ->value plus a computed label.
Watch Out For
- Declaration order in one file — define the enum and trait before the class that uses them, and build objects last, or PHP complains the symbol isn't found.
- Encoding the raw enum — without jsonSerialize() you'd leak the enum object's shape. Convert it to ->value in the array you return.
- An exhaustive match($this) — with no default arm, adding a new enum case forces you to update label() — a feature, not a bug.
- strict_types placement — it must be the file's first statement, or you'll get a fatal error.
Pro Tips
- 💡 Let the types do the talking. A typed Status property plus an enum makes an invalid status literally unrepresentable.
- 💡 Keep your JSON contract in one place. jsonSerialize() is the single source of truth for what your object exposes.
- 💡 Revisit, don't rush. If a quiz question stumped you, the matching lesson is one click away — solid foundations pay off in the projects ahead.
Frequently Asked Questions
🎉 Checkpoint Complete!
- ✅ You combined an enum , a trait , typed properties and JSON in one class
- ✅ You saw how JsonSerializable gives you full control over output shape
- ✅ You reinforced strict_types , match($this) , and trait composition
- ✅ The quiz surfaced anything worth revisiting before moving on
- ✅ Next lesson: Operators — the building blocks for the work ahead
Practice quiz
A user submits an unknown status string. Which enum converter returns null instead of throwing?
- Status::from($input)
- Status::cases()
- Status::tryFrom($input)
- Status::value($input)
Answer: Status::tryFrom($input). tryFrom() returns null for an invalid value, so Status::tryFrom($input) ?? Status::Todo gives a safe default. from() throws a ValueError.
What is the difference between use ($x) and use (&$x) in a closure?
- use ($x) captures a copy; use (&$x) captures by reference
- No difference
- use (&$x) is invalid syntax
- use ($x) captures by reference
Answer: use ($x) captures a copy; use (&$x) captures by reference. use ($x) copies the value at definition time; use (&$x) shares one live variable with the outer scope, which is how counters work.
Two traits a class uses both define save(). What must you do?
- Nothing, the last one wins
- Rename one trait
- Delete one save() method
- Resolve it with A::save insteadof B in the use block
Answer: Resolve it with A::save insteadof B in the use block. You must resolve the conflict explicitly: A::save insteadof B; picks the winner, optionally B::save as saveB; keeps the other. Otherwise PHP raises a fatal error.
Why use a generator to read a 5 GB log file instead of file()?
- Generators are faster to type
- file() loads every line into memory at once; a generator yields one line at a time
- file() cannot open large files
- Generators sort the lines
Answer: file() loads every line into memory at once; a generator yields one line at a time. file() loads all lines into an array (5 GB in memory). A generator yields one line at a time, keeping memory near-constant regardless of file size.
With declare(strict_types=1), what happens when you call f("9") on function f(int $n)?
- A TypeError is thrown
- It becomes 9 silently
- It returns null
- It becomes 9.0
Answer: A TypeError is thrown. Strict mode disables coercion, so the string "9" is not converted to int 9 and a TypeError is thrown. Coercive mode would have made it 9.
When you json_encode an object implementing JsonSerializable, what gets encoded?
- All public properties as-is
- Only private properties
- Whatever jsonSerialize() returns
- Nothing, it throws
Answer: Whatever jsonSerialize() returns. The output is whatever jsonSerialize() returns (typically a curated associative array), not the raw public properties.
In the Task build, how does jsonSerialize() output the enum status cleanly?
- It returns $this->status directly
- It returns $this->status->value
- It calls json_encode on the enum
- It casts the enum to string with (string)
Answer: It returns $this->status->value. A backed enum exposes ->value, so returning $this->status->value yields the plain string ("done") instead of the raw enum object.
Why is an exhaustive match($this) with no default arm a feature in an enum method?
- It runs faster
- It allows duplicate cases
- It silences all errors
- Adding a new enum case forces you to update the method
Answer: Adding a new enum case forces you to update the method. With no default arm, adding a new case makes match throw UnhandledMatchError until you handle it, so the compiler reminds you to update label().
Where must declare(strict_types=1) appear in a file?
- Anywhere before the class
- As the very first statement in the file
- Inside the class body
- After the namespace only
Answer: As the very first statement in the file. strict_types must be the file first statement (only an opening tag may precede it), or PHP raises a fatal error.
In a single-file script, in what order must the enum, trait, class, and object creation appear?
- Object first, then class
- Class first, then enum
- Enum and trait before the class that uses them, objects last
- Order does not matter
Answer: Enum and trait before the class that uses them, objects last. PHP needs each symbol defined before use, so declare the enum and trait before the class, and create objects only after the classes exist.
Continue this course
- Previous: Type Declarations & Typed Properties
- Next: Operators