Porto SAP
- title
- Porto SAP
- type
- concept
- summary
- Layered PHP/Laravel-oriented architectural pattern โ Containers (per-domain business logic) + Ship (shared infrastructure), Actions orchestrating single-
run()Tasks, every artifact in its own class and predictable folder - tags
- software-architecture, php, laravel, ddd, clean-architecture
- sources
- porto-readme
- created
- 2026-05-13
- updated
- 2026-05-13
Porto is an architectural pattern published by Mahmoud Zalt (MIT, ~1.6kโ on github.com/Mahmoudz/Porto) for organising server-side application code โ primarily aimed at PHP/Laravel but framework-agnostic in principle. The framework that implements Porto for Laravel is Apiato (apiato.io); the Porto repo itself is the pattern spec and documentation, not a runtime.
The current README is a thin landing page โ the real content lives in the docs site. Two ideas carry the pattern.
Two layers: Containers and Ship
Application code splits in half:
app/
โโโ Containers/
โ โโโ {Section}/
โ โโโ {Container}/
โ โโโ Actions/
โ โโโ Tasks/
โ โโโ Models/
โ โโโ Controllers/
โ โโโ Requests/
โ โโโ Routes/
โ โโโ Transformers/
โ โโโ Events/
โ โโโ Listeners/
โ โโโ Jobs/
โ โโโ Commands/
โ โโโ Migrations/
โ โโโ Tests/
โ โโโ Configs/
โ โโโ Exceptions/
โโโ Ship/
โโโ Parents/ # base classes (Controller, Model, Request, ...)
โโโ Providers/
โโโ Middleware/
โโโ Exceptions/
โโโ Configs/
- Containers hold business logic for one bounded area (
User,Billing,Order, ...). Sections group related Containers (Authenticationsection might holdUser,Login,Roles). - Ship holds everything cross-cutting: framework base classes Containers extend, global middleware, shared service providers, app-wide configs.
The stated payoff is a monolith-to-microservices migration path: a Container is meant to be self-contained enough that splitting it into its own service is mostly a build/deploy change, not a code rewrite. Whether real-world Porto codebases actually keep Containers that clean is another question โ same gap that bites bounded-context-as-microservice in DDD literature.
Actions and Tasks
Inside a Container, business logic is split into two component types:
- Action โ orchestrates one use case (e.g.
RegisterUserAction). It composes Tasks in a sequence and is what Controllers, Jobs, or Commands call into. - Task โ does one thing, has one public method
run(), is reusable across Actions (e.g.CreateUserTask,SendWelcomeEmailTask,AssignRoleTask). - Sub-Action โ a named group of Tasks shared by multiple Actions, to avoid repeating Task sequences.
This is the same idea as use-case classes in Clean Architecture or interactors/services in DDD-flavoured codebases, with the rule tightened to one-class-one-method-one-job. Controllers stay thin: parse the request, call an Action, return the result.
Everything-has-its-own-class
The pattern's most distinctive trait isn't the layers โ it's the insistence that each conceptually distinct artifact lives in its own class in a predictable folder. Routes (web / api / cli) live in dedicated files; request validation in Form Request classes; response shaping in Transformers; cross-cutting concerns in Middleware; domain events in Events + Listeners; queue work in Jobs; CLI in Commands; data access in Repositories; persistent state in Models; DTOs for inter-Container data transfer; Exceptions per error condition.
The result is a wide-and-shallow tree: many small files, each named after what it does. Authors of conventional Laravel codebases sometimes object that this is over-engineering; Porto's counter is that the cost of one more file is small and the cost of finding "where does X happen" in a monolithic Controller is high. The pattern bets on tooling โ IDE jump-to-symbol, search โ being cheap and exhaustive class-splitting therefore being a structural win.
The AI-friendliness pitch
The current README leads with this:
Porto's strict adherence to the single responsibility principle enhances its compatibility with AI tools like GitHub Copilot, which thrive on clear, well-defined classes.
That argument lines up with clean-code-coding-agents (and its parent reasoning from Uncle Bob's Clean Architecture): LLM context windows are finite, and a codebase where each file does one named thing is cheaper to navigate per token than a codebase where you have to read a 600-line Controller to figure out what happens during POST /users. Porto's small-classes-in-predictable-folders shape is what an agent's grep-and-read loop wants: a file named RegisterUserAction.php in app/Containers/Authentication/User/Actions/ tells the agent what it does and where to look for the next hop without opening the file.
This is the pattern's most defensible 2026-era selling point. The monolith-to-microservices claim is harder to verify (it's the kind of thing that's true on day one and degrades silently as Container boundaries leak), but the agent-friendliness claim is testable per-file: open a file, ask whether its name tells you what it does. Porto says yes by construction; conventional MVC says "maybe, depending on the developer."
Lineage
Porto blends:
- MVC โ keeps Models/Views/Controllers as Laravel uses them, but demotes Controllers to thin adapters.
- DDD-lite โ Containers are bounded contexts in everything but name. Sections are a coarser grouping.
- Clean Architecture โ Actions are use cases; Tasks are the granular operations under them.
- Hexagonal / ports-and-adapters โ Ship/Container split echoes the "your domain core doesn't depend on infrastructure" rule, though Porto enforces it by folder convention rather than by dependency-rule lint.
What it doesn't take from DDD: aggregates, value objects, domain events as first-class invariants, ubiquitous language drilling. Porto is a structural pattern, not a modelling philosophy.
When it's a fit
- Laravel apps that have grown past the default
app/Http/Controllers/flat layout and need a place for cross-cutting features. - Teams that want to enforce structure by convention instead of by review.
- Codebases worked on by coding agents alongside humans (see clean-code-coding-agents).
When it isn't
- Small apps where the file overhead is real cost, not paid-back-later cost.
- Teams that haven't bought into one-class-one-job โ Porto without that discipline degrades into Laravel-with-more-folders.
- Languages without Laravel-shaped lifecycle hooks (Providers, Middleware, Form Requests, Events) โ the pattern transplants, but the implementation specifics don't.
Where it sits in the wiki
- clean-code-coding-agents โ the broader argument that structure matters more, not less, with agents in the loop.
- programming-as-theory-building โ Naur's frame for why folder-convention enforcement is a weak proxy for the team's shared model, but the best one a pattern can offer.
- no-silver-bullet โ Brooks on accidental vs essential complexity; Porto attacks accidental complexity (where does X go?) and leaves essential complexity (what should X do?) to the domain.