Separating Read and Write Models in PHP: Why Using Write Models for Retrieval Is Problematic
The article explains why employing write‑oriented models for data retrieval in PHP leads to security leaks, unnecessary validation, and performance issues, and proposes dedicated read models, static constructors, and specialized repositories to cleanly separate read and write concerns.
Models are powerful for interacting with data storage, defining structures and ensuring consistency, but using them for data retrieval—especially beyond simple CRUD—introduces several drawbacks.
Creating a Model to Work
A simple example shows a User class with constructor‑based validation and a UserRepository interface for saving instances.
class User {
public function __construct(
public string $email,
public string $name,
public string $password,
) {
Assert::email($email);
Assert::notEmpty($name);
Assert::password($password, strength: 3);
}
}
interface UserRepository {
public function save(User $user): void;
}When creating a new user, the model validates the email, name, and password before the repository persists the data.
$user = new User(
$request->get('email'),
$request->get('name'),
$request->get('password')
);
$repository->save($user);Problem: Reading Model Attributes That Should Not Be Exposed
Adding a get method to the repository and returning the full model as JSON leaks sensitive fields such as the hashed password and any irrelevant data (e.g., an active flag), causing security and performance concerns.
interface UserRepository {
public function save(User $user): void;
public function get(string $email): User;
}
return new Response(
json_encode($repository->get($request->get('email')))
);The resulting JSON includes the password hash, which should never be transmitted.
Problem: Unnecessary Validation
Validating data again when reading already‑validated records wastes resources and can break when validation rules evolve, potentially causing legitimate reads to fail.
Problem: Adding Extra Data to the Model
Clients often need additional information (e.g., comment count). Using the write model forces extra queries and duplicate code, reducing performance and reusability.
return new Response(
json_encode(array_merge(
$repository->get($request->get('email')),
['comments' => $commentRepository->count($request->get('email'))]
))
);Problem: Are Insert and Update Really the Same?
Reusing the same model for updates forces callers to supply placeholder values for fields that are not being changed (e.g., password when only the name changes), leading to fragile code.
Solution: Separate Model per Use‑Case
Introduce dedicated read‑only models such as UserRead that contain only the fields needed for presentation, and specialized repositories to fetch them.
final readonly class UserRead {
public function __construct(
public string $email,
public string $name,
public int $commentCount,
) {}
}
interface UserReadRepository {
public function get(string $email): UserRead;
}Similarly, create distinct update models for data changes and password changes, each with its own validation logic.
final readonly class UserDataUpdate {
public function __construct(
public string $email,
public string $name,
) {
Assert::notEmpty($name);
}
}
final readonly class UserPasswordUpdate {
public function __construct(
public string $email,
public string $password,
) {
Assert::password($password, strength: 3);
}
}
interface UserRepository {
public function save(User $user): void;
public function updateData(UserDataUpdate $userDataUpdate): void;
public function updatePassword(UserPasswordUpdate $userPasswordUpdate): void;
}This approach eliminates accidental exposure of sensitive data, removes redundant validation on reads, and allows each operation to be optimized and secured independently.
Summary
Just as real‑world objects are modeled differently for drivers, mechanics, or learners, software models should be tailored to their specific use‑cases; separating read and write concerns leads to safer, cleaner, and more maintainable backend code.
php中文网 Courses
php中文网's platform for the latest courses and technical articles, helping PHP learners advance quickly.
How this landed with the community
Was this worth your time?
0 Comments
Thoughtful readers leave field notes, pushback, and hard-won operational detail here.