clavicula-nox/pendingactions-bundle
Installation:
composer require clavicula-nox/pendingactions-bundle
Add to config/bundles.php:
return [
// ...
ClaviculaNox\PendingActionsBundle\PendingActionsBundle::class => ['all' => true],
];
Configure Database:
Run migrations (check docs/installation.md for schema details) to create the pending_action table.
First Use Case: Create a command to trigger a pending action:
use ClaviculaNox\PendingActionsBundle\Entity\PendingAction;
use ClaviculaNox\PendingActionsBundle\Manager\PendingActionManager;
class MyCommand extends Command
{
private $pendingActionManager;
public function __construct(PendingActionManager $pendingActionManager)
{
$this->pendingActionManager = $pendingActionManager;
}
protected function execute(InputInterface $input, OutputInterface $output)
{
$pendingAction = new PendingAction();
$pendingAction->setAction('App\Action\MyHeavyTaskAction');
$pendingAction->setPayload(['param1' => 'value1']);
$pendingAction->setScheduledAt(new \DateTime('+1 hour')); // Schedule for 1 hour later
$this->pendingActionManager->create($pendingAction);
}
}
Run the Consumer: Add a cron job or Symfony command to process pending actions:
php bin/console clavicula-nox:pending-actions:consume
Enqueue Actions:
Use PendingActionManager to create actions with:
App\Action\ProcessOrderAction).['order_id' => 123]).scheduledAt (for delayed execution) or immediate (for instant processing).$pendingAction = new PendingAction();
$pendingAction->setAction('App\Action\SendEmailAction');
$pendingAction->setPayload(['user_id' => 42, 'template' => 'welcome']);
$pendingAction->setScheduledAt(new \DateTime('+5 minutes')); // Delayed by 5 mins
$manager->create($pendingAction);
Process Actions:
Implement a handler (e.g., ServiceHandler) to execute actions. Default handlers:
# config/packages/clavicula_nox_pending_actions.yaml
clavicula_nox_pending_actions:
handlers:
service:
enabled: true
namespace: 'App\Action' # Auto-discover actions in this namespace
Retry Logic: Configure retries for failed actions:
clavicula_nox_pending_actions:
retry:
max_attempts: 3
delay: 60 # seconds between retries
Bulk Processing:
Use the consume command with --limit to process actions in batches:
php bin/console clavicula-nox:pending-actions:consume --limit=20
Event-Driven Triggers:
Hook into Symfony events (e.g., kernel.request) to enqueue actions dynamically:
$eventDispatcher->addListener('kernel.request', function (KernelEvent $event) {
if ($event->getRequest()->isXmlHttpRequest()) {
$pendingAction = new PendingAction();
$pendingAction->setAction('App\Action\LogRequestAction');
$pendingAction->setPayload(['request' => $event->getRequest()->query->all()]);
$manager->create($pendingAction);
}
});
Background Processing: Run the consumer in a separate process (e.g., using Supervisor) for high-throughput systems:
[program:pending-actions]
command=php bin/console clavicula-nox:pending-actions:consume --limit=50
autostart=true
autorestart=true
Monitoring:
Extend the PendingAction entity to track metadata (e.g., created_at, updated_at, status):
// src/Entity/CustomPendingAction.php
class CustomPendingAction extends PendingAction
{
#[ORM\Column(type: 'string', length: 50)]
private $status = 'pending';
#[ORM\Column(type: 'datetime')]
private $createdAt;
}
Testing:
Mock PendingActionManager in tests to verify actions are enqueued:
$mockManager = $this->createMock(PendingActionManager::class);
$mockManager->expects($this->once())
->method('create')
->with($this->isInstanceOf(PendingAction::class));
$service = new MyService($mockManager);
$service->triggerHeavyTask();
Serialization Issues:
json_encode()/json_decode() for complex objects:
$payload = json_encode(['data' => $complexObject->toArray()]);
Handler Resolution:
ServiceHandler, ensure the action class is autowireable and its method is public static.namespace App\Action;
class SendEmailAction {
public static function execute(array $payload, ManagerRegistry $registry) {
// Logic here
}
}
Timezone Handling:
scheduledAt uses the system timezone. Explicitly set timezone in code:
$pendingAction->setScheduledAt((new \DateTime('+1 hour'))->setTimezone(new \DateTimeZone('UTC')));
Database Locking:
SELECT ... FOR UPDATE to lock rows. Ensure your database supports this (e.g., PostgreSQL, MySQL with innodb_lock_wait_timeout configured).Circular Dependencies:
A references B, B references A). Use IDs or minimal data.Log Failed Actions:
Enable logging in config/packages/clavicula_nox_pending_actions.yaml:
clavicula_nox_pending_actions:
logging:
enabled: true
channel: 'pending_actions'
Check logs for errors like:
[2023-01-01 12:00:00] clavicula_nox.pending_actions.ERROR: Failed to execute action "App\Action\BrokenAction": Class not found []
Manual Retries:
Use the retry command to reprocess failed actions:
php bin/console clavicula-nox:pending-actions:retry --id=123
Query Performance:
Add indexes to the pending_action table for large queues:
CREATE INDEX idx_pending_action_scheduled_at ON pending_action(scheduled_at);
CREATE INDEX idx_pending_action_status ON pending_action(status);
Custom Handlers: Create a custom handler to extend functionality (e.g., HTTP callbacks):
namespace App\PendingAction\Handler;
use ClaviculaNox\PendingActionsBundle\Handler\HandlerInterface;
class HttpHandler implements HandlerInterface
{
public function handle(PendingAction $action)
{
$client = new \GuzzleHttp\Client();
$client->post('https://api.example.com/webhook', [
'json' => $action->getPayload(),
]);
}
}
Register it in services.yaml:
services:
App\PendingAction\Handler\HttpHandler:
tags:
- { name: clavicula_nox_pending_actions.handler, alias: 'http' }
State Machine:
Extend the state column to support custom workflows (e.g., pending → processing → completed → archived):
// src/Entity/CustomPendingAction.php
const STATE_PENDING = 'pending';
const STATE_PROCESSING = 'processing';
const STATE_COMPLETED = 'completed';
const STATE_ARCHIVED = 'archived';
Priority Queues:
Add a priority column to the pending_action table and sort queries accordingly:
$qb->orderBy('p.priority', '
How can I help you explore Laravel packages today?