objects namespaces and application architecture
Application Architecture Overview
Application architecture is how a codebase organises responsibilities. It decides where request handling, validation, business rules, persistence, external services, rendering, and background work live.
Architecture is not about using the most impressive pattern names. Good architecture makes common changes easier, keeps dangerous boundaries visible, and helps developers find the right place for code.
Think In Boundaries
Most PHP applications have the same broad boundaries:
HTTP request
-> controller or route handler
-> application service or action
-> domain objects and rules
-> database, queue, email, filesystem, API clients
-> response
The names vary by framework, but the responsibilities keep coming back.
Controllers Should Coordinate
A controller or route handler should translate the web request into an application action. It should not become the home for every business rule.
<?php
declare(strict_types=1);
final class RegisterUserController
{
public function __construct(private RegisterUser $registerUser)
{
}
public function __invoke(array $request): string
{
$email = (string) ($request['email'] ?? '');
$this->registerUser->handle($email);
return 'registered';
}
}
final class RegisterUser
{
public function handle(string $email): void
{
if (filter_var($email, FILTER_VALIDATE_EMAIL) === false) {
throw new InvalidArgumentException('Email address is invalid.');
}
}
}
$controller = new RegisterUserController(new RegisterUser());
echo $controller(['email' => 'nia@example.com']) . PHP_EOL;
// Prints:
// registered
The controller handles request shape. The application service owns the use case.
Application Services Represent Use Cases
An application service coordinates a user action or system action.
<?php
declare(strict_types=1);
interface Mailer
{
public function send(string $to, string $subject): void;
}
final class EchoMailer implements Mailer
{
public function send(string $to, string $subject): void
{
echo 'Sending ' . $subject . ' to ' . $to . PHP_EOL;
}
}
final class SendWelcomeEmail
{
public function __construct(private Mailer $mailer)
{
}
public function handle(string $email): void
{
$this->mailer->send($email, 'Welcome');
}
}
(new SendWelcomeEmail(new EchoMailer()))->handle('nia@example.com');
// Prints:
// Sending Welcome to nia@example.com
The service is not tied to HTTP. It can be called from a controller, command, queue worker, or test.
Domain Objects Hold Business Rules
Domain objects represent important concepts and rules.
<?php
declare(strict_types=1);
final class OrderLine
{
public function __construct(
private int $unitPricePennies,
private int $quantity,
) {
if ($unitPricePennies < 0 || $quantity < 1) {
throw new InvalidArgumentException('Invalid order line.');
}
}
public function totalPennies(): int
{
return $this->unitPricePennies * $this->quantity;
}
}
echo (new OrderLine(1299, 3))->totalPennies() . PHP_EOL;
// Prints:
// 3897
This rule does not need to know about controllers, HTML, SQL, or email.
Infrastructure Talks To The Outside
Infrastructure code handles databases, queues, filesystems, mailers, HTTP clients, and framework adapters.
<?php
declare(strict_types=1);
interface UserRepository
{
public function existsByEmail(string $email): bool;
}
final class InMemoryUserRepository implements UserRepository
{
public function __construct(private array $emails)
{
}
public function existsByEmail(string $email): bool
{
return in_array(strtolower($email), $this->emails, true);
}
}
$repository = new InMemoryUserRepository(['nia@example.com']);
echo $repository->existsByEmail('NIA@example.com') ? 'exists' : 'missing';
echo PHP_EOL;
// Prints:
// exists
A real implementation might use PDO, an ORM, Redis, or an API. The interface lets application code stay focused on the use case.
Read Architecture From The Change You Are Making
When joining a codebase, do not start by arguing about pattern names. Trace one feature.
Where does the request enter?
Where is input validated?
Where is the business rule?
Where is data saved or loaded?
Where are external systems called?
Where is the response built?
These questions reveal the architecture faster than a folder list alone.
What To Remember
Architecture is responsibility management. Keep request handling, business rules, persistence, external integrations, and presentation from collapsing into one place. As a junior developer, the practical skill is finding the right existing layer before adding code.
Practice
Task: Separate A Registration Flow
Sketch a small registration flow with separate responsibilities.
Requirements
- Use
declare(strict_types=1);. - Create a controller-like class that reads request data.
- Create an application service that handles registration.
- Validate the email in the application service.
- Use an injected mailer interface.
- Print output for one valid registration.
- Show one invalid email by catching the exception.
- Include the expected output as comments in the same PHP code block.
The goal is to keep request handling, use-case logic, and external email sending separate.
Show solution
<?php
declare(strict_types=1);
interface Mailer
{
public function send(string $to, string $subject): void;
}
final class EchoMailer implements Mailer
{
public function send(string $to, string $subject): void
{
echo 'Email: ' . $subject . ' -> ' . $to . PHP_EOL;
}
}
final class RegisterUser
{
public function __construct(private Mailer $mailer)
{
}
public function handle(string $email): void
{
if (filter_var($email, FILTER_VALIDATE_EMAIL) === false) {
throw new InvalidArgumentException('Email address is invalid.');
}
$this->mailer->send(strtolower($email), 'Welcome');
}
}
final class RegisterUserController
{
public function __construct(private RegisterUser $registerUser)
{
}
public function __invoke(array $request): string
{
$this->registerUser->handle((string) ($request['email'] ?? ''));
return 'registered';
}
}
$controller = new RegisterUserController(new RegisterUser(new EchoMailer()));
echo $controller(['email' => 'NIA@example.com']) . PHP_EOL;
try {
$controller(['email' => 'not-an-email']);
} catch (InvalidArgumentException $exception) {
echo $exception->getMessage() . PHP_EOL;
}
// Prints:
// Email: Welcome -> nia@example.com
// registered
// Email address is invalid.
The controller reads request-shaped data. The application service owns the use case and validation. The mailer interface represents the external email boundary.