mirror of
https://github.com/silverstripe/silverstripe-framework
synced 2024-10-22 14:05:37 +02:00
Merge pull request #11359 from creative-commoners/pulls/5/varchar-classname
NEW Create DBClassNameVarchar
This commit is contained in:
commit
b2a8baa931
@ -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:
|
||||
|
@ -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;
|
||||
}
|
||||
}
|
||||
|
@ -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();
|
||||
}
|
||||
}
|
||||
|
131
src/ORM/FieldType/DBClassNameTrait.php
Normal file
131
src/ORM/FieldType/DBClassNameTrait.php
Normal 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);
|
||||
}
|
||||
}
|
27
src/ORM/FieldType/DBClassNameVarchar.php
Normal file
27
src/ORM/FieldType/DBClassNameVarchar.php
Normal 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;
|
||||
}
|
35
tests/php/ORM/DBClassNameVarcharTest.php
Normal file
35
tests/php/ORM/DBClassNameVarcharTest.php
Normal 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));
|
||||
}
|
||||
}
|
@ -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,
|
||||
|
@ -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));
|
||||
}
|
||||
|
||||
|
Loading…
Reference in New Issue
Block a user