regnerisch/laravel-beyond
Laravel Beyond adds Artisan generators and base classes for action-oriented Laravel apps, including Actions, Data Transfer Objects, and View Models. It helps organize domain logic and move beyond traditional CRUD structure.
Version note (v7.1.0): This release drops Laravel 10.x support and adds Laravel 12.x support. The package now targets Laravel 11.x and 12.x only. If you're still on Laravel 10, stay on the 7.0.x line or plan your framework upgrade first.
Install
composer require <vendor>/laravel-adr:^7.1
php artisan vendor:publish --tag=adr-config
Confirm your composer.json platform requirement is laravel/framework: ^11.0|^12.0 before upgrading — Composer will otherwise resolve you back to 7.0.x without warning.
Where to look first
config/adr.php — namespace roots and paths for Actions, Domains, and Responders. This is the first thing to align with your team's conventions (App\Actions vs App\Domain\...\Actions).php artisan vendor:publish --tag=adr-stubs) — these define what a generated class looks like on day one.php artisan list beyond — confirms the available generators for your installed version.First use case
The flagship addition in 7.1.0 is the action generator. Scaffold your first vertical slice:
php artisan beyond:make:adr-action CreateOrderAction
Then wire it into a controller as an invokable dependency:
final class OrderController extends Controller
{
public function __construct(private CreateOrderAction $action) {}
public function store(CreateOrderRequest $request): JsonResponse
{
$order = ($this->action)($request->validated());
return (new OrderResponder($order))->toResponse($request);
}
}
That's the whole loop: request → action → domain → responder. Start with one endpoint, not a full refactor.
One action per use case. Actions are invokable classes that take primitives/DTOs and return a result object or model. Keep them container-resolvable (constructor injection only) so Laravel can resolve them and so you can swap them in tests.
final class CreateOrderAction
{
public function __construct(
private OrderRepository $orders,
private PaymentGateway $payments,
) {}
public function __invoke(CreateData $data): Order
{
return DB::transaction(function () use ($data) {
$order = $this->orders->create($data);
$this->payments->charge($order, $data->paymentToken);
return $order;
});
}
}
Responders handle the "what do we send back". Keep the responder unaware of the action and vice-versa. This is the part that pays off when you add an API alongside an existing web flow:
final class OrderResponder
{
public function __construct(private Order $order) {}
public function toResponse(Request $request): JsonResponse|Response
{
return $request->expectsJson()
? new JsonResponse(new OrderResource($this->order), 201)
: response()->view('orders.show', ['order' => $this->order]);
}
}
Workflow tips that actually get used
beyond:make:adr-action for every new use case; it's the fastest way to keep the folder structure consistent across the team. Publish and edit stubs (vendor:publish --tag=adr-stubs) once, so every generated action has final class, readonly promoted properties, and your docblock conventions baked in.dispatch() — that keeps it testable and transport-agnostic.it('creates an order', function () {
$order = app(CreateOrderAction)(new CreateData(...));
expect($order)->toBeInstanceOf(Order::class);
});
AppServiceProvider — actions resolved from the container will pick them up automatically, and you get free mocking in tests via $this->mock(PaymentGateway::class).->validated() into the action. Don't let actions take Request instances — it's the most common way to leak HTTP concerns into your domain layer.beyond: command prefix is shared with other Beyond Code packages; run php artisan list after install to confirm you're seeing the generators you expect, not a namespace clash.Framework version is the new tripwire. Dropping Laravel 10 is a hard break:
composer update on an L10 app will silently keep you on 7.0.x — check composer show <vendor>/laravel-adr after upgrading and don't assume you got 7.1.laravel/framework: ^10|^11, the constraint resolution will fight you. Upgrade the framework first, then the package, not the other way around.Autoload traps. Generated classes land in the namespaces defined in config/adr.php. If you change that config after generating files, you'll get "class not found" errors that look like a package bug. Fix:
php artisan config:clear
composer dump-autoload
Stubs are copied, not inherited. Once you publish stubs, they're yours — package upgrades will not update them. Re-publish to a temp tag or diff against vendor/.../stubs/ after upgrading to 7.1 so your generators pick up any formatting or signature changes.
Config caching. In production (php artisan config:cache), changes to config/adr.php are ignored until you re-cache. Generators run in dev, so this bites mainly when the config defines runtime bindings or paths used by service providers — clear the cache as part of deploy.
Debugging generators. Most "nothing happened" reports are one of:
-v and missing the "file already exists" skip.App\ PSR-4 root points somewhere the config doesn't.php artisan beyond:make:adr-action Foo -v tells you the resolved path before writing.
Extension points to lean on
Responder contract and generate against it rather than editing each class.Modules\Sales\Actions etc. rather than patching the generator.One last thing for 7.1 adopters: the new adr-action command is additive, so existing projects upgrading from 7.0.x need no migration work beyond the framework bump. The only real cost of this release is the Laravel 10 boundary — plan that as a separate PR from any feature work so a rollback is trivial.
How can I help you explore Laravel packages today?