Weave Code
Weaver
Helps Laravel developers discover, compare, and choose open-source packages. See popularity, security, maintainers, and scores at a glance to make better decisions.
Feedback
Share your thoughts, report bugs, or suggest improvements.
Subject
Message

Laravel Porto Laravel Package

ibecsystems/laravel-porto

A Laravel package for building applications with Porto architecture. It helps organize code into Containers, Ship, and Components, with Artisan scaffolding for modular, domain-focused development that stays clean and maintainable as projects grow.

View on GitHub
Deep Wiki
Context7

Getting Started

Porto (the "Porto" / Containers-Ship pattern by Mahmoud Zalt) splits a Laravel app into two top-level namespaces:

  • Ship/ — shared infrastructure: base classes, abstract repositories, cross-cutting helpers.
  • Containers/ — one directory per business capability, each self-contained (Actions/, Tasks/, Models/, Data/, UI/ with routes/controllers, plus its own Providers/, Migrations/, Tests/).

ibecsystems/laravel-porto is a scaffolding package: it registers a service provider that boots a set of porto:* generator commands so you don't hand-create that tree every time.

Minimal steps:

  1. Install into a Laravel app, pinned to the current major:
    composer require ibecsystems/laravel-porto:^3.0 --dev
    
    Treat it as a dev dependency — it exists to generate code, not to serve runtime logic.
  2. Confirm what commands actually shipped:
    php artisan list | grep porto
    
    The list output is your source of truth. This is a low-traffic package (maintained by the IBEC-BOX org, now on the v3 line with v3.0.2 as the latest patch), so do not assume the README's command names are complete or current — verify against src/Commands/ in vendor/ibecsystems/laravel-porto.
  3. Decide the two PSR-4 roots before generating anything. Add to composer.json:
    "autoload": {
      "psr-4": {
        "App\\": "app/",
        "Containers\\": "src/Containers/",
        "Ship\\": "src/Ship/"
      }
    }
    
    Then composer dump-autoload.
  4. First use case: generate a single vertical slice. Pick one real feature (e.g. "Blog") and generate the container, then an Action, then the controller/route inside that container's UI/ folder. Wire the route in that container's own service provider and register that provider in bootstrap/providers.php.

Where to look first, in order: the package's composer.json and src/PortoServiceProvider.php (what's registered, config key names), then src/Commands/ (signature + available stubs), then a generated container (to see the exact namespace/class layout the tool assumes).

The day-to-day value is narrow but real: it standardizes where a "feature" lives so you never debate folder structure again, and it makes containers detachable — you can delete a container folder and the app still boots if nothing references it.

Implementation Patterns

Action-per-endpoint, Task-per-step. The core discipline is that an Action is a single public entry point (usually run() or handle()) that orchestrates Tasks. Controllers should be thin: validate via a Request, call one Action, return a Transformer-formatted response. If an Action starts containing if branches over persistence details, extract a Task.

