Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 1 addition & 1 deletion README.md
Original file line number Diff line number Diff line change
Expand Up @@ -34,7 +34,7 @@ Using this task, you can do the following:
- `sake dev/tasks/MigrateTask down`
3. Make a new migration file for you with boilerplate code.
- `sake dev/tasks/MigrateTask make:migration_name`
- **Note:** This file will be automatically placed in your project directory in the path `<project>/code/migrations`. You can customize this location by defining a `MIGRATIONS_PATH` constant which should be the absolute path to the desired directory (either in your `_ss_environment.php` or `_config.php` files). Also, migration files that are automatically generated will be pseudo-namespaced with a `Migration_` prefix to help reduce possible class name collisions.
- **Note:** This file will be automatically placed in your project directory in the path `<project>/src/migrations`. You can customize this location by defining a `MIGRATIONS_PATH` constant which should be the absolute path to the desired directory (either in your `_ss_environment.php` or `_config.php` files). Also, migration files that are automatically generated will be pseudo-namespaced with a `Migration_` prefix to help reduce possible class name collisions.

### How it Works

Expand Down
8 changes: 6 additions & 2 deletions code/DatabaseMigrations.php
Original file line number Diff line number Diff line change
@@ -1,5 +1,9 @@
<?php

namespace PatrickNelson\SilverStripeMigrations;

use SilverStripe\ORM\DataObject;

