From 2fb7cfa096d9e104ed4cc637a6601f72edee125e Mon Sep 17 00:00:00 2001 From: Guy Sartorelli Date: Tue, 12 Nov 2024 10:49:38 +1300 Subject: [PATCH 1/2] API Replace DBEnum::flushCache() with DBEnum::reset() --- src/ORM/DataObject.php | 2 +- src/ORM/FieldType/DBEnum.php | 13 +++++++++++-- tests/php/ORM/DBEnumTest.php | 6 +++--- tests/php/ORM/DataObjectSchemaGenerationTest.php | 8 ++++---- tests/php/Security/SecurityTest.php | 2 +- 5 files changed, 20 insertions(+), 11 deletions(-) diff --git a/src/ORM/DataObject.php b/src/ORM/DataObject.php index 7545b8362e8..96909dcbe17 100644 --- a/src/ORM/DataObject.php +++ b/src/ORM/DataObject.php @@ -3552,7 +3552,7 @@ public static function flush_and_destroy_cache() */ public static function reset() { - DBEnum::flushCache(); + DBEnum::reset(); ClassInfo::reset_db_cache(); static::getSchema()->reset(); DataObject::$_cache_get_one = []; diff --git a/src/ORM/FieldType/DBEnum.php b/src/ORM/FieldType/DBEnum.php index b85dec01361..d5336f7445d 100644 --- a/src/ORM/FieldType/DBEnum.php +++ b/src/ORM/FieldType/DBEnum.php @@ -3,6 +3,8 @@ namespace SilverStripe\ORM\FieldType; use SilverStripe\Core\Config\Config; +use SilverStripe\Core\Resettable; +use SilverStripe\Dev\Deprecation; use SilverStripe\Forms\DropdownField; use SilverStripe\ORM\ArrayLib; use SilverStripe\ORM\Connect\MySQLDatabase; @@ -13,7 +15,7 @@ * * See {@link DropdownField} for a {@link FormField} to select enum values. */ -class DBEnum extends DBString +class DBEnum extends DBString implements Resettable { /** @@ -42,8 +44,15 @@ class DBEnum extends DBString /** * Clear all cached enum values. + * @deprecated 5.4.0 Use reset() instead. */ public static function flushCache() + { + Deprecation::notice('5.4.0', 'Use reset() instead.'); + static::reset(); + } + + public static function reset(): void { DBEnum::$enum_cache = []; } @@ -195,7 +204,7 @@ public function getEnum() * If table or name are not set, or if it is not a valid field on the given table, * then only known enum values are returned. * - * Values cached in this method can be cleared via `DBEnum::flushCache();` + * Values cached in this method can be cleared via `DBEnum::reset();` * * @return array */ diff --git a/tests/php/ORM/DBEnumTest.php b/tests/php/ORM/DBEnumTest.php index c0d1f71c0b1..17751d98fa9 100644 --- a/tests/php/ORM/DBEnumTest.php +++ b/tests/php/ORM/DBEnumTest.php @@ -94,7 +94,7 @@ public function testObsoleteValues() // Test values with a record $obj->Colour = 'Red'; $obj->write(); - DBEnum::flushCache(); + DBEnum::reset(); $this->assertEquals( ['Red', 'Blue', 'Green'], @@ -103,7 +103,7 @@ public function testObsoleteValues() // If the value is removed from the enum, obsolete content is still retained $colourField->setEnum(['Blue', 'Green', 'Purple']); - DBEnum::flushCache(); + DBEnum::reset(); $this->assertEquals( ['Blue', 'Green', 'Purple', 'Red'], // Red on the end now, because it's obsolete @@ -134,7 +134,7 @@ public function testObsoleteValues() // If obsolete records are deleted, the extra values go away $obj->delete(); $obj2->delete(); - DBEnum::flushCache(); + DBEnum::reset(); $this->assertEquals( ['Blue', 'Green'], $colourField->getEnumObsolete() diff --git a/tests/php/ORM/DataObjectSchemaGenerationTest.php b/tests/php/ORM/DataObjectSchemaGenerationTest.php index e5c04ce185e..05909e6259b 100644 --- a/tests/php/ORM/DataObjectSchemaGenerationTest.php +++ b/tests/php/ORM/DataObjectSchemaGenerationTest.php @@ -197,7 +197,7 @@ public function testClassNameSpecGeneration() $schema = DataObject::getSchema(); // Test with blank entries - DBEnum::flushCache(); + DBEnum::reset(); $do1 = new TestObject(); $fields = $schema->databaseFields(TestObject::class, false); // May be overridden from DBClassName to DBClassNameVarchar by config @@ -215,7 +215,7 @@ public function testClassNameSpecGeneration() // Test with instance of subclass $item1 = new TestIndexObject(); $item1->write(); - DBEnum::flushCache(); + DBEnum::reset(); $this->assertEquals( [ TestObject::class, @@ -228,7 +228,7 @@ public function testClassNameSpecGeneration() // Test with instance of main class $item2 = new TestObject(); $item2->write(); - DBEnum::flushCache(); + DBEnum::reset(); $this->assertEquals( [ TestObject::class, @@ -243,7 +243,7 @@ public function testClassNameSpecGeneration() $item1->write(); $item2 = new TestObject(); $item2->write(); - DBEnum::flushCache(); + DBEnum::reset(); $this->assertEquals( [ TestObject::class, diff --git a/tests/php/Security/SecurityTest.php b/tests/php/Security/SecurityTest.php index bad715f7b7b..0ed842485b3 100644 --- a/tests/php/Security/SecurityTest.php +++ b/tests/php/Security/SecurityTest.php @@ -687,7 +687,7 @@ public function testSuccessfulLoginAttempts() public function testDatabaseIsReadyWithInsufficientMemberColumns() { Security::clear_database_is_ready(); - DBEnum::flushCache(); + DBEnum::reset(); // Assumption: The database has been built correctly by the test runner, // and has all columns present in the ORM From 5b16f7de82037e9363f5ff6402d40652dae42614 Mon Sep 17 00:00:00 2001 From: Guy Sartorelli Date: Tue, 12 Nov 2024 10:50:15 +1300 Subject: [PATCH 2/2] NEW Provide new class description config and methods. This provides a description of what a class is intented to be used for, which is useful for content authors picking a class to create in the CMS UI. --- src/ORM/DataObject.php | 50 +++++++- tests/php/ORM/DataObjectTest.php | 140 +++++++++++++++++------ tests/php/ORM/DataObjectTest/Team.php | 2 + tests/php/i18n/i18nTest/MyObject.php | 2 + tests/php/i18n/i18nTextCollectorTest.php | 1 + 5 files changed, 160 insertions(+), 35 deletions(-) diff --git a/src/ORM/DataObject.php b/src/ORM/DataObject.php index 96909dcbe17..76172c96177 100644 --- a/src/ORM/DataObject.php +++ b/src/ORM/DataObject.php @@ -127,6 +127,15 @@ class DataObject extends ViewableData implements DataObjectInterface, i18nEntity */ private static $plural_name = null; + /** + * Description of the class. + * Unlike most configuration, this is usually used uninherited, meaning it should be defined + * on each subclass. + * + * Used in some areas of the CMS, e.g. when selecting what type of record to create. + */ + private static ?string $class_description = null; + /** * @config */ @@ -940,6 +949,44 @@ public function i18n_plural_name() return _t(static::class . '.PLURALNAME', $this->plural_name()); } + /** + * Get description for this class + * @return null|string + */ + public function classDescription() + { + return static::config()->get('class_description', Config::UNINHERITED); + } + + /** + * Get localised description for this class + * @return null|string + */ + public function i18n_classDescription() + { + $notDefined = 'NOT_DEFINED'; + $baseDescription = $this->classDescription() ?? $notDefined; + + // Check the new i18n key first + $description = _t(static::class . '.CLASS_DESCRIPTION', $baseDescription); + if ($description !== $baseDescription) { + return $description; + } + + // Fall back on the deprecated localisation key + $legacyI18n = _t(static::class . '.DESCRIPTION', $baseDescription); + if ($legacyI18n !== $baseDescription) { + return $legacyI18n; + } + + // If there was no description available in config nor in i18n, return null + if ($baseDescription === $notDefined) { + return null; + } + // Return raw description + return $baseDescription; + } + /** * Standard implementation of a title/label for a specific * record. Tries to find properties 'Title' or 'Name', @@ -4372,7 +4419,8 @@ public function provideI18nEntities() $singularName = $this->singular_name(); $conjunction = preg_match('/^[aeiou]/i', $singularName ?? '') ? 'An ' : 'A '; return [ - static::class . '.SINGULARNAME' => $this->singular_name(), + static::class . '.CLASS_DESCRIPTION' => $this->classDescription(), + static::class . '.SINGULARNAME' => $singularName, static::class . '.PLURALNAME' => $pluralName, static::class . '.PLURALS' => [ 'one' => $conjunction . $singularName, diff --git a/tests/php/ORM/DataObjectTest.php b/tests/php/ORM/DataObjectTest.php index af99c2decc5..fec9aa4c164 100644 --- a/tests/php/ORM/DataObjectTest.php +++ b/tests/php/ORM/DataObjectTest.php @@ -1907,52 +1907,124 @@ public function testManyManyUnlimitedRowCount() $this->assertEquals(2, $player->Teams()->dataQuery()->query()->unlimitedRowCount()); } + public function provideSingularName(): array + { + return [ + [ + 'class' => DataObjectTest\Player::class, + 'expected' => 'Player', + ], + [ + 'class' => DataObjectTest\Team::class, + 'expected' => 'Team', + ], + [ + 'class' => DataObjectTest\Fixture::class, + 'expected' => 'Fixture', + ], + ]; + } + /** * Tests that singular_name() generates sensible defaults. + * @dataProvider provideSingularName */ - public function testSingularName() + public function testSingularName(string $class, string $expected): void { - $assertions = [ - DataObjectTest\Player::class => 'Player', - DataObjectTest\Team::class => 'Team', - DataObjectTest\Fixture::class => 'Fixture', - ]; + i18n::set_locale('en_NZ'); + /** @var DataObject $object */ + $object = new $class(); + $this->assertEquals( + $expected, + $object->singular_name(), + "Assert that the singular_name for '$class' is correct." + ); + $this->assertEquals( + $expected, + $object->i18n_singular_name(), + "Assert that the i18n_singular_name for '$class' is correct." + ); + } - foreach ($assertions as $class => $expectedSingularName) { - $this->assertEquals( - $expectedSingularName, - singleton($class)->singular_name(), - "Assert that the singular_name for '$class' is correct." - ); - } + public function providePluralName(): array + { + return [ + [ + 'class' => DataObjectTest\Player::class, + 'expected' => 'Players', + ], + [ + 'class' => DataObjectTest\Team::class, + 'expected' => 'Teams', + ], + [ + 'class' => DataObjectTest\Fixture::class, + 'expected' => 'Fixtures', + ], + [ + 'class' => DataObjectTest\Play::class, + 'expected' => 'Plays', + ], + [ + 'class' => DataObjectTest\Bogey::class, + 'expected' => 'Bogeys', + ], + [ + 'class' => DataObjectTest\Ploy::class, + 'expected' => 'Ploys', + ], + ]; } /** * Tests that plural_name() generates sensible defaults. + * @dataProvider providePluralName */ - public function testPluralName() - { - $assertions = [ - DataObjectTest\Player::class => 'Players', - DataObjectTest\Team::class => 'Teams', - DataObjectTest\Fixture::class => 'Fixtures', - DataObjectTest\Play::class => 'Plays', - DataObjectTest\Bogey::class => 'Bogeys', - DataObjectTest\Ploy::class => 'Ploys', + public function testPluralName(string $class, string $expected): void + { + i18n::set_locale('en_NZ'); + /** @var DataObject $object */ + $object = new $class(); + $this->assertEquals( + $expected, + $object->plural_name(), + "Assert that the plural_name for '$class' is correct." + ); + $this->assertEquals( + $expected, + $object->i18n_plural_name(), + "Assert that the i18n_plural_name for '$class' is correct." + ); + } + + public function provideClassDescription(): array + { + return [ + 'no description by default' => [ + 'class' => DataObjectTest\Player::class, + 'expected' => null, + ], + 'explicitly set description' => [ + 'class' => DataObjectTest\Team::class, + 'expected' => 'A team of players', + ], + 'cannot inherit description from superclass' => [ + 'class' => DataObjectTest\SubTeam::class, + 'expected' => null, + ], ]; + } + + /** + * @dataProvider provideClassDescription + */ + public function testClassDescription(string $class, ?string $expected): void + { i18n::set_locale('en_NZ'); - foreach ($assertions as $class => $expectedPluralName) { - $this->assertEquals( - $expectedPluralName, - DataObject::singleton($class)->plural_name(), - "Assert that the plural_name for '$class' is correct." - ); - $this->assertEquals( - $expectedPluralName, - DataObject::singleton($class)->i18n_plural_name(), - "Assert that the i18n_plural_name for '$class' is correct." - ); - } + /** @var DataObject $object */ + $object = new $class(); + $this->assertEquals($expected, $object->classDescription()); + $this->assertEquals($expected, $object->i18n_classDescription()); } public function testHasDatabaseField() diff --git a/tests/php/ORM/DataObjectTest/Team.php b/tests/php/ORM/DataObjectTest/Team.php index 07c0b310ec6..42de8d3ad56 100644 --- a/tests/php/ORM/DataObjectTest/Team.php +++ b/tests/php/ORM/DataObjectTest/Team.php @@ -28,6 +28,8 @@ class Team extends DataObject implements TestOnly { private static $table_name = 'DataObjectTest_Team'; + private static $class_description = 'A team of players'; + private static $db = [ 'Title' => 'Varchar', 'DatabaseField' => 'HTMLVarchar', diff --git a/tests/php/i18n/i18nTest/MyObject.php b/tests/php/i18n/i18nTest/MyObject.php index 63b2e76254d..9405faad7a2 100644 --- a/tests/php/i18n/i18nTest/MyObject.php +++ b/tests/php/i18n/i18nTest/MyObject.php @@ -24,6 +24,8 @@ class MyObject extends DataObject implements TestOnly private static $plural_name = "My Objects"; + private static $class_description = 'A class that represents objects'; + public function provideI18nEntities() { $entities = parent::provideI18nEntities(); diff --git a/tests/php/i18n/i18nTextCollectorTest.php b/tests/php/i18n/i18nTextCollectorTest.php index 5005c814a8e..6c3600738f4 100644 --- a/tests/php/i18n/i18nTextCollectorTest.php +++ b/tests/php/i18n/i18nTextCollectorTest.php @@ -709,6 +709,7 @@ public function testCollectFromEntityProvidersInCustomObject() 'other' => '{count} My Objects', ], 'SilverStripe\i18n\Tests\i18nTest\MyObject.SINGULARNAME' => 'My Object', + 'SilverStripe\i18n\Tests\i18nTest\MyObject.CLASS_DESCRIPTION' => 'A class that represents objects', ], $matches );