What you'll learn
Integrate the entire persistence phase in a secure content application. You will plan the schema, build authenticated CRUD, add search and pagination, and defend every trust boundary with tests.
By the end of this lesson, you'll be able to:
- Plan a vertical slice from request to database
- Combine authorization, validation, transactions, and search
- Review a project using objective release criteria
Core mental model
Professional PHP is less about memorizing APIs and more about choosing a clear boundary for each responsibility. Use this table as a decision guide while reading the examples.
| Concept | What it protects | Decision rule |
|---|---|---|
| Vertical slice | End-to-end feedback | Finish one useful workflow before creating every layer. |
| Ownership rule | Object-level authorization | Check the authenticated actor for every write and private read. |
| Release checklist | Consistent quality | Turn security, tests, and operations into evidence. |
Professional workflow
Build the feature in small, verifiable steps. Each step leaves the system in a state you can test.
- Describe the data-driven application boundary: its inputs, outputs, invariants, and expected failures.
- Implement the smallest happy path behind an explicit contract.
- Add validation and translate low-level failures into language the caller understands.
- Exercise the boundary with realistic data, then inspect output, logs, and resource cleanup.
- Refactor only after behavior is protected by a repeatable check.
Make the boundary visible
Guided code lab
Coordinate article creation
Validation and authorization happen before the transaction; persistence owns the atomic write.
<?php
final readonly class CreateArticle
{
public function __construct(private ArticleRepository $articles, private Transaction $tx) {}
public function handle(User $author, CreateArticleData $data): Article
{
if (!$author->canPublish()) throw new Forbidden('Publishing permission required');
return $this->tx->run(function () use ($author, $data): Article {
$article = Article::draft($author->id, $data->title, $data->body);
$this->articles->save($article);
return $article;
});
}
}Define the acceptance matrix
A small matrix ensures failure paths receive the same attention as the demo path.
Create: valid author -> 201 + persisted article
Create: invalid title -> 422 + field error + no row
Edit: owner -> 200 + updated version
Edit: different user -> 403 + unchanged row
Delete: stale CSRF token -> 419 + unchanged row
Search: empty result -> 200 + empty collection
Pagination: page beyond end -> 200 + empty collectionProduction practice
Contract
Define request DTOs, use-case results, repository ports, and HTTP translation before polishing UI.
Verification
Cover authorization and transaction rollbacks with integration tests; manually review escaping and CSRF.
Operations
Ship structured logs, health checks, migrations, backups, and a rollback note with the feature.
Common failure mode
Independent workshop
Build a multi-user knowledge base with drafts, publishing, tags, comments, search, and paginated author dashboards.
Your finished workshop must include:
- Schema and architecture notes
- Authenticated vertical slices with tests
- Security and deployment checklist plus demo data
Definition of done
Recap & quick check
Key takeaways
- Vertical slices expose integration risk early.
- Authorization applies to individual resources.
- Transactions protect multi-row workflows.
- A release includes operational evidence, not only features.
Quick check
1. Where must ownership be enforced?
2. What should a failed transactional test assert?
3. Why build a vertical slice first?
Keep the workshop: later phases deliberately build on these boundaries, so today's small example can become part of your portfolio architecture.