summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
-rwxr-xr-xbin/migrate21
-rw-r--r--src/Database/DatabaseServiceProvider.php1
-rw-r--r--src/Database/Migration/Migrate.php192
-rw-r--r--src/Database/Migration/Migration.php16
-rw-r--r--src/Database/Migration/MigrationServiceProvider.php20
-rw-r--r--tests/Unit/Database/DatabaseServiceProviderTest.php1
-rw-r--r--tests/Unit/Database/Migration/MigrateTest.php160
-rw-r--r--tests/Unit/Database/Migration/MigrationServiceProviderTest.php55
-rw-r--r--tests/Unit/Database/Migration/MigrationTest.php24
-rw-r--r--tests/Unit/Database/Migration/Stub/2001_04_11_123456_create_lorem_ipsum_table.php27
-rw-r--r--tests/Unit/Database/Migration/Stub/2017_12_24_053300_another_stuff.php22
-rw-r--r--tests/Unit/Database/Migration/Stub/2022_12_22_221222_add_some_feature.php22
12 files changed, 561 insertions, 0 deletions
diff --git a/bin/migrate b/bin/migrate
new file mode 100755
index 00000000..20ae2a93
--- /dev/null
+++ b/bin/migrate
@@ -0,0 +1,21 @@
+#!/usr/bin/env php
+<?php
+
+use Composer\Autoload\ClassLoader;
+use Engelsystem\Application;
+use Engelsystem\Database\Migration\Migrate;
+use Engelsystem\Database\Migration\MigrationServiceProvider;
+
+require_once __DIR__ . '/../includes/application.php';
+
+/** @var $loader ClassLoader */
+$baseDir = __DIR__ . '/../db/migrations';
+
+/** @var Application $app */
+$app = app();
+$app->register(MigrationServiceProvider::class);
+
+/** @var Migrate $migration */
+$migration = $app->get('db.migration');
+$migration->setOutput(function ($text) { echo $text . PHP_EOL; });
+$migration->run($baseDir, Migrate::UP);
diff --git a/src/Database/DatabaseServiceProvider.php b/src/Database/DatabaseServiceProvider.php
index 818b76c4..7328bc4e 100644
--- a/src/Database/DatabaseServiceProvider.php
+++ b/src/Database/DatabaseServiceProvider.php
@@ -28,6 +28,7 @@ class DatabaseServiceProvider extends ServiceProvider
$capsule->setAsGlobal();
$capsule->bootEloquent();
+ $capsule->getConnection()->useDefaultSchemaGrammar();
try {
$capsule->getConnection()->getPdo();
diff --git a/src/Database/Migration/Migrate.php b/src/Database/Migration/Migrate.php
new file mode 100644
index 00000000..3a08bb6e
--- /dev/null
+++ b/src/Database/Migration/Migrate.php
@@ -0,0 +1,192 @@
+<?php
+
+namespace Engelsystem\Database\Migration;
+
+use Engelsystem\Application;
+use Illuminate\Database\Query\Builder;
+use Illuminate\Database\Schema\Blueprint;
+use Illuminate\Database\Schema\Builder as SchemaBuilder;
+use Illuminate\Support\Collection;
+use Illuminate\Support\Str;
+
+class Migrate
+{
+ const UP = 'up';
+ const DOWN = 'down';
+
+ /** @var Application */
+ protected $app;
+
+ /** @var SchemaBuilder */
+ protected $scheme;
+
+ /** @var callable */
+ protected $output;
+
+ /** @var string */
+ protected $table = 'migrations';
+
+ /**
+ * Migrate constructor
+ *
+ * @param SchemaBuilder $scheme
+ * @param Application $app
+ */
+ public function __construct(SchemaBuilder $scheme, Application $app)
+ {
+ $this->app = $app;
+ $this->scheme = $scheme;
+ $this->output = function () { };
+ }
+
+ /**
+ * Run a migration
+ *
+ * @param string $path
+ * @param string $type (up|down)
+ * @param bool $oneStep
+ */
+ public function run($path, $type = self::UP, $oneStep = false)
+ {
+ $this->initMigration();
+ $migrations = $this->getMigrations($path);
+ $migrated = $this->getMigrated();
+
+ if ($type == self::DOWN) {
+ $migrations = array_reverse($migrations, true);
+ }
+
+ foreach ($migrations as $file => $migration) {
+ if (
+ ($type == self::UP && $migrated->contains('migration', $migration))
+ || ($type == self::DOWN && !$migrated->contains('migration', $migration))
+ ) {
+ call_user_func($this->output, 'Skipping ' . $migration);
+ continue;
+ }
+
+ call_user_func($this->output, 'Migrating ' . $migration . ' (' . $type . ')');
+
+ $this->migrate($file, $migration, $type);
+ $this->setMigrated($migration, $type);
+
+ if ($oneStep) {
+ return;
+ }
+ }
+ }
+
+ /**
+ * Get all migrated migrations
+ *
+ * @return Collection
+ */
+ protected function getMigrated()
+ {
+ return $this->getTableQuery()->get();
+ }
+
+ /**
+ * Migrate a migration
+ *
+ * @param string $file
+ * @param string $migration
+ * @param string $type (up|down)
+ */
+ protected function migrate($file, $migration, $type = self::UP)
+ {
+ require_once $file;
+
+ $className = Str::studly(preg_replace('/\d+_/', '', $migration));
+ /** @var Migration $class */
+ $class = $this->app->make($className);
+
+ if (method_exists($class, $type)) {
+ $class->{$type}();
+ }
+ }
+
+ /**
+ * Set a migration to migrated
+ *
+ * @param string $migration
+ * @param string $type (up|down)
+ */
+ protected function setMigrated($migration, $type = self::UP)
+ {
+ $table = $this->getTableQuery();
+
+ if ($type == self::DOWN) {
+ $table->where(['migration' => $migration])->delete();
+ return;
+ }
+
+ $table->insert(['migration' => $migration]);
+ }
+
+ /**
+ * Get a list of migration files
+ *
+ * @param string $dir
+ * @return array
+ */
+ protected function getMigrations($dir)
+ {
+ $files = $this->getMigrationFiles($dir);
+
+ $migrations = [];
+ foreach ($files as $dir) {
+ $name = str_replace('.php', '', basename($dir));
+ $migrations[$dir] = $name;
+ }
+
+ asort($migrations);
+ return $migrations;
+ }
+
+ /**
+ * List all migration files from the given directory
+ *
+ * @param string $dir
+ * @return array
+ */
+ protected function getMigrationFiles($dir)
+ {
+ return glob($dir . '/*_*.php');
+ }
+
+ /**
+ * Setup migration tables
+ */
+ protected function initMigration()
+ {
+ if ($this->scheme->hasTable($this->table)) {
+ return;
+ }
+
+ $this->scheme->create($this->table, function (Blueprint $table) {
+ $table->increments('id');
+ $table->string('migration');
+ });
+ }
+
+ /**
+ * Init a table query
+ *
+ * @return Builder
+ */
+ protected function getTableQuery()
+ {
+ return $this->scheme->getConnection()->table($this->table);
+ }
+
+ /**
+ * Set the output function
+ *
+ * @param callable $output
+ */
+ public function setOutput(callable $output)
+ {
+ $this->output = $output;
+ }
+}
diff --git a/src/Database/Migration/Migration.php b/src/Database/Migration/Migration.php
new file mode 100644
index 00000000..fcc57b82
--- /dev/null
+++ b/src/Database/Migration/Migration.php
@@ -0,0 +1,16 @@
+<?php
+
+namespace Engelsystem\Database\Migration;
+
+use Illuminate\Database\Schema\Builder as SchemaBuilder;
+
+abstract class Migration
+{
+ /** @var SchemaBuilder */
+ protected $schema;
+
+ public function __construct(SchemaBuilder $schemaBuilder)
+ {
+ $this->schema = $schemaBuilder;
+ }
+}
diff --git a/src/Database/Migration/MigrationServiceProvider.php b/src/Database/Migration/MigrationServiceProvider.php
new file mode 100644
index 00000000..15d06eaf
--- /dev/null
+++ b/src/Database/Migration/MigrationServiceProvider.php
@@ -0,0 +1,20 @@
+<?php
+
+namespace Engelsystem\Database\Migration;
+
+use Engelsystem\Container\ServiceProvider;
+use Engelsystem\Database\Db;
+use Illuminate\Database\Schema\Builder as SchemaBuilder;
+
+class MigrationServiceProvider extends ServiceProvider
+{
+ public function register()
+ {
+ $schema = Db::connection()->getSchemaBuilder();
+ $this->app->instance('db.scheme', $schema);
+ $this->app->bind(SchemaBuilder::class, 'db.scheme');
+
+ $migration = $this->app->make(Migrate::class);
+ $this->app->instance('db.migration', $migration);
+ }
+}
diff --git a/tests/Unit/Database/DatabaseServiceProviderTest.php b/tests/Unit/Database/DatabaseServiceProviderTest.php
index 2e05d27f..8f7898cd 100644
--- a/tests/Unit/Database/DatabaseServiceProviderTest.php
+++ b/tests/Unit/Database/DatabaseServiceProviderTest.php
@@ -74,6 +74,7 @@ class DatabaseServiceProviderTest extends ServiceProviderTest
$this->setExpects($dbManager, 'setAsGlobal');
$this->setExpects($dbManager, 'bootEloquent');
+ $this->setExpects($connection, 'useDefaultSchemaGrammar');
$connection->expects($this->once())
->method('getPdo')
->willReturnCallback(function () use ($getPdoThrowException) {
diff --git a/tests/Unit/Database/Migration/MigrateTest.php b/tests/Unit/Database/Migration/MigrateTest.php
new file mode 100644
index 00000000..c88ad777
--- /dev/null
+++ b/tests/Unit/Database/Migration/MigrateTest.php
@@ -0,0 +1,160 @@
+<?php
+
+namespace Engelsystem\Test\Unit\Database;
+
+use Engelsystem\Application;
+use Engelsystem\Database\Migration\Migrate;
+use Illuminate\Database\Capsule\Manager as CapsuleManager;
+use Illuminate\Database\Schema\Builder as SchemaBuilder;
+use Illuminate\Support\Collection;
+use Illuminate\Support\Str;
+use PHPUnit\Framework\MockObject\MockObject;
+use PHPUnit\Framework\TestCase;
+
+class MigrateTest extends TestCase
+{
+ /**
+ * @covers \Engelsystem\Database\Migration\Migrate::__construct
+ * @covers \Engelsystem\Database\Migration\Migrate::run
+ * @covers \Engelsystem\Database\Migration\Migrate::getMigrations
+ * @covers \Engelsystem\Database\Migration\Migrate::setOutput
+ */
+ public function testRun()
+ {
+ /** @var MockObject|Application $app */
+ $app = $this->getMockBuilder(Application::class)
+ ->setMethods(['instance'])
+ ->getMock();
+ /** @var MockObject|SchemaBuilder $builder */
+ $builder = $this->getMockBuilder(SchemaBuilder::class)
+ ->disableOriginalConstructor()
+ ->getMock();
+ /** @var MockObject|Migrate $migration */
+ $migration = $this->getMockBuilder(Migrate::class)
+ ->setConstructorArgs([$builder, $app])
+ ->setMethods(['initMigration', 'getMigrationFiles', 'getMigrated', 'migrate', 'setMigrated'])
+ ->getMock();
+
+ $migration->expects($this->atLeastOnce())
+ ->method('initMigration');
+ $migration->expects($this->atLeastOnce())
+ ->method('getMigrationFiles')
+ ->willReturn([
+ 'foo/1234_01_23_123456_init_foo.php',
+ 'foo/9876_03_22_210000_random_hack.php',
+ 'foo/4567_11_01_000000_do_stuff.php',
+ 'foo/9999_99_99_999999_another_foo.php',
+ ]);
+ $migration->expects($this->atLeastOnce())
+ ->method('getMigrated')
+ ->willReturn(new Collection([
+ ['id' => 1, 'migration' => '1234_01_23_123456_init_foo'],
+ ['id' => 2, 'migration' => '4567_11_01_000000_do_stuff'],
+ ]));
+ $migration->expects($this->atLeastOnce())
+ ->method('migrate')
+ ->withConsecutive(
+ ['foo/9876_03_22_210000_random_hack.php', '9876_03_22_210000_random_hack', Migrate::UP],
+ ['foo/9999_99_99_999999_another_foo.php', '9999_99_99_999999_another_foo', Migrate::UP],
+ ['foo/9876_03_22_210000_random_hack.php', '9876_03_22_210000_random_hack', Migrate::UP],
+ ['foo/4567_11_01_000000_do_stuff.php', '4567_11_01_000000_do_stuff', Migrate::DOWN]
+ );
+ $migration->expects($this->atLeastOnce())
+ ->method('setMigrated')
+ ->withConsecutive(
+ ['9876_03_22_210000_random_hack', Migrate::UP],
+ ['9999_99_99_999999_another_foo', Migrate::UP],
+ ['9876_03_22_210000_random_hack', Migrate::UP],
+ ['4567_11_01_000000_do_stuff', Migrate::DOWN]
+ );
+
+ $messages = [];
+ $migration->setOutput(function ($text) use (&$messages) {
+ $messages[] = $text;
+ });
+
+ $migration->run('foo', Migrate::UP);
+
+ $this->assertCount(4, $messages);
+ foreach (
+ [
+ 'init_foo' => 'skipping',
+ 'do_stuff' => 'skipping',
+ 'random_hack' => 'migrating',
+ 'another_foo' => 'migrating',
+ ] as $value => $type
+ ) {
+ $contains = false;
+ foreach ($messages as $message) {
+ if (!Str::contains(strtolower($message), $type) || !Str::contains(strtolower($message), $value)) {
+ continue;
+ }
+
+ $contains = true;
+ break;
+ }
+
+ $this->assertTrue($contains, sprintf('Missing message "%s: %s"', $type, $value));
+ }
+
+ $messages = [];
+ $migration->run('foo', Migrate::UP, true);
+ $this->assertCount(3, $messages);
+
+ $migration->run('foo', Migrate::DOWN, true);
+ }
+
+ /**
+ * @covers \Engelsystem\Database\Migration\Migrate::getMigrated
+ * @covers \Engelsystem\Database\Migration\Migrate::migrate
+ * @covers \Engelsystem\Database\Migration\Migrate::setMigrated
+ * @covers \Engelsystem\Database\Migration\Migrate::getMigrationFiles
+ * @covers \Engelsystem\Database\Migration\Migrate::initMigration
+ * @covers \Engelsystem\Database\Migration\Migrate::getTableQuery
+ */
+ public function testRunIntegration()
+ {
+ $app = new Application();
+ $dbManager = new CapsuleManager($app);
+ $dbManager->addConnection(['driver' => 'sqlite', 'database' => ':memory:']);
+ $dbManager->bootEloquent();
+ $db = $dbManager->getConnection();
+ $db->useDefaultSchemaGrammar();
+ $scheme = $db->getSchemaBuilder();
+
+ $app->instance('scheme', $scheme);
+ $app->bind(SchemaBuilder::class, 'scheme');
+
+ $migration = new Migrate($scheme, $app);
+
+ $messages = [];
+ $migration->setOutput(function ($msg) use (&$messages) {
+ $messages[] = $msg;
+ });
+
+ $migration->run(__DIR__ . '/Stub', Migrate::UP);
+
+ $this->assertTrue($scheme->hasTable('migrations'));
+
+ $migrations = $db->table('migrations')->get();
+ $this->assertCount(3, $migrations);
+
+ $this->assertTrue($migrations->contains('migration', '2001_04_11_123456_create_lorem_ipsum_table'));
+ $this->assertTrue($migrations->contains('migration', '2017_12_24_053300_another_stuff'));
+ $this->assertTrue($migrations->contains('migration', '2022_12_22_221222_add_some_feature'));
+
+ $this->assertTrue($scheme->hasTable('lorem_ipsum'));
+
+ $migration->run(__DIR__ . '/Stub', Migrate::DOWN, true);
+
+ $migrations = $db->table('migrations')->get();
+ $this->assertCount(2, $migrations);
+
+ $migration->run(__DIR__ . '/Stub', Migrate::DOWN);
+
+ $migrations = $db->table('migrations')->get();
+ $this->assertCount(0, $migrations);
+
+ $this->assertFalse($scheme->hasTable('lorem_ipsum'));
+ }
+}
diff --git a/tests/Unit/Database/Migration/MigrationServiceProviderTest.php b/tests/Unit/Database/Migration/MigrationServiceProviderTest.php
new file mode 100644
index 00000000..a99cdebe
--- /dev/null
+++ b/tests/Unit/Database/Migration/MigrationServiceProviderTest.php
@@ -0,0 +1,55 @@
+<?php
+
+namespace Engelsystem\Test\Unit\Database\Migration;
+
+use Engelsystem\Database\Db;
+use Engelsystem\Database\Migration\Migrate;
+use Engelsystem\Database\Migration\MigrationServiceProvider;
+use Engelsystem\Test\Unit\ServiceProviderTest;
+use Illuminate\Database\Capsule\Manager as CapsuleManager;
+use Illuminate\Database\Connection;
+use Illuminate\Database\Schema\Builder as SchemaBuilder;
+use PHPUnit_Framework_MockObject_MockObject as MockObject;
+
+class MigrationServiceProviderTest extends ServiceProviderTest
+{
+ /**
+ * @covers \Engelsystem\Database\Migration\MigrationServiceProvider::register()
+ */
+ public function testRegister()
+ {
+ /** @var MockObject|Migrate $migration */
+ $migration = $this->getMockBuilder(Migrate::class)
+ ->disableOriginalConstructor()
+ ->getMock();
+ /** @var MockObject|CapsuleManager $dbManager */
+ $dbManager = $this->getMockBuilder(CapsuleManager::class)
+ ->disableOriginalConstructor()
+ ->getMock();
+ /** @var MockObject|Connection $dbConnection */
+ $dbConnection = $this->getMockBuilder(Connection::class)
+ ->disableOriginalConstructor()
+ ->getMock();
+ /** @var MockObject|SchemaBuilder $schemaBuilder */
+ $schemaBuilder = $this->getMockBuilder(SchemaBuilder::class)
+ ->disableOriginalConstructor()
+ ->getMock();
+
+ $app = $this->getApp(['make', 'instance', 'bind']);
+
+ $app->expects($this->atLeastOnce())
+ ->method('instance')
+ ->withConsecutive(['db.scheme'], ['db.migration'])
+ ->willReturnOnConsecutiveCalls($schemaBuilder, $migration);
+
+ $this->setExpects($app, 'bind', [SchemaBuilder::class, 'db.scheme']);
+ $this->setExpects($app, 'make', [Migrate::class], $migration);
+
+ $this->setExpects($dbConnection, 'getSchemaBuilder', null, $schemaBuilder);
+ $this->setExpects($dbManager, 'getConnection', null, $dbConnection);
+ Db::setDbManager($dbManager);
+
+ $serviceProvider = new MigrationServiceProvider($app);
+ $serviceProvider->register();
+ }
+}
diff --git a/tests/Unit/Database/Migration/MigrationTest.php b/tests/Unit/Database/Migration/MigrationTest.php
new file mode 100644
index 00000000..43bded09
--- /dev/null
+++ b/tests/Unit/Database/Migration/MigrationTest.php
@@ -0,0 +1,24 @@
+<?php
+
+namespace Engelsystem\Test\Unit\Database;
+
+use AnotherStuff;
+use Illuminate\Database\Schema\Builder as SchemaBuilder;
+use PHPUnit\Framework\MockObject\MockBuilder;
+use PHPUnit\Framework\TestCase;
+
+class MigrationTest extends TestCase
+{
+ public function testConstructor()
+ {
+ require_once __DIR__ . '/Stub/2017_12_24_053300_another_stuff.php';
+
+ /** @var MockBuilder|SchemaBuilder $schemaBuilder */
+ $schemaBuilder = $this->getMockBuilder(SchemaBuilder::class)
+ ->disableOriginalConstructor()
+ ->getMock();
+
+ $instance = new AnotherStuff($schemaBuilder);
+ $this->assertAttributeEquals($schemaBuilder, 'schema', $instance);
+ }
+}
diff --git a/tests/Unit/Database/Migration/Stub/2001_04_11_123456_create_lorem_ipsum_table.php b/tests/Unit/Database/Migration/Stub/2001_04_11_123456_create_lorem_ipsum_table.php
new file mode 100644
index 00000000..0cc89e07
--- /dev/null
+++ b/tests/Unit/Database/Migration/Stub/2001_04_11_123456_create_lorem_ipsum_table.php
@@ -0,0 +1,27 @@
+<?php
+
+use Engelsystem\Database\Migration\Migration;
+use Illuminate\Database\Schema\Blueprint;
+
+class CreateLoremIpsumTable extends Migration
+{
+ /**
+ * Run the migration
+ */
+ public function up()
+ {
+ $this->schema->create('lorem_ipsum', function (Blueprint $table) {
+ $table->increments('id');
+ $table->string('name')->unique();
+ $table->string('email');
+ });
+ }
+
+ /**
+ * Reverse the migration
+ */
+ public function down()
+ {
+ $this->schema->dropIfExists('lorem_ipsum');
+ }
+}
diff --git a/tests/Unit/Database/Migration/Stub/2017_12_24_053300_another_stuff.php b/tests/Unit/Database/Migration/Stub/2017_12_24_053300_another_stuff.php
new file mode 100644
index 00000000..d4d7e5f8
--- /dev/null
+++ b/tests/Unit/Database/Migration/Stub/2017_12_24_053300_another_stuff.php
@@ -0,0 +1,22 @@
+<?php
+
+use Engelsystem\Database\Migration\Migration;
+
+class AnotherStuff extends Migration
+{
+ /**
+ * Run the migration
+ */
+ public function up()
+ {
+ // nope
+ }
+
+ /**
+ * Reverse the migration
+ */
+ public function down()
+ {
+ // nope
+ }
+}
diff --git a/tests/Unit/Database/Migration/Stub/2022_12_22_221222_add_some_feature.php b/tests/Unit/Database/Migration/Stub/2022_12_22_221222_add_some_feature.php
new file mode 100644
index 00000000..cf2762de
--- /dev/null
+++ b/tests/Unit/Database/Migration/Stub/2022_12_22_221222_add_some_feature.php
@@ -0,0 +1,22 @@
+<?php
+
+use Engelsystem\Database\Migration\Migration;
+
+class AddSomeFeature extends Migration
+{
+ /**
+ * Run the migration
+ */
+ public function up()
+ {
+ // nope
+ }
+
+ /**
+ * Reverse the migration
+ */
+ public function down()
+ {
+ // nope
+ }
+}