Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Web 4344 event machine behavior dependency injection #70

Merged
merged 34 commits into from
Jan 12, 2024
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
34 commits
Select commit Hold shift + click to select a range
1bbc333
test: Introduce CalculatorMachine (WEB-4344)
deligoez Dec 4, 2023
11c1317
feat: Ability to inject ContextManager into behaviors (WEB-4344)
deligoez Dec 4, 2023
55efbe9
feat: Ability to inject EventBehavior into behaviors (WEB-4344)
deligoez Dec 4, 2023
f682823
test: Updated the test 'it can inject requested parameters' (WEB-4344)
deligoez Dec 4, 2023
b2f6999
feat: Add assertion for `EventBehavior` type in `BehaviorDependencyIn…
deligoez Dec 4, 2023
c9e525f
feat: Add `State` parameter to `contextAction` in `BehaviorDependency…
deligoez Dec 4, 2023
b7671e0
feat: Add `EventCollection` class in `src/EventCollection.php` (WEB-4…
deligoez Dec 4, 2023
b66ac96
feat: Inject `EventCollection` into `runAction` in `MachineDefinition…
deligoez Dec 4, 2023
085df29
chore: Update .gitignore (WEB-4344)
deligoez Dec 4, 2023
b9c9b6d
feat: Enhance parameter type handling in `runAction` method (WEB-4344)
deligoez Dec 4, 2023
8b6e425
refactor: Change parent class of `EventCollection` to `Illuminate\Dat…
deligoez Dec 4, 2023
c5fd633
feat: Override `newCollection` method in `MachineEvent.php` (WEB-4344)
deligoez Dec 4, 2023
daffe6b
test: Replace `EventDefinition` with `EventBehavior` in action method…
deligoez Dec 4, 2023
a78ccd5
refactor: Improve parameter type handling in `runAction` method (WEB-…
deligoez Dec 4, 2023
d4e4356
feat(MachineDefinition): refactor action behavior parameter assignmen…
deligoez Dec 4, 2023
eb6a900
refactor(MachineDefinition): improve parameter assignment in commit (…
deligoez Dec 4, 2023
4802a69
test(InvokableBehaviorArgumentsTest): update argument variable names …
deligoez Dec 4, 2023
f07c8a6
refactor: Update `__invoke()` to `definition()` in behaviors and guar…
deligoez Dec 4, 2023
a3592ff
test: Fix Actions (WEB-4344)
deligoez Dec 4, 2023
6d53771
refactor: Update how `runAction` method handles `InvokableBehavior` i…
deligoez Dec 4, 2023
d8eb1cb
refactor: Enhance guard behavior handling in `getFirstValidTransition…
deligoez Dec 4, 2023
bc055c0
test: Fix guards (WEB-4344)
deligoez Dec 4, 2023
bf66715
test: fix (WEB-4344)
deligoez Dec 4, 2023
48539b7
WEB-4344: refactor: GuardBehavior and related classes
deligoez Dec 5, 2023
36ebfb6
WEB-4344: refactor: actionBehavior and guardBehavior reflection
deligoez Dec 5, 2023
1376841
WEB-4344: refactor: Replaced Closure with direct invocation in Action…
deligoez Dec 5, 2023
4d8aac0
WEB-4344: Add ReflectionException to runActions docblock
deligoez Dec 5, 2023
145b399
WEB-4344: Implement centralized parameter injection for invokable beh…
deligoez Dec 5, 2023
baa4fd8
WEB-4344: Refactored InvokableBehavior injection logic
deligoez Dec 5, 2023
7d2ac02
Resolve conflicts (WEB-4344)
deligoez Jan 11, 2024
d1e31e8
WEB-4344: Apply styles
deligoez Jan 11, 2024
f7b469a
WEB-4344: Update pint command in composer.json
deligoez Jan 11, 2024
4d2a27f
WEB-4344: Change ResultBehaviour type annotation to callable
deligoez Jan 11, 2024
039bc55
WEB-4344: Refactor ResultBehavior definition to an invokable method
deligoez Jan 11, 2024
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
1 change: 1 addition & 0 deletions .gitignore
Original file line number Diff line number Diff line change
Expand Up @@ -9,3 +9,4 @@ phpstan.neon
testbench.yaml
vendor
node_modules
.DS_Store
2 changes: 1 addition & 1 deletion composer.json
Original file line number Diff line number Diff line change
Expand Up @@ -52,7 +52,7 @@
"profile": "vendor/bin/pest --profile --colors=always --order-by=random --configuration=phpunit.xml.dist",
"coverage": "vendor/bin/pest --coverage --colors=always --order-by=random --configuration=phpunit.xml.dist",
"coveragep": "vendor/bin/pest --parallel --coverage --colors=always --order-by=random --configuration=phpunit.xml.dist",
"pint": "vendor/bin/pint",
"pint": "vendor/bin/pint --config=pint.json",
"lint": "@pint",
"lintc": "vendor/bin/pint && (git diff-index --quiet HEAD || (git add . && git commit -m 'chore: Fix styling'))",
"infection": "vendor/bin/infection --test-framework=pest --show-mutations --threads=max --min-msi=100 --min-covered-msi=100 --ansi"
Expand Down
2 changes: 1 addition & 1 deletion src/Actor/Machine.php
Original file line number Diff line number Diff line change
Expand Up @@ -165,16 +165,16 @@
bool $shouldPersist = true,
): State {
if ($this->state !== null) {
$lock = Cache::lock('mre:'.$this->state->history->first()->root_event_id, 60);

Check failure on line 168 in src/Actor/Machine.php

View workflow job for this annotation

GitHub Actions / larastan

Access to an undefined property Illuminate\Database\Eloquent\Model::$root_event_id.
}

if (isset($lock) && !$lock->get()) {
throw MachineAlreadyRunningException::build($this->state->history->first()->root_event_id);

Check failure on line 172 in src/Actor/Machine.php

View workflow job for this annotation

GitHub Actions / larastan

Access to an undefined property Illuminate\Database\Eloquent\Model::$root_event_id.
}

try {
$lastPreviousEventNumber = $this->state !== null
? $this->state->history->last()->sequence_number

Check failure on line 177 in src/Actor/Machine.php

View workflow job for this annotation

GitHub Actions / larastan

Access to an undefined property Illuminate\Database\Eloquent\Model::$sequence_number.
: 0;

// If the event is a string, we assume it's the event type.
Expand Down Expand Up @@ -224,12 +224,12 @@
$lastHistoryEvent = $this->state->history->last();

MachineEvent::upsert(
values: $this->state->history->map(function (MachineEvent $machineEvent, int $index) use (&$incrementalContext, $lastHistoryEvent) {

Check failure on line 227 in src/Actor/Machine.php

View workflow job for this annotation

GitHub Actions / larastan

Parameter #1 $callback of method Illuminate\Database\Eloquent\Collection<(int|string),Illuminate\Database\Eloquent\Model>::map() expects callable(Illuminate\Database\Eloquent\Model, int|string): non-empty-array, Closure(Tarfinlabs\EventMachine\Models\MachineEvent, int): non-empty-array given.
// Get the context of the current machine event.
$changes = $machineEvent->context;

// If the current machine event is not the last one, compare its context with the incremental context and get the differences.
if ($machineEvent->id !== $lastHistoryEvent->id && $index > 0) {

Check failure on line 232 in src/Actor/Machine.php

View workflow job for this annotation

GitHub Actions / larastan

Access to an undefined property Illuminate\Database\Eloquent\Model::$id.
$changes = $this->arrayRecursiveDiff($changes, $incrementalContext);
}

Expand Down Expand Up @@ -285,8 +285,8 @@
$lastMachineEvent = $machineEvents->last();

return new State(
context: $this->restoreContext($lastMachineEvent->context),

Check failure on line 288 in src/Actor/Machine.php

View workflow job for this annotation

GitHub Actions / larastan

Access to an undefined property Illuminate\Database\Eloquent\Model::$context.
currentStateDefinition: $this->restoreCurrentStateDefinition($lastMachineEvent->machine_value),

Check failure on line 289 in src/Actor/Machine.php

View workflow job for this annotation

GitHub Actions / larastan

Access to an undefined property Illuminate\Database\Eloquent\Model::$machine_value.
currentEventBehavior: $this->restoreCurrentEventBehavior($lastMachineEvent),
history: $machineEvents,
);
Expand Down Expand Up @@ -493,7 +493,7 @@
$resultBehavior = new $resultBehavior();
}

/* @var \Tarfinlabs\EventMachine\Behavior\ResultBehavior $resultBehavior */
/* @var callable $resultBehavior */
return $resultBehavior(
$this->state->context,
$this->state->currentEventBehavior,
Expand Down
5 changes: 3 additions & 2 deletions src/Actor/State.php
Original file line number Diff line number Diff line change
Expand Up @@ -8,6 +8,7 @@
use Illuminate\Support\Collection;
use Illuminate\Support\Facades\Log;
use Tarfinlabs\EventMachine\ContextManager;
use Tarfinlabs\EventMachine\EventCollection;
use Tarfinlabs\EventMachine\Enums\SourceType;
use Tarfinlabs\EventMachine\Enums\InternalEvent;
use Tarfinlabs\EventMachine\Models\MachineEvent;
Expand Down Expand Up @@ -41,9 +42,9 @@ public function __construct(
public ContextManager $context,
public ?StateDefinition $currentStateDefinition,
public ?EventBehavior $currentEventBehavior = null,
public ?Collection $history = null,
public ?EventCollection $history = null,
) {
$this->history ??= (new MachineEvent())->newCollection();
$this->history ??= new EventCollection();

$this->updateMachineValueFromState();
}
Expand Down
14 changes: 0 additions & 14 deletions src/Behavior/ActionBehavior.php
Original file line number Diff line number Diff line change
Expand Up @@ -4,8 +4,6 @@

namespace Tarfinlabs\EventMachine\Behavior;

use Tarfinlabs\EventMachine\ContextManager;

/**
* ActionBehavior class.
*
Expand All @@ -14,16 +12,4 @@
*/
abstract class ActionBehavior extends InvokableBehavior
{
/**
* Invokes the method with the given parameters.
*
* @param ContextManager $context Provides access to the context in which the method is being invoked.
* @param EventBehavior $eventBehavior The event behavior associated with the method invocation.
* @param array|null $arguments Optional parameters to be passed to the method.
*/
abstract public function __invoke(
ContextManager $context,
EventBehavior $eventBehavior,
?array $arguments = null,
): void;
}
16 changes: 0 additions & 16 deletions src/Behavior/GuardBehavior.php
Original file line number Diff line number Diff line change
Expand Up @@ -4,8 +4,6 @@

namespace Tarfinlabs\EventMachine\Behavior;

use Tarfinlabs\EventMachine\ContextManager;

/**
* Class GuardBehavior.
*
Expand All @@ -14,18 +12,4 @@
*/
abstract class GuardBehavior extends InvokableBehavior
{
/**
* Invokes the method.
*
* @param ContextManager $context The context manager.
* @param EventBehavior $eventBehavior The event behavior.
* @param array|null $arguments The optional arguments for the method.
*
* @return bool Returns true if the method is invoked successfully, false otherwise.
*/
abstract public function __invoke(
ContextManager $context,
EventBehavior $eventBehavior,
?array $arguments = null,
): bool;
}
78 changes: 56 additions & 22 deletions src/Behavior/InvokableBehavior.php
Original file line number Diff line number Diff line change
Expand Up @@ -4,8 +4,12 @@

namespace Tarfinlabs\EventMachine\Behavior;

use ReflectionMethod;
use ReflectionFunction;
use ReflectionUnionType;
use Illuminate\Support\Str;
use Illuminate\Support\Collection;
use Tarfinlabs\EventMachine\Actor\State;
use Tarfinlabs\EventMachine\ContextManager;
use Tarfinlabs\EventMachine\Exceptions\MissingMachineContextException;

Expand All @@ -30,33 +34,13 @@ abstract class InvokableBehavior
*
* @return void
*/
public function __construct(
protected ?Collection $eventQueue = null
) {
public function __construct(protected ?Collection $eventQueue = null)
{
if ($this->eventQueue === null) {
$this->eventQueue = new Collection();
}
}

/**
* Executes the behavior with the given context and event.
*
* This method defines the contract for implementing behaviors
* within classes. The behavior should be directly invokable by
* passing in a ContextManager instance and an array of event payload.
*
* @param ContextManager $context The context to be used during
* invocation.
* @param \Tarfinlabs\EventMachine\Behavior\EventBehavior $eventBehavior The event related to the
* current behavior.
* @param array|null $arguments The arguments to be passed to the behavior.
*/
abstract public function __invoke(
ContextManager $context,
EventBehavior $eventBehavior,
?array $arguments = null,
);

/**
* Raises an event by adding it to the event queue.
*
Expand Down Expand Up @@ -133,4 +117,54 @@ public static function getType(): string
->classBasename()
->toString();
}

/**
* Injects invokable behavior parameters.
*
* Retrieves the parameters of the given invokable behavior and injects the corresponding values
* based on the provided state, event behavior, and action arguments.
* The injected values are added to an array and returned.
*
* @param callable $actionBehavior The invokable behavior to inject parameters for.
* @param State $state The state object used for parameter matching.
* @param EventBehavior|null $eventBehavior The event behavior used for parameter matching. (Optional)
* @param array|null $actionArguments The action arguments used for parameter matching. (Optional)
*
* @return array The injected invokable behavior parameters.
*
* @throws \ReflectionException
*/
public static function injectInvokableBehaviorParameters(
callable $actionBehavior,
State $state,
?EventBehavior $eventBehavior = null,
?array $actionArguments = null,
): array {
$invocableBehaviorParameters = [];

$invocableBehaviorReflection = $actionBehavior instanceof self
? new ReflectionMethod($actionBehavior, '__invoke')
: new ReflectionFunction($actionBehavior);

foreach ($invocableBehaviorReflection->getParameters() as $parameter) {
$parameterType = $parameter->getType();

$typeName = $parameterType instanceof ReflectionUnionType
? $parameterType->getTypes()[0]->getName()
: $parameterType->getName();

$value = match (true) {
is_a($state->context, $typeName) => $state->context, // ContextManager
is_a($eventBehavior, $typeName) => $eventBehavior, // EventBehavior
is_a($state, $typeName) => $state, // State
is_a($state->history, $typeName) => $state->history, // EventCollection
$typeName === 'array' => $actionArguments, // Behavior Arguments
default => null,
};

$invocableBehaviorParameters[] = $value;
}

return $invocableBehaviorParameters;
}
}
7 changes: 0 additions & 7 deletions src/Behavior/ResultBehavior.php
Original file line number Diff line number Diff line change
Expand Up @@ -4,13 +4,6 @@

namespace Tarfinlabs\EventMachine\Behavior;

use Tarfinlabs\EventMachine\ContextManager;

abstract class ResultBehavior extends InvokableBehavior
{
abstract public function __invoke(
ContextManager $context,
EventBehavior $eventBehavior,
?array $arguments = null,
): mixed;
}
17 changes: 0 additions & 17 deletions src/Behavior/ValidationGuardBehavior.php
Original file line number Diff line number Diff line change
Expand Up @@ -4,8 +4,6 @@

namespace Tarfinlabs\EventMachine\Behavior;

use Tarfinlabs\EventMachine\ContextManager;

/**
* An abstract class that represents a validation guard behavior.
*
Expand All @@ -18,19 +16,4 @@ abstract class ValidationGuardBehavior extends GuardBehavior
{
/** @var string|null Holds an error message, which is initially null. */
public ?string $errorMessage = null;

/**
* Invokes the method.
*
* @param ContextManager $context The context manager.
* @param EventBehavior $eventBehavior The event behavior.
* @param array|null $arguments The arguments for the method (optional).
*
* @return bool Returns true if the method invocation was successful, false otherwise.
*/
abstract public function __invoke(
ContextManager $context,
EventBehavior $eventBehavior,
?array $arguments = null,
): bool;
}
14 changes: 12 additions & 2 deletions src/Definition/MachineDefinition.php
Original file line number Diff line number Diff line change
Expand Up @@ -684,6 +684,8 @@ public function transition(
*
* @param string $actionDefinition The action definition, either a class
* @param EventBehavior|null $eventBehavior The event (optional).
*
* @throws \ReflectionException
*/
public function runAction(
string $actionDefinition,
Expand Down Expand Up @@ -720,8 +722,16 @@ public function runAction(
// Get the number of events in the queue before the action is executed.
$numberOfEventsInQueue = $this->eventQueue->count();

// Execute the action behavior.
$actionBehavior($state->context, $eventBehavior, $actionArguments);
// Inject action behavior parameters
$actionBehaviorParemeters = InvokableBehavior::injectInvokableBehaviorParameters(
actionBehavior: $actionBehavior,
state: $state,
eventBehavior: $eventBehavior,
actionArguments: $actionArguments
);

// Execute the action behavior
($actionBehavior)(...$actionBehaviorParemeters);

// Get the number of events in the queue after the action is executed.
$newNumberOfEventsInQueue = $this->eventQueue->count();
Expand Down
2 changes: 2 additions & 0 deletions src/Definition/TransitionBranch.php
Original file line number Diff line number Diff line change
Expand Up @@ -166,6 +166,8 @@ protected function initializeInlineBehaviors(array $inlineBehaviors, BehaviorTyp
* If there are no actions associated with the transition definition, do nothing.
*
* @param EventBehavior|null $eventBehavior The event or null if none is provided.
*
* @throws \ReflectionException
*/
public function runActions(
State $state,
Expand Down
16 changes: 15 additions & 1 deletion src/Definition/TransitionDefinition.php
Original file line number Diff line number Diff line change
Expand Up @@ -10,6 +10,7 @@
use Tarfinlabs\EventMachine\Behavior\EventBehavior;
use Tarfinlabs\EventMachine\Behavior\GuardBehavior;
use Tarfinlabs\EventMachine\Enums\TransitionProperty;
use Tarfinlabs\EventMachine\Behavior\InvokableBehavior;
use Tarfinlabs\EventMachine\Behavior\ValidationGuardBehavior;

/**
Expand Down Expand Up @@ -135,6 +136,8 @@ protected function isAMultiPathGuardedTransition(null|array|string $transitionCo
*
* @return TransitionDefinition|null The first eligible transition or
* null if no eligible transition is found.
*
* @throws \ReflectionException
*/
public function getFirstValidTransitionBranch(
EventBehavior $eventBehavior,
Expand Down Expand Up @@ -162,7 +165,18 @@ public function getFirstValidTransitionBranch(
$guardBehavior->validateRequiredContext($state->context);
}

if ($guardBehavior($state->context, $eventBehavior, $guardArguments) === false) {
// Inject guard behavior parameters
$guardBehaviorParameters = InvokableBehavior::injectInvokableBehaviorParameters(
actionBehavior: $guardBehavior,
state: $state,
eventBehavior: $eventBehavior,
actionArguments: $guardArguments,
);

// Execute the guard behavior
$guardResult = ($guardBehavior)(...$guardBehaviorParameters);

if ($guardResult === false) {
$guardsPassed = false;

$payload = null;
Expand Down
11 changes: 11 additions & 0 deletions src/EventCollection.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,11 @@
<?php

declare(strict_types=1);

namespace Tarfinlabs\EventMachine;

use Illuminate\Database\Eloquent\Collection;

class EventCollection extends Collection
{
}
17 changes: 17 additions & 0 deletions src/Models/MachineEvent.php
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,7 @@

use Illuminate\Support\Carbon;
use Illuminate\Database\Eloquent\Model;
use Tarfinlabs\EventMachine\EventCollection;
use Tarfinlabs\EventMachine\Enums\SourceType;
use Illuminate\Database\Eloquent\Concerns\HasUlids;
use Illuminate\Database\Eloquent\Factories\HasFactory;
Expand Down Expand Up @@ -77,4 +78,20 @@ protected static function newFactory(): MachineEventFactory
{
return MachineEventFactory::new();
}

/**
* Create a new collection of models.
*
* This method overrides the default Eloquent collection with a custom
* EventCollection. This allows for additional methods to be available
* on the collection of MachineEvent models.
*
* @param array $models An array of MachineEvent models.
*
* @return EventCollection A new instance of EventCollection.
*/
public function newCollection(array $models = []): EventCollection
{
return new EventCollection($models);
}
}
20 changes: 8 additions & 12 deletions tests/ActionTest.php
Original file line number Diff line number Diff line change
Expand Up @@ -29,11 +29,11 @@
],
behavior: [
'actions' => [
'additionAction' => function (ContextManager $context, EventDefinition $eventDefinition): void {
$context->set('count', $context->get('count') + $eventDefinition->payload['value']);
'additionAction' => function (ContextManager $context, EventBehavior $eventBehavior): void {
$context->set('count', $context->get('count') + $eventBehavior->payload['value']);
},
'subtractionAction' => function (ContextManager $context, EventDefinition $eventDefinition): void {
$context->set('count', $context->get('count') - $eventDefinition->payload['value']);
'subtractionAction' => function (ContextManager $context, EventBehavior $eventBehavior): void {
$context->set('count', $context->get('count') - $eventBehavior->payload['value']);
},
'incrementAction' => function (ContextManager $context): void {
$context->set('count', $context->get('count') + 1);
Expand Down Expand Up @@ -92,19 +92,15 @@
$value = random_int(10, 20);

$multipleWithItselfAction = new class extends ResultBehavior {
public function __invoke(
ContextManager $context,
EventBehavior $eventBehavior,
?array $arguments = null
): int {
return $eventBehavior->payload['value'] * $eventBehavior->payload['value'];
public function __invoke(EventBehavior $event): int
{
return $event->payload['value'] * $event->payload['value'];
}
};

// 2. Act
$result = $multipleWithItselfAction(
context: new ContextManager(),
eventBehavior: EventDefinition::from([
EventDefinition::from([
'type' => 'ADD',
'payload' => [
'value' => $value,
Expand Down
Loading
Loading