Source of file MigrationService.php
Size: 7,733 Bytes - Last Modified: 2021-12-23T10:36:11+00:00
/var/www/docs.ssmods.com/process/src/src/Migration/MigrationService.php
123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293 | <?php namespace SilverStripe\Snapshots\Migration; use SilverStripe\Core\ClassInfo; use SilverStripe\Core\Config\Config; use SilverStripe\Core\Injector\Injectable; use SilverStripe\Core\Injector\Injector; use SilverStripe\ORM\DataObject; use SilverStripe\ORM\DB; use SilverStripe\ORM\ValidationException; use SilverStripe\Snapshots\Snapshot; use SilverStripe\Snapshots\SnapshotEvent; use SilverStripe\Snapshots\SnapshotItem; use SilverStripe\Versioned\Versioned; use ReflectionException; class MigrationService { use Injectable; /** * @var string */ private $snapshotsTable; /** * @var string */ private $itemsTable; /** * @var array|null */ private $classMap; /** * @var int */ private $baseID = 0; /** * @var string|null */ private $baseClassSubquery; /** * MigrationService constructor. * @throws ReflectionException */ public function __construct() { $this->snapshotsTable = DataObject::getSchema()->baseDataTable(Snapshot::class); $this->itemsTable = DataObject::getSchema()->baseDataTable(SnapshotItem::class); } /** * @param string $baseClass * @return int */ public function migrate(string $baseClass): int { /* @var DataObject $sng */ $sng = $baseClass::singleton(); $baseTable = $sng->baseTable(); $versionsTable = $baseTable . '_Versions'; $rows = $this->migrateSnapshots($versionsTable); $rows += $this->migrateItems($versionsTable); $this->baseID = (int) DB::query("SELECT MAX(\"ID\") FROM \"$this->snapshotsTable\"")->value(); return $rows; } /** * For objects that have explicitly opted into relation tracking, we need to provide * a placeholder SnapshotItem that they can refer to (even if it's orphaned), * because implicit changes (checkboxes, elemental editor) don't necessarily * create a new version for the owner * * @throws ReflectionException * @throws ValidationException */ public function seedRelationTracking(): void { foreach (ClassInfo::subclassesFor(DataObject::class, false) as $class) { $tracking = $class::config()->uninherited('snapshot_relation_tracking'); if (empty($tracking)) { continue; } foreach ($class::get() as $record) { SnapshotItem::create() ->hydrateFromDataObject($record) ->write(); } } } /** * @return array */ public function getClassesToMigrate(): array { return array_unique(array_values($this->getClassMap())); } /** * Restart the task */ public function setup(): void { DB::query("DELETE FROM \"$this->snapshotsTable\""); DB::query("DELETE FROM \"$this->itemsTable\""); $eventTable = DataObject::getSchema()->baseDataTable(SnapshotEvent::class); DB::query("DELETE FROM \"$eventTable\""); $this->createTemporaryTable(); $this->baseClassSubquery = <<<SQL ( SELECT "BaseClassName" FROM "__ClassNameLookup" WHERE "ObjectClassName" = "ClassName" LIMIT 1 ) SQL; } public function tearDown(): void { $this->removeTemporaryTable(); } /** * @param string $versionsTable * @return int */ private function migrateSnapshots(string $versionsTable): int { DB::query( "INSERT INTO \"$this->snapshotsTable\" ( \"ID\", \"Created\", \"LastEdited\", \"OriginHash\", \"AuthorID\", \"OriginID\", \"OriginClass\" ) ( SELECT \"ID\" + $this->baseID, \"Created\", \"LastEdited\", MD5(CONCAT($this->baseClassSubquery, ':', \"RecordID\")), \"AuthorID\", \"RecordID\", $this->baseClassSubquery FROM \"$versionsTable\" WHERE \"WasDeleted\" = 0 ORDER BY \"ID\" ASC ) " ); return (int) DB::affected_rows(); } /** * @param string $versionsTable * @return int */ private function migrateItems(string $versionsTable): int { DB::query( "INSERT INTO \"$this->itemsTable\" ( \"Created\", \"LastEdited\", \"Version\", \"WasPublished\", \"WasDraft\", \"WasDeleted\", \"ObjectHash\", \"Modification\", \"SnapshotID\", \"ObjectID\", \"ObjectClass\" ) ( SELECT \"Created\", \"LastEdited\", \"Version\", \"WasPublished\", \"WasDraft\", \"WasDeleted\", MD5(CONCAT($this->baseClassSubquery, ':', \"RecordID\")), 1, \"ID\" + $this->baseID, \"RecordID\", $this->baseClassSubquery FROM \"$versionsTable\" WHERE \"WasDeleted\" = 0 ORDER BY \"ID\" ASC ) " ); return (int) DB::affected_rows(); } private function createTemporaryTable() { DB::query("DROP TABLE IF EXISTS \"__ClassNameLookup\""); DB::create_table( '__ClassNameLookup', [ 'ObjectClassName' => 'varchar(255) not null', 'BaseClassName' => 'varchar(255) not null', ] ); $lines = []; foreach ($this->getClassMap() as $className => $baseClassName) { $lines[] = sprintf( "('%s', '%s')", $this->sanitiseClassName($className), $this->sanitiseClassName($baseClassName) ); } $values = implode(",\n", $lines); $query = <<<SQL INSERT INTO "__ClassNameLookup" ("ObjectClassName", "BaseClassName") VALUES $values SQL; DB::query($query); } private function removeTemporaryTable(): void { DB::query("DROP TABLE \"__ClassNameLookup\""); } /** * @return array */ private function getClassMap(): array { if ($this->classMap === null) { $this->generateClassMap(); } return $this->classMap; } private function generateClassMap(): void { $map = []; foreach (ClassInfo::subclassesFor(DataObject::class, false) as $class) { $sng = Injector::inst()->get($class); if (!$sng->hasExtension(Versioned::class)) { continue; } $baseClass = $sng->baseClass(); $map[$class] = $baseClass; } $this->classMap = $map; } /** * @param $class * @return string */ private function sanitiseClassName($class): string { return str_replace('\\', '\\\\', $class); } public function getBaseID() { return $this->baseID; } } |