Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

BUG Manually create singleton when building table #9952

Merged
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
69 changes: 69 additions & 0 deletions src/ORM/Connect/TableBuilder.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,69 @@
<?php

namespace SilverStripe\ORM\Connect;

use SilverStripe\Control\Director;
use SilverStripe\Core\Injector\Injectable;
use SilverStripe\Dev\TestOnly;
use SilverStripe\ORM\DataObject;
use SilverStripe\ORM\DB;

class TableBuilder
{
use Injectable;

public function buildTables(DBSchemaManager $dbSchema, array $dataClasses, array $extraDataObjects = [], bool $quiet = false, bool $testMode = false, bool $showRecordCounts = false)
{
$dbSchema->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 "<li>$tableName$countSuffix</li>\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();
}
}
}
});
}
}
24 changes: 2 additions & 22 deletions src/ORM/Connect/TempDatabase.php
Original file line number Diff line number Diff line change
Expand Up @@ -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);

Expand Down
43 changes: 3 additions & 40 deletions src/ORM/DatabaseAdmin.php
Original file line number Diff line number Diff line change
Expand Up @@ -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;
Expand Down Expand Up @@ -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 "<li>$tableName$countSuffix</li>\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()) {
Expand Down
24 changes: 24 additions & 0 deletions tests/php/ORM/DataObjectTest.php
Original file line number Diff line number Diff line change
Expand Up @@ -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;
Expand Down Expand Up @@ -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) {
Expand Down Expand Up @@ -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...
Expand Down
12 changes: 12 additions & 0 deletions tests/php/ORM/DataObjectTest/InjectedDataObject.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,12 @@
<?php

namespace SilverStripe\ORM\Tests\DataObjectTest;

use SilverStripe\Dev\TestOnly;

class InjectedDataObject extends OverriddenDataObject implements TestOnly
{
private static $db = [
'NewField' => 'Varchar',
];
}
20 changes: 20 additions & 0 deletions tests/php/ORM/DataObjectTest/OverriddenDataObject.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,20 @@
<?php

namespace SilverStripe\ORM\Tests\DataObjectTest;

use SilverStripe\Dev\TestOnly;
use SilverStripe\ORM\DataObject;

class OverriddenDataObject extends DataObject implements TestOnly
{
private static $table_name = 'DataObjectTest_OverriddenDataObject';

private static $db = [
'Salary' => 'BigInt',
'EmploymentType' => 'Varchar',
];

private static $has_one = [
'CurrentCompany' => Company::class,
];
}