Skip to content
Snippets Groups Projects
Unverified Commit c927aa90 authored by Thomas Flori's avatar Thomas Flori
Browse files

get current status from database

parent 622f5971
No related branches found
No related tags found
No related merge requests found
...@@ -4,7 +4,8 @@ ...@@ -4,7 +4,8 @@
"license": "MIT", "license": "MIT",
"require": { "require": {
"php": "^7.1", "php": "^7.1",
"ext-pdo": "*" "ext-pdo": "*",
"ext-json": "*"
}, },
"require-dev": { "require-dev": {
"mockery/mockery": "^1.1", "mockery/mockery": "^1.1",
......
<?php
namespace Breyta;
class Execution
{
/** @var string */
public $teaser;
/** @var string */
public $action;
/** @var string */
public $type;
/** @var string */
public $name;
/** @var double */
public $executionTime;
public static function createInstance(array $data = []): self
{
$new = new static;
foreach ($data as $key => $value) {
$new->$key = $value;
}
return $new;
}
}
<?php
namespace Breyta;
class Migration
{
/** @var string */
public $file;
/** @var \DateTime */
public $executed;
/** @var string */
public $status;
/** @var string|array|Execution[] */
public $executions;
/** @var double */
public $executionTime;
public function __construct()
{
if (!empty($this->executed) && is_string($this->executed)) {
$this->executed = new \DateTime($this->executed, new \DateTimeZone('UTC'));
}
if (!empty($this->executions) && is_string($this->executions)) {
$this->executions = array_map(function ($data) {
return Execution::createInstance($data);
}, json_decode($this->executions, true));
}
}
public static function createInstance(array $data = []): self
{
$new = new static;
foreach ($data as $key => $value) {
$new->$key = $value;
}
$new->__construct();
return $new;
}
}
...@@ -12,12 +12,15 @@ class Migrations ...@@ -12,12 +12,15 @@ class Migrations
/** @var string */ /** @var string */
protected $path; protected $path;
/** @var array */ /** @var array|Migration[] */
protected $migrations; protected $migrations;
/** @var array */ /** @var array|String[] */
protected $classes; protected $classes;
/** @var array|Migration[] */
protected $missingMigrations = [];
public function __construct(\PDO $db, string $path) public function __construct(\PDO $db, string $path)
{ {
if (!file_exists($path) || !is_dir($path)) { if (!file_exists($path) || !is_dir($path)) {
...@@ -30,16 +33,43 @@ class Migrations ...@@ -30,16 +33,43 @@ class Migrations
public function getStatus(): \stdClass public function getStatus(): \stdClass
{ {
return (object)[ $this->loadMigrations();
'migrations' => $this->getMigrations(),
'count' => 0, $status = (object)[
'migrations' => $this->migrations,
'count' => count(array_filter($this->migrations, function ($migration) {
return $migration->status !== 'done';
})),
]; ];
if (count($this->missingMigrations)) {
$status->missing = $this->missingMigrations;
}
return $status;
} }
protected function getMigrations(): array protected function loadMigrations(): array
{ {
if (!$this->migrations) { if (!$this->migrations) {
$this->migrations = $this->findMigrations(); $this->migrations = $this->findMigrations();
// get the status of migrations from database
try {
$statement = $this->db->query('SELECT * FROM migrations');
if ($statement) {
$statement->setFetchMode(\PDO::FETCH_CLASS, Migration::class);
while ($migration = $statement->fetch()) {
if (!isset($this->migrations[$migration->file])) {
$this->missingMigrations[] = $migration;
continue;
}
$this->migrations[$migration->file] = $migration;
}
}
} catch (\PDOException $exception) {
// the table does not exist - so nothing to do here
}
} }
return $this->migrations; return $this->migrations;
...@@ -48,10 +78,10 @@ class Migrations ...@@ -48,10 +78,10 @@ class Migrations
protected function findMigrations(): array protected function findMigrations(): array
{ {
$this->classes['@breyta/CreateMigrationTable.php'] = CreateMigrationTable::class; $this->classes['@breyta/CreateMigrationTable.php'] = CreateMigrationTable::class;
$migrations = [(object)[ $migrations = [Migration::createInstance([
'file' => '@breyta/CreateMigrationTable.php', 'file' => '@breyta/CreateMigrationTable.php',
'status' => 'new', 'status' => 'new',
]]; ])];
/** @var \SplFileInfo $fileInfo */ /** @var \SplFileInfo $fileInfo */
foreach (new \RecursiveIteratorIterator(new \RecursiveDirectoryIterator($this->path)) as $fileInfo) { foreach (new \RecursiveIteratorIterator(new \RecursiveDirectoryIterator($this->path)) as $fileInfo) {
...@@ -62,7 +92,7 @@ class Migrations ...@@ -62,7 +92,7 @@ class Migrations
continue; continue;
} }
$className = $this->getClassFromFile($fileInfo->getPathname()); $className = self::getClassFromFile($fileInfo->getPathname());
if (!$className) { if (!$className) {
continue; continue;
} }
...@@ -73,10 +103,10 @@ class Migrations ...@@ -73,10 +103,10 @@ class Migrations
$file = substr($fileInfo->getPathname(), strlen($this->path) + 1); $file = substr($fileInfo->getPathname(), strlen($this->path) + 1);
$this->classes[$file] = $className; $this->classes[$file] = $className;
$migrations[] = (object)[ $migrations[] = Migration::createInstance([
'file' => $file, 'file' => $file,
'status' => 'new' 'status' => 'new'
]; ]);
} }
usort($migrations, function ($left, $right) { usort($migrations, function ($left, $right) {
...@@ -116,10 +146,16 @@ class Migrations ...@@ -116,10 +146,16 @@ class Migrations
// sort criteria 4: alphabetically // sort criteria 4: alphabetically
return strcmp($leftBaseName, $rightBaseName); return strcmp($leftBaseName, $rightBaseName);
}); });
// key by identifier...
$migrations = array_combine(array_map(function ($migration) {
return $migration->file;
}, $migrations), $migrations);
return $migrations; return $migrations;
} }
protected function getClassFromFile(string $path): ?string protected static function getClassFromFile(string $path): ?string
{ {
$fp = fopen($path, 'r'); $fp = fopen($path, 'r');
$buffer = ''; $buffer = '';
......
...@@ -26,30 +26,30 @@ interface ProgressInterface ...@@ -26,30 +26,30 @@ interface ProgressInterface
* - `file` - the file name (and unique identifier) of the migration * - `file` - the file name (and unique identifier) of the migration
* - `status` - the current status of the migration (new, done or failed) * - `status` - the current status of the migration (new, done or failed)
*/ */
public function beforeMigration(\stdClass $migration); public function beforeMigration(Migration $migration);
/** /**
* Output information about the $statement (before it gets executed) * Output information about the $statement (before it gets executed)
* *
* Statement contains: * Execution contains:
* - `teaser` - a brief text (without line breaks) that describes the query (e. g. CREATE TABLE migrations) * - `teaser` - a brief text (without line breaks) that describes the query (e. g. CREATE TABLE migrations)
* - `action` - **optional** the database action to execute (e. g. create) * - `action` - **optional** the database action to execute (e. g. create)
* - `type` - **optional** the type on which the action gets executed (e. g. table) * - `type` - **optional** the type on which the action gets executed (e. g. table)
* - `name` - **optional** the name of the object (e. g. migrations) * - `name` - **optional** the name of the object (e. g. migrations)
*/ */
public function beforeExecution(\stdClass $statement); public function beforeExecution(Execution $execution);
/** /**
* Output information about the $statement (after it gets executed) * Output information about the $statement (after it gets executed)
* *
* Statement contains: * Execution contains:
* - `teaser` - a brief text (without line breaks) that describes the query (e. g. CREATE TABLE migrations) * - `teaser` - a brief text (without line breaks) that describes the query (e. g. CREATE TABLE migrations)
* - `action` - **optional** the database action to execute (e. g. create) * - `action` - **optional** the database action to execute (e. g. create)
* - `type` - **optional** the type on which the action gets executed (e. g. table) * - `type` - **optional** the type on which the action gets executed (e. g. table)
* - `name` - **optional** the name of the object (e. g. migrations) * - `name` - **optional** the name of the object (e. g. migrations)
* - `executionTime` - the time it required to execute the satement (in seconds) * - `executionTime` - the time it required to execute the satement (in seconds)
*/ */
public function afterExecution(\stdClass $statement); public function afterExecution(Execution $execution);
/** /**
* Output information about the $migration (after the migration) * Output information about the $migration (after the migration)
...@@ -60,7 +60,7 @@ interface ProgressInterface ...@@ -60,7 +60,7 @@ interface ProgressInterface
* - `executionTime` - the time it required to execute the migration (in seconds) * - `executionTime` - the time it required to execute the migration (in seconds)
* - `statements` - an array of statements that got executed * - `statements` - an array of statements that got executed
*/ */
public function afterMigration(\stdClass $migration); public function afterMigration(Migration $migration);
/** /**
* Output information about what just happened * Output information about what just happened
......
...@@ -2,21 +2,13 @@ ...@@ -2,21 +2,13 @@
namespace Breyta\Test\Migrations; namespace Breyta\Test\Migrations;
use Breyta\Migration;
use Breyta\Migrations; use Breyta\Migrations;
use Breyta\Test\TestCase; use Breyta\Test\TestCase;
use Mockery as m; use Mockery as m;
class LocatingMigrationsTest extends TestCase class LocatingMigrationsTest extends TestCase
{ {
protected $pdo;
protected function setUp()
{
parent::setUp();
$this->pdo = m::mock(\PDO::class);
}
/** @test */ /** @test */
public function returnsAStatusObject() public function returnsAStatusObject()
{ {
...@@ -35,10 +27,10 @@ class LocatingMigrationsTest extends TestCase ...@@ -35,10 +27,10 @@ class LocatingMigrationsTest extends TestCase
$status = $migrations->getStatus(); $status = $migrations->getStatus();
self::assertContains((object)[ self::assertContains(Migration::createInstance([
'file' => 'CreateAnimalsTable.php', 'file' => 'CreateAnimalsTable.php',
'status' => 'new', 'status' => 'new',
], $status->migrations, '', false, false); ]), $status->migrations, '', false, false);
} }
/** @test */ /** @test */
...@@ -48,10 +40,10 @@ class LocatingMigrationsTest extends TestCase ...@@ -48,10 +40,10 @@ class LocatingMigrationsTest extends TestCase
$status = $migrations->getStatus(); $status = $migrations->getStatus();
self::assertContains((object)[ self::assertContains(Migration::createInstance([
'file' => 'Grouped/2018-11-22T22-59-59Z_FooBar.php', 'file' => 'Grouped/2018-11-22T22-59-59Z_FooBar.php',
'status' => 'new', 'status' => 'new',
], $status->migrations, '', false, false); ]), $status->migrations, '', false, false);
} }
/** @test */ /** @test */
...@@ -106,7 +98,7 @@ class LocatingMigrationsTest extends TestCase ...@@ -106,7 +98,7 @@ class LocatingMigrationsTest extends TestCase
return in_array($file, $expectedOrder); return in_array($file, $expectedOrder);
}); });
self::assertSame($expectedOrder, $migrationFiles); self::assertSame($expectedOrder, array_values($migrationFiles));
} }
/** @test */ /** @test */
......
<?php
namespace Breyta\Test\Migrations;
use Breyta\Migration;
use Breyta\Migrations;
use Breyta\Test\TestCase;
use Mockery as m;
class StatusTest extends TestCase
{
/** @test */
public function queriesAllAppliedMigrations()
{
$migrations = new Migrations($this->pdo, __DIR__ . '/../Example');
$this->pdo->shouldReceive('query')->with(m::pattern('/SELECT .* FROM migrations/'))
->once()->andReturn(false);
$migrations->getStatus();
}
/** @test */
public function appliesCurrentStatusToMigrationList()
{
$migration = Migration::createInstance([
'file' => '@breyta/CreateMigrationTable.php',
'executed' => date('Y-m-dTH:i:sZ', strtotime('-1 Hour')),
'status' => 'done',
'executions' => json_encode([
[
'teaser' => 'CREATE TABLE migrations',
'action' => 'create',
'type' => 'table',
'name' => 'migrations',
'executionTime' => 0.1,
],
]),
'executionTime' => 0.1,
]);
$migrations = new Migrations($this->pdo, __DIR__ . '/../Example');
$this->pdo->shouldReceive('query')->with(m::pattern('/SELECT .* FROM migrations/'))
->once()->andReturn($statement = m::mock(\PDOStatement::class));
$statement->shouldReceive('setFetchMode')->with(\PDO::FETCH_CLASS, Migration::class)
->once()->andReturn(true);
$statement->shouldReceive('fetch')->with()
->twice()->andReturn($migration, false);
$status = $migrations->getStatus();
self::assertEquals($migration, $status->migrations['@breyta/CreateMigrationTable.php']);
}
/** @test */
public function returnsACountOfNotAppliedMigrations()
{
$migration = Migration::createInstance([
'file' => '@breyta/CreateMigrationTable.php',
'executed' => date('Y-m-dTH:i:sZ', strtotime('-1 Hour')),
'status' => 'done',
'executions' => json_encode([
[
'teaser' => 'CREATE TABLE migrations',
'action' => 'create',
'type' => 'table',
'name' => 'migrations',
'executionTime' => 0.1,
],
]),
'executionTime' => 0.1,
]);
$migrations = new Migrations($this->pdo, __DIR__ . '/../Example');
$this->pdo->shouldReceive('query')->with(m::pattern('/SELECT .* FROM migrations/'))
->once()->andReturn($statement = m::mock(\PDOStatement::class));
$statement->shouldReceive('setFetchMode')->with(\PDO::FETCH_CLASS, Migration::class)
->once()->andReturn(true);
$statement->shouldReceive('fetch')->with()
->twice()->andReturn($migration, false);
$status = $migrations->getStatus();
self::assertSame(count($status->migrations) - 1, $status->count); // the expected count is one less
}
/** @test */
public function ignoresExceptionsFromQuerying()
{
$migrations = new Migrations($this->pdo, __DIR__ . '/../Example');
$this->pdo->shouldReceive('query')->with(m::pattern('/SELECT .* FROM migrations/'))
->once()->andThrow(new \PDOException('unknown table migrations'));
$status = $migrations->getStatus();
self::assertObjectHasAttribute('migrations', $status);
self::assertObjectHasAttribute('count', $status);
}
/** @test */
public function returnsAnArrayOfMissingMigrations()
{
$migration = Migration::createInstance([
'file' => 'manually executed',
'executed' => date('Y-m-dTH:i:sZ', strtotime('-1 Hour')),
'status' => 'done',
'executions' => json_encode([]),
'executionTime' => 0.1,
]);
$migrations = new Migrations($this->pdo, __DIR__ . '/../Example');
$this->pdo->shouldReceive('query')->with(m::pattern('/SELECT .* FROM migrations/'))
->once()->andReturn($statement = m::mock(\PDOStatement::class));
$statement->shouldReceive('setFetchMode')->with(\PDO::FETCH_CLASS, Migration::class)
->once()->andReturn(true);
$statement->shouldReceive('fetch')->with()
->twice()->andReturn($migration, false);
$status = $migrations->getStatus();
self::assertObjectHasAttribute('missing', $status);
self::assertSame(1, count($status->missing));
self::assertContains($migration, $status->missing);
}
}
...@@ -3,7 +3,18 @@ ...@@ -3,7 +3,18 @@
namespace Breyta\Test; namespace Breyta\Test;
use Mockery\Adapter\Phpunit\MockeryTestCase; use Mockery\Adapter\Phpunit\MockeryTestCase;
use Mockery as m;
abstract class TestCase extends MockeryTestCase abstract class TestCase extends MockeryTestCase
{ {
/** @var m\Mock|\PDO */
protected $pdo;
protected function setUp()
{
parent::setUp();
$pdo = $this->pdo = m::mock(\PDO::class);
$pdo->shouldReceive('query')->andReturn(false)->byDefault();
}
} }
0% Loading or .
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment