From 9235fcf8a67cfc755993469b2c17d09fd3725f72 Mon Sep 17 00:00:00 2001 From: brandonkelly Date: Thu, 4 Jan 2024 02:41:19 -0800 Subject: [PATCH 01/11] $normalizer option for memoizable arrays Moves the complexity from 81f6766212b67fb20b8e55e64ae8f43479af8c92 into MemoizableArray, so other things can take advantage of lazy instantiation --- CHANGELOG-WIP.md | 2 ++ CHANGELOG.md | 2 ++ src/base/MemoizableArray.php | 60 +++++++++++++++++++++++++++---- src/helpers/ArrayHelper.php | 13 +++++-- src/services/Fields.php | 68 +++++++++++------------------------- 5 files changed, 88 insertions(+), 57 deletions(-) diff --git a/CHANGELOG-WIP.md b/CHANGELOG-WIP.md index 35933402c2e..92e48c3b663 100644 --- a/CHANGELOG-WIP.md +++ b/CHANGELOG-WIP.md @@ -330,6 +330,7 @@ - `craft\base\ElementInterface::setRevisionNotes()` no longer needs to specify a default value for the `notes` argument. - `craft\base\Field::inputHtml()` now has an `$inline` argument. - `craft\base\FieldInterface::getIsTranslatable()`, `getTranslationDescription()`, `getInputHtml()`, `normalizeValue()`, `normalizeValueFromRequest()`, and `serializeValue()` no longer need to specify a default value for the `$element` argument. +- `craft\base\MemoizableArray` now supports passing a normalizer method to the constructor, which will be lazily applied to each array item once, only if returned by `all()` or `firstWhere()`. - `craft\db\Connection::getSupportsMb4()` is now dynamic for MySQL installs, based on whether the `elements_sites` table has an `mb4` charset. - `craft\elemens\db\ElementQueryInterface::collect()` now has an `ElementCollection` return type, rather than `Collection`. - `craft\elements\Entry::getSection()` can now return `null`, for nested entries. @@ -339,6 +340,7 @@ - `craft\fields\BaseOptionsField::$multi` and `$optgroups` properties are now static. - `craft\fields\Matrix::$propagationMethod` now has a type of `craft\enums\PropagationMethod`. - `craft\gql\mutations\Entry::createSaveMutations()` now accepts a `$section` argument. +- `craft\helpers\ArrayHelper::firstWhere()` now has a `$valueKey` argument, which can be passed a variable by reference that should be set to the resulting value’s key in the array. - `craft\helpers\Cp::fieldHtml()` now supports a `labelExtra` config value. - `craft\helpers\Db::parseParam()`, `parseDateParam()`, `parseMoneyParam()`, and `parseNumericParam()` now return `null` instead of an empty string if no condition should be applied. - `craft\helpers\Html::id()` and `Craft.formatInputId()` now retain colons and periods, and ensure the string begins with a letter. diff --git a/CHANGELOG.md b/CHANGELOG.md index d54131256a6..b1e53186271 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -4,6 +4,8 @@ - Added the `showFirstAndLastNameFields` config setting. ([#14097](https://github.com/craftcms/cms/pull/14097)) - `queue/get-job-info` action requests no longer create a mutex lock. +- `craft\base\MemoizableArray` now supports passing a normalizer method to the constructor, which will be lazily applied to each array item once, only if returned by `all()` or `firstWhere()`. +- `craft\helpers\ArrayHelper::firstWhere()` now has a `$valueKey` argument, which can be passed a variable by reference that should be set to the resulting value’s key in the array. - Fixed a PHP error that occurred when viewing a user’s addresses. ## 5.0.0-alpha.4 - 2024-01-02 diff --git a/src/base/MemoizableArray.php b/src/base/MemoizableArray.php index c1f46cd6396..0a1c5dc181d 100644 --- a/src/base/MemoizableArray.php +++ b/src/base/MemoizableArray.php @@ -39,6 +39,16 @@ class MemoizableArray implements IteratorAggregate, Countable */ private array $_elements; + /** + * @var callable|null Normalizer method + */ + private $_normalizer; + + /** + * @var array Normalized elements + */ + private array $_normalized = []; + /** * @var array Memoized array elements */ @@ -46,10 +56,41 @@ class MemoizableArray implements IteratorAggregate, Countable /** * Constructor + * + * @param array $elements The items to be memoized + * @param callable|null $normalizer A method that the items should be normalized with when first returned by + * [[all()]] or [[firstWhere()]]. */ - public function __construct(array $elements) + public function __construct(array $elements, ?callable $normalizer = null) { $this->_elements = $elements; + $this->_normalizer = $normalizer; + } + + private function normalize(array $elements): array + { + if (!isset($this->_normalizer)) { + return $elements; + } + + return array_values(array_map(fn($key) => $this->normalizeByKey($key), array_keys($elements))); + } + + private function normalizeByKey(int|string|null $key): mixed + { + if ($key === null) { + return null; + } + + if (!isset($this->_normalizer)) { + return $this->_elements[$key]; + } + + if (!isset($this->_normalized[$key])) { + $this->_normalized[$key] = call_user_func($this->_normalizer, $this->_elements[$key], $key); + } + + return $this->_normalized[$key]; } /** @@ -60,7 +101,7 @@ public function __construct(array $elements) */ public function all(): array { - return $this->_elements; + return $this->normalize($this->_elements); } /** @@ -78,7 +119,10 @@ public function where(string $key, mixed $value = true, bool $strict = false): s $memKey = $this->_memKey(__METHOD__, $key, $value, $strict); if (!isset($this->_memoized[$memKey])) { - $this->_memoized[$memKey] = new MemoizableArray(ArrayHelper::where($this, $key, $value, $strict, false)); + $this->_memoized[$memKey] = new MemoizableArray( + ArrayHelper::where($this->_elements, $key, $value, $strict), + isset($this->_normalizer) ? fn($element, $key) => $this->normalizeByKey($key) : null, + ); } return $this->_memoized[$memKey]; @@ -100,7 +144,10 @@ public function whereIn(string $key, array $values, bool $strict = false): self $memKey = $this->_memKey(__METHOD__, $key, $values, $strict); if (!isset($this->_memoized[$memKey])) { - $this->_memoized[$memKey] = new MemoizableArray(ArrayHelper::whereIn($this, $key, $values, $strict, false)); + $this->_memoized[$memKey] = new MemoizableArray( + ArrayHelper::whereIn($this->_elements, $key, $values, $strict), + isset($this->_normalizer) ? fn($element, $key) => $this->normalizeByKey($key) : null, + ); } return $this->_memoized[$memKey]; @@ -120,7 +167,8 @@ public function firstWhere(string $key, mixed $value = true, bool $strict = fals // Use array_key_exists() because it could be null if (!array_key_exists($memKey, $this->_memoized)) { - $this->_memoized[$memKey] = ArrayHelper::firstWhere($this, $key, $value, $strict); + ArrayHelper::firstWhere($this->_elements, $key, $value, $strict, valueKey: $valueKey); + $this->_memoized[$memKey] = $this->normalizeByKey($valueKey); } return $this->_memoized[$memKey]; @@ -148,7 +196,7 @@ private function _memKey(string $method, string $key, mixed $value, bool $strict */ public function getIterator(): ArrayIterator { - return new ArrayIterator($this->_elements); + return new ArrayIterator($this->normalize($this->_elements)); } /** diff --git a/src/helpers/ArrayHelper.php b/src/helpers/ArrayHelper.php index 2c302ef4aeb..86cb32af59d 100644 --- a/src/helpers/ArrayHelper.php +++ b/src/helpers/ArrayHelper.php @@ -237,12 +237,18 @@ public static function whereMultiple(iterable $array, array $conditions, bool $s * @param callable|string $key the column name or anonymous function which must be set to $value * @param mixed $value the value that $key should be compared with * @param bool $strict whether a strict type comparison should be used when checking array element values against $value + * @param int|string|null $valueKey The key of the resulting value, or null if it can't be found * @return mixed the value, or null if it can't be found * @since 3.1.0 */ - public static function firstWhere(iterable $array, callable|string $key, mixed $value = true, bool $strict = false): mixed - { - foreach ($array as $element) { + public static function firstWhere( + iterable $array, + callable|string $key, + mixed $value = true, + bool $strict = false, + int|string|null &$valueKey = null, + ): mixed { + foreach ($array as $valueKey => $element) { $elementValue = static::getValue($element, $key); /** @noinspection TypeUnsafeComparisonInspection */ if (($strict && $elementValue === $value) || (!$strict && $elementValue == $value)) { @@ -250,6 +256,7 @@ public static function firstWhere(iterable $array, callable|string $key, mixed $ } } + $valueKey = null; return null; } diff --git a/src/services/Fields.php b/src/services/Fields.php index ea215a89ae4..bb6f5d89600 100644 --- a/src/services/Fields.php +++ b/src/services/Fields.php @@ -154,16 +154,10 @@ class Fields extends Component public string $fieldContext = 'global'; /** - * @var MemoizableArray|null - * @see _fieldConfigs() + * @var MemoizableArray|null + * @see _fields() */ - private ?MemoizableArray $_fieldConfigs = null; - - /** - * @var FieldInterface[] - * @see _field() - */ - private array $_fields = []; + private ?MemoizableArray $_fields = null; /** * @var MemoizableArray|null @@ -332,55 +326,33 @@ public function createField(mixed $config): FieldInterface } /** - * Returns a memoizable array field configs. + * Returns a memoizable array of fields. * * @param string|string[]|false|null $context The field context(s) to fetch fields from. Defaults to [[\craft\services\Fields::$fieldContext]]. * Set to `false` to get all fields regardless of context. * - * @return MemoizableArray + * @return MemoizableArray */ - private function _fieldConfigs(mixed $context = null): MemoizableArray + private function _fields(mixed $context = null): MemoizableArray { $context ??= $this->fieldContext; - if (!isset($this->_fieldConfigs)) { - $this->_fieldConfigs = new MemoizableArray($this->_createFieldQuery()->all()); + if (!isset($this->_fields)) { + $this->_fields = new MemoizableArray( + $this->_createFieldQuery()->all(), + fn(array $config) => $this->createField($config), + ); } if ($context === false) { - return $this->_fieldConfigs; + return $this->_fields; } if (is_array($context)) { - return $this->_fieldConfigs->whereIn('context', $context, true); + return $this->_fields->whereIn('context', $context, true); } - return $this->_fieldConfigs->where('context', $context, true); - } - - /** - * @param array[] $configs - * @return FieldInterface[] - */ - private function _fields(array $configs): array - { - return array_map(fn(array $config) => $this->_field($config), $configs); - } - - /** - * @param array|null $config - * @return FieldInterface|null - */ - private function _field(?array $config): ?FieldInterface - { - if ($config === null) { - return null; - } - - if (!isset($this->_fields[$config['id']])) { - $this->_fields[$config['id']] = $this->createField($config); - } - return $this->_fields[$config['id']]; + return $this->_fields->where('context', $context, true); } /** @@ -392,7 +364,7 @@ private function _field(?array $config): ?FieldInterface */ public function getAllFields(mixed $context = null): array { - return $this->_fields($this->_fieldConfigs($context)->all()); + return $this->_fields($context)->all(); } /** @@ -455,7 +427,7 @@ public function getFieldsByType(string $type, mixed $context = null): array */ public function getFieldById(int $fieldId): ?FieldInterface { - return $this->_field($this->_fieldConfigs(false)->firstWhere('id', $fieldId)); + return $this->_fields(false)->firstWhere('id', $fieldId); } /** @@ -466,7 +438,7 @@ public function getFieldById(int $fieldId): ?FieldInterface */ public function getFieldByUid(string $fieldUid): ?FieldInterface { - return $this->_field($this->_fieldConfigs(false)->firstWhere('uid', $fieldUid, true)); + return $this->_fields(false)->firstWhere('uid', $fieldUid, true); } /** @@ -489,7 +461,7 @@ public function getFieldByUid(string $fieldUid): ?FieldInterface */ public function getFieldByHandle(string $handle, mixed $context = null): ?FieldInterface { - return $this->_field($this->_fieldConfigs($context)->firstWhere('handle', $handle, true)); + return $this->_fields($context)->firstWhere('handle', $handle, true); } /** @@ -729,7 +701,7 @@ public function applyFieldDelete(string $fieldUid): void } // Clear caches - $this->_fieldConfigs = null; + $this->_fields = null; // Update the field version $this->updateFieldVersion(); @@ -755,7 +727,7 @@ public function applyFieldDelete(string $fieldUid): void */ public function refreshFields(): void { - $this->_fieldConfigs = null; + $this->_fields = null; $this->updateFieldVersion(); } From ba63b2c1c106c59c7d6aaf93c121613390f818db Mon Sep 17 00:00:00 2001 From: brandonkelly Date: Thu, 4 Jan 2024 02:03:44 -0800 Subject: [PATCH 02/11] Defer field layout instantiation Fixes a bug where all field layouts were getting instantiated before the debug toolbar had a chance to register its `*` wildcard event --- CHANGELOG.md | 1 + src/services/Fields.php | 38 +++++++++++++++++++++----------------- 2 files changed, 22 insertions(+), 17 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index b1e53186271..34f49d33c07 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -7,6 +7,7 @@ - `craft\base\MemoizableArray` now supports passing a normalizer method to the constructor, which will be lazily applied to each array item once, only if returned by `all()` or `firstWhere()`. - `craft\helpers\ArrayHelper::firstWhere()` now has a `$valueKey` argument, which can be passed a variable by reference that should be set to the resulting value’s key in the array. - Fixed a PHP error that occurred when viewing a user’s addresses. +- Fixed a bug where all field layouts were getting instantiated before the Debug Toolbar had a chance to register its `*` wildcard event ## 5.0.0-alpha.4 - 2024-01-02 diff --git a/src/services/Fields.php b/src/services/Fields.php index bb6f5d89600..197976ec4d4 100644 --- a/src/services/Fields.php +++ b/src/services/Fields.php @@ -742,28 +742,32 @@ public function refreshFields(): void private function _layouts(): MemoizableArray { if (!isset($this->_layouts)) { - $layouts = []; if (Craft::$app->getIsInstalled()) { - foreach ($this->_createLayoutQuery()->all() as $result) { - if (array_key_exists('config', $result)) { - $config = ArrayHelper::remove($result, 'config'); - if ($config) { - $result += Json::decode($config); - } - $loadTabs = false; - } else { - $loadTabs = true; + $layoutConfigs = $this->_createLayoutQuery()->all(); + } else { + $layoutConfigs = []; + } + + $this->_layouts = new MemoizableArray($layoutConfigs, function($config) { + if (array_key_exists('config', $config)) { + $nestedConfig = ArrayHelper::remove($config, 'config'); + if ($nestedConfig) { + $config += Json::decode($nestedConfig); } + $loadTabs = false; + } else { + $loadTabs = true; + } - $layouts[] = $layout = $this->createLayout($result); + $layout = $this->createLayout($config); - // todo: remove after the next breakpoint - if ($loadTabs) { - $this->_legacyTabsByLayoutId($layout); - } + // todo: remove after the next breakpoint + if ($loadTabs) { + $this->_legacyTabsByLayoutId($layout); } - } - $this->_layouts = new MemoizableArray($layouts); + + return $layout; + }); } return $this->_layouts; From d2d84048ee416b1ece28f0fc9099d6a85697c9e9 Mon Sep 17 00:00:00 2001 From: brandonkelly Date: Thu, 4 Jan 2024 02:08:58 -0800 Subject: [PATCH 03/11] Defer entry type instantiation --- src/services/Entries.php | 16 ++-------------- 1 file changed, 2 insertions(+), 14 deletions(-) diff --git a/src/services/Entries.php b/src/services/Entries.php index 3a91e34111a..8d568f2d4aa 100644 --- a/src/services/Entries.php +++ b/src/services/Entries.php @@ -1191,22 +1191,10 @@ public function getEntryTypesBySectionId(int $sectionId): array private function _entryTypes(): MemoizableArray { if (!isset($this->_entryTypes)) { - $entryTypes = array_map( - fn(array $result) => new EntryType($result), + $this->_entryTypes = new MemoizableArray( $this->_createEntryTypeQuery()->all(), + fn(array $result) => new EntryType($result), ); - $this->_entryTypes = new MemoizableArray($entryTypes); - - if (!empty($entryTypes) && Craft::$app->getRequest()->getIsCpRequest()) { - // Eager load the field layouts - /** @var EntryType[] $entryTypesByLayoutId */ - $entryTypesByLayoutId = ArrayHelper::index($entryTypes, 'fieldLayoutId'); - $allLayouts = Craft::$app->getFields()->getLayoutsByIds(array_filter(array_keys($entryTypesByLayoutId))); - - foreach ($allLayouts as $layout) { - $entryTypesByLayoutId[$layout->id]->setFieldLayout($layout); - } - } } return $this->_entryTypes; From 74eede2dd7e2fed929b531f8c021e20392f2940b Mon Sep 17 00:00:00 2001 From: brandonkelly Date: Thu, 4 Jan 2024 02:09:17 -0800 Subject: [PATCH 04/11] Defer category group instantiation --- src/services/Categories.php | 12 ++++-------- 1 file changed, 4 insertions(+), 8 deletions(-) diff --git a/src/services/Categories.php b/src/services/Categories.php index 618948aff5e..d434fe2bc87 100644 --- a/src/services/Categories.php +++ b/src/services/Categories.php @@ -116,19 +116,15 @@ public function getEditableGroupIds(): array private function _groups(): MemoizableArray { if (!isset($this->_groups)) { - $groups = []; - - /** @var CategoryGroupRecord[] $groupRecords */ $groupRecords = CategoryGroupRecord::find() ->orderBy(['name' => SORT_ASC]) ->with('structure') ->all(); - foreach ($groupRecords as $groupRecord) { - $groups[] = $this->_createCategoryGroupFromRecord($groupRecord); - } - - $this->_groups = new MemoizableArray($groups); + $this->_groups = new MemoizableArray( + $groupRecords, + fn(CategoryGroupRecord $record) => $this->_createCategoryGroupFromRecord($record), + ); } return $this->_groups; From ac21929d04faca3d0247cacc84b24681571cc3e3 Mon Sep 17 00:00:00 2001 From: brandonkelly Date: Thu, 4 Jan 2024 02:26:57 -0800 Subject: [PATCH 05/11] Defer section instantiation --- src/services/Entries.php | 43 ++++++++++++++++++++-------------------- 1 file changed, 22 insertions(+), 21 deletions(-) diff --git a/src/services/Entries.php b/src/services/Entries.php index 8d568f2d4aa..37f306bce20 100644 --- a/src/services/Entries.php +++ b/src/services/Entries.php @@ -209,34 +209,35 @@ public function getEditableSectionIds(): array private function _sections(): MemoizableArray { if (!isset($this->_sections)) { - $sections = []; + $results = $this->_createSectionQuery()->all(); + $siteSettingsBySection = []; - foreach ($this->_createSectionQuery()->all() as $result) { + if (!empty($results) && Craft::$app->getRequest()->getIsCpRequest()) { + // Eager load the site settings + $sectionIds = array_map(fn(array $result) => $result['id'], $results); + $siteSettingsBySection = ArrayHelper::index( + $this->_createSectionSiteSettingsQuery()->where(['sections_sites.sectionId' => $sectionIds])->all(), + null, + ['sectionId'], + ); + } + + $this->_sections = new MemoizableArray($results, function(array $result) use (&$siteSettingsBySection) { if (!empty($result['previewTargets'])) { $result['previewTargets'] = Json::decode($result['previewTargets']); } else { $result['previewTargets'] = []; } - $sections[$result['id']] = new Section($result); - } - - $this->_sections = new MemoizableArray(array_values($sections)); - - if (!empty($sections) && Craft::$app->getRequest()->getIsCpRequest()) { - // Eager load the site settings - $allSiteSettings = $this->_createSectionSiteSettingsQuery() - ->where(['sections_sites.sectionId' => array_keys($sections)]) - ->all(); - - $siteSettingsBySection = []; - foreach ($allSiteSettings as $siteSettings) { - $siteSettingsBySection[$siteSettings['sectionId']][] = new Section_SiteSettings($siteSettings); + $section = new Section($result); + /** @phpstan-ignore-next-line */ + $siteSettings = ArrayHelper::remove($siteSettingsBySection, $section->id); + if ($siteSettings !== null) { + $section->setSiteSettings( + array_map(fn(array $config) => new Section_SiteSettings($config), $siteSettings), + ); } - - foreach ($siteSettingsBySection as $sectionId => $sectionSiteSettings) { - $sections[$sectionId]->setSiteSettings($sectionSiteSettings); - } - } + return $section; + }); } return $this->_sections; From c50ef275594c05b73b3c7093c034874db562658b Mon Sep 17 00:00:00 2001 From: brandonkelly Date: Thu, 4 Jan 2024 02:30:07 -0800 Subject: [PATCH 06/11] Defer filesystem instantiation --- src/services/Fs.php | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/src/services/Fs.php b/src/services/Fs.php index 25289db6f4c..35ae5e7da1d 100644 --- a/src/services/Fs.php +++ b/src/services/Fs.php @@ -117,12 +117,12 @@ private function _filesystems(): MemoizableArray { if (!isset($this->_filesystems)) { $configs = Craft::$app->getProjectConfig()->get(ProjectConfig::PATH_FS) ?? []; - $filesystems = array_map(function(string $handle, array $config) { + $configs = array_map(function(string $handle, array $config) { $config['handle'] = $handle; $config['settings'] = ProjectConfigHelper::unpackAssociativeArrays($config['settings'] ?? []); - return $this->createFilesystem($config); + return $config; }, array_keys($configs), $configs); - $this->_filesystems = new MemoizableArray($filesystems); + $this->_filesystems = new MemoizableArray($configs, fn(array $config) => $this->createFilesystem($config)); } return $this->_filesystems; From 97a0ad64cac62b1768e65dc4ce9c39d3d9237eb9 Mon Sep 17 00:00:00 2001 From: brandonkelly Date: Thu, 4 Jan 2024 02:32:02 -0800 Subject: [PATCH 07/11] Defer image transform instantiation --- src/services/ImageTransforms.php | 9 ++++----- 1 file changed, 4 insertions(+), 5 deletions(-) diff --git a/src/services/ImageTransforms.php b/src/services/ImageTransforms.php index 17d336bd2ae..dc662971a9a 100644 --- a/src/services/ImageTransforms.php +++ b/src/services/ImageTransforms.php @@ -126,11 +126,10 @@ public function init(): void private function _transforms(): MemoizableArray { if (!isset($this->_transforms)) { - $transforms = []; - foreach ($this->_createTransformQuery()->all() as $result) { - $transforms[] = new ImageTransform($result); - } - $this->_transforms = new MemoizableArray($transforms); + $this->_transforms = new MemoizableArray( + $this->_createTransformQuery()->all(), + fn(array $result) => new ImageTransform($result), + ); } return $this->_transforms; From 5fd6d7ca783340bff22b12fdebc7e33ec2934cc6 Mon Sep 17 00:00:00 2001 From: brandonkelly Date: Thu, 4 Jan 2024 04:04:59 -0800 Subject: [PATCH 08/11] Defer site group instantiation --- src/services/Sites.php | 9 ++++----- 1 file changed, 4 insertions(+), 5 deletions(-) diff --git a/src/services/Sites.php b/src/services/Sites.php index 0f8d99c5fe2..2b92f619d6c 100644 --- a/src/services/Sites.php +++ b/src/services/Sites.php @@ -194,11 +194,10 @@ public function init(): void private function _groups(): MemoizableArray { if (!isset($this->_groups)) { - $groups = []; - foreach ($this->_createGroupQuery()->all() as $result) { - $groups[] = new SiteGroup($result); - } - $this->_groups = new MemoizableArray($groups); + $this->_groups = new MemoizableArray( + $this->_createGroupQuery()->all(), + fn(array $result) => new SiteGroup($result), + ); } return $this->_groups; From 3a800fec00a3193a3171920a097671ae02c45f52 Mon Sep 17 00:00:00 2001 From: brandonkelly Date: Thu, 4 Jan 2024 04:08:02 -0800 Subject: [PATCH 09/11] Defer tag group instantiation --- src/services/Tags.php | 11 ++++------- 1 file changed, 4 insertions(+), 7 deletions(-) diff --git a/src/services/Tags.php b/src/services/Tags.php index 2116b3206a0..949ea57d849 100644 --- a/src/services/Tags.php +++ b/src/services/Tags.php @@ -99,17 +99,14 @@ public function getAllTagGroupIds(): array private function _tagGroups(): MemoizableArray { if (!isset($this->_tagGroups)) { - $groups = []; - /** @var TagGroupRecord[] $records */ $records = TagGroupRecord::find() ->orderBy(['name' => SORT_ASC]) ->all(); - foreach ($records as $record) { - $groups[] = $this->_createTagGroupFromRecord($record); - } - - $this->_tagGroups = new MemoizableArray($groups); + $this->_tagGroups = new MemoizableArray( + $records, + fn(TagGroupRecord $record) => $this->_createTagGroupFromRecord($record), + ); } return $this->_tagGroups; From 1f0f4f38e153dcc15e798e801551c316e7231214 Mon Sep 17 00:00:00 2001 From: brandonkelly Date: Thu, 4 Jan 2024 04:09:31 -0800 Subject: [PATCH 10/11] Defer volume instantiation --- src/services/Volumes.php | 9 ++++----- 1 file changed, 4 insertions(+), 5 deletions(-) diff --git a/src/services/Volumes.php b/src/services/Volumes.php index 0947444872c..e0e0ec37c75 100644 --- a/src/services/Volumes.php +++ b/src/services/Volumes.php @@ -160,11 +160,10 @@ public function getTotalViewableVolumes(): int private function _volumes(): MemoizableArray { if (!isset($this->_volumes)) { - $volumes = []; - foreach ($this->_createVolumeQuery()->all() as $result) { - $volumes[] = Craft::createObject(Volume::class, [$result]); - } - $this->_volumes = new MemoizableArray($volumes); + $this->_volumes = new MemoizableArray( + $this->_createVolumeQuery()->all(), + fn(array $result) => Craft::createObject(Volume::class, [$result]), + ); } return $this->_volumes; From 856c3d6099a56f3fb77b4c60594da62028802314 Mon Sep 17 00:00:00 2001 From: brandonkelly Date: Thu, 4 Jan 2024 04:26:48 -0800 Subject: [PATCH 11/11] Update release notes [ci skip] --- CHANGELOG-WIP.md | 2 -- CHANGELOG.md | 2 +- 2 files changed, 1 insertion(+), 3 deletions(-) diff --git a/CHANGELOG-WIP.md b/CHANGELOG-WIP.md index 92e48c3b663..35933402c2e 100644 --- a/CHANGELOG-WIP.md +++ b/CHANGELOG-WIP.md @@ -330,7 +330,6 @@ - `craft\base\ElementInterface::setRevisionNotes()` no longer needs to specify a default value for the `notes` argument. - `craft\base\Field::inputHtml()` now has an `$inline` argument. - `craft\base\FieldInterface::getIsTranslatable()`, `getTranslationDescription()`, `getInputHtml()`, `normalizeValue()`, `normalizeValueFromRequest()`, and `serializeValue()` no longer need to specify a default value for the `$element` argument. -- `craft\base\MemoizableArray` now supports passing a normalizer method to the constructor, which will be lazily applied to each array item once, only if returned by `all()` or `firstWhere()`. - `craft\db\Connection::getSupportsMb4()` is now dynamic for MySQL installs, based on whether the `elements_sites` table has an `mb4` charset. - `craft\elemens\db\ElementQueryInterface::collect()` now has an `ElementCollection` return type, rather than `Collection`. - `craft\elements\Entry::getSection()` can now return `null`, for nested entries. @@ -340,7 +339,6 @@ - `craft\fields\BaseOptionsField::$multi` and `$optgroups` properties are now static. - `craft\fields\Matrix::$propagationMethod` now has a type of `craft\enums\PropagationMethod`. - `craft\gql\mutations\Entry::createSaveMutations()` now accepts a `$section` argument. -- `craft\helpers\ArrayHelper::firstWhere()` now has a `$valueKey` argument, which can be passed a variable by reference that should be set to the resulting value’s key in the array. - `craft\helpers\Cp::fieldHtml()` now supports a `labelExtra` config value. - `craft\helpers\Db::parseParam()`, `parseDateParam()`, `parseMoneyParam()`, and `parseNumericParam()` now return `null` instead of an empty string if no condition should be applied. - `craft\helpers\Html::id()` and `Craft.formatInputId()` now retain colons and periods, and ensure the string begins with a letter. diff --git a/CHANGELOG.md b/CHANGELOG.md index 34f49d33c07..3ba3b63b992 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -4,7 +4,7 @@ - Added the `showFirstAndLastNameFields` config setting. ([#14097](https://github.com/craftcms/cms/pull/14097)) - `queue/get-job-info` action requests no longer create a mutex lock. -- `craft\base\MemoizableArray` now supports passing a normalizer method to the constructor, which will be lazily applied to each array item once, only if returned by `all()` or `firstWhere()`. +- `craft\base\MemoizableArray` now supports passing a normalizer method to the constructor, which will be lazily applied to each array item once, only if returned by `all()` or `firstWhere()`. ([#14104](https://github.com/craftcms/cms/pull/14104)) - `craft\helpers\ArrayHelper::firstWhere()` now has a `$valueKey` argument, which can be passed a variable by reference that should be set to the resulting value’s key in the array. - Fixed a PHP error that occurred when viewing a user’s addresses. - Fixed a bug where all field layouts were getting instantiated before the Debug Toolbar had a chance to register its `*` wildcard event