pektiyaz/laravel-event-engine

Event Driven Core for laravel applications

v1.0.0 2025-04-09 09:14 UTC

This package is auto-updated.

Last update: 2025-04-14 12:21:34 UTC


README

EventEngine is a powerful and declarative extension for Laravel’s event system, providing a clear can → do → after pattern for modular business logic.

Installation

composer require pektiyaz/laravel-event-engine

Features

Method Description

  • can() Checks if the action is allowed before proceeding
  • do() Performs an action and expects a success/failure response
  • doAtomic() Performs an action inside a DB transaction (rollback-safe)
  • after() Fires events that don’t affect control flow

Listener Contracts

can(...) listeners:

Must return:

return new EventCanValue(true)
// OR
return new EventCanValue(true, $data)
// OR
return new EventCanValue(false, "Reason..")

do(...) listeners:

Must return:

return new EventDoValue(true)
// OR
return new EventDoValue(true, $data) 
// OR
return new EventDoValue(false, "Reason...")

ask(...) listeners:

Must return:

//Listener in module Account
return new EventAskValue(true, ["name" => "John"])

//Listener in module Finance
return new EventAskValue(true, ["balance" => 500])

//Result
[
    "name" => "John",
    "balance" => 500
]

after(...) listeners:

Should not return anything. These may implement ShouldQueue.

Setup

Create an event and a listener for can(), do(), or after() patterns.

Register the listener in Laravel (EventServiceProvider).

1. Create an Event

Example: CanClientPay

namespace App\Events;

class CanClientPay
{
    public function __construct(
        public int   $clientId,
        public float $amount
    ) {}
}

2. Create a Listener

Listener that handles this event and returns a EventCanValue response.

namespace App\Listeners;

use App\Events\CanClientPay;use Pektiyaz\LaravelEventEngine\CanValues\EventCanValue;

class CheckClientBalanceListener
{
    public function handle(CanClientPay $event): EventCanValue
    {
        // Example logic: client can pay if they have more than $event->amount
        $balance = 500; // You’d normally fetch this from DB
    
        if ($balance >= $event->amount) {
            return new EventCanValue(true);
        }
    
        return new EventCanValue(false, 'Insufficient balance');
    }
}

Note: Always return an array from the listener (for can() and do() usage).

3. Register the Listener

Open app/Providers/EventServiceProvider.php and add your event + listener:

protected $listen = [
    \App\Events\CanClientPay::class => [
        \App\Listeners\CheckClientBalanceListener::class,
    ],
];

Then run:

php artisan event:clear
php artisan event:cache

Usage Examples

can() — check permission

use App\Support\EventEngine;
use App\Events\CanClientPay;

$result = EventEngine::can(new CanClientPay($clientId, $amount));

if (!$result->can) {
    // Listing all answers
    foreach ($result->answers as $answer) {
        //$answer->can
        //$answer->data
        //$answer->toArray()
        Log::debug('Event Answer', $answer->toArray());
    }
    //$result->toArray()
    return response()->json(['error' => 'Payment denied', 'details' => $result->data]);
}

do() — execute an action

use App\Events\ClientProcessPay;

$process = EventEngine::do(new ClientProcessPay($clientId, $amount));

if (!$process->success) {
    // Listing all answers
    foreach ($result->answers as $answer) {
        //$answer->success
        //$answer->data
        //$answer->toArray()
        Log::debug('Event Answer', $answer->toArray());
    }
    //$result->toArray()
    return response()->json(['error' => 'Payment failed', 'details' => $process->data]);
}

return response()->json(['message' => 'Payment success', 'data' => $process->data]);

doAtomic() — run EventEngine::do in DB transaction

$response = EventEngine::doAtomic(new ClientPaid($clientId, $amount));

if (!$response->success) {
    return response()->json([
        'error' => 'Transaction rolled back',
        'details' => $response->data,
    ]);
}

after() — fire an event without expecting a response

use App\Events\ClientPaid;

EventEngine::after(new ClientPaid($clientId, $amount));

ask() — collect data from listeners and returning array of answers

use App\Events\GetUserInfo;

$process = EventEngine::ask(new GetUserInfo($clientId));
if($process->success){
    return response()->json([
        'user' => $response->data,
    ]);
}

With Listener name

namespace App\Listeners;

use App\Events\CanClientPay;use Pektiyaz\LaravelEventEngine\CanValues\EventCanValue;

class CanClientPayListener
{
    public function handle(CanClientPay $event): EventCanValue
    {
        // Example logic: client can pay if they have more than $event->amount
        $balance = 500; // You’d normally fetch this from DB
    
        if ($balance >= $event->amount) {
            return new EventCanValue(true);
        }
    
        return new EventCanValue(false, 'Insufficient balance', "CanClientPayListener");
    }
}
$result = EventEngine::can(new CanClientPay($clientId, $amount));
foreach($result->answers as $answer){
    $answer->listener // CanClientPayListener
}

Why use EventEngine?

  • Clear separation of responsibilities (can, do, after)

  • Easy to test and maintain

  • Decouples business logic from the framework

  • Centralized event orchestration

  • Scales well with modular systems

Coming Soon (ideas)

  • Configurable behavior via config file

  • failFast and collectAllErrors options

  • Automatic event registration

  • Publishable Laravel package with service provider