41 lines
1.8 KiB
PHP
41 lines
1.8 KiB
PHP
<?php
|
|
|
|
namespace App\Services;
|
|
|
|
use App\Exceptions\OpportunityWorkflowUnavailable;
|
|
use App\Models\Opportunity;
|
|
use App\Models\User;
|
|
use Illuminate\Auth\Access\AuthorizationException;
|
|
use Illuminate\Auth\AuthenticationException;
|
|
use Illuminate\Database\Eloquent\ModelNotFoundException;
|
|
use Illuminate\Http\Exceptions\HttpResponseException;
|
|
use Illuminate\Validation\ValidationException;
|
|
use Symfony\Component\HttpKernel\Exception\HttpExceptionInterface;
|
|
use Throwable;
|
|
|
|
/** Observe an already-authorized operation after its transaction unwinds. Never retry it. */
|
|
final class OpportunityTechnicalExecution
|
|
{
|
|
public function __construct(private OpportunityTechnicalIncidents $incidents) {}
|
|
|
|
/** @template T
|
|
* @param array<string,mixed> $input
|
|
* @param callable():T $operation
|
|
* @return T */
|
|
public function run(User $actor, Opportunity $before, string $action, array $input, callable $operation): mixed
|
|
{
|
|
try {
|
|
return $operation();
|
|
} catch (Throwable $failure) {
|
|
if ($failure instanceof ValidationException || $failure instanceof AuthorizationException || $failure instanceof AuthenticationException
|
|
|| $failure instanceof ModelNotFoundException || ($failure instanceof HttpExceptionInterface && $failure->getStatusCode() < 500)
|
|
|| ($failure instanceof HttpResponseException && $failure->getResponse()->getStatusCode() < 500)) {
|
|
throw $failure;
|
|
}
|
|
$etag = is_string($input['etag'] ?? null) && preg_match('/^[a-f0-9]{64}$/D', $input['etag']) === 1 ? $input['etag'] : '';
|
|
$incident = $this->incidents->record($actor, $before, $action, $etag, $failure);
|
|
throw new OpportunityWorkflowUnavailable($incident['reference'], $incident['recorded'], $failure);
|
|
}
|
|
}
|
|
}
|