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 @@
"license": "MIT",
"require": {
"php": "^7.1",
"ext-pdo": "*"
"ext-pdo": "*",
"ext-json": "*"
},
"require-dev": {
"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
/** @var string */
protected $path;
/** @var array */
/** @var array|Migration[] */
protected $migrations;
/** @var array */
/** @var array|String[] */
protected $classes;
/** @var array|Migration[] */
protected $missingMigrations = [];
public function __construct(\PDO $db, string $path)
{
if (!file_exists($path) || !is_dir($path)) {
......@@ -30,16 +33,43 @@ class Migrations
public function getStatus(): \stdClass
{
return (object)[
'migrations' => $this->getMigrations(),
'count' => 0,
$this->loadMigrations();
$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) {
$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;
......@@ -48,10 +78,10 @@ class Migrations
protected function findMigrations(): array
{
$this->classes['@breyta/CreateMigrationTable.php'] = CreateMigrationTable::class;
$migrations = [(object)[
$migrations = [Migration::createInstance([
'file' => '@breyta/CreateMigrationTable.php',
'status' => 'new',
]];
])];
/** @var \SplFileInfo $fileInfo */
foreach (new \RecursiveIteratorIterator(new \RecursiveDirectoryIterator($this->path)) as $fileInfo) {
......@@ -62,7 +92,7 @@ class Migrations
continue;
}
$className = $this->getClassFromFile($fileInfo->getPathname());
$className = self::getClassFromFile($fileInfo->getPathname());
if (!$className) {
continue;
}
......@@ -73,10 +103,10 @@ class Migrations
$file = substr($fileInfo->getPathname(), strlen($this->path) + 1);
$this->classes[$file] = $className;
$migrations[] = (object)[
$migrations[] = Migration::createInstance([
'file' => $file,
'status' => 'new'
];
]);
}
usort($migrations, function ($left, $right) {
......@@ -116,10 +146,16 @@ class Migrations
// sort criteria 4: alphabetically
return strcmp($leftBaseName, $rightBaseName);
});
// key by identifier...
$migrations = array_combine(array_map(function ($migration) {
return $migration->file;
}, $migrations), $migrations);
return $migrations;
}
protected function getClassFromFile(string $path): ?string
protected static function getClassFromFile(string $path): ?string
{
$fp = fopen($path, 'r');
$buffer = '';
......
......@@ -26,30 +26,30 @@ interface ProgressInterface
* - `file` - the file name (and unique identifier) of the migration
* - `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)
*
* Statement contains:
* Execution contains:
* - `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)
* - `type` - **optional** the type on which the action gets executed (e. g. table)
* - `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)
*
* Statement contains:
* Execution contains:
* - `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)
* - `type` - **optional** the type on which the action gets executed (e. g. table)
* - `name` - **optional** the name of the object (e. g. migrations)
* - `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)
......@@ -60,7 +60,7 @@ interface ProgressInterface
* - `executionTime` - the time it required to execute the migration (in seconds)
* - `statements` - an array of statements that got executed
*/
public function afterMigration(\stdClass $migration);
public function afterMigration(Migration $migration);
/**
* Output information about what just happened
......
......@@ -2,21 +2,13 @@
namespace Breyta\Test\Migrations;
use Breyta\Migration;
use Breyta\Migrations;
use Breyta\Test\TestCase;
use Mockery as m;
class LocatingMigrationsTest extends TestCase
{
protected $pdo;
protected function setUp()
{
parent::setUp();
$this->pdo = m::mock(\PDO::class);
}
/** @test */
public function returnsAStatusObject()
{
......@@ -35,10 +27,10 @@ class LocatingMigrationsTest extends TestCase
$status = $migrations->getStatus();
self::assertContains((object)[
self::assertContains(Migration::createInstance([
'file' => 'CreateAnimalsTable.php',
'status' => 'new',
], $status->migrations, '', false, false);
]), $status->migrations, '', false, false);
}
/** @test */
......@@ -48,10 +40,10 @@ class LocatingMigrationsTest extends TestCase
$status = $migrations->getStatus();
self::assertContains((object)[
self::assertContains(Migration::createInstance([
'file' => 'Grouped/2018-11-22T22-59-59Z_FooBar.php',
'status' => 'new',
], $status->migrations, '', false, false);
]), $status->migrations, '', false, false);
}
/** @test */
......@@ -106,7 +98,7 @@ class LocatingMigrationsTest extends TestCase
return in_array($file, $expectedOrder);
});
self::assertSame($expectedOrder, $migrationFiles);
self::assertSame($expectedOrder, array_values($migrationFiles));
}
/** @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 @@
namespace Breyta\Test;
use Mockery\Adapter\Phpunit\MockeryTestCase;
use Mockery as m;
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