use Dimita\BusinessOrchestration\BusinessOrchestration;
// Define your saga steps
class ValidateOrderStep
{
public function execute($payload)
{
$order = Order::find($payload['order_id']);
if (!$order->isValid()) {
throw new \Exception('Invalid order');
}
return true;
}
}
class ChargePaymentStep
{
public function execute($payload)
{
$order = Order::find($payload['order_id']);
// Charge payment
$payment = PaymentGateway::charge($order->total);
if (!$payment->success) {
throw new \Exception('Payment failed');
}
return true;
}
}
class ShipOrderStep
{
public function execute($payload)
{
$order = Order::find($payload['order_id']);
// Ship the order
ShippingService::ship($order);
return true;
}
}
// Start the saga synchronously
$saga = BusinessOrchestration::saga()->startSaga('OrderProcessing', [
'validate' => ValidateOrderStep::class,
'charge' => ChargePaymentStep::class,
'ship' => ShipOrderStep::class,
], ['order_id' => 123]);
// If a step fails, completed steps will be automatically compensated
class ChargePaymentStep
{
public function execute($payload)
{
$order = Order::find($payload['order_id']);
$payment = PaymentGateway::charge($order->total);
if (!$payment->success) {
throw new \Exception('Payment failed');
}
return true;
}
// Define compensation logic
public function compensate($payload)
{
$order = Order::find($payload['order_id']);
// Refund the payment
PaymentGateway::refund($order->total);
// Update order status
$order->update(['status' => 'payment_refunded']);
}
}
// If your server crashes during execution, resume the saga
$sagaEngine = BusinessOrchestration::saga();
$sagaEngine->resumeSaga($sagaId);
// Cancel a saga that's pending or running
$sagaEngine = BusinessOrchestration::saga();
$sagaEngine->cancelSaga($sagaId);
use Dimita\BusinessOrchestration\BusinessOrchestration;
$workflow = BusinessOrchestration::workflow();
// Define possible transitions
$workflow->defineTransition('submit', 'draft', 'submitted');
$workflow->defineTransition('review', 'submitted', 'in_review');
$workflow->defineTransition('approve', 'in_review', 'approved');
$workflow->defineTransition('reject', 'in_review', 'rejected');
$workflow->defineTransition('revise', 'rejected', 'draft');
// Use workflow on a model
$contract = Contract::find(1);
$builder = $workflow->for($contract);
// Check if transition is possible
if ($builder->can('approve')) {
$builder->apply('approve');
}
// Get current state
echo $builder->getState(); // 'approved'
// Define transition with condition
$workflow->defineTransition(
'auto_approve',
'submitted',
'approved',
'return $context["amount"] < 1000;' // Guard expression
);
// Transition only possible if amount < 1000
// Execute custom logic before transitions
$workflow->beforeTransition('approve', function($instance) {
Log::info("Approving workflow for {$instance->model_type}");
// Send notification, update related records, etc.
});
// Execute custom logic after transitions
$workflow->afterTransition('approve', function($instance) {
Mail::to($user)->send(new ApprovalConfirmation());
});
$builder = $workflow->for($document);
// Get all transitions available from current state
$availableTransitions = $builder->getEnabledTransitions(['amount' => 500]);
// Returns: ['approve', 'reject', 'request_changes']
// Check if model is in specific state
if ($workflow->isInState($order, 'approved')) {
// Process approved order
}
// Get all possible states in the workflow
$allStates = $workflow->getAllStates();
// Returns: ['draft', 'pending', 'approved', 'rejected']
// Override guards and force a state change (use carefully)
$builder->forceTransition('cancelled', 'Manual cancellation by admin');
$events = $es->getEvents('cart-123');
foreach ($events as $event) {
echo "{$event['event_type']} at version {$event['version']}\n";
}
class OrderTotalProjector
{
// Called when MoneyAdded event is stored
public function onMoneyAdded($event)
{
$account = Account::findOrFail($event->aggregate_id);
$account->increment('balance', $event->payload['amount']);
}
// Called when MoneySubtracted event is stored
public function onMoneySubtracted($event)
{
$account = Account::findOrFail($event->aggregate_id);
$account->decrement('balance', $event->payload['amount']);
}
}
// Register the projector
$es->addProjector(OrderTotalProjector::class);
// Now when you store events, projector will automatically update read models
$es->storeEvent('account-123', 'MoneyAdded', ['amount' => 100]);
class SendEmailReactor
{
public function onOrderPlaced($event)
{
// Send confirmation email
Mail::to($event->payload['email'])->send(new OrderConfirmation($event));
}
}
// Register the reactor
$es->addReactor(SendEmailReactor::class);
// Reactor will handle side effects asynchronously
$es->storeEvent('order-456', 'OrderPlaced', ['email' => '[email protected]']);
// Replay all events through projectors
$count = $es->replay();
echo "Replayed {$count} events";
// Replay only specific aggregate
$count = $es->replay('account-123');
// Replay through specific projectors only
$count = $es->replay(null, [OrderTotalProjector::class]);
// Create a snapshot of current state
$cart = $es->rebuildAggregate('cart-123', $reducer);
$es->snapshot('cart-123', $cart);
// Retrieve latest snapshot instead of rebuilding from all events
$cart = $es->getLatestSnapshot('cart-123');
if (!$cart) {
// No snapshot exists, rebuild from events
$cart = $es->rebuildAggregate('cart-123', $reducer);
}
// Store event with metadata
$es->storeEvent('order-789', 'OrderShipped',
['tracking_number' => 'ABC123'],
['user_id' => auth()->id(), 'ip_address' => request()->ip()]
);
// Get events by type
$shippedOrders = $es->getEventsByType('OrderShipped', 10);
// Get latest version number
$latestVersion = $es->getLatestVersion('order-789');
// Get events from specific version
$newEvents = $es->getEvents('order-789', $fromVersion = 5);
use Dimita\BusinessOrchestration\BusinessOrchestration;
$version = BusinessOrchestration::version();
$document = Document::find(1);
// Create snapshot before modification
$version->snapshot($document);
// Modify document
$document->content = 'New content';
$document->save();
// Create another snapshot
$version->snapshot($document);
// Modify again
$document->content = 'Even newer content';
$document->save();
// Create third snapshot
$version->snapshot($document);
// View all versions
$versions = $version->getVersions($document);
// 3 versions available
// Restore to version 2
$version->restore($document, 2);
echo $document->content; // 'New content'
$contract = Contract::find(1);
// Create snapshot at each important change
$contract->status = 'draft';
$contract->save();
$version->snapshot($contract);
$contract->status = 'submitted';
$contract->save();
$version->snapshot($contract);
$contract->status = 'approved';
$contract->amount = 50000;
$contract->save();
$version->snapshot($contract);
// View complete history
$versions = $version->getVersions($contract);
foreach ($versions as $v) {
echo "Version {$v['version']}: Status = {$v['snapshot']['status']}\n";
}
// Exclude timestamps and sensitive data
$version->excludeFields(['password', 'remember_token', 'last_login_at'])
->snapshot($user);
// Only critical fields are versioned
$user->makeHidden(['password']); // Hidden by default
// Include hidden fields in version
$version->
// Revert 1 version back
$version->revert($document);
// Revert 3 versions back
$version->revert($document, 3);
// Latest version is now the restored one
// Get latest version number
$latestVersion = $version->getLatestVersion($document); // e.g., 15
// Check if specific version exists
if ($version->hasVersion($document, 5)) {
// Version 5 exists
}
// Get total version count
$count = $version->getVersionCount($document); // e.g., 15
// Get version by hash
$versionModel = $version->getVersionByHash($document, $hash);
// Delete all versions
$deletedCount = $version->purge($document);
// Mobile client requests changes since last sync
$lastSyncVersion = 5; // Version from last sync
$deltas = $sync->getDeltas(
'App\\Models\\Task',
$taskId,
$lastSyncVersion
);
// Client receives only changes after version 5
foreach ($deltas as $delta) {
echo "Version {$delta['version']}: {$delta['operation']}\n";
// Apply changes locally
applyChange($delta);
}
// 1. Mobile app syncs
$clientVersion = 0;
$deltas = $sync->getDeltas('App\\Models\\Task', 1, $clientVersion);
// Receives all modifications
// 2. Client goes offline and makes local modifications
// Modifications stored locally
// 3. Client comes back online
// Send local modifications to server
foreach ($localChanges as $change) {
$sync->logChange($model, $change['operation'], $change['fields']);
}
// 4. Get new changes from server
$newClientVersion = 15; // Version after upload
$newDeltas = $sync->getDeltas('App\\Models\\Task', 1, $newClientVersion);
// Keep only last 100 logs per model type
$deletedCount = $sync->purgeOldLogs('App\\Models\\Task', 100);
echo "Deleted {$deletedCount} old sync logs";
use Dimita\BusinessOrchestration\BusinessOrchestration;
$dep = BusinessOrchestration::dependency();
// Define that Category cannot be deleted if Products exist
$dep->addDependency(
'App\\Models\\Product',
'App\\Models\\Category',
'prevent_delete'
);
// Before deleting a category
$categoryId = 5;
if (!$dep->checkDeletion('App\\Models\\Category', $categoryId)) {
return response()->json([
'error' => 'Cannot delete category with existing products'
], 422);
}
// Otherwise, delete
Category::destroy($categoryId);
// Define dependency graph
$dep->addDependency('App\\Models\\OrderItem', 'App\\Models\\Order', 'cascade_delete');
$dep->addDependency('App\\Models\\Order', 'App\\Models\\Customer', 'prevent_delete');
$dep->addDependency('App\\Models\\Product', 'App\\Models\\Category', 'prevent_delete');
$dep->addDependency('App\\Models\\OrderItem', 'App\\Models\\Product', 'prevent_delete');
// Get all dependencies for a model
$dependencies = $dep->getDependencies('App\\Models\\Product');
// Before deletion, check all constraints
if (!$dep->checkDeletion('App\\Models\\Customer', $customerId)) {
throw new \Exception('Customer has active orders');
}
return [
/*
|--------------------------------------------------------------------------
| Enabled Engines
|--------------------------------------------------------------------------
|
| Configure which engines should be loaded and available in your application.
| Set to false to disable an engine completely and improve performance.
| By default, all engines are enabled.
|
*/
'engines' => [
'saga' => env('ORCHESTRATION_SAGA_ENABLED', true),
'workflow' => env('ORCHESTRATION_WORKFLOW_ENABLED', true),
'sync' => env('ORCHESTRATION_SYNC_ENABLED', true),
'version' => env('ORCHESTRATION_VERSION_ENABLED', true),
'event_sourcing' => env('ORCHESTRATION_EVENT_SOURCING_ENABLED', true),
'rule' => env('ORCHESTRATION_RULE_ENABLED', true),
'dependency' => env('ORCHESTRATION_DEPENDENCY_ENABLED', true),
],
/*
|--------------------------------------------------------------------------
| Storage Drivers
|--------------------------------------------------------------------------
|
| Configure how orchestration data is stored and retrieved.
| Supports: database, redis, queue
|
*/
'drivers' => [
'default' => env('BUSINESS_ORCHESTRATION_DRIVER', 'database'),
'database' => [
'connection' => env('DB_CONNECTION', 'mysql'),
],
'redis' => [
'connection' => env('REDIS_CONNECTION', 'default'),
],
'queue' => [
'connection' => env('QUEUE_CONNECTION', 'sync'),
],
],
];
'engines' => [
'saga' => true, // For order processing
'workflow' => true, // For order status transitions
'sync' => false, // No mobile sync needed
'version' => true, // For order audit trail
'event_sourcing' => false, // Not needed
'rule' => true, // For discount rules
'dependency' => false, // Not needed
],
'engines' => [
'saga' => false,
'workflow' => false,
'sync' => true, // Critical for mobile sync
'version' => true, // Version tracking
'event_sourcing' => true, // Event history
'rule' => false,
'dependency' => false,
],