PHP Object-Oriented Programming: Composition
In this PHP lesson, we will study Composition through small executable cases. First we will make the normal result clear; then inspect data flow, ownership, failure behavior, and the caller contract before adding framework or infrastructure complexity.
Professional code is readable under change. A future developer should be able to identify which input is trusted, who may update state, where I/O happens, and how an expected error reaches a caller without reconstructing the whole application.
What You Will Build
10 focused PHP examples for Composition. Each has a visible result, an explicit contract, and a next change to test so the lesson becomes reusable knowledge rather than passive reading.
Example 1: Enable strict types
Let’s isolate one PHP Object-Oriented Programming rule together. Run it, trace the inputs and output, then change one boundary value before reusing the pattern. Small observable programs turn syntax into a reliable engineering habit.
Keep contracts explicit: validate data before use, make ownership and mutation visible, and return a named result or failure a caller can test. Static types and a successful build help, but runtime boundaries still need intentional checks.
PHP
<?php
declare(strict_types=1);
$name = 'Asha';
echo "Hello, {$name}\n";Expected Result
Hello, Asha
Try next: make the nearest empty, invalid, missing, or boundary case observable instead of leaving behavior to an unchecked assumption.
Example 2: Return a typed value
Let’s isolate one PHP Object-Oriented Programming rule together. Run it, trace the inputs and output, then change one boundary value before reusing the pattern. Small observable programs turn syntax into a reliable engineering habit.
Keep contracts explicit: validate data before use, make ownership and mutation visible, and return a named result or failure a caller can test. Static types and a successful build help, but runtime boundaries still need intentional checks.
PHP
function square(int $value): int {
return $value * $value;
}
echo square(7);Expected Result
49
Try next: make the nearest empty, invalid, missing, or boundary case observable instead of leaving behavior to an unchecked assumption.
Example 3: Use an immutable value object
Let’s isolate one PHP Object-Oriented Programming rule together. Run it, trace the inputs and output, then change one boundary value before reusing the pattern. Small observable programs turn syntax into a reliable engineering habit.
Keep contracts explicit: validate data before use, make ownership and mutation visible, and return a named result or failure a caller can test. Static types and a successful build help, but runtime boundaries still need intentional checks.
PHP
final readonly class Topic {
public function __construct(
public string $id,
public string $title,
) {}
}
echo (new Topic('p-1', 'PHP'))->title;Expected Result
PHP
Try next: make the nearest empty, invalid, missing, or boundary case observable instead of leaving behavior to an unchecked assumption.
Example 4: Transform a list
Let’s isolate one PHP Object-Oriented Programming rule together. Run it, trace the inputs and output, then change one boundary value before reusing the pattern. Small observable programs turn syntax into a reliable engineering habit.
Keep contracts explicit: validate data before use, make ownership and mutation visible, and return a named result or failure a caller can test. Static types and a successful build help, but runtime boundaries still need intentional checks.
PHP
$titles = ['php', 'sql', 'spring'];
$upper = array_map(
static fn (string $title): string => strtoupper($title),
$titles,
);
print_r($upper);Expected Result
PHP, SQL, and SPRING are printed
Try next: make the nearest empty, invalid, missing, or boundary case observable instead of leaving behavior to an unchecked assumption.
Example 5: Validate an explicit boundary
Let’s isolate one PHP Object-Oriented Programming rule together. Run it, trace the inputs and output, then change one boundary value before reusing the pattern. Small observable programs turn syntax into a reliable engineering habit.
Keep contracts explicit: validate data before use, make ownership and mutation visible, and return a named result or failure a caller can test. Static types and a successful build help, but runtime boundaries still need intentional checks.
PHP
function label(?string $value): string {
$trimmed = trim((string) $value);
if ($trimmed === '') {
throw new InvalidArgumentException('label is required');
}
return $trimmed;
}
echo label(' PHP ');Expected Result
PHP
Try next: make the nearest empty, invalid, missing, or boundary case observable instead of leaving behavior to an unchecked assumption.
Example 6: Return a deliberate API error
Let’s isolate one PHP Object-Oriented Programming rule together. Run it, trace the inputs and output, then change one boundary value before reusing the pattern. Small observable programs turn syntax into a reliable engineering habit.
Keep contracts explicit: validate data before use, make ownership and mutation visible, and return a named result or failure a caller can test. Static types and a successful build help, but runtime boundaries still need intentional checks.
PHP
try {
$title = label($_POST['title'] ?? null);
echo json_encode(['title' => $title]);
} catch (InvalidArgumentException $error) {
http_response_code(422);
echo json_encode(['code' => 'VALIDATION_ERROR']);
}Expected Result
Either a JSON title or a 422 validation error
Try next: make the nearest empty, invalid, missing, or boundary case observable instead of leaving behavior to an unchecked assumption.
Example 7: Decode a JSON request
Let’s isolate one PHP Object-Oriented Programming rule together. Run it, trace the inputs and output, then change one boundary value before reusing the pattern. Small observable programs turn syntax into a reliable engineering habit.
Keep contracts explicit: validate data before use, make ownership and mutation visible, and return a named result or failure a caller can test. Static types and a successful build help, but runtime boundaries still need intentional checks.
PHP
$payload = json_decode(
'{"title":"PHP"}',
true,
512,
JSON_THROW_ON_ERROR,
);
echo $payload['title'];Expected Result
PHP
Try next: make the nearest empty, invalid, missing, or boundary case observable instead of leaving behavior to an unchecked assumption.
Example 8: Use a prepared database statement
Let’s isolate one PHP Object-Oriented Programming rule together. Run it, trace the inputs and output, then change one boundary value before reusing the pattern. Small observable programs turn syntax into a reliable engineering habit.
Keep contracts explicit: validate data before use, make ownership and mutation visible, and return a named result or failure a caller can test. Static types and a successful build help, but runtime boundaries still need intentional checks.
PHP
$pdo = new PDO($dsn, $user, $password, [
PDO::ATTR_ERRMODE => PDO::ERRMODE_EXCEPTION,
]);
$statement = $pdo->prepare(
'SELECT id, title FROM topic WHERE id = :id'
);
$statement->execute(['id' => 'p-1']);Expected Result
The id is bound separately from the SQL text
Try next: make the nearest empty, invalid, missing, or boundary case observable instead of leaving behavior to an unchecked assumption.
Example 9: Hash a password
Let’s isolate one PHP Object-Oriented Programming rule together. Run it, trace the inputs and output, then change one boundary value before reusing the pattern. Small observable programs turn syntax into a reliable engineering habit.
Keep contracts explicit: validate data before use, make ownership and mutation visible, and return a named result or failure a caller can test. Static types and a successful build help, but runtime boundaries still need intentional checks.
PHP
$hash = password_hash(
'replace-at-registration',
PASSWORD_DEFAULT,
);
var_dump(password_verify('replace-at-registration', $hash));Expected Result
bool(true)
Try next: make the nearest empty, invalid, missing, or boundary case observable instead of leaving behavior to an unchecked assumption.
Example 10: Model finite state with an enum
Let’s isolate one PHP Object-Oriented Programming rule together. Run it, trace the inputs and output, then change one boundary value before reusing the pattern. Small observable programs turn syntax into a reliable engineering habit.
Keep contracts explicit: validate data before use, make ownership and mutation visible, and return a named result or failure a caller can test. Static types and a successful build help, but runtime boundaries still need intentional checks.
PHP
enum PublishState: string {
case Draft = 'draft';
case Published = 'published';
}
$state = PublishState::Published;
echo $state->value;Expected Result
published
Try next: make the nearest empty, invalid, missing, or boundary case observable instead of leaving behavior to an unchecked assumption.
Design Review and Worked Trace
Review PHP Object-Oriented Programming: Composition as a full trace: input, validation, calculation or side effect, output, and cleanup. State the normal result, then state the boundary behavior. If either is hard to explain, split the work into a smaller function with a narrower contract.
Keep secret values and privileged decisions outside display code. Authentication establishes an identity; authorization decides what that identity may do. Check both close to protected operations, and use intentional failure responses rather than leaking internal details or crashing generically.
- Run the normal input and record the exact result.
- Test an empty, malformed, or boundary input deliberately.
- Keep state, ownership, and side effects visible to the caller.
- Return a result or named failure that can be tested.
- Never log credentials, tokens, or sensitive input.
This discipline makes PHP code easier to review, safer to change, and more useful in production than a collection of untested syntax fragments.
Common Mistakes
- Skipping input validation because a local example happened to work.
- Hiding mutation, ownership, or failure behavior inside a convenient shortcut.
- Using a client-side display rule as the only protection for sensitive work.
- Copying code without changing an input to prove its contract.
