Skip to content

Commit

Permalink
add commands migrations
Browse files Browse the repository at this point in the history
  • Loading branch information
Baptouuuu committed Sep 29, 2024
1 parent 0ee60d2 commit c2283bb
Show file tree
Hide file tree
Showing 8 changed files with 386 additions and 0 deletions.
1 change: 1 addition & 0 deletions .gitattributes
Original file line number Diff line number Diff line change
@@ -1,2 +1,3 @@
/.github export-ignore
/proofs export-ignore
/fixtures export-ignore
5 changes: 5 additions & 0 deletions composer.json
Original file line number Diff line number Diff line change
Expand Up @@ -24,6 +24,11 @@
"Formal\\Migrations\\": "src/"
}
},
"autoload-dev": {
"psr-4": {
"Fixtures\\Formal\\Migrations\\": "fixtures/"
}
},
"require-dev": {
"vimeo/psalm": "~5.13",
"innmind/black-box": "~5.7",
Expand Down
17 changes: 17 additions & 0 deletions fixtures/Ref.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,17 @@
<?php
declare(strict_types = 1);

namespace Fixtures\Formal\Migrations;

use Formal\Migrations\Commands\Reference;
use Innmind\Server\Control\Server\Command;

enum Ref implements Reference
{
case rm;

public function command(): Command
{
return Command::foreground('rm test');
}
}
120 changes: 120 additions & 0 deletions proofs/commands.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,120 @@
<?php
declare(strict_types = 1);

use Fixtures\Formal\Migrations\Ref;
use Formal\Migrations\{
Commands,
Commands\Migration,
Version,
};
use Formal\ORM\{
Manager,
Definition\Aggregates,
Definition\Types,
Definition\Type\Support,
Definition\Type\PointInTimeType,
};
use Innmind\OperatingSystem\Factory;
use Innmind\Filesystem\Adapter\InMemory;
use Innmind\Server\Control\Server\Command;
use Innmind\TimeContinuum\PointInTime;
use Innmind\Url\Path;
use Innmind\Immutable\Sequence;
use Innmind\BlackBox\Set;

return static function() {
yield proof(
'Commands migrations',
given(
Set\MutuallyExclusive::of(
Set\Strings::madeOf(Set\Chars::alphanumerical())->atLeast(1),
Set\Strings::madeOf(Set\Chars::alphanumerical())->atLeast(1),
Set\Strings::madeOf(Set\Chars::alphanumerical())->atLeast(1),
Set\Strings::madeOf(Set\Chars::alphanumerical())->atLeast(1),
),
),
static function($assert, $names) {
[$a, $b, $c, $d] = $names;
$tmp = \sys_get_temp_dir().'/formal/migrations';
@\mkdir($tmp, recursive: true);

$os = Factory::build();

$migrations = Commands::of(
$storage = Manager::filesystem(
InMemory::emulateFilesystem(),
Aggregates::of(
Types::of(
Support::class(
PointInTime::class,
PointInTimeType::new($os->clock()),
),
),
),
),
$os,
null,
static fn() => static fn($command) => $command->withWorkingDirectory(Path::of($tmp)),
);

$versions = $migrations(Sequence::of(
Migration::of(
$a,
Command::foreground('touch test')
->withWorkingDirectory(Path::of($tmp)),
),
Migration::of(
$b,
Ref::rm,
),
Migration::of(
$c,
Command::foreground('echo foo >> test')
->withWorkingDirectory(Path::of($tmp)),
),
Migration::of(
$d,
Ref::rm,
),
));

$assert->count(4, $versions);
$assert->same(
[$a, $b, $c, $d],
$versions
->map(static fn($version) => $version->name())
->toList(),
);
$assert->same(
[$a, $b, $c, $d],
$versions
->sort(static fn($a, $b) => $a->appliedAt()->aheadOf($b->appliedAt()) ? 1 : -1)
->map(static fn($version) => $version->name())
->toList(),
);
$stored = $storage
->repository(Version::class)
->all()
->map(static fn($version) => $version->name())
->toList();
$assert
->expected($a)
->in($stored);
$assert
->expected($b)
->in($stored);
$assert
->expected($c)
->in($stored);
$assert
->expected($d)
->in($stored);

$assert->true(\file_exists($tmp.'/test'));
$assert->same(
"foo\n",
\file_get_contents($tmp.'/test'),
);
},
);
};
106 changes: 106 additions & 0 deletions src/Commands.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,106 @@
<?php
declare(strict_types = 1);

namespace Formal\Migrations;

use Formal\Migrations\Commands\{
Run,
Reference,
};
use Formal\ORM\Manager;
use Innmind\OperatingSystem\OperatingSystem;
use Innmind\Server\Control\Server\{
Processes,
Command,
};
use Innmind\Specification\{
Comparator\Property,
Sign,
};
use Innmind\Immutable\{
Sequence,
Either,
};

final class Commands
{
private Manager $storage;
private OperatingSystem $os;
/** @var callable(OperatingSystem): Processes */
private $build;
/** @var callable(Reference): (callable(Command): Command) */
private $configure;

/**
* @param callable(OperatingSystem): Processes $build
* @param callable(Reference): (callable(Command): Command) $configure
*/
private function __construct(
Manager $storage,
OperatingSystem $os,
callable $build,
callable $configure,
) {
$this->storage = $storage;
$this->os = $os;
$this->build = $build;
$this->configure = $configure;
}

/**
* @param Sequence<Migration<Run>> $migrations
*
* @return Sequence<Version>
*/
public function __invoke(Sequence $migrations): Sequence
{
$versions = $this->storage->repository(Version::class);
$processes = ($this->build)($this->os);
$run = Run::of($processes, $this->configure);

return $migrations
->exclude(static fn($migration) => $versions->any(
Property::of(
'name',
Sign::equality,
$migration->name(),
),
))
->map(function($migration) use ($run, $versions) {
$migration($run);

$version = Version::new(
$migration->name(),
$this->os->clock(),
);

$this->storage->transactional(
static function() use ($version, $versions) {
$versions->put($version);

return Either::right(null);
},
);

return $version;
});
}

/**
* @param ?callable(OperatingSystem): Processes $build
* @param ?callable(Reference): (callable(Command): Command) $configure
*/
public static function of(
Manager $storage,
OperatingSystem $os,
callable $build = null,
callable $configure = null,
): self {
return new self(
$storage,
$os,
$build ?? static fn(OperatingSystem $os) => $os->control()->processes(),
$configure ?? static fn(Reference $ref) => static fn(Command $command) => $command,
);
}
}
51 changes: 51 additions & 0 deletions src/Commands/Migration.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,51 @@
<?php
declare(strict_types = 1);

namespace Formal\Migrations\Commands;

use Formal\Migrations\Migration as MigrationInterface;
use Innmind\Server\Control\Server\Command;
use Innmind\Immutable\Sequence;

/**
* @implements MigrationInterface<Run>
*/
final class Migration implements MigrationInterface
{
/**
* @param non-empty-string $name
* @param Sequence<Command|Reference> $commands
*/
private function __construct(
private string $name,
private Sequence $commands,
) {
}

public function __invoke($kind): void
{
$_ = $this->commands->foreach(
static fn($command) => $kind($command)->match(
static fn() => null,
static fn($error) => throw new \RuntimeException($error::class),
),
);
}

/**
* @no-named-arguments
*
* @param non-empty-string $name
*/
public static function of(
string $name,
Command|Reference ...$commands,
): self {
return new self($name, Sequence::of(...$commands));
}

public function name(): string
{
return $this->name;
}
}
11 changes: 11 additions & 0 deletions src/Commands/Reference.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,11 @@
<?php
declare(strict_types = 1);

namespace Formal\Migrations\Commands;

use Innmind\Server\Control\Server\Command;

interface Reference extends \UnitEnum
{
public function command(): Command;
}
Loading

0 comments on commit c2283bb

Please sign in to comment.