PHP code example of tiny-blocks / outbox

1. Go to this page and download the library: Download tiny-blocks/outbox library. Choose the download type require.

2. Extract the ZIP file and open the index.php.

3. Add this code to the index.php.
    
        
<?php
require_once('vendor/autoload.php');

/* Start to develop here. Best regards https://php-download.com/ */

    

tiny-blocks / outbox example snippets




declare(strict_types=1);

use TinyBlocks\BuildingBlocks\Event\IntegrationEventTranslators;
use TinyBlocks\Outbox\DoctrineOutboxRepository;
use TinyBlocks\Outbox\Serialization\PayloadSerializerReflection;
use TinyBlocks\Outbox\Serialization\PayloadSerializers;

$repository = new DoctrineOutboxRepository(
    connection: $connection,
    serializers: PayloadSerializers::createFrom(elements: [
        new PayloadSerializerReflection()
    ]),
    translators: IntegrationEventTranslators::createFrom(elements: [
        new OrderPlacedTranslator()
    ])
);



declare(strict_types=1);

use TinyBlocks\BuildingBlocks\Event\DomainEvent;
use TinyBlocks\BuildingBlocks\Event\DomainEventBehavior;

# A domain event. Stays internal to the bounded context. A translator maps it to the public contract.
final readonly class OrderPlaced implements DomainEvent
{
    use DomainEventBehavior;

    public function __construct(public string $orderId)
    {
    }
}

# Both the aggregate state change and the outbox write happen inside the same transaction.
$connection->beginTransaction();

try {
    $order = Order::place(orderId: 'order-123');
    $orderRepository->save(order: $order);
    $outboxRepository->push(records: $order->recordedEvents());
    $connection->commit();
} catch (Throwable $exception) {
    $connection->rollBack();
    throw $exception;
}



declare(strict_types=1);

use TinyBlocks\BuildingBlocks\Event\IntegrationEvent;
use TinyBlocks\BuildingBlocks\Event\IntegrationEventBehavior;
use TinyBlocks\BuildingBlocks\Event\Revision;

# Integration event class names use ubiquitous-language terms, not a suffix like IntegrationEvent.
final readonly class OrderShipped implements IntegrationEvent
{
    use IntegrationEventBehavior;

    public function __construct(public string $orderId)
    {
    }
}

# Bump revision() only when the public payload schema changes.
final readonly class PaymentConfirmed implements IntegrationEvent
{
    use IntegrationEventBehavior;

    public function revision(): Revision
    {
        return Revision::of(value: 2);
    }
}



declare(strict_types=1);

use TinyBlocks\BuildingBlocks\Event\EventRecord;
use TinyBlocks\BuildingBlocks\Event\IntegrationEvent;
use TinyBlocks\BuildingBlocks\Event\IntegrationEventTranslator;

final readonly class OrderPlacedTranslator implements IntegrationEventTranslator
{
    public function supports(EventRecord $record): bool
    {
        return $record->event instanceof OrderPlaced;
    }

    public function translate(EventRecord $record): IntegrationEvent
    {
        return new OrderShipped(orderId: $record->event->orderId);
    }
}



declare(strict_types=1);

use TinyBlocks\BuildingBlocks\Event\IntegrationEventTranslators;
use TinyBlocks\Outbox\DoctrineOutboxRepository;
use TinyBlocks\Outbox\Serialization\PayloadSerializerReflection;
use TinyBlocks\Outbox\Serialization\PayloadSerializers;

$repository = new DoctrineOutboxRepository(
    connection: $connection,
    serializers: PayloadSerializers::createFrom(elements: [
        new PayloadSerializerReflection()
    ]),
    translators: IntegrationEventTranslators::createFrom(elements: [
        new OrderPlacedTranslator(),
        new PaymentReceivedTranslator()
    ])
);



declare(strict_types=1);

use TinyBlocks\BuildingBlocks\Event\IntegrationEventTranslators;
use TinyBlocks\Outbox\DoctrineOutboxRepository;
use TinyBlocks\Outbox\Serialization\PayloadSerializerReflection;
use TinyBlocks\Outbox\Serialization\PayloadSerializers;

