PHP code example of bear / event-sourcing

1. Go to this page and download the library: Download bear/event-sourcing 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/ */

    

bear / event-sourcing example snippets


use BEAR\EventSourcing\SemanticLogExtractor;

$log = $semanticLogger->flush(); // Koriym\SemanticLogger\LogJson
$events = (new SemanticLogExtractor())->extract($log);

foreach ($events as $event) {
    // $event->id, $event->uri, $event->method, $event->params, $event->result, $event->timestamp
    echo $event->method, ' ', $event->uri, "\n";
}

use BEAR\EventSourcing\Module\EventSourcingModule;
use BEAR\EventSourcing\RecordedMethods;
use Override;
use Ray\Di\AbstractModule;

final class AppModule extends AbstractModule
{
    #[Override]
    protected function configure(): void
    {
        $this->install(new EventSourcingModule(
            methods: new RecordedMethods(RecordedMethods::WITH_READS),
        ));
    }
}

use BEAR\EventSourcing\Filtered;
use BEAR\EventSourcing\Resource\FilteredParams;
use BEAR\EventSourcing\Resource\ParamsFilterInterface;
use BEAR\EventSourcing\Resource\SensitiveParamsFilter;
use Override;

final class AppParamsFilter implements ParamsFilterInterface
{
    private SensitiveParamsFilter $default;

    public function __construct()
    {
        // Composed, never replaced, and `otp` goes through the constructor rather than a
        // hand-rolled check: the default matches it the way it matches its own set — at every
        // depth, case and `_`/`-` ignored — where an isset() on the top level would sail past
        // ['user' => ['otp' => '123456']].
        $this->default = new SensitiveParamsFilter(['otp']);
    }

    #[Override]
    public function __invoke(array $params): FilteredParams
    {
        $filtered = ($this->default)($params);

        // What the constructor cannot express: a verdict this application reaches for the
        // request as a whole, whatever its params turned out to hold.
        $replayable = $filtered->replayable && ($params['mode'] ?? null) !== 'sandbox';

        return new FilteredParams($filtered->params, $replayable);
    }
}

$this->bind(ParamsFilterInterface::class)->annotatedWith(Filtered::class)->to(AppParamsFilter::class);

use BEAR\EventSourcing\Event;

$userEvents = new CallbackFilterIterator(
    $events->getIterator(),
    static fn (Event $event): bool => ($event->params['id'] ?? null) === 'koriym',
);

$userWrites = new CallbackFilterIterator(
    $userEvents,
    static fn (Event $event): bool => $event->method !== 'GET',
);

foreach ($userWrites as $event) {
    // replay, project, or inspect events for id=koriym
}

$orderEvents = new CallbackFilterIterator(
    $events->getIterator(),
    static fn (Event $event): bool => str_starts_with($event->uri, 'app://self/orders/123'),
);

use BEAR\EventSourcing\Store\InMemoryEventStore;

$store = new InMemoryEventStore();
$store->appendAll($events);

use BEAR\EventSourcing\EventStoreInterface;
use BEAR\EventSourcing\Module\EventSourcingModule;
use BEAR\EventSourcing\Module\MediaQueryEventStoreModule;
use Override;
use Ray\AuraSqlModule\AuraSqlModule;
use Ray\Di\AbstractModule;
use Ray\Di\Injector;
use Ray\MediaQuery\MediaQuerySqlModule;

final class AppModule extends AbstractModule
{
    #[Override]
    protected function configure(): void
    {
        $packageDir = __DIR__ . '/vendor/bear/event-sourcing';
        $this->install(new AuraSqlModule('sqlite:' . __DIR__ . '/events.sqlite'));
        $this->install(new MediaQuerySqlModule(
            interfaceDir: $packageDir . '/src/Query',
            sqlDir: $packageDir . '/sql/event_store',
        ));
        $this->install(new EventSourcingModule());
        $this->install(new MediaQueryEventStoreModule());
    }
}