/**
* DataObject used to keep track of previously run migrations.
*
Expand All @@ -13,7 +17,7 @@ class DatabaseMigrations extends DataObject {
"BaseName" => "Varchar(255)",
"MigrationClass" => "Varchar(255)",
"Batch" => "Int",
"Stamp" => "SS_DateTime",
"Stamp" => "DBDatetime",
);

private static $table_name = 'DatabaseMigrations';
}
42 changes: 29 additions & 13 deletions code/MigrateTask.php
Original file line number Diff line number Diff line change
@@ -1,5 +1,18 @@
<?php

namespace PatrickNelson\SilverStripeMigrations;

use Exception;

use PatrickNelson\SilverStripeMigrations\Migration;
use PatrickNelson\SilverStripeMigrations\MigrationBoilerplate;
use ReflectionClass;
use SilverStripe\Control\Director;
use SilverStripe\Control\HTTPRequest;
use SilverStripe\Core\Manifest\ClassLoader;
use SilverStripe\Dev\BuildTask;
use SilverStripe\ORM\DB;

/**
* Task which allows you to do the following:
*
Expand All @@ -19,7 +32,7 @@
*
* YYYY_MM_DD_HHMMSS_change_serialize_to_json.php
*
* IMPORTANT: This file will be automatically placed in your project directory in the path "<project>/code/migrations".
* IMPORTANT: This file will be automatically placed in your project directory in the path "<project>/src/migrations".
* This can be overridden by defining an absolute path in the constant "MIGRATION_PATH" in your _ss_environment.php file.
* Migration files that are automatically generated will be pseudo-namespaced with a "Migration_" prefix to help reduce
* possible class name collisions.
Expand All @@ -45,8 +58,10 @@ class MigrateTask extends BuildTask {
// Used for error reporting purposes.
protected $lastMigrationFile = '';

private static $segment = 'MigrateTask';

/**
* @param SS_HTTPRequest $request
* @param HTTPRequest $request
* @throws MigrationException
*/
public function run($request) {
Expand Down Expand Up @@ -81,7 +96,7 @@ public function run($request) {
register_shutdown_function(array($this, "shutdown"));

// Determine action to take. Wrap everything in a transaction so it can be rolled back in case of error.
DB::getConn()->transactionStart();
DB::get_conn()->transactionStart();
try {
if (isset($args["up"])) {
$this->up();
Expand All @@ -97,7 +112,7 @@ public function run($request) {
}

// Commit and clean up error state..
DB::getConn()->transactionEnd();
DB::get_conn()->transactionEnd();
$this->error = false;

} catch (Exception $e) {
Expand Down Expand Up @@ -125,7 +140,7 @@ public function shutdown(Exception $e = null) {
if ($this->error && !$e) $e = new MigrationException("The migration" . ($this->lastMigrationFile ? " '$this->lastMigrationFile.php'" : "") . " terminated unexpectedly.");
if ($e) {
// Rollback database changes and notify user.
DB::getConn()->transactionRollback();
DB::get_conn()->transactionRollback();
$this->output("ERROR" . ($e->getCode() != 0 ? " (" . $e->getCode() . ")" : "") . ": " . $e->getMessage());
$this->output("\nNote: Any database changes have been rolled back.");
$this->output("\nStack Trace:");
Expand All @@ -143,7 +158,6 @@ public function isEnabled() {
return Director::is_cli();
}


########################
## MAIN FUNCTIONALITY ##
########################
Expand Down Expand Up @@ -251,16 +265,18 @@ public function make($baseName) {
}

// Get boilerplate file contents, find/replace some contents and write to file path.
$sourceFile = __DIR__ . DIRECTORY_SEPARATOR . "MigrationBoilerplate.php";
$sourceData = file_get_contents($sourceFile);
$sourceData = str_replace("MigrationBoilerplate", $camelCase, $sourceData);
$reflect = new ReflectionClass(MigrationBoilerplate::class);
$sourceData = str_replace(
[$reflect->getShortName(), 'namespace '.$reflect->getNamespaceName()],
[$camelCase, 'use '.Migration::class],
file_get_contents($reflect->getFileName())
);
file_put_contents($filePath, $sourceData);

// Output status and exit.
$this->output("Created new migration: $filePath");
}


####################
## HELPER METHODS ##
####################
Expand Down Expand Up @@ -301,7 +317,7 @@ public static function getMigrationPath() {
if (empty($project)) throw new MigrationException("Please either define a global '\$project' variable or define a MIGRATION_PATH constant in order to setup a path for migration files to live.");

// Build path.
$migrationPath = join(DIRECTORY_SEPARATOR, array(BASE_PATH, $project, "code", "migrations"));
$migrationPath = join(DIRECTORY_SEPARATOR, array(BASE_PATH, $project, "src", "migrations"));
}

return $migrationPath;
Expand All @@ -314,8 +330,8 @@ public static function getMigrationPath() {
*/
public static function getAllMigrations() {
// Get all descendants of the abstract "Migration" class but ensure the class "MigrationBoilerplate" is skipped.
$manifest = SS_ClassLoader::instance()->getManifest();
$classes = array_diff($manifest->getDescendantsOf("Migration"), array("MigrationBoilerplate"));
$manifest = ClassLoader::inst()->getManifest();
$classes = array_diff($manifest->getDescendantsOf(Migration::class), array(MigrationBoilerplate::class));
$classesOrdered = array();
foreach ($classes as $className) {
// Get actual filename of migration class and use that as the key.
Expand Down
34 changes: 26 additions & 8 deletions code/Migration.php
Original file line number Diff line number Diff line change
@@ -1,5 +1,24 @@
<?php

namespace PatrickNelson\SilverStripeMigrations;

use Exception;

use SilverStripe\CMS\Model\SiteTree;
use SilverStripe\Control\Session;
use SilverStripe\Core\ClassInfo;
use SilverStripe\Core\Convert;
use SilverStripe\Dev\Deprecation;
use SilverStripe\ORM\DataObject;
use SilverStripe\ORM\DB;
use SilverStripe\ORM\Queries\SQLDelete;
use SilverStripe\ORM\Queries\SQLInsert;
use SilverStripe\ORM\Queries\SQLSelect;
use SilverStripe\ORM\Queries\SQLUpdate;
use SilverStripe\Security\Member;
use SilverStripe\Security\Security;
use SilverStripe\Versioned\Versioned;

/**
* All migrations that must be executed must be descended from this class and define both an ->up() and a ->down()
* method. Migrations will be executed in alphanumeric order
Expand Down Expand Up @@ -27,7 +46,6 @@ public function isObsolete() {
return $this->obsolete;
}


#######################################
## DATABASE MIGRATION HELPER METHODS ##
#######################################
Expand Down Expand Up @@ -92,8 +110,8 @@ public static function getTableForField($className, $field) {
// Let's get our hands dirty on this ancestry filth and reference the database because the private static ::$db isn't reliable (seriously).
$ancestors = ClassInfo::ancestry($className, true);
foreach($ancestors as $ancestor) {
if (DataObject::has_own_table($ancestor)) {
if (DB::get_schema()->hasField($ancestor, $field)) return $ancestor;
if ($tableName = DataObject::getSchema()->tableForField($ancestor, $field)) {
return $ancestor;
}
}

Expand Down Expand Up @@ -267,8 +285,8 @@ public static function setRowValuesOnTable($table, array $values, $id = null, $i
public static function publish(SiteTree $page, $force = true) {
try {
static::whileAdmin(function () use ($page, $force) {
if (!$page->getIsModifiedOnStage() || $force) {
$page->doPublish();
if (!$page->isModifiedOnDraft() || $force) {
$page->publishRecursive();
} else {
$page->write();
}
Expand Down Expand Up @@ -387,7 +405,7 @@ protected static function loginAsAdmin() {
* @throws MigrationException
*/
public static function setPageType(SiteTree $page, $pageType) {
if (!is_a($pageType, "SiteTree", true)) throw new MigrationException("The specifed page type '$pageType' must be an instance (or child) of 'SiteTree'.");
if (!is_a($pageType, SiteTree::class, true)) throw new MigrationException("The specifed page type '$pageType' must be an instance (or child) of 'SiteTree'.");
$page = $page->newClassInstance($pageType);
static::publish($page);
}
Expand Down Expand Up @@ -502,11 +520,11 @@ public static function copyVersionedTable($fromObject, $toObject, array $fieldMa
// Quick validation.
foreach(array($fromObject, $toObject) as $validateObject) {
if (!class_exists($validateObject)) throw new MigrationException("'$validateObject' doesn't appear to be an object.");
if (is_a($validateObject, 'DataObject')) throw new MigrationException("'$validateObject' must be an instance of DataObject.");
if (is_a($validateObject, DataObject::class)) throw new MigrationException("'$validateObject' must be an instance of DataObject.");

/** @var $validateInstance DataObject */
$validateInstance = singleton($validateObject);
if (!$validateInstance->hasExtension('Versioned')) throw new MigrationException("'$validateObject' must be a versioned object (i.e. have the Versioned extension).");
if (!$validateInstance->hasExtension(Versioned::class)) throw new MigrationException("'$validateObject' must be a versioned object (i.e. have the Versioned extension).");
}

// Repeat on each instance of the objects' tables.
Expand Down
2 changes: 2 additions & 0 deletions code/MigrationBoilerplate.php
Original file line number Diff line number Diff line change
@@ -1,5 +1,7 @@
<?php

namespace PatrickNelson\SilverStripeMigrations;

class MigrationBoilerplate extends Migration {

/**
Expand Down
5 changes: 5 additions & 0 deletions code/MigrationException.php
Original file line number Diff line number Diff line change
@@ -1,4 +1,9 @@
<?php

namespace PatrickNelson\SilverStripeMigrations;

use Exception;

/**
* Setup only to better differentiate between exceptions coming directly from migration validation and any other
* exceptions.
Expand Down
2 changes: 2 additions & 0 deletions code/MigrationInterface.php
Original file line number Diff line number Diff line change
@@ -1,5 +1,7 @@
<?php

namespace PatrickNelson\SilverStripeMigrations;

/**
* Needed for consistency between unit tests and actual migrations.
*
Expand Down
21 changes: 16 additions & 5 deletions composer.json
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
{
"name": "patricknelson/silverstripe-migrations",
"description": "SilverStripe Database Migration module. Facilitates atomic database migrations in SilverStripe 3.x",
"type": "silverstripe-module",
"type": "silverstripe-vendormodule",
"homepage": "https://github.com/patricknelson/silverstripe-migrations",
"keywords": [
"silverstripe","database migrations","migrate"
Expand All @@ -13,11 +13,22 @@
"support": {
"issues": "https://github.com/patricknelson/silverstripe-migrations/issues"
},
"autoload": {
"psr-4": {
"PatrickNelson\\SilverStripeMigrations\\": "code/",
"PatrickNelson\\SilverStripeMigrations\\Tests\\": "tests/"
}
},
"require": {
"silverstripe/cms": "^3.0.0",
"silverstripe/framework": "^3.0.0"
"silverstripe/cms": "^4.9.0"
},
"require-dev": {
"sminnee/phpunit": "^5.7.29"
},
"extra": {
"installer-name": "migrations"
"config": {
"allow-plugins": {
"composer/installers": true,
"silverstripe/vendor-plugin": true
}
}
}
21 changes: 16 additions & 5 deletions tests/MigrateTaskTest.php
Original file line number Diff line number Diff line change
@@ -1,10 +1,21 @@
<?php

namespace PatrickNelson\SilverStripeMigrations\Tests;

use Exception;

use PatrickNelson\SilverStripeMigrations\MigrateTask;
use PatrickNelson\SilverStripeMigrations\Migration;
use PatrickNelson\SilverStripeMigrations\MigrationInterface;
use SilverStripe\Dev\SapphireTest;
use SilverStripe\Dev\TestOnly;
use SilverStripe\ORM\DataObject;

class MigrateTaskTest extends SapphireTest {

protected static $fixture_file = 'MigrateTaskTest.yml';
protected static $fixture_file = 'MigrateTaskTest.yml';

protected $extraDataObjects = [
protected static $extra_dataobjects = [
Migration_TestParent::class,
Migration_TestChild::class,
Migration_TestGrandchild::class,
Expand Down Expand Up @@ -99,21 +110,21 @@ class Migration_TestParent extends DataObject implements TestOnly {
private static $db = [
'ParentField' => 'Varchar(255)',
];

private static $table_name = 'Migration_TestParent';
}

class Migration_TestChild extends Migration_TestParent implements TestOnly {

private static $db = [
'ChildField' => 'Varchar(255)',
];

private static $table_name = 'Migration_TestChild';
}

class Migration_TestGrandchild extends Migration_TestChild implements TestOnly {

private static $db = [
'Grandchild' => 'Varchar(255)',
];

private static $table_name = 'Migration_TestGrandchild';
}
6 changes: 3 additions & 3 deletions tests/MigrateTaskTest.yml
Original file line number Diff line number Diff line change
@@ -1,16 +1,16 @@
DatabaseMigrations:
test1:
BaseName: 2015_04_27_000637_test1.php
MigrationClass: Migration_UnitTestOnly
MigrationClass: PatrickNelson\SilverStripeMigrations\Tests\Migration_UnitTestOnly
Batch: 1
Stamp: 1430107790
test2:
BaseName: 2015_04_27_000638_test2.php
MigrationClass: Migration_UnitTestOnly
MigrationClass: PatrickNelson\SilverStripeMigrations\Tests\Migration_UnitTestOnly
Batch: 2
Stamp: 1430107791
test3:
BaseName: 2015_04_27_000639_test3.php
MigrationClass: Migration_UnitTestOnly
MigrationClass: PatrickNelson\SilverStripeMigrations\Tests\Migration_UnitTestOnly
Batch: 2
Stamp: 1430107792