Structure

Directory Structure

Every top-level directory of the backend repository, explained. Use this page to figure out which folder a change belongs in before you open the file.

The backend has three top-level source directories that matter most: src/Domain/ for business logic, src/Presentation/ for HTTP and view layers, and app/ for legacy and framework glue. Knowing which belongs to which saves a lot of time when you go hunting for a file.

This page walks each directory at a level of detail that is useful for orienting yourself. For the rules that govern what lives where, see DDD overview and the rest of Key Concepts.

src/Domain/

One subdirectory per bounded context. There are roughly forty of them — the domain glossary lists each one with a description. Inside each domain you will find the same core directories, with a few optional ones that only appear when the domain needs them:

src/Domain/Order/
├── Actions/                     # one class per business operation
│   └── StateMachine/            # state machine + guards (when the domain uses one)
│       └── Conditions/          # one BaseCondition subclass per guard rule
├── Contracts/                   # interfaces — the domain's public API
├── Services/                    # Contract implementations; delegate to Actions
├── DataTransferObjects/         # *Data classes for inter-layer transport
├── Enums/                       # domain-owned enums (statuses, types)
├── Exceptions/                  # domain-specific exception classes
├── Jobs/                        # queued work owned by the domain
├── Models/
│   ├── Entities/                # Eloquent models
│   ├── Scopes/                  # scopeFilterBy traits
│   ├── Abilities/               # per-instance permission objects (state machine wrappers)
│   ├── Features/                # domain-scoped Pennant feature flags (optional)
│   └── Traits/                  # model-only shared traits
├── Observers/                   # Eloquent observers (optional)
├── Providers/                   # the domain's ServiceProvider
└── Traits/                      # domain-level traits for Services/Actions (optional)

StateMachine/, Observers/, Traits/, and Models/Features/ are only present in domains that use them — most domains do not have all four. Cross-domain reads and writes go through app(SomeContract::class) — never through another domain's Action or Model. Same-domain code imports its sibling Actions directly. Domain boundaries covers this in detail.

src/Presentation/

One subdirectory per control panel — Admin, Business, Common, Customer, Driver, Owner, Pdf, Shared. Each panel has its own controllers, requests, resources, routes, and providers:

src/Presentation/Admin/
├── Controllers/
├── Enums/
├── Providers/
├── Requests/
├── Resources/
├── Routes/
│   └── web.php             # or api.php for API panels
├── Rules/
├── Traits/
├── ViewData/               # view-model helpers passed to Blade templates
└── Views/                  # Blade — only Admin and Pdf carry views

A controller belongs to exactly one panel. If two panels need the same behaviour, the shared logic moves into a domain Service, and both controllers call that Service. Cross-panel inheritance is forbidden — it produces routes you can't reason about.

The HTTP shape varies by panel:

  • Admin is server-rendered (Blade + redirects). Use a View or RedirectResponse return type and flash messages on success.
  • Business, Customer, Driver, Owner are JSON APIs authenticated with Sanctum tokens. Use the ResponseBuilder from res()->data(...) so the standard envelope wraps every response.
  • Pdf renders binary PDFs via mPDF.
  • Common and Shared hold helpers, abstract base classes, constants, and rules used by the other panels.

See controllers for the SSR/API split in detail.

app/

Standard Laravel app/ directory — kept for framework glue and legacy code that has not been migrated to the DDD layout. New business code does not land here; it goes in src/Domain/.

Legacy models that predate the DDD migration live as loose .php files directly in app/ (e.g. app/User.php, app/Order.php). They have not been moved into a subfolder. New entities go in src/Domain/{D}/Models/Entities/.

app/
├── *.php                # legacy model files (loose in app/ root)
├── Console/
│   └── Commands/        # artisan commands
├── Constants/           # application-wide constant classes
├── Exceptions/          # the global exception handler
├── Http/
│   ├── Controllers/     # legacy controllers; new code goes in src/Presentation/
│   ├── Middleware/      # HTTP middleware
│   └── Requests/        # legacy form requests
├── Packages/            # in-tree packages (CpMenu, Localisation, …)
├── Providers/           # framework-level service providers
├── Traits/              # application-wide shared traits
├── Validators/          # custom validation rule classes
└── helpers.php          # global helpers — res(), isWinch(), projectFlavor()

A few specific files are worth knowing by name:

  • app/helpers.php exports res() (the API response builder), isWinch() / isSaas() / projectFlavor() (the flavor helpers), and a handful of other utilities.
  • app/Packages/Menus/ builds the admin sidebar from a config-driven menu array.
  • app/Packages/Localisation/ handles locale switching and translation loading.

When you touch a legacy file, read refactoring legacy before deciding whether to move it.

bootstrap/

Laravel 12 trims the historical app/Http/Kernel.php, app/Console/Kernel.php, and routing-registration code into a single bootstrap/app.php. Middleware, exception handling, and routing all configure declaratively here:

return Application::configure(basePath: dirname(__DIR__))
    ->withRouting(/* ... */)
    ->withMiddleware(/* ... */)
    ->withExceptions(/* ... */)
    ->create();

bootstrap/providers.php lists every service provider — both framework-level providers and the per-domain providers (Domain\Order\Providers\OrderServiceProvider, etc.). Adding a new domain means adding its provider to this file; forgetting to do so is the most common cause of BindingResolutionException for a freshly written Contract.

database/

database/
├── factories/              # model factories for tests and seeders
├── migrations/             # anonymous-class migrations
├── seeders/                # development seeders
└── sql/                    # raw SQL scripts (bulk seeds, one-off data fixes)

Migrations follow modern Laravel patterns: anonymous classes, foreignIdFor()->constrained(), no speculative indexes. See migrations.

lang/

lang/
├── en/
│   ├── dashboard.php
│   ├── pages.php
│   ├── permissions.php
│   ├── inputs.php
│   └── …
├── ar/
│   └── (same files)
├── hi/
│   └── (same files)
└── ur/
    └── (same files)

Every translation key must exist in all active locales. The pre-push hook (php artisan translations:check --changed) enforces this — pushes fail if the files you're pushing reference a key missing from any locale.

tests/

tests/
├── Feature/        # HTTP-level integration tests
├── Unit/           # isolated unit tests (Actions, DTOs, helpers)
├── Pest.php        # global Pest configuration
└── TestCase.php

See testing for how to write a test against the DDD layout.

config/

Standard Laravel config. The files most often touched in domain work:

  • config/app.php'flavor' key for the current ProjectFlavor.
  • config/features.php — Pennant flag defaults.
  • config/cp-menu.php — sidebar configuration including group_features / route_features / group_flavors / route_flavors.
  • DDD overview — the rationale behind the Domain / Presentation split.
  • Your first PR — apply all of the above to a real change.