Intermediate · 45 MINUTES · VERSION 1
PHP objects, constructors and validated state
Protect a small rule with typed objects, composition and a carefully explained readonly boundary.
Reviewed 23 Oct 2026Your setup may differ. Windows, Ubuntu, and optional tool bundles →
What you will learn
- Build an object with validated state through a constructor.
- Explain class, instance, property and method.
- Compose two small objects and describe the boundary of readonly.
Theory: state and behavior
A class describes a kind of object. An instance is one object made from that class. A property holds its state; a method is a function attached to it. A constructor runs during object creation. If it throws, the caller does not receive a successfully constructed instance.
A useful object protects a rule, not just a collection of getters. Here StudyMinutes accepts only integers between 0 and 480. StudySession combines a topic with that validated duration. This is composition: one object contains another. The caller chooses the duration rather than letting the session secretly read a database.
Use this in backend work when the same rule appears in several entry points. Framework request validation still checks unknown HTTP input; a domain object protects its own invariant after that boundary. A valid duration says nothing about who is authorized to save a session.
Worked example: validated study objects
Required: PHP 8.4+ CLI. No framework, database or paid service. Save the complete file as study_objects.php. Laragon/XAMPP are optional tool bundles; use any compatible PHP installation.
<?php
declare(strict_types=1);
final readonly class StudyMinutes
{
public function __construct(public int $value)
{
if ($value < 0 || $value > 480) {
throw new InvalidArgumentException('Use whole minutes from 0 to 480.');
}
}
}
final readonly class StudySession
{
public string $topic;
public function __construct(string $topic, public StudyMinutes $duration)
{
$topic = trim($topic);
if ($topic === '' || strlen($topic) > 80) {
throw new InvalidArgumentException('Use a topic from 1 to 80 bytes.');
}
$this->topic = $topic;
}
public function describe(): string
{
return $this->topic.': '.$this->duration->value.' minutes';
}
}
if (realpath($_SERVER['SCRIPT_FILENAME'] ?? '') === __FILE__) {
$session = new StudySession(' PHP OOP ', new StudyMinutes(30));
echo $session->describe().PHP_EOL;
echo 'Original minutes: '.$session->duration->value.PHP_EOL;
try {
new StudyMinutes(-1);
} catch (InvalidArgumentException $error) {
echo 'Rejected: '.$error->getMessage().PHP_EOL;
}
}
Run and understand the result
Windows PowerShell:
php --version
php ./study_objects.php
Ubuntu Bash:
php --version
php ./study_objects.php
PHP OOP: 30 minutes
Original minutes: 30
Rejected: Use whole minutes from 0 to 480.
Both commands assume PHP is on PATH; the general setup guide explains alternatives. The same source is used on both systems. Windows PHP 8.4.26 was executed; Ubuntu execution is not claimed.
Constructor property promotion declares and initializes value and duration. Topic is assigned once after trimming. A zero-minute session is allowed; this is planned data, not proof of study. The demo guard runs examples only when this file is executed directly. Requiring it in a test defines classes without printing the demo.
Readonly is a boundary
The two classes are final, so this example does not offer inheritance. Their readonly properties cannot be reassigned after initialization. A new StudyMinutes object represents a changed value.
Readonly is not automatically deep immutability. A readonly property that points at an ordinary mutable object can still expose changes inside that other object. Here the nested duration is also readonly and holds an integer; that deliberate design keeps this small object graph stable. Readonly also does not provide database transactions, validation of every possible input, thread safety or authorization.
Guided lab
- Predict the three output lines before running the program.
- Try 0 and 480; both must construct successfully. Try -1 and 481; both must throw.
- Try a topic with spaces around it, an empty topic and a topic longer than 80 bytes. The bound is bytes, not Unicode characters.
- In a separate test, attempt to assign a different value to an initialized duration. Catch Error and confirm the original value is unchanged.
- Require the file twice with require_once. Creating and describing objects should return values without printing anything. Record your actual results in the private practice journal.
Independent exercise
Add an immutable SessionPair object containing two StudySession instances. Give it totalMinutes() that returns their sum without changing either session. Decide whether the pair permits more than 480 total minutes; document that separate rule instead of accidentally reusing the single-session limit.
Test two zero sessions, 30+45, 480+480, repeated calls and unchanged child objects. Use a separate practice file. Hint: sum the two duration values; do not add mutable counters or database access.
Common mistakes
- Treating type declarations as permission checks.
- Validating only in a controller and allowing other callers to create invalid objects.
- Counting bytes as if they were Malayalam characters.
- Assuming readonly freezes any object reachable from a property.
- Printing or saving inside a value method, which makes reuse harder.
- Introducing inheritance before a concrete requirement needs it.
Self-check and interview
What invariant does StudyMinutes protect? What happens if its constructor rejects input? Why is composition enough here?
Interview: Explain the difference between a validated value and an authorized action. Outline the input boundary, constructor rule, caller permission check and side-effect-free tests. Be ready to explain where object design would add unnecessary complexity for a one-line calculation.
English practice and glossary
- Class: a definition used to construct objects.
- Instance: one object created from a class.
- Constructor: initialization code run during construction.
- Invariant: a rule that must remain true for a valid object.
- Composition: combining objects rather than inheriting their implementation.
- Readonly: a property restriction on reassignment after initialization.
Say: “This object protects __. Invalid input causes __. The caller still checks __.” Write your explanation in the English practice journal; self-ratings are not automatic language grades.
Recap and next step
Use small objects to keep a concrete rule valid. Prefer clear composition and pure returned results. Next, study interfaces and explicit dependency injection; do not add abstractions before you can explain the requirement. Official source links are recorded with this version.
Reveal exercise hints and self-check answers
A valid StudyMinutes value is a whole integer from 0 to 480. A rejected constructor throws instead of returning a usable instance. Composition models a session containing its duration without an inheritance hierarchy. Readonly does not grant save permission.
Exercise outline: store two readonly session properties, return the sum of their duration values, and choose a documented pair-total rule. If the pair has no 480 limit, 480+480 returns 960; a caller must not pretend that means one permitted 960-minute session.