diff --git a/wire/core/FieldgroupsArray.php b/wire/core/FieldgroupsArray.php index 00fbb2d7..e99288bc 100644 --- a/wire/core/FieldgroupsArray.php +++ b/wire/core/FieldgroupsArray.php @@ -5,7 +5,7 @@ * * WireArray of Fieldgroup instances as used by Fieldgroups class. * - * ProcessWire 3.x, Copyright 2016 by Ryan Cramer + * ProcessWire 3.x, Copyright 2024 by Ryan Cramer * https://processwire.com * * @@ -25,6 +25,7 @@ class FieldgroupsArray extends WireArray { * */ public function getItemKey($item) { + /** @var Fieldgroup $item */ return $item->id; } diff --git a/wire/core/Fieldtypes.php b/wire/core/Fieldtypes.php index a5750c5b..56e0aee3 100644 --- a/wire/core/Fieldtypes.php +++ b/wire/core/Fieldtypes.php @@ -6,7 +6,7 @@ * #pw-summary Maintains a collection of Fieldtype modules. * #pw-var $fieldtypes * - * ProcessWire 3.x, Copyright 2020 by Ryan Cramer + * ProcessWire 3.x, Copyright 2024 by Ryan Cramer * https://processwire.com * * @property FieldtypeCheckbox $FieldtypeCheckbox @@ -88,13 +88,24 @@ class Fieldtypes extends WireArray { */ protected $isAPI = false; + /** + * Construct + * + */ + public function __construct() { + parent::__construct(); + $this->usesNumericKeys = false; + $this->indexedByName = true; + $this->nameProperty = 'className'; + } + /** * Construct the $fieldtypes API var (load all Fieldtype modules into it) * */ public function init() { $this->isAPI = true; - foreach($this->wire()->modules->findByPrefix('Fieldtype', 3) as $name => $module) { + foreach($this->wire()->modules->findByPrefix('Fieldtype', 3) as /* $name => */ $module) { $this->add($module); } } @@ -153,16 +164,6 @@ class Fieldtypes extends WireArray { return $item->className(); } - /** - * Does this WireArray use numeric keys only? - * - * @return bool - * - */ - protected function usesNumericKeys() { - return false; - } - /** * Per the WireArray interface, return a blank copy * @@ -229,5 +230,3 @@ class Fieldtypes extends WireArray { public function getNext($item, $strict = true) { $this->preload(); return parent::getNext($item, $strict); } public function getPrev($item, $strict = true) { $this->preload(); return parent::getPrev($item, $strict); } } - - diff --git a/wire/core/InputfieldsArray.php b/wire/core/InputfieldsArray.php index 033461f3..4bb4c304 100644 --- a/wire/core/InputfieldsArray.php +++ b/wire/core/InputfieldsArray.php @@ -5,12 +5,18 @@ * * The default numeric indexing of a WireArray is not overridden. * - * ProcessWire 3.x, Copyright 2016 by Ryan Cramer + * ProcessWire 3.x, Copyright 2024 by Ryan Cramer * https://processwire.com * */ class InputfieldsArray extends WireArray { + + public function __construct() { + parent::__construct(); + $this->usesNumericKeys = true; + $this->indexedByName = false; + } /** * Per WireArray interface, only Inputfield instances are accepted. @@ -31,6 +37,7 @@ class InputfieldsArray extends WireArray { * */ public function find($selector) { + /** @var WireArray|InputfieldsArray $a */ $a = parent::find($selector); foreach($this as $item) { if(!$item instanceof InputfieldWrapper) continue; @@ -44,8 +51,4 @@ class InputfieldsArray extends WireArray { return null; // Inputfield is abstract, so there is nothing to return here } - public function usesNumericKeys() { - return true; - } - } diff --git a/wire/core/Notices.php b/wire/core/Notices.php index 104a21ee..4df58eae 100644 --- a/wire/core/Notices.php +++ b/wire/core/Notices.php @@ -507,6 +507,12 @@ class NoticeWarning extends Notice { class Notices extends WireArray { const logAllNotices = false; // for debugging/dev purposes + + public function __construct() { + parent::__construct(); + $this->usesNumericKeys = true; + $this->indexedByName = false; + } /** * Initialize Notices API var @@ -548,8 +554,9 @@ class Notices extends WireArray { * */ protected function allowNotice(Notice $item) { - - $user = $this->wire()->user; + + // intentionally not using $this->wire()->user; in case this gets called early in boot + $user = $this->wire('user'); if($item->flags & Notice::debug) { if(!$this->wire()->config->debug) return false; diff --git a/wire/core/PageArray.php b/wire/core/PageArray.php index a16d736f..2d6f35f5 100644 --- a/wire/core/PageArray.php +++ b/wire/core/PageArray.php @@ -74,6 +74,16 @@ class PageArray extends PaginatedArray implements WirePaginatable { */ protected $keyIndex = array(); + /** + * Construct + * + */ + public function __construct() { + parent::__construct(); + $this->indexedByName = false; + $this->usesNumericKeys = true; + } + /** * Template method that descendant classes may use to validate items added to this WireArray * @@ -142,18 +152,6 @@ class PageArray extends PaginatedArray implements WirePaginatable { } } - /** - * Does this PageArray use numeric keys only? (yes it does) - * - * Defined here to override the slower check in WireArray - * - * @return bool - * - */ - protected function usesNumericKeys() { - return true; - } - /** * Per WireArray interface, return a blank Page * diff --git a/wire/core/Pagefiles.php b/wire/core/Pagefiles.php index b9b7da74..de51cc8c 100644 --- a/wire/core/Pagefiles.php +++ b/wire/core/Pagefiles.php @@ -124,6 +124,8 @@ class Pagefiles extends WireArray implements PageFieldValueInterface { public function __construct(Page $page) { $this->setPage($page); parent::__construct(); + $this->usesNumericKeys = false; + $this->indexedByName = true; } /** diff --git a/wire/core/Selectors.php b/wire/core/Selectors.php index 4f38dd65..14ab00a4 100644 --- a/wire/core/Selectors.php +++ b/wire/core/Selectors.php @@ -109,6 +109,8 @@ class Selectors extends WireArray { */ public function __construct($selector = null) { parent::__construct(); + $this->usesNumericKeys = false; + $this->indexedByName = false; if(!is_null($selector)) $this->init($selector); } diff --git a/wire/core/TemplatesArray.php b/wire/core/TemplatesArray.php index f765a902..8aa3b538 100644 --- a/wire/core/TemplatesArray.php +++ b/wire/core/TemplatesArray.php @@ -5,7 +5,7 @@ * * WireArray of Template instances as used by Templates class. * - * ProcessWire 3.x, Copyright 2016 by Ryan Cramer + * ProcessWire 3.x, Copyright 2024 by Ryan Cramer * https://processwire.com * */ @@ -21,6 +21,7 @@ class TemplatesArray extends WireArray { } public function getItemKey($item) { + /** @var Template $item */ return $item->id; } diff --git a/wire/core/WireArray.php b/wire/core/WireArray.php index 3a1dcc5b..d68c387e 100644 --- a/wire/core/WireArray.php +++ b/wire/core/WireArray.php @@ -11,7 +11,7 @@ * * @todo can we implement next() and prev() like on Page, as alias to getNext() and getPrev()? * - * ProcessWire 3.x, Copyright 2022 by Ryan Cramer + * ProcessWire 3.x, Copyright 2024 by Ryan Cramer * https://processwire.com * * @method WireArray and($item) @@ -88,12 +88,47 @@ class WireArray extends Wire implements \IteratorAggregate, \ArrayAccess, \Count */ protected $sortFlags = 0; // 0 == SORT_REGULAR + /** + * For WireArray that holds WireData objects, property that contains the item’s name + * + * @var string + * @since 3.0.240 + * + */ + protected $nameProperty = 'name'; + + /** + * Is this WireArray indexed by the name property? + * + * This will be auto-detected at runtime unless specifically set in the constructor. + * + * @var bool|null Bool once known, null if not yet known + * @since 3.0.240 + * + */ + protected $indexedByName = null; + + /** + * Does this WireArray use numeric keys? + * + * This will be auto-detected at runtime unless specifically set in the constructor. + * + * @var bool|null + * @since 3.0.240 + * + */ + protected $usesNumericKeys = null; + /** * Construct * */ public function __construct() { - if($this->className() === 'WireArray') $this->duplicateChecking = false; + if($this->className() === 'WireArray') { + $this->duplicateChecking = false; + $this->indexedByName = false; + $this->usesNumericKeys = true; + } parent::__construct(); } @@ -296,7 +331,7 @@ class WireArray extends Wire implements \IteratorAggregate, \ArrayAccess, \Count $key = key($this->data); } - $this->trackChange("add", null, $item); + if($this->trackChanges) $this->trackChange("add", null, $item); $this->trackAdd($item, $key); return $this; @@ -461,7 +496,7 @@ class WireArray extends Wire implements \IteratorAggregate, \ArrayAccess, \Count throw new WireException("Key '$key' is not an allowed key for " . get_class($this)); } - $this->trackChange($key, isset($this->data[$key]) ? $this->data[$key] : null, $value); + if($this->trackChanges) $this->trackChange($key, isset($this->data[$key]) ? $this->data[$key] : null, $value); $this->data[$key] = $value; $this->trackAdd($value, $key); return $this; @@ -602,7 +637,7 @@ class WireArray extends Wire implements \IteratorAggregate, \ArrayAccess, \Count if(isset($this->data[$k])) { $match = $this->data[$k]; } else if($numericKeys) { - $match = $this->getItemThatMatches('name', $k); + $match = $this->getItemThatMatches($this->nameProperty, $k); } if($match) break; } @@ -613,7 +648,7 @@ class WireArray extends Wire implements \IteratorAggregate, \ArrayAccess, \Count // if the WireArray uses numeric keys, then it's okay to // match a 'name' field if the provided key is a string if($this->usesNumericKeys()) { - $match = $this->getItemThatMatches('name', $key); + $match = $this->getItemThatMatches($this->nameProperty, $key); } } @@ -760,7 +795,7 @@ class WireArray extends Wire implements \IteratorAggregate, \ArrayAccess, \Count $match = $this->findOne($key); } else if($this->usesNumericKeys()) { - $match = $this->getItemThatMatches('name', $key); + $match = $this->getItemThatMatches($this->nameProperty, $key); } } @@ -997,7 +1032,7 @@ class WireArray extends Wire implements \IteratorAggregate, \ArrayAccess, \Count reset($this->data); $key = key($this->data); } - $this->trackChange('prepend', null, $item); + if($this->trackChanges) $this->trackChange('prepend', null, $item); $this->trackAdd($item, $key); return $this; } @@ -1058,7 +1093,7 @@ class WireArray extends Wire implements \IteratorAggregate, \ArrayAccess, \Count $key = key($this->data); $item = array_shift($this->data); if(is_null($item)) return null; - $this->trackChange('shift', $item, null); + if($this->trackChanges) $this->trackChange('shift', $item, null); $this->trackRemove($item, $key); return $item; } @@ -1094,7 +1129,7 @@ class WireArray extends Wire implements \IteratorAggregate, \ArrayAccess, \Count $key = key($this->data); $item = array_pop($this->data); if(is_null($item)) return null; - $this->trackChange('pop', $item, null); + if($this->trackChanges) $this->trackChange('pop', $item, null); $this->trackRemove($item, $key); return $item; } @@ -1119,7 +1154,7 @@ class WireArray extends Wire implements \IteratorAggregate, \ArrayAccess, \Count $data[$key] = $this->data[$key]; } - $this->trackChange('shuffle', $this->data, $data); + if($this->trackChanges) $this->trackChange('shuffle', $this->data, $data); $this->data = $data; @@ -1219,7 +1254,7 @@ class WireArray extends Wire implements \IteratorAggregate, \ArrayAccess, \Count if(array_key_exists($key, $this->data)) { $item = $this->data[$key]; unset($this->data[$key]); - $this->trackChange("remove", $item, null); + if($this->trackChanges) $this->trackChange("remove", $item, null); $this->trackRemove($item, $key); } else if(!$obj && is_string($key) && Selectors::stringHasSelector($key)) { foreach($this->find($key) as $item) { @@ -1329,6 +1364,13 @@ class WireArray extends Wire implements \IteratorAggregate, \ArrayAccess, \Count if(!$isArray) $properties = explode(',', $properties); if(empty($properties)) return $this; + + if($propertiesStr === $this->nameProperty && $this->indexedByName) { + // optimization when it's a very simple sort by name + ksort($this->data, $this->sortFlags); + if($this->trackChanges) $this->trackChange("sort:$propertiesStr"); + return $this; + } // shortcut for random (only allowed as the sole sort property) // no warning/error for issuing more properties though @@ -2000,7 +2042,20 @@ class WireArray extends Wire implements \IteratorAggregate, \ArrayAccess, \Count * */ protected function trackAdd($item, $key) { - if($key) {} + if($key !== null && $key !== false) { + if($this->usesNumericKeys === null) { + $this->usesNumericKeys = is_int($key); + } + if($this->indexedByName === null) { + $this->indexedByName = false; + if($item instanceof WireData) { + $name = $item->get($this->nameProperty); + if($name === $key && isset($this->data[$name]) && $this->data[$name] === $item) { + $this->indexedByName = true; + } + } + } + } if($this->trackChanges()) $this->itemsAdded[] = $item; // wire this WireArray to the same instance of $item, if it isn’t already wired if($this->_wire === null && $item instanceof Wire && $item->isWired()) $item->wire($this); @@ -2118,16 +2173,29 @@ class WireArray extends Wire implements \IteratorAggregate, \ArrayAccess, \Count */ protected function usesNumericKeys() { - static $testItem = null; - static $usesNumericKeys = null; + if($this->usesNumericKeys !== null) { + return $this->usesNumericKeys; + } + + if(!empty($this->data)) { + reset($this->data); + $key = key($this->data); + if($key !== null) { + $this->usesNumericKeys = is_int($key); + return $this->usesNumericKeys; + } + } + + $testItem = $this->makeBlankItem(); + + if($testItem === null) { + $this->usesNumericKeys = true; + } else { + $key = $this->getItemKey($testItem); + $this->usesNumericKeys = is_int($key); + } - if(!is_null($usesNumericKeys)) return $usesNumericKeys; - if(is_null($testItem)) $testItem = $this->makeBlankItem(); - if(is_null($testItem)) return true; - - $key = $this->getItemKey($testItem); - $usesNumericKeys = is_int($key) ? true : false; - return $usesNumericKeys; + return $this->usesNumericKeys; } /** @@ -2552,7 +2620,7 @@ class WireArray extends Wire implements \IteratorAggregate, \ArrayAccess, \Count } } else { // array or string or null - if(is_null($func)) $func = 'name'; + if(is_null($func)) $func = $this->nameProperty; $result = $this->explode($func); } @@ -2658,7 +2726,7 @@ class WireArray extends Wire implements \IteratorAggregate, \ArrayAccess, \Count $item = $item->debugInfoSmall(); } else if($item instanceof WireData) { $_item = $item; - $item = $item->get('name'); + $item = $item->get($this->nameProperty); if(!$item) $item = $_item->get('id'); if(!$item) $item = $_item->className(); } else {