Merge pull request #11359 from creative-commoners/pulls/5/varchar-classname

NEW Create DBClassNameVarchar
This commit is contained in:
Guy Sartorelli 2024-09-10 16:28:43 +12:00 committed by GitHub
commit b2a8baa931
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194
8 changed files with 209 additions and 122 deletions

View File

@ -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:

View File

@ -15,6 +15,7 @@ use SilverStripe\Dev\DevelopmentAdmin;
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 @@ class DatabaseAdmin extends Controller
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;
}
}

View File

@ -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 @@ class DBClassName extends DBEnum
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 @@ class DBClassName extends DBEnum
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();
}
}

View File

@ -0,0 +1,131 @@
<?php
namespace SilverStripe\ORM\FieldType;
use SilverStripe\Core\ClassInfo;
use SilverStripe\Core\Config\Config;
use SilverStripe\ORM\DataObject;
trait DBClassNameTrait
{
/**
* 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);
}
/**
* 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);
}
}

View File

@ -0,0 +1,27 @@
<?php
namespace SilverStripe\ORM\FieldType;
use SilverStripe\ORM\FieldType\DBVarchar;
/**
* An alternative to DBClassName that stores the class name as a varchar instead of an enum
* This will use more disk space, though will prevent issues with long dev/builds on
* very large database tables when a ALTER TABLE queries are required to update the enum.
*
* Use the following config to use this class in your project:
*
* <code>
* SilverStripe\ORM\DataObject:
* fixed_fields:
* ClassName: DBClassNameVarchar
*
* SilverStripe\ORM\FieldType\DBPolymorphicForeignKey:
* composite_db:
* Class: DBClassNameVarchar('SilverStripe\ORM\DataObject', ['index' => false])
* </code>
*/
class DBClassNameVarchar extends DBVarchar
{
use DBClassNameTrait;
}

View File

@ -0,0 +1,35 @@
<?php
namespace SilverStripe\ORM\Tests;
use SilverStripe\Core\Config\Config;
use SilverStripe\ORM\DataObject;
use SilverStripe\ORM\FieldType\DBClassNameVarchar;
use SilverStripe\ORM\FieldType\DBVarchar;
use SilverStripe\ORM\Tests\DataObjectSchemaTest\HasFields;
/**
* These unit tests test will change DBClassName to a varchar column
* and then test that the tests in DataObjectSchemaTest still pass
*
* There's also a test that a ClassName of an arbitary DataObject is a Varchar
*/
class DBClassNameVarcharTest extends DataObjectSchemaTest
{
public function setup(): void
{
parent::setup();
$fixedFields = Config::inst()->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));
}
}

View File

@ -200,7 +200,9 @@ class DataObjectSchemaGenerationTest extends SapphireTest
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,

View File

@ -165,6 +165,11 @@ class DataObjectSchemaTest extends SapphireTest
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));
}