# InventoryReserved has no translator registered: its row carries the domain event itself,
# with event_type InventoryReserved and a payload reflecting its public properties.
$repository = new DoctrineOutboxRepository(
    connection: $connection,
    serializers: PayloadSerializers::createFrom(elements: [
        new PayloadSerializerReflection()
    ]),
    translators: IntegrationEventTranslators::createFrom(elements: [
        new OrderPlacedTranslator()
    ])
);



declare(strict_types=1);

use TinyBlocks\BuildingBlocks\Event\IntegrationEventTranslators;
use TinyBlocks\Outbox\DoctrineOutboxRepository;
use TinyBlocks\Outbox\Schema\Columns;
use TinyBlocks\Outbox\Schema\IdentityColumnType;
use TinyBlocks\Outbox\Schema\TableLayout;
use TinyBlocks\Outbox\Serialization\PayloadSerializerReflection;
use TinyBlocks\Outbox\Serialization\PayloadSerializers;

$tableLayout = TableLayout::builder()
    ->withColumns(columns: Columns::builder()
        ->withId(name: 'id', type: IdentityColumnType::STRING)
        ->withEventType(name: 'kind')
        ->withAggregateId(name: 'aggregate_id', type: IdentityColumnType::STRING)
        ->withAggregateType(name: 'entity_class')
        ->withAggregateVersion(name: 'position')
        ->build())
    ->withTableName(tableName: 'my_outbox')
    ->build();

$repository = new DoctrineOutboxRepository(
    connection: $connection,
    serializers: PayloadSerializers::createFrom(elements: [new PayloadSerializerReflection()]),
    translators: IntegrationEventTranslators::createFrom(elements: [new OrderPlacedTranslator()]),
    tableLayout: $tableLayout
);



declare(strict_types=1);

use TinyBlocks\BuildingBlocks\Event\IntegrationEventRecord;
use TinyBlocks\BuildingBlocks\Event\IntegrationEventTranslators;
use TinyBlocks\Outbox\DoctrineOutboxRepository;
use TinyBlocks\Outbox\Serialization\PayloadSerializer;
use TinyBlocks\Outbox\Serialization\PayloadSerializerReflection;
use TinyBlocks\Outbox\Serialization\PayloadSerializers;
use TinyBlocks\Outbox\Serialization\SerializedPayload;

final readonly class OrderEventSerializer implements PayloadSerializer
{
    public function supports(IntegrationEventRecord $record): bool
    {
        return $record->event instanceof OrderShipped || $record->event instanceof OrderCanceled;
    }

    public function serialize(IntegrationEventRecord $record): SerializedPayload
    {
        return match (true) {
            $record->event instanceof OrderShipped => SerializedPayload::from(
                payload: json_encode(['orderId' => $record->event->orderId], JSON_THROW_ON_ERROR)
            ),
            $record->event instanceof OrderCanceled => SerializedPayload::from(
                payload: json_encode(
                    ['orderId' => $record->event->orderId, 'reason' => $record->event->reason],
                    JSON_THROW_ON_ERROR
                )
            )
        };
    }
}

# Register custom serializers before PayloadSerializerReflection.
# PayloadSerializerReflection always returns true from supports(), so it must come last.
$repository = new DoctrineOutboxRepository(
    connection: $connection,
    serializers: PayloadSerializers::createFrom(elements: [
        new OrderEventSerializer(),
        new PayloadSerializerReflection()
    ]),
    translators: IntegrationEventTranslators::createFrom(elements: [
        new OrderPlacedTranslator(),
        new OrderCanceledTranslator()
    ])
);



declare(strict_types=1);

use TinyBlocks\BuildingBlocks\Event\IntegrationEvent;
use TinyBlocks\BuildingBlocks\Event\IntegrationEventBehavior;
use TinyBlocks\BuildingBlocks\Event\Revision;

# Revision 2: a currency field was added to the public contract. Override revision() to declare the schema bump.
final readonly class OrderShipped implements IntegrationEvent
{
    use IntegrationEventBehavior;

    public function __construct(public string $orderId, public string $currency)
    {
    }

    public function revision(): Revision
    {
        return Revision::of(value: 2);
    }
}