From d0d7d14709a14a930ad2afb33eb2f95098c6f4da Mon Sep 17 00:00:00 2001 From: Steve Boyd Date: Wed, 29 Nov 2023 14:07:57 +1300 Subject: [PATCH] ENH Use SearchableDropdownField for autoscaffolded has_one relationships --- src/ORM/FieldType/DBForeignKey.php | 64 ++++++++---------------------- 1 file changed, 16 insertions(+), 48 deletions(-) diff --git a/src/ORM/FieldType/DBForeignKey.php b/src/ORM/FieldType/DBForeignKey.php index 814b2287daf..d5b3583584b 100644 --- a/src/ORM/FieldType/DBForeignKey.php +++ b/src/ORM/FieldType/DBForeignKey.php @@ -5,9 +5,8 @@ use SilverStripe\Assets\File; use SilverStripe\Assets\Image; use SilverStripe\Core\Injector\Injector; -use SilverStripe\Forms\DropdownField; use SilverStripe\Forms\FileHandleField; -use SilverStripe\Forms\NumericField; +use SilverStripe\Forms\SearchableDropdownField; use SilverStripe\ORM\DataList; use SilverStripe\ORM\DataObject; @@ -23,7 +22,6 @@ */ class DBForeignKey extends DBInt { - /** * @var DataObject */ @@ -36,9 +34,16 @@ class DBForeignKey extends DBInt * * @config * @var int + * @deprecated 5.2.0 Use dropdown_lazyload_threshold instead */ private static $dropdown_field_threshold = 100; + /** + * Number of related objects to show in a dropdown before it switches to using lazyloading + * This will also be used as the lazy load limit + */ + private static int $dropdown_lazyload_threshold = 10; + private static $index = true; private static $default_search_filter_class = 'ExactMatchFilter'; @@ -47,6 +52,7 @@ class DBForeignKey extends DBInt * Cache for multiple subsequent calls to scaffold form fields with the same foreign key object * * @var array + * @deprecated 5.2.0 Will be removed without equivalent functionality to replace it */ protected static $foreignListCache = []; @@ -77,52 +83,14 @@ public function scaffoldFormField($title = null, $params = null) } return $field; } - - // Build selector / numeric field - $titleField = $hasOneSingleton->hasField('Title') ? 'Title' : 'Name'; + $labelField = $hasOneSingleton->hasField('Title') ? 'Title' : 'Name'; $list = DataList::create($hasOneClass); - // Don't scaffold a dropdown for large tables, as making the list concrete - // might exceed the available PHP memory in creating too many DataObject instances - $threshold = self::config()->get('dropdown_field_threshold'); - - // Add the count of the list to a cache for subsequent calls - if (!isset(static::$foreignListCache[$hasOneClass])) { - // Let the DB do the threshold check as it will be faster - depending on the SQL engine it might only have - // to count indexes - $dataQuery = $list->dataQuery()->getFinalisedQuery(); - - // Clear order-by as it's not relevant for counts - $dataQuery->setOrderBy(false); - // Remove distinct. Applying distinct shouldn't be required provided relations are not applied. - $dataQuery->setDistinct(false); - - $dataQuery->setSelect(['over_threshold' => '(CASE WHEN count(*) > ' . (int)$threshold . ' THEN 1 ELSE 0 END)']); - $result = $dataQuery->execute()->column('over_threshold'); - - $overThreshold = !empty($result) && ((int) $result[0] === 1); - - static::$foreignListCache[$hasOneClass] = [ - 'overThreshold' => $overThreshold, - ]; - } - - $overThreshold = static::$foreignListCache[$hasOneClass]['overThreshold']; - - if (!$overThreshold) { - // Add the mapped list for the cache - if (!isset(static::$foreignListCache[$hasOneClass]['map'])) { - static::$foreignListCache[$hasOneClass]['map'] = $list->map('ID', $titleField); - } - - $field = new DropdownField($this->name, $title, static::$foreignListCache[$hasOneClass]['map']); - $field->setEmptyString(' '); - } else { - $field = new NumericField($this->name, $title); - $field->setRightTitle(_t( - self::class . '.DROPDOWN_THRESHOLD_FALLBACK_MESSAGE', - 'Too many related objects; fallback field in use' - )); - } + $threshold = self::config()->get('dropdown_lazyload_threshold'); + $overThreshold = $list->count() > $threshold; + $field = SearchableDropdownField::create($this->name, $title, $list, $labelField) + ->setHasEmptyDefault(true) + ->setIsLazyLoaded($overThreshold) + ->setLazyLoadLimit($threshold); return $field; }