Map

Porto SAP

Wiki conceptsoftware-architecturephplaraveldddclean-architecture โ†ณ show in map Markdown
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
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 (Authentication section might hold User, 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.