Skip to content

Commit

Permalink
Add a way for JobExecution storage to be setup before used (#107)
Browse files Browse the repository at this point in the history
* Add a way for JobExecution storage to be setup before used

* Fixed code style

* Test DoctrineDBALJobExecutionStorage::createSchema deprecated method

* Fixed wrong usage of magic constants

* Removed deprecation test

* Proper test without deprecation
  • Loading branch information
yann-eugone authored Dec 6, 2023
1 parent 91d1785 commit 9fe827c
Show file tree
Hide file tree
Showing 8 changed files with 222 additions and 15 deletions.
25 changes: 23 additions & 2 deletions src/batch-doctrine-dbal/src/DoctrineDBALJobExecutionStorage.php
Original file line number Diff line number Diff line change
Expand Up @@ -20,12 +20,15 @@
use Yokai\Batch\Storage\JobExecutionStorageInterface;
use Yokai\Batch\Storage\Query;
use Yokai\Batch\Storage\QueryableJobExecutionStorageInterface;
use Yokai\Batch\Storage\SetupableJobExecutionStorageInterface;

/**
* This {@see JobExecutionStorageInterface} will store
* {@see JobExecution} in an SQL database using doctrine/dbal.
*/
final class DoctrineDBALJobExecutionStorage implements QueryableJobExecutionStorageInterface
final class DoctrineDBALJobExecutionStorage implements
QueryableJobExecutionStorageInterface,
SetupableJobExecutionStorageInterface
{
private const DEFAULT_OPTIONS = [
'table' => 'yokai_batch_job_execution',
Expand Down Expand Up @@ -54,7 +57,7 @@ public function __construct(ConnectionRegistry $doctrine, array $options)
/**
* Create required table for this storage.
*/
public function createSchema(): void
public function setup(): void
{
$assetFilter = $this->connection->getConfiguration()->getSchemaAssetsFilter();
$this->connection->getConfiguration()->setSchemaAssetsFilter(null);
Expand Down Expand Up @@ -88,6 +91,24 @@ public function createSchema(): void
$this->connection->getConfiguration()->setSchemaAssetsFilter($assetFilter);
}

/**
* Create required table for this storage.
* @deprecated
*/
public function createSchema(): void
{
@\trigger_error(
\sprintf(
'Since yokai/batch-doctrine-dbal 0.5.8: ' .
'Method "%s()" is deprecated and will be removed in 0.6.0. Use %s::setup() instead.',
__METHOD__,
__CLASS__,
),
\E_USER_DEPRECATED,
);
$this->setup();
}

public function store(JobExecution $execution): void
{
try {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -34,7 +34,7 @@ public function testCreateStandardTable(): void
$schemaManager = $this->connection->getSchemaManager();

self::assertFalse($schemaManager->tablesExist(['yokai_batch_job_execution']));
$this->createStorage()->createSchema();
$this->createStorage()->setup();
self::assertTrue($schemaManager->tablesExist(['yokai_batch_job_execution']));

$columns = $schemaManager->listTableColumns('yokai_batch_job_execution');
Expand All @@ -61,7 +61,7 @@ public function testCreateCustomTable(): void
$schemaManager = $this->connection->getSchemaManager();

self::assertFalse($schemaManager->tablesExist(['acme_job_executions']));
$this->createStorage(['table' => 'acme_job_executions'])->createSchema();
$this->createStorage(['table' => 'acme_job_executions'])->setup();
self::assertTrue($schemaManager->tablesExist(['acme_job_executions']));

$columns = $schemaManager->listTableColumns('acme_job_executions');
Expand All @@ -86,7 +86,7 @@ public function testCreateCustomTable(): void
public function testStoreInsert(): void
{
$storage = $this->createStorage();
$storage->createSchema();
$storage->setup();

$export = JobExecution::createRoot('123', 'export', new BatchStatus(BatchStatus::RUNNING));
$export->setStartTime(new DateTimeImmutable('2021-09-23 11:05:00'));
Expand Down Expand Up @@ -122,7 +122,7 @@ public function testStoreInsert(): void
public function testStoreUpdate(): void
{
$storage = $this->createStorage();
$storage->createSchema();
$storage->setup();
$storage->store($execution = JobExecution::createRoot('123', 'export'));
$execution->setStatus(BatchStatus::COMPLETED);
$storage->store($execution);
Expand All @@ -138,7 +138,7 @@ public function testStoreFailing(): void
$this->expectException(CannotStoreJobExecutionException::class);

$storage = $this->createStorage();
/** not calling {@see DoctrineDBALJobExecutionStorage::createSchema} will cause table to not exists */
/** not calling {@see DoctrineDBALJobExecutionStorage::setup} will cause table to not exists */
$storage->store(JobExecution::createRoot('123', 'export'));
}

Expand All @@ -147,7 +147,7 @@ public function testRemove(): void
$this->expectException(JobExecutionNotFoundException::class);

$storage = $this->createStorage();
$storage->createSchema();
$storage->setup();
$storage->store($execution = JobExecution::createRoot('123', 'export'));
$storage->remove($execution);

Expand All @@ -159,14 +159,14 @@ public function testRemoveFailing(): void
$this->expectException(CannotRemoveJobExecutionException::class);

$storage = $this->createStorage();
/** not calling {@see DoctrineDBALJobExecutionStorage::createSchema} will cause table to not exists */
/** not calling {@see DoctrineDBALJobExecutionStorage::setup} will cause table to not exists */
$storage->remove(JobExecution::createRoot('123', 'export'));
}

public function testRetrieve(): void
{
$storage = $this->createStorage();
$storage->createSchema();
$storage->setup();
$storage->store(JobExecution::createRoot('123', 'export'));
$storage->store(JobExecution::createRoot('456', 'import'));

Expand All @@ -184,7 +184,7 @@ public function testRetrieveNotFound(): void
$this->expectException(JobExecutionNotFoundException::class);

$storage = $this->createStorage();
$storage->createSchema();
$storage->setup();
$storage->store(JobExecution::createRoot('123', 'export'));

$storage->retrieve('export', '456');
Expand All @@ -195,7 +195,7 @@ public function testRetrieveFailing(): void
$this->expectException(JobExecutionNotFoundException::class);

$storage = $this->createStorage();
/** not calling {@see DoctrineDBALJobExecutionStorage::createSchema} will cause table to not exists */
/** not calling {@see DoctrineDBALJobExecutionStorage::setup} will cause table to not exists */
$storage->retrieve('export', '456');
}

Expand All @@ -217,7 +217,7 @@ public function testRetrieveInvalid(array $data, Throwable $error): void
$data['logs'] ??= '';

$storage = $this->createStorage();
$storage->createSchema();
$storage->setup();
$this->connection->insert('yokai_batch_job_execution', $data);
$storage->retrieve('export', '123');
}
Expand Down Expand Up @@ -249,7 +249,7 @@ public function retrieveInvalid(): \Generator
public function testList(): void
{
$storage = $this->createStorage();
$storage->createSchema();
$storage->setup();
$this->loadFixtures($storage);

self::assertExecutionIds(['123'], $storage->list('export'));
Expand All @@ -262,7 +262,7 @@ public function testList(): void
public function testQuery(QueryBuilder $queryBuilder, array $expectedCouples): void
{
$storage = $this->createStorage();
$storage->createSchema();
$storage->setup();
$this->loadFixtures($storage);

self::assertExecutions($expectedCouples, $storage->query($queryBuilder->getQuery()));
Expand Down Expand Up @@ -343,6 +343,14 @@ public function queries(): Generator
];
}

public function testCreateSchemaDeprecated(): void
{
$schemaManager = $this->connection->getSchemaManager();
self::assertFalse($schemaManager->tablesExist(['yokai_batch_job_execution']));
$this->createStorage()->createSchema();
self::assertTrue($schemaManager->tablesExist(['yokai_batch_job_execution']));
}

public static function assertExecutionIds(array $ids, iterable $executions): void
{
$actualIds = [];
Expand Down
53 changes: 53 additions & 0 deletions src/batch-symfony-console/src/SetupStorageCommand.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,53 @@
<?php

declare(strict_types=1);

namespace Yokai\Batch\Bridge\Symfony\Console;

use Symfony\Component\Console\Attribute\AsCommand;
use Symfony\Component\Console\Command\Command;
use Symfony\Component\Console\Input\InputInterface;
use Symfony\Component\Console\Output\OutputInterface;
use Symfony\Component\Console\Style\SymfonyStyle;
use Yokai\Batch\Storage\JobExecutionStorageInterface;
use Yokai\Batch\Storage\SetupableJobExecutionStorageInterface;

/**
* Prepare the required infrastructure for the job execution storage.
*/
#[AsCommand(name: 'yokai:batch:setup-storage', description: 'Prepare the required infrastructure for the storage')]
final class SetupStorageCommand extends Command
{
public function __construct(
private JobExecutionStorageInterface $storage,
) {
parent::__construct();
}

protected function configure(): void
{
$this
->setHelp(
<<<EOF
The <info>%command.name%</info> command setups the job execution storage:
<info>php %command.full_name%</info>
EOF
)
;
}

protected function execute(InputInterface $input, OutputInterface $output): int
{
$io = new SymfonyStyle($input, $output);

if ($this->storage instanceof SetupableJobExecutionStorageInterface) {
$this->storage->setup();
$io->success('The storage was set up successfully.');
} else {
$io->note('The storage does not support setup.');
}

return self::SUCCESS;
}
}
76 changes: 76 additions & 0 deletions src/batch-symfony-console/tests/SetupStorageCommandTest.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,76 @@
<?php

declare(strict_types=1);

namespace Yokai\Batch\Tests\Bridge\Symfony\Console;

use PHPUnit\Framework\TestCase;
use Symfony\Component\Console\Tester\CommandTester;
use Yokai\Batch\Bridge\Symfony\Console\SetupStorageCommand;
use Yokai\Batch\Exception\JobExecutionNotFoundException;
use Yokai\Batch\JobExecution;
use Yokai\Batch\Storage\JobExecutionStorageInterface;
use Yokai\Batch\Storage\SetupableJobExecutionStorageInterface;

final class SetupStorageCommandTest extends TestCase
{
public function testSetupRequired(): void
{
$this->execute(
$storage = new class() implements
JobExecutionStorageInterface,
SetupableJobExecutionStorageInterface {
public bool $wasSetup = false;

public function setup(): void
{
$this->wasSetup = true;
}

public function store(JobExecution $execution): void
{
}

public function remove(JobExecution $execution): void
{
}

public function retrieve(string $jobName, string $executionId): JobExecution
{
throw new JobExecutionNotFoundException($jobName, $executionId);
}
},
'[OK] The storage was set up successfully.',
);
self::assertTrue($storage->wasSetup);
}

public function testSetupNotRequired(): void
{
$this->execute(
new class() implements JobExecutionStorageInterface {
public function store(JobExecution $execution): void
{
}

public function remove(JobExecution $execution): void
{
}

public function retrieve(string $jobName, string $executionId): JobExecution
{
throw new JobExecutionNotFoundException($jobName, $executionId);
}
},
'! [NOTE] The storage does not support setup.',
);
}

private function execute(JobExecutionStorageInterface $storage, string $expected): void
{
$tester = new CommandTester(new SetupStorageCommand($storage));
$tester->execute([]);
$tester->assertCommandIsSuccessful();
self::assertSame($expected, \trim($tester->getDisplay(true)));
}
}
1 change: 1 addition & 0 deletions src/batch-symfony-framework/composer.json
Original file line number Diff line number Diff line change
Expand Up @@ -14,6 +14,7 @@
"php": "^8.0",
"composer-runtime-api": "^2.0",
"symfony/config": "^5.0|^6.0",
"symfony/console": "^5.0|^6.0",
"symfony/dependency-injection": "^5.0|^6.0",
"symfony/http-kernel": "^5.0|^6.0",
"symfony/framework-bundle": "^5.0|^6.0",
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -13,5 +13,11 @@
<argument type="service" id="yokai_batch.job_executor"/>
<tag name="console.command"/>
</service>

<service id="yokai_batch.cli.setup_storage_command"
class="Yokai\Batch\Bridge\Symfony\Console\SetupStorageCommand">
<argument type="service" id="Yokai\Batch\Storage\JobExecutionStorageInterface"/>
<tag name="console.command"/>
</service>
</services>
</container>
26 changes: 26 additions & 0 deletions src/batch-symfony-framework/tests/CliTest.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,26 @@
<?php

declare(strict_types=1);

namespace Yokai\Batch\Tests\Bridge\Symfony\Framework;

use Symfony\Bundle\FrameworkBundle\Console\Application;
use Symfony\Bundle\FrameworkBundle\Test\KernelTestCase;

final class CliTest extends KernelTestCase
{
public function testRegisteredCommands(): void
{
$names = \array_keys(
(new Application(self::bootKernel()))->all('yokai'),
);
\sort($names);
self::assertSame(
[
'yokai:batch:run',
'yokai:batch:setup-storage',
],
$names,
);
}
}
16 changes: 16 additions & 0 deletions src/batch/src/Storage/SetupableJobExecutionStorageInterface.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,16 @@
<?php

declare(strict_types=1);

namespace Yokai\Batch\Storage;

/**
* A job execution having this interface tells the developers it should be setuped before being used.
*/
interface SetupableJobExecutionStorageInterface
{
/**
* Setup the storage.
*/
public function setup(): void;
}

0 comments on commit 9fe827c

Please sign in to comment.