Container ownership of routes. Keep route files inside each container (Containers/<Name>/UI/API/Routes/*.php) and register them from that container's own provider with Route::middleware(...)->group(...). This is the pattern's main ergonomic win — routes, controllers, and requests for a capability travel together. Downside: debugging route lists needs php artisan route:list plus knowing that each provider loads its own file, so caching behavior deserves attention (see Gotchas).

Repository interface bound in the container provider. Put the contract in Containers/<Name>/Data/Repositories/<X>RepositoryInterface.php, the Eloquent implementation beside it, and bind in Containers/<Name>/Providers/. Resolve the interface in Actions via constructor injection — never app(XRepository::class) inline, or you lose the swap point the pattern is selling you.

Admin UI stubs are part of the offer. The v3.0.x line ships Filament-oriented generators alongside the plain controller/route ones — the recent v3.0.2 patch is literally a stub fix for Filament 2 use imports. If you use Filament, generate the resource into the owning container's UI/ tree rather than under app/Filament, so admin concerns stay inside the capability that owns them. Verify the generated imports resolve before you build on them (see Gotchas).

Generate-then-edit, not generate-then-trust. The generators produce skeletons. Expect to immediately: add use statements, extend the correct base Ship class, and adjust namespaces. Budget for this — a generator that gets you 70% there is still faster than 0%, but do not paste generated code into a PR without reading it.

Per-container migrations and tests. Keep migrations under Containers/<Name>/Database/Migrations and load them from the container provider with $this->loadMigrationsFrom(...). Same instinct for tests: a Pest/PHPUnit suite that lives next to the container is the only reliable way to prove the container is self-contained enough to extract.

Shared abstractions go to Ship, not to a container. When a second container needs the same base repository or value object, promote it to Ship/. The failure mode of Porto in practice is Containers/Foo importing from Containers/Bar — one such import and you've rebuilt a monolith with extra directories. Add a CI grep or an architecture test to ban cross-container imports:

# fail the build on cross-container coupling
grep -rn "use Containers\\\\" src/Containers --include=*.php \
  | grep -vE "use Containers\\\\($(...))" # allow only own container

Integration with the rest of Laravel. Porto does not replace Eloquent, service container, or queue. Jobs, Events, Listeners, and Policies all fit inside the container that owns them. Register event listeners in the container provider. For queued Actions, add ShouldQueue to the Action itself rather than wrapping in a Job — it keeps the call-site identical between sync and async.

Testing shape that actually validates the pattern. One feature test per Action (Action::run(dto) → assert repository calls/response), one unit test per Task with the repository mocked. Because the Action is the seam, these tests survive controller/route refactors — that's the payoff you're buying.

Gotchas and Tips

  • You are on v3, and the pin must reflect that. The v3.0.2 release (fix: filament2 use classes) confirms active patch releases on the 3.x line. Pin "ibecsystems/laravel-porto": "^3.0" (or a commit hash if you need to reproduce a specific stub output) and pin your Laravel version too. The generators are small; if the package breaks on a future Laravel, forking the porto:* commands into your own app/Console/Commands is a half-day job, not a week.
  • The Filament stub fix tells you where the risk lives. v3.0.2 patched wrong/absent use imports in the Filament 2 templates — meaning Filament-generated code was previously emitting classes that didn't resolve. Two consequences: (a) if you generated Filament resources before upgrading, regenerate or manually correct those imports, don't assume the fix retroactively applies to files already on disk; (b) validate generated output by actually booting the resource (php artisan route:list, open the panel), not just by reading the file.
  • Filament 2 vs Filament 3. The patch targets Filament 2 class paths. If your app runs Filament 3, the shipped stubs will still emit v2-era namespaces and you must override them — this is a stub problem, not a package bug you can wait out.
  • Autoload before generate. Running a generator before adding the Containers\ / Ship\ PSR-4 mappings produces classes that exist but cannot be autoloaded. Symptom: Class "Containers\Blog\Actions\..." not found immediately after generation. Fix: composer dump-autoload (-o in CI).
  • --dev vs. production. If you install it in require rather than require-dev, you ship generator commands to production where php artisan list exposes them. Move it to require-dev or wrap command registration in $this->app->isLocal().
  • Provider auto-discovery only covers the package's own provider, not your containers'. Your generated Containers/<Name>/Providers/*ServiceProvider.php must be added to bootstrap/providers.php (Laravel 11+) or config/app.php by hand. A container whose provider isn't registered looks functional until routes are missing — check route:list and event:list first when something "silently doesn't exist."
  • Route caching is the classic trap. Container providers that call Route::group inside boot() are fine, but anything that does runtime discovery or dynamic middleware in that callback will break under php artisan route:cache. Test route:cache && route:clear in CI on every container-provider change.
  • Migration loading order. loadMigrationsFrom inside container providers registers files but ordering is by filename, not by provider boot order. If Foo's migration alters Bar's table, name files with a numeric prefix (2024_01_01_000100_...) so they sort deterministically.
  • Stubs are the extension point — and this release proves it. Before editing generated files by hand for the fifth time (or hand-fixing Filament imports), publish/override the stubs (check src/Commands for a --stubs publish target, or read the command's getStub() path). That's the intended customization hook and it survives regeneration. If no publish hook exists, copy the command class into your app and point it at your own stubs/ directory.
  • Debugging generator output. Generated classes frequently reference base classes from Ship\Parents\* that may not exist yet. Error like Class "Ship\Parents\Action" not found means either the PSR-4 root is wrong or the package's Ship scaffolding wasn't published/copied. Read src/ for what Ship files the package ships, and copy them into src/Ship/ once, up front.
  • Upgrade discipline across the 3.x line. There's no documented changelog beyond the GitHub compare links, so before bumping: diff src/Commands/ and the stub directory between tags (git diff v3.0.1..v3.0.2 -- src/), then regenerate one throwaway container to eyeball the output. Patch versions have already touched generator output, which is the thing most likely to silently drift.
  • Don't let Data/ become an anemic DTO graveyard. Porto encourages a Data/ folder for DTOs and value objects. In practice, half of them end up unused. Prune aggressively — a container with 12 models and 3 actions is a sign you under-used Actions, not that you over-used Data.
  • Namespace-case sensitivity. Container names generated with different casing (Blog vs blog) produce mismatched directory/namespace pairs that work on macOS and fail on Linux CI. Enforce PascalCase at the generator call site or in an architecture test.
  • Measure the pattern's payoff before committing the team. Porto's cost is indirection (controller → Action → Task → Repository → Model). With fewer than ~5 real domains it's pure overhead. A one-hour team spike on one container will tell you more than the README will.
Weaver

How can I help you explore Laravel packages today?

Conversation history is not saved when not logged in.
Prompt
Add packages to context
No packages found.
jinas/dhivatar
store-doc/laravel-storage-driver
plus-it/laravel-translation-manager
diephp/perhaps
diephp/telegraph
osmaviation/spreadsheet
microsoft/fluentui-system-icons
mmccook/php-json-canonicalization-scheme
middlewares/firewall
qubiqx/montapacking
nyoncode/laravel-package-console
pnscripts/filament-broadquill-lite
pnscripts/laravel-sentfold-lite
vleap/mx-sdk-laravel-internal
twidpay/utils
htetmyathlaing/scout-elasticsearch-driver
artisan-toolbox/front-awesome
ls/client-assistant
users-au/laravel-client
cgv/localized-resource