diff --git a/_config/model.yml b/_config/model.yml index cfb60c02aa6..4046feddb65 100644 --- a/_config/model.yml +++ b/_config/model.yml @@ -8,6 +8,8 @@ SilverStripe\Core\Injector\Injector: class: SilverStripe\ORM\FieldType\DBCurrency DBClassName: class: SilverStripe\ORM\FieldType\DBClassName + DBClassNameVarchar: + class: SilverStripe\ORM\FieldType\DBClassNameVarchar Date: class: SilverStripe\ORM\FieldType\DBDate Datetime: diff --git a/src/ORM/DatabaseAdmin.php b/src/ORM/DatabaseAdmin.php index 8d08336fc22..5b222599b73 100644 --- a/src/ORM/DatabaseAdmin.php +++ b/src/ORM/DatabaseAdmin.php @@ -15,6 +15,7 @@ use SilverStripe\ORM\Connect\DatabaseException; use SilverStripe\ORM\Connect\TableBuilder; use SilverStripe\ORM\FieldType\DBClassName; +use SilverStripe\ORM\FieldType\DBClassNameVarchar; use SilverStripe\Security\Permission; use SilverStripe\Security\Security; use SilverStripe\Versioned\Versioned; @@ -460,7 +461,8 @@ protected function getClassNameRemappingFields() foreach ($dataClasses as $className) { $fieldSpecs = $schema->fieldSpecs($className); foreach ($fieldSpecs as $fieldName => $fieldSpec) { - if (Injector::inst()->create($fieldSpec, 'Dummy') instanceof DBClassName) { + $dummy = Injector::inst()->create($fieldSpec, 'Dummy'); + if ($dummy instanceof DBClassName || $dummy instanceof DBClassNameVarchar) { $remapping[$className][] = $fieldName; } } diff --git a/src/ORM/FieldType/DBClassName.php b/src/ORM/FieldType/DBClassName.php index 4848b05734e..0406f1c4111 100644 --- a/src/ORM/FieldType/DBClassName.php +++ b/src/ORM/FieldType/DBClassName.php @@ -2,47 +2,15 @@ namespace SilverStripe\ORM\FieldType; -use SilverStripe\Core\ClassInfo; -use SilverStripe\Core\Config\Config; -use SilverStripe\ORM\DataObject; use SilverStripe\ORM\DB; +use SilverStripe\Core\Config\Config; /** * Represents a classname selector, which respects obsolete clasess. */ class DBClassName extends DBEnum { - - /** - * Base classname of class to enumerate. - * If 'DataObject' then all classes are included. - * If empty, then the baseClass of the parent object will be used - * - * @var string|null - */ - protected $baseClass = null; - - /** - * Parent object - * - * @var DataObject|null - */ - protected $record = null; - - private static $index = true; - - /** - * Create a new DBClassName field - * - * @param string $name Name of field - * @param string|null $baseClass Optional base class to limit selections - * @param array $options Optional parameters for this DBField instance - */ - public function __construct($name = null, $baseClass = null, $options = []) - { - $this->setBaseClass($baseClass); - parent::__construct($name, null, null, $options); - } + use DBClassNameTrait; /** * @return void @@ -67,82 +35,6 @@ public function requireField() DB::require_field($this->getTable(), $this->getName(), $values); } - /** - * Get the base dataclass for the list of subclasses - * - * @return string - */ - public function getBaseClass() - { - // Use explicit base class - if ($this->baseClass) { - return $this->baseClass; - } - // Default to the basename of the record - $schema = DataObject::getSchema(); - if ($this->record) { - return $schema->baseDataClass($this->record); - } - // During dev/build only the table is assigned - $tableClass = $schema->tableClass($this->getTable()); - if ($tableClass && ($baseClass = $schema->baseDataClass($tableClass))) { - return $baseClass; - } - // Fallback to global default - return DataObject::class; - } - - /** - * Get the base name of the current class - * Useful as a non-fully qualified CSS Class name in templates. - * - * @return string|null - */ - public function getShortName() - { - $value = $this->getValue(); - if (empty($value) || !ClassInfo::exists($value)) { - return null; - } - return ClassInfo::shortName($value); - } - - /** - * Assign the base class - * - * @param string $baseClass - * @return $this - */ - public function setBaseClass($baseClass) - { - $this->baseClass = $baseClass; - return $this; - } - - /** - * Get list of classnames that should be selectable - * - * @return array - */ - public function getEnum() - { - $classNames = ClassInfo::subclassesFor($this->getBaseClass()); - $dataobject = strtolower(DataObject::class); - unset($classNames[$dataobject]); - return array_values($classNames ?? []); - } - - public function setValue($value, $record = null, $markChanged = true) - { - parent::setValue($value, $record, $markChanged); - - if ($record instanceof DataObject) { - $this->record = $record; - } - - return $this; - } - public function getDefault() { // Check for assigned default @@ -151,15 +43,6 @@ public function getDefault() return $default; } - // Allow classes to set default class - $baseClass = $this->getBaseClass(); - $defaultClass = Config::inst()->get($baseClass, 'default_classname'); - if ($defaultClass && class_exists($defaultClass ?? '')) { - return $defaultClass; - } - - // Fallback to first option - $enum = $this->getEnum(); - return reset($enum); + return $this->getDefaultClassName(); } } diff --git a/src/ORM/FieldType/DBClassNameTrait.php b/src/ORM/FieldType/DBClassNameTrait.php new file mode 100644 index 00000000000..b2d6aac1dd6 --- /dev/null +++ b/src/ORM/FieldType/DBClassNameTrait.php @@ -0,0 +1,131 @@ +setBaseClass($baseClass); + parent::__construct($name, null, null, $options); + } + + /** + * Get the base dataclass for the list of subclasses + * + * @return string + */ + public function getBaseClass() + { + // Use explicit base class + if ($this->baseClass) { + return $this->baseClass; + } + // Default to the basename of the record + $schema = DataObject::getSchema(); + if ($this->record) { + return $schema->baseDataClass($this->record); + } + // During dev/build only the table is assigned + $tableClass = $schema->tableClass($this->getTable()); + if ($tableClass && ($baseClass = $schema->baseDataClass($tableClass))) { + return $baseClass; + } + // Fallback to global default + return DataObject::class; + } + + /** + * Get the base name of the current class + * Useful as a non-fully qualified CSS Class name in templates. + * + * @return string|null + */ + public function getShortName() + { + $value = $this->getValue(); + if (empty($value) || !ClassInfo::exists($value)) { + return null; + } + return ClassInfo::shortName($value); + } + + /** + * Assign the base class + * + * @param string $baseClass + * @return $this + */ + public function setBaseClass($baseClass) + { + $this->baseClass = $baseClass; + return $this; + } + + /** + * Get list of classnames that should be selectable + * + * @return array + */ + public function getEnum() + { + $classNames = ClassInfo::subclassesFor($this->getBaseClass()); + $dataobject = strtolower(DataObject::class); + unset($classNames[$dataobject]); + return array_values($classNames ?? []); + } + + public function setValue($value, $record = null, $markChanged = true) + { + parent::setValue($value, $record, $markChanged); + + if ($record instanceof DataObject) { + $this->record = $record; + } + + return $this; + } + + private function getDefaultClassName() + { + // Allow classes to set default class + $baseClass = $this->getBaseClass(); + $defaultClass = Config::inst()->get($baseClass, 'default_classname'); + if ($defaultClass && class_exists($defaultClass ?? '')) { + return $defaultClass; + } + + // Fallback to first option + $subClassNames = $this->getEnum(); + return reset($subClassNames); + } +} diff --git a/src/ORM/FieldType/DBClassNameVarchar.php b/src/ORM/FieldType/DBClassNameVarchar.php new file mode 100644 index 00000000000..16755ead430 --- /dev/null +++ b/src/ORM/FieldType/DBClassNameVarchar.php @@ -0,0 +1,23 @@ + + * SilverStripe\ORM\DataObject: + * fixed_fields: + * ClassName: DBClassNameVarchar + * + */ +class DBClassNameVarchar extends DBVarchar +{ + use DBClassNameTrait; +} diff --git a/tests/php/ORM/DBClassNameVarcharTest.php b/tests/php/ORM/DBClassNameVarcharTest.php new file mode 100644 index 00000000000..80dfe8aff35 --- /dev/null +++ b/tests/php/ORM/DBClassNameVarcharTest.php @@ -0,0 +1,35 @@ +get(DataObject::class, 'fixed_fields'); + $fixedFields['ClassName'] = 'DBClassNameVarchar'; + Config::modify()->set(DataObject::class, 'fixed_fields', $fixedFields); + } + + public function testVarcharType(): void + { + /** @var DataObject $obj */ + $obj = HasFields::create(); + $class = get_class($obj->dbObject('ClassName')); + $this->assertSame(DBClassNameVarchar::class, $class); + $this->assertTrue(is_a($class, DBVarchar::class, true)); + } +} diff --git a/tests/php/ORM/DataObjectSchemaGenerationTest.php b/tests/php/ORM/DataObjectSchemaGenerationTest.php index 391c782c2f1..e5c04ce185e 100644 --- a/tests/php/ORM/DataObjectSchemaGenerationTest.php +++ b/tests/php/ORM/DataObjectSchemaGenerationTest.php @@ -200,7 +200,9 @@ public function testClassNameSpecGeneration() DBEnum::flushCache(); $do1 = new TestObject(); $fields = $schema->databaseFields(TestObject::class, false); - $this->assertEquals("DBClassName", $fields['ClassName']); + // May be overridden from DBClassName to DBClassNameVarchar by config + $expectedClassName = DataObject::config()->get('fixed_fields')['ClassName']; + $this->assertEquals($expectedClassName, $fields['ClassName']); $this->assertEquals( [ TestObject::class, diff --git a/tests/php/ORM/DataObjectSchemaTest.php b/tests/php/ORM/DataObjectSchemaTest.php index daa14152f68..e4d122e1b4f 100644 --- a/tests/php/ORM/DataObjectSchemaTest.php +++ b/tests/php/ORM/DataObjectSchemaTest.php @@ -165,6 +165,11 @@ public function testTableForObjectField() public function testFieldSpec(array $args, array $expected): void { $schema = DataObject::getSchema(); + // May be overridden from DBClassName to DBClassNameVarchar by config + $expectedClassName = DataObject::config()->get('fixed_fields')['ClassName']; + if (array_key_exists('ClassName', $expected) && $expectedClassName !== 'DBClassName') { + $expected['ClassName'] = str_replace('DBClassName', $expectedClassName, $expected['ClassName']); + } $this->assertEquals($expected, $schema->fieldSpecs(...$args)); }