$injector = new Injector(new AppModule());
$store = $injector->getInstance(EventStoreInterface::class);
$store->appendAll($events);

  $this->install(new MediaQuerySqlModule(interfaceDir: $appQueryDir, sqlDir: $appSqlDir));
  $this->install(new MediaQuerySqlModule(
      interfaceDir: $packageDir . '/src/Query',
      sqlDir: $appSqlDir, // event_store_*.sql copied here
  ));
  

  $pdo = $injector->getInstance(\Aura\Sql\ExtendedPdoInterface::class);

  $pdo->beginTransaction();
  $store->appendAll($events);
  $pdo->commit();
  

use BEAR\EventSourcing\Resource\ResourceObservationModule;
use BEAR\Resource\Module\ResourceClientModule;
use Ray\Di\Injector;

$injector = new Injector(new ResourceObservationModule(
    module: new ResourceClientModule(),
));

use BEAR\EventSourcing\EventCollector;

$collect = new EventCollector($logger, $extractor, $store); // store is optional

$events = $collect(); // once per request, at the boundary

$log = $logger->flush();                          // the tree, for inspection
file_put_contents($logFile, json_encode($log));
$events = $extractor->extract($log);              // the facts

use BEAR\EventSourcing\Resource\DevLogModule;
use BEAR\Resource\Module\ResourceClientModule;
use Ray\Di\Injector;

$injector = new Injector(new DevLogModule(
    bodyDir: __DIR__ . '/var/es/bodies',
    module: new ResourceClientModule(),
));

final class DevModule extends AbstractAppModule
{
    #[Override]
    protected function configure(): void
    {
        $bodyDir = $this->appMeta->logDir . '/es-bodies';
        FileBodyStore::clearDirectory($bodyDir);

        $this->rename(InvokerInterface::class, 'original_invoker');
        $this->bind(InvokerInterface::class)
            ->toConstructor(SemanticLogInvoker::class, [
                'invoker' => 'original_invoker',
                'recordedMethods' => Recorded::class,
                'paramsFilter' => Filtered::class,
            ])
            ->in(Scope::SINGLETON);
        $this->bind(RecordedMethods::class)->annotatedWith(Recorded::class)
            ->toInstance(new RecordedMethods(RecordedMethods::WITH_READS));
        $this->bind(BodyStoreInterface::class)->toInstance(new FileBodyStore($bodyDir));
        $this->bind(SemanticLoggerInterface::class)->to(SemanticLogger::class)->in(Scope::SINGLETON);
        $this->install(new EventSourcingModule());
    }
}

use BEAR\EventSourcing\Module\MediaQueryObservationModule;

$this->install(new MediaQueryObservationModule()); // before the MediaQuery modules

use Koriym\SemanticLogger\SemanticLogValidator;

(new SemanticLogValidator())->validate($logFile, $projectDir . '/docs/schemas');

use BEAR\RepositoryModule\Annotation\CacheLog;
use Koriym\SemanticLogger\SemanticLogger;
use Koriym\SemanticLogger\SemanticLoggerInterface;

$logger = new SemanticLogger();

$this->install(new DevLogModule($bodyDir, logger: $logger, module: $appModule));
$this->bind(SemanticLoggerInterface::class)->annotatedWith(CacheLog::class)->toInstance($logger);

final class CacheLogProvider implements ProviderInterface
{
    public function __construct(private readonly SemanticLoggerInterface $logger)
    {
    }

    public function get(): SemanticLoggerInterface
    {
        return $this->logger;
    }
}

$this->bind(SemanticLoggerInterface::class)->annotatedWith(CacheLog::class)
    ->toProvider(CacheLogProvider::class)->in(Scope::SINGLETON);
text
request="POST app://self/orders?order_id=O-1000"
├── request="PUT app://self/inventory/SKU-1?sku=SKU-1&quantity=1"
│   ├── media_query name=inventory_reserve durationMs=0.42 [event]
│   └── code=200 body_ref=file://var/es/bodies/000001.json
└── code=201 body_ref=file://var/es/bodies/000002.json