PHPackages                             antroly/foundation - PHPackages - PHPackages  [Skip to content](#main-content)[PHPackages](/)[Directory](/)[Categories](/categories)[Trending](/trending)[Leaderboard](/leaderboard)[Changelog](/changelog)[Analyze](/analyze)[Collections](/collections)[Log in](/login)[Sign up](/register)

1. [Directory](/)
2. /
3. [Framework](/categories/framework)
4. /
5. antroly/foundation

ActiveLibrary[Framework](/categories/framework)

antroly/foundation
==================

Core package powering the Antroly architecture system for Laravel.

1.0.0(3mo ago)00MITPHPPHP ^8.2

Since Mar 25Pushed 3mo agoCompare

[ Source](https://github.com/antroly/foundation)[ Packagist](https://packagist.org/packages/antroly/foundation)[ RSS](/packages/antroly-foundation/feed)WikiDiscussions main Synced 3w ago

READMEChangelog (1)Dependencies (7)Versions (2)Used By (0)

antroly/foundation
==================

[](#antrolyfoundation)

The core package powering the **Antroly architecture system** — a strict, minimal-ceremony approach to building Laravel applications with Actions and typed DTOs.

Every request follows a single pipeline:

```
ActionRequest → Dto → Action → Dto → Resource / ViewModel

```

Each step is mandatory — the pipeline must not be skipped.

---

How it works
------------

[](#how-it-works)

`antroly/foundation` is a **dev dependency**. It publishes architecture classes, contracts, and scaffolding into your application. Once published, **you own the code**. Modify the base classes freely to fit your application's needs.

After publishing, your application is fully independent. The Antroly package can be removed without breaking your application.

---

Installation
------------

[](#installation)

**1. Require the package**

```
composer require antroly/foundation --dev
```

**2. Run the installer**

```
php artisan antroly:install
```

This publishes all base classes and architecture tests into your application. You will be prompted to publish the activity log migration.

**3. Register the service provider**

In `bootstrap/app.php`:

```
->withProviders([
    App\Providers\AppServiceProvider::class,
])
```

This wires `ResponseMacros` (registers `response()->success()` and `response()->error()`) and binds `AppLogger` to `DatabaseLogger`.

**4. Register the exception handler**

In `bootstrap/app.php`:

```
->withExceptions(function (Exceptions $exceptions) {
    App\Exceptions\AppExceptionHandler::register($exceptions);
})
```

This handles `ValidationException`, `DomainException`, and unexpected errors — returning a consistent JSON envelope for API requests and redirecting for web requests.

**5. Run the migration**

```
php artisan migrate
```

Creates the `logs` table used by `DatabaseLogger`.

---

### Swapping the logger

[](#swapping-the-logger)

`AppServiceProvider` binds `AppLogger` to `DatabaseLogger` by default. To use a different implementation, rebind it in your own provider:

```
$this->app->singleton(AppLogger::class, YourCustomLogger::class);
```

---

What gets published
-------------------

[](#what-gets-published)

### `app/Actions/Action.php`

[](#appactionsactionphp)

Base class for all use cases. Each Action represents a single application use case and owns the full flow for it.

Every Action is `final`, exposes one public method `execute()`, and is dispatched via `::run($dto)` — which resolves the Action from the container and injects constructor dependencies automatically.

**Output contract** — Actions may return only:

Return typeWhen to use`Dto` subclassSingle item — create, read, update`CollectionResult`Non-paginated list`PaginatedResult`Paginated list`void`Side-effect only — delete, dispatch, toggleActions must not return arrays, Eloquent models, Eloquent collections, paginator instances, or HTTP responses. These are enforced by architecture tests.

```
final class CreateCourseAction extends Action
{
    public function execute(CreateCourseData $dto): CourseData
    {
        $course = Course::create([
            'title' => $dto->title,
            'code'  => $dto->code,
        ]);

        return new CourseData(
            id:    $course->id,
            title: $course->title,
            code:  $course->code,
        );
    }
}

CreateCourseAction::run($dto);
```

---

### `app/Dtos/Dto.php`

[](#appdtosdtophp)

Base class for all DTOs. DTOs are pure typed data containers — they carry input into Actions and output out of them.

```
final class CreateCourseData extends Dto
{
    public function __construct(
        public readonly string $title,
        public readonly string $code,
    ) {}
}
```

The package provides one DTO abstraction: `Dto`. Naming is application-level convention — use names that describe the data, not the pipeline position:

```
CreateCourseData       — input to CreateCourseAction
CourseData             — single course output
CourseListItemData     — item in a list result
ListCoursesData        — filter/pagination input

```

DTOs must not know about `Request`, `FormRequest`, Eloquent models, or HTTP concerns.

---

### `app/Http/Requests/ActionRequest.php`

[](#apphttprequestsactionrequestphp)

Base class for all action requests. Enforces `toDto()` on every concrete request.

```
abstract class ActionRequest extends FormRequest
{
    abstract public function toDto(): Dto;
}
```

Every request extends `ActionRequest` and implements `toDto()` to map validated HTTP input into a typed Dto:

```
final class CreateCourseRequest extends ActionRequest
{
    public function rules(): array
    {
        return [
            'title' => ['required', 'string', 'max:255'],
            'code'  => ['required', 'string', 'unique:courses,code'],
        ];
    }

    public function toDto(): CreateCourseData
    {
        return new CreateCourseData(
            title: $this->validated('title'),
            code:  $this->validated('code'),
        );
    }
}
```

The Request owns the mapping. DTOs do not know about the request.

---

### `app/Exceptions/DomainException.php`

[](#appexceptionsdomainexceptionphp)

Base class for all business errors. Every domain exception carries a status code and a machine-readable error code.

```
final class CourseExpiredException extends DomainException
{
    public function __construct()
    {
        parent::__construct(
            'Course expired.',
            422,
            'course.course_expired',
        );
    }
}
```

Caught automatically by `AppExceptionHandler` — returns a structured JSON error or redirects for web requests.

---

### `app/Exceptions/AppExceptionHandler.php`

[](#appexceptionsappexceptionhandlerphp)

Modern Laravel 11/12 exception handler using `withExceptions()`. Handles `ValidationException`, `DomainException`, and unexpected errors consistently for both API and Blade responses.

---

### `app/Http/Controllers/BaseController.php`

[](#apphttpcontrollersbasecontrollerphp)

Provides `toApiError()`, `handleException()`, and `buildRuleBasedErrorBags()` used by the exception handler. Extend this in your own controllers.

---

### `app/Http/Resources/BaseResource.php`

[](#apphttpresourcesbaseresourcephp)

API responses extend `BaseResource`. Resources must receive DTOs, never Eloquent models.

```
final class CourseResource extends BaseResource
{
    public function toArray(Request $request): array
    {
        return [
            'id'    => $this->resource->id,
            'title' => $this->resource->title,
        ];
    }
}
```

---

### `app/Http/ViewModels/BaseViewModel.php`

[](#apphttpviewmodelsbaseviewmodelphp)

For Blade applications. ViewModels convert DTOs into view data via `$this->data`.

```
final class CourseViewModel extends BaseViewModel
{
    public function toArray(): array
    {
        return [
            'title' => $this->data->title,
        ];
    }
}
```

---

### `app/Http/Macros/ResponseMacros.php`

[](#apphttpmacrosresponsemacrosphp)

Registers `response()->success()` and `response()->error()` with a consistent JSON envelope:

```
response()->success(200, $data);
response()->success(201, $data, 'Course created.');
response()->error(422, 'Validation failed.', $errorBags, 'validation.failed');
response()->error(404);
```

Both return the same envelope structure:

```
{
    "statusCode": 200,
    "error": false,
    "message": null,
    "errorCode": null,
    "errorBags": null,
    "data": {}
}
```

---

### `app/Dtos/Common/CollectionResult.php` and `app/Dtos/Common/PaginatedResult.php`

[](#appdtoscommoncollectionresultphp-and-appdtoscommonpaginatedresultphp)

Output wrappers for list and paginated results. Actions must use these instead of returning raw arrays or paginators.

```
// Collection
return new CollectionResult(
    items: $courses->map(fn($c) => new CourseListItemData(...))->all(),
);

// Paginated
return PaginatedResult::fromPaginator(
    paginator: Course::query()->paginate($dto->perPage),
    mapper: fn($course) => new CourseListItemData(
        id:    $course->id,
        title: $course->title,
    ),
);
```

---

### `app/Logging/Contracts/AppLogger.php`

[](#apploggingcontractsapploggerphp)

Contract for application logging. Inject this wherever logging is needed — never depend on the concrete logger or Laravel's `Log` facade directly.

---

### `app/Logging/DatabaseLogger.php`

[](#apploggingdatabaseloggerphp)

Default implementation writing to the `logs` database table. Bound to `AppLogger` in `AppServiceProvider`. Swap it by rebinding in your own provider.

---

### `app/Models/ActivityLog.php`

[](#appmodelsactivitylogphp)

Eloquent model for the `logs` table. Immutable — no `updated_at`.

---

### `app/Providers/AppServiceProvider.php`

[](#appprovidersappserviceproviderphp)

Wires `ResponseMacros` and binds `AppLogger` to `DatabaseLogger`. Register this in `bootstrap/app.php`.

---

Scaffolding commands
--------------------

[](#scaffolding-commands)

### `make:action`

[](#makeaction)

Generates an Action and its test. Domain prefix is optional:

```
# Without domain
php artisan make:action CreateCourse

# With domain
php artisan make:action Course/CreateCourse
```

Generates:

```
app/Actions/CreateCourseAction.php
tests/Unit/Actions/CreateCourseActionTest.php

app/Actions/Course/CreateCourseAction.php
tests/Unit/Actions/Course/CreateCourseActionTest.php

```

---

### `make:action-dto`

[](#makeaction-dto)

Generates a Dto class:

```
php artisan make:action-dto Course/CreateCourseData
php artisan make:action-dto Course/CourseData
```

Generates:

```
app/Dtos/Course/CreateCourseData.php
app/Dtos/Course/CourseData.php

```

Naming is application-level convention. The package does not enforce input/output subtype naming — use names that describe the data.

---

### `make:action-request`

[](#makeaction-request)

Generates an `ActionRequest` with `toDto()`:

```
php artisan make:action-request Course/CreateCourse
```

Generates:

```
app/Http/Requests/Course/CreateCourseRequest.php

```

The generated request extends `ActionRequest` and maps validated input into a Dto via `toDto()`.

---

### `make:action-resource`

[](#makeaction-resource)

Generates an API `Resource` (default) or a `ViewModel` for Blade:

```
# API Resource (default)
php artisan make:action-resource Course/CreateCourse

# ViewModel for Blade
php artisan make:action-resource Course/CreateCourse --type=web
```

FlagOutput`--type=api` (default)`CreateCourseResource` extending `BaseResource``--type=web``CreateCourseViewModel` extending `BaseViewModel`---

### `make:domain-exception`

[](#makedomain-exception)

Generates a domain exception with a derived message and error code:

```
php artisan make:domain-exception Course/CourseExpired
```

Generates:

```
app/Exceptions/Course/CourseExpiredException.php

```

---

Scaffolding workflow
--------------------

[](#scaffolding-workflow)

Run these commands in sequence to scaffold a complete feature:

```
php artisan make:action Course/CreateCourse
php artisan make:action-dto Course/CreateCourseData
php artisan make:action-dto Course/CourseData
php artisan make:action-request Course/CreateCourse
php artisan make:action-resource Course/CreateCourse --type=api
```

This generates the full pipeline — Action, both DTOs, ActionRequest, and API Resource — ready for your business logic.

---

Example flow
------------

[](#example-flow)

### Create — single item

[](#create--single-item)

```
// Controller
final class CourseController extends BaseController
{
    public function store(CreateCourseRequest $request): JsonResponse
    {
        $result = CreateCourseAction::run($request->toDto());

        return response()->success(201, new CourseResource($result));
    }
}

// Request — validates and maps to Dto
final class CreateCourseRequest extends ActionRequest
{
    public function rules(): array
    {
        return ['title' => ['required', 'string', 'max:255']];
    }

    public function toDto(): CreateCourseData
    {
        return new CreateCourseData(title: $this->validated('title'));
    }
}

// Action
final class CreateCourseAction extends Action
{
    public function execute(CreateCourseData $dto): CourseData
    {
        $course = Course::create(['title' => $dto->title]);

        return new CourseData(id: $course->id, title: $course->title);
    }
}

// Resource
final class CourseResource extends BaseResource
{
    public function toArray(Request $request): array
    {
        return ['id' => $this->resource->id, 'title' => $this->resource->title];
    }
}
```

---

### List — paginated results

[](#list--paginated-results)

```
// Controller
final class CourseController extends BaseController
{
    public function index(ListCoursesRequest $request): JsonResponse
    {
        $result = ListCoursesAction::run($request->toDto());

        return response()->success(200, [
            'items'       => CourseListItemResource::collection($result->items),
            'total'       => $result->total,
            'perPage'     => $result->perPage,
            'currentPage' => $result->currentPage,
            'lastPage'    => $result->lastPage,
        ]);
    }
}

// Action — maps models to DTOs inside the Action, wraps pagination metadata
final class ListCoursesAction extends Action
{
    public function execute(ListCoursesData $dto): PaginatedResult
    {
        return PaginatedResult::fromPaginator(
            paginator: Course::query()
                ->with('instructor')
                ->paginate($dto->perPage),
            mapper: fn($course) => new CourseListItemData(
                id:         $course->id,
                title:      $course->title,
                instructor: new InstructorData(name: $course->instructor->name),
            ),
        );
    }
}
```

No paginator reaches the controller. No database query runs in the Resource.

---

Architecture tests
------------------

[](#architecture-tests)

Publishing `antroly-tests` adds `tests/Architecture/ArchitectureTest.php` to your project. Run it as part of your normal Pest suite:

```
./vendor/bin/pest
```

### Rules enforced by arch tests

[](#rules-enforced-by-arch-tests)

RuleEnforcementActions extend `Action`, are `final`Arch testActions must not depend on `Request` / `FormRequest`Arch testActions must not return Eloquent models or collectionsArch testActions must not return paginator instancesArch testActions must not return HTTP responses or `Responsable`Arch testDTOs extend `Dto`, are `final`Arch testDTOs must not depend on EloquentArch testDTOs must not depend on `Request` / `FormRequest`Arch testRequests extend `ActionRequest`, are `final`Arch testResources extend `BaseResource`, are `final`, no EloquentArch testViewModels extend `BaseViewModel`, are `final`, no EloquentArch testControllers extend `BaseController`, are `final`, no EloquentArch testDomain exceptions extend `DomainException`, are `final`Arch test### Rules that remain convention-based

[](#rules-that-remain-convention-based)

RuleReasonActions must not call other ActionsMethod-level; not structurally enforceableDTOs must be readonly (no mutable state)Property-level; not in current arch toolingResources must receive DTOs, not modelsType-level; not statically checkableControllers must call `$request->toDto()` and not reconstruct DTOs manuallyNot statically checkableSee [RULEBOOK.md](RULEBOOK.md) for the full architectural guidelines.

---

What Antroly does not include
-----------------------------

[](#what-antroly-does-not-include)

Intentionally absent: repository layers, generic service interfaces, command buses, DTO auto-hydration frameworks, complex transformer layers. Actions interact directly with Eloquent. That is sufficient.

---

Contributing
------------

[](#contributing)

```
./vendor/bin/pest   # run tests
composer lint       # pint code style
composer analyse    # phpstan static analysis
```

---

License
-------

[](#license)

MIT

###  Health Score

35

—

LowBetter than 77% of packages

Maintenance82

Actively maintained with recent releases

Popularity0

Limited adoption so far

Community6

Small or concentrated contributor base

Maturity46

Maturing project, gaining track record

 Bus Factor1

Top contributor holds 100% of commits — single point of failure

How is this calculated?**Maintenance (25%)** — Last commit recency, latest release date, and issue-to-star ratio. Uses a 2-year decay window.

**Popularity (30%)** — Total and monthly downloads, GitHub stars, and forks. Logarithmic scaling prevents top-heavy scores.

**Community (15%)** — Contributors, dependents, forks, watchers, and maintainers. Measures real ecosystem engagement.

**Maturity (30%)** — Project age, version count, PHP version support, and release stability.

###  Release Activity

Cadence

Unknown

Total

1

Last Release

91d ago

### Community

Maintainers

![](https://www.gravatar.com/avatar/d6cc6811308e1e65b451cd702f011a416b4d903d38b81b74bc0754b3bd70ed9b?d=identicon)[ch17](/maintainers/ch17)

---

Top Contributors

[![CH17](https://avatars.githubusercontent.com/u/39479222?v=4)](https://github.com/CH17 "CH17 (13 commits)")

---

Tags

laravelarchitecturedtoactionsantroly

###  Code Quality

TestsPest

Static AnalysisPHPStan

Code StyleLaravel Pint

Type Coverage Yes

### Embed Badge

![Health badge](/badges/antroly-foundation/health.svg)

```
[![Health](https://phpackages.com/badges/antroly-foundation/health.svg)](https://phpackages.com/packages/antroly-foundation)
```

###  Alternatives

[laravel/mcp

Rapidly build MCP servers for your Laravel applications.

76518.2M120](/packages/laravel-mcp)[larastan/larastan

Larastan - Discover bugs in your code without running it. A phpstan/phpstan extension for Laravel

6.4k51.0M7.6k](/packages/larastan-larastan)[laravel/passport

Laravel Passport provides OAuth2 server support to Laravel.

3.4k89.4M574](/packages/laravel-passport)[laravel/cashier

Laravel Cashier provides an expressive, fluent interface to Stripe's subscription billing services.

2.5k28.4M137](/packages/laravel-cashier)[spatie/laravel-responsecache

Speed up a Laravel application by caching the entire response

2.8k8.7M64](/packages/spatie-laravel-responsecache)[laravel/pulse

Laravel Pulse is a real-time application performance monitoring tool and dashboard for your Laravel application.

1.7k14.1M122](/packages/laravel-pulse)

PHPackages © 2026

[Directory](/)[Categories](/categories)[Trending](/trending)[Changelog](/changelog)[Analyze](/analyze)
