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.
🛠️ Build Challenge: A Typed, Serializable Task
Your mission is to build one small class that pulls together four features from this block:
Start from this scaffold — fill in each TODO , then run it and match the target output in the comments. Try it yourself first.
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.
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.
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.