diff --git a/src/ORM/Connect/TableBuilder.php b/src/ORM/Connect/TableBuilder.php new file mode 100644 index 00000000000..7b2e3cc41ef --- /dev/null +++ b/src/ORM/Connect/TableBuilder.php @@ -0,0 +1,69 @@ +schemaUpdate(function () use ($dataClasses, $extraDataObjects, $testMode, $quiet, $showRecordCounts) { + $dataObjectSchema = DataObject::getSchema(); + + foreach ($dataClasses as $dataClass) { + // Check if class exists before trying to instantiate - this sidesteps any manifest weirdness + if (!class_exists($dataClass)) { + continue; + } + + // Check if this class should be excluded as per testing conventions + /** @var DataObject $SNG */ + $SNG = new $dataClass([], DataObject::CREATE_SINGLETON); + if (!$testMode && $SNG instanceof TestOnly) { + continue; + } + + // Log data + if (!$quiet) { + $tableName = $dataObjectSchema->tableName($dataClass); + if ($showRecordCounts && DB::get_schema()->hasTable($tableName)) { + try { + $count = DB::query("SELECT COUNT(*) FROM \"$tableName\"")->value(); + $countSuffix = " ($count records)"; + } catch (\Exception $e) { + $countSuffix = " (error getting record count)"; + } + } else { + $countSuffix = ""; + } + + if (Director::is_cli()) { + echo " * $tableName$countSuffix\n"; + } else { + echo "
  • $tableName$countSuffix
  • \n"; + } + } + + // Instruct the class to apply its schema to the database + $SNG->requireTable(); + } + + // If we have additional dataobjects which need schema (i.e. for tests), do so here: + if ($extraDataObjects) { + foreach ($extraDataObjects as $dataClass) { + $SNG = new $dataClass([], DataObject::CREATE_SINGLETON); + if ($SNG instanceof DataObject) { + $SNG->requireTable(); + } + } + } + }); + } +} diff --git a/src/ORM/Connect/TempDatabase.php b/src/ORM/Connect/TempDatabase.php index a268adf8943..ced43d8469d 100644 --- a/src/ORM/Connect/TempDatabase.php +++ b/src/ORM/Connect/TempDatabase.php @@ -244,29 +244,9 @@ protected function rebuildTables($extraDataObjects = []) $schema = $this->getConn()->getSchemaManager(); $schema->quiet(); - $schema->schemaUpdate( - function () use ($dataClasses, $extraDataObjects) { - foreach ($dataClasses as $dataClass) { - // Check if class exists before trying to instantiate - this sidesteps any manifest weirdness - if (class_exists($dataClass ?? '')) { - $SNG = singleton($dataClass); - if (!($SNG instanceof TestOnly)) { - $SNG->requireTable(); - } - } - } - // If we have additional dataobjects which need schema, do so here: - if ($extraDataObjects) { - foreach ($extraDataObjects as $dataClass) { - $SNG = singleton($dataClass); - if (singleton($dataClass) instanceof DataObject) { - $SNG->requireTable(); - } - } - } - } - ); + $tableBuilder = TableBuilder::singleton(); + $tableBuilder->buildTables($schema, $dataClasses, $extraDataObjects, true); Config::modify()->set(DBSchemaManager::class, 'check_and_repair_on_build', $oldCheckAndRepairOnBuild); diff --git a/src/ORM/DatabaseAdmin.php b/src/ORM/DatabaseAdmin.php index b795690ee26..c10ef679b45 100644 --- a/src/ORM/DatabaseAdmin.php +++ b/src/ORM/DatabaseAdmin.php @@ -14,6 +14,7 @@ use SilverStripe\Dev\DevelopmentAdmin; use SilverStripe\Dev\TestOnly; use SilverStripe\ORM\Connect\DatabaseException; +use SilverStripe\ORM\Connect\TableBuilder; use SilverStripe\ORM\FieldType\DBClassName; use SilverStripe\Security\Permission; use SilverStripe\Security\Security; @@ -304,46 +305,8 @@ public function doBuild($quiet = false, $populate = true, $testMode = false) // Initiate schema update $dbSchema = DB::get_schema(); - $dbSchema->schemaUpdate(function () use ($dataClasses, $testMode, $quiet, $showRecordCounts) { - $dataObjectSchema = DataObject::getSchema(); - - foreach ($dataClasses as $dataClass) { - // Check if class exists before trying to instantiate - this sidesteps any manifest weirdness - if (!class_exists($dataClass ?? '')) { - continue; - } - - // Check if this class should be excluded as per testing conventions - $SNG = singleton($dataClass); - if (!$testMode && $SNG instanceof TestOnly) { - continue; - } - $tableName = $dataObjectSchema->tableName($dataClass); - - // Log data - if (!$quiet) { - if ($showRecordCounts && DB::get_schema()->hasTable($tableName)) { - try { - $count = DB::query("SELECT COUNT(*) FROM \"$tableName\"")->value(); - $countSuffix = " ($count records)"; - } catch (Exception $e) { - $countSuffix = " (error getting record count)"; - } - } else { - $countSuffix = ""; - } - - if (Director::is_cli()) { - echo " * $tableName$countSuffix\n"; - } else { - echo "
  • $tableName$countSuffix
  • \n"; - } - } - - // Instruct the class to apply its schema to the database - $SNG->requireTable(); - } - }); + $tableBuilder = TableBuilder::singleton(); + $tableBuilder->buildTables($dbSchema, $dataClasses, [], $quiet, $testMode, $showRecordCounts); ClassInfo::reset_db_cache(); if (!$quiet && !Director::is_cli()) { diff --git a/tests/php/ORM/DataObjectTest.php b/tests/php/ORM/DataObjectTest.php index 6bc8350ded0..9ec9e162990 100644 --- a/tests/php/ORM/DataObjectTest.php +++ b/tests/php/ORM/DataObjectTest.php @@ -5,6 +5,7 @@ use InvalidArgumentException; use LogicException; use SilverStripe\Core\Config\Config; +use SilverStripe\Core\Injector\Injector; use SilverStripe\Dev\SapphireTest; use SilverStripe\i18n\i18n; use SilverStripe\ORM\Connect\MySQLDatabase; @@ -63,11 +64,14 @@ class DataObjectTest extends SapphireTest DataObjectTest\RelationChildSecond::class, DataObjectTest\MockDynamicAssignmentDataObject::class, DataObjectTest\TreeNode::class, + DataObjectTest\OverriddenDataObject::class, + DataObjectTest\InjectedDataObject::class, ]; protected function setUp(): void { parent::setUp(); + Config::modify()->merge(Injector::class, DataObjectTest\OverriddenDataObject::class, ['class' => DataObjectTest\InjectedDataObject::class]); $validator = Member::password_validator(); if ($validator) { @@ -186,6 +190,26 @@ public function testDb() ); } + public function testTableBuiltForInjectedDataObject() + { + // Test we get the correct injected class + $obj = DataObjectTest\OverriddenDataObject::create(); + $this->assertSame(DataObjectTest\InjectedDataObject::class, get_class($obj)); + + // Test both tables are built + $schema = DataObject::getSchema(); + $this->assertTrue($schema->classHasTable(DataObjectTest\OverriddenDataObject::class)); + $this->assertTrue($schema->classHasTable(DataObjectTest\InjectedDataObject::class)); + + // Test fields from both the overridden and injected class exist + $obj->EmploymentType = 'Some type'; + $obj->NewField = 'Some value'; + $obj->write(); + $objFromOrm = DataObjectTest\OverriddenDataObject::get()->first(); + $this->assertSame('Some type', $objFromOrm->EmploymentType); + $this->assertSame('Some value', $objFromOrm->NewField); + } + public function testConstructAcceptsValues() { // Values can be an array... diff --git a/tests/php/ORM/DataObjectTest/InjectedDataObject.php b/tests/php/ORM/DataObjectTest/InjectedDataObject.php new file mode 100644 index 00000000000..0d6a117aed4 --- /dev/null +++ b/tests/php/ORM/DataObjectTest/InjectedDataObject.php @@ -0,0 +1,12 @@ + 'Varchar', + ]; +} diff --git a/tests/php/ORM/DataObjectTest/OverriddenDataObject.php b/tests/php/ORM/DataObjectTest/OverriddenDataObject.php new file mode 100644 index 00000000000..97242185394 --- /dev/null +++ b/tests/php/ORM/DataObjectTest/OverriddenDataObject.php @@ -0,0 +1,20 @@ + 'BigInt', + 'EmploymentType' => 'Varchar', + ]; + + private static $has_one = [ + 'CurrentCompany' => Company::class, + ]; +}