Skip to content

Commit

Permalink
Merge pull request #11359 from creative-commoners/pulls/5/varchar-cla…
Browse files Browse the repository at this point in the history
…ssname

NEW Create DBClassNameVarchar
  • Loading branch information
GuySartorelli authored Sep 10, 2024
2 parents b13b657 + a0ad753 commit b2a8baa
Show file tree
Hide file tree
Showing 8 changed files with 209 additions and 122 deletions.
2 changes: 2 additions & 0 deletions _config/model.yml
Original file line number Diff line number Diff line change
Expand Up @@ -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:
Expand Down
4 changes: 3 additions & 1 deletion src/ORM/DatabaseAdmin.php
Original file line number Diff line number Diff line change
Expand Up @@ -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;
Expand Down Expand Up @@ -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;
}
}
Expand Down
123 changes: 3 additions & 120 deletions src/ORM/FieldType/DBClassName.php
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand All @@ -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
Expand All @@ -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();
}
}
131 changes: 131 additions & 0 deletions src/ORM/FieldType/DBClassNameTrait.php
Original file line number Diff line number Diff line change
@@ -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 changes: 27 additions & 0 deletions src/ORM/FieldType/DBClassNameVarchar.php
Original file line number Diff line number Diff line change
@@ -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 changes: 35 additions & 0 deletions tests/php/ORM/DBClassNameVarcharTest.php
Original file line number Diff line number Diff line change
@@ -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));
}
}
4 changes: 3 additions & 1 deletion tests/php/ORM/DataObjectSchemaGenerationTest.php
Original file line number Diff line number Diff line change
Expand Up @@ -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,
Expand Down
5 changes: 5 additions & 0 deletions tests/php/ORM/DataObjectSchemaTest.php
Original file line number Diff line number Diff line change
Expand Up @@ -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));
}

Expand Down

0 comments on commit b2a8baa

Please sign in to comment.