1
0
mirror of https://github.com/processwire/processwire.git synced 2025-08-06 14:57:10 +02:00

Refactoring and optimizations to PagesLoader class, plus addition of a new $pages->has('selector') API method that enables you to quickly check if a page exists matching a selector. This is an alternative to using the $pages->count() method which is what you would have previously used for this. But the $pages->has() method can do it more quickly than $pages->count(), if all you need to know is if a page exists. As a bonus, it returns the first found page ID, so this method essentially does the same thing as a $pages->getID() method (if such a method existed).

This commit is contained in:
Ryan Cramer
2020-03-20 15:29:51 -04:00
parent 1020fb84e7
commit 833736a1fc
2 changed files with 208 additions and 62 deletions

View File

@@ -217,15 +217,15 @@ class Pages extends Wire {
*
* @param string|int|array|Selectors $selector Specify selector (standard usage), but can also accept page ID or array of page IDs.
* @param array|string $options One or more options that can modify certain behaviors. May be associative array or "key=value" selector string.
* - `findOne` (boolean): Apply optimizations for finding a single page (default=false).
* - `findAll` (boolean): Find all pages with no exclusions, same as "include=all" option (default=false).
* - `findIDs` (boolean|int): Specify 1 to return array of only page IDs, or true to return verbose array (default=false).
* - `getTotal` (boolean): Whether to set returning PageArray's "total" property (default=true, except when findOne=true).
* - `loadPages` (boolean): Whether to populate the returned PageArray with found pages (default=true).
* - `findOne` (bool): Apply optimizations for finding a single page (default=false).
* - `findAll` (bool): Find all pages with no exclusions, same as "include=all" option (default=false).
* - `findIDs` (bool|int): 1 to get array of page IDs, true to return verbose array, 2 to return verbose array with all cols in 3.0.153+. (default=false).
* - `getTotal` (bool): Whether to set returning PageArray's "total" property (default=true, except when findOne=true).
* - `loadPages` (bool): Whether to populate the returned PageArray with found pages (default=true).
* The only reason why you'd want to change this to false would be if you only needed the count details from
* the PageArray: getTotal(), getStart(), getLimit, etc. This is intended as an optimization for $pages->count().
* Does not apply if $selector argument is an array.
* - `cache` (boolean): Allow caching of selectors and loaded pages? (default=true). Also sets loadOptions[cache].
* - `cache` (bool): Allow caching of selectors and loaded pages? (default=true). Also sets loadOptions[cache].
* - `allowCustom` (boolean): Allow use of _custom="another selector" in given $selector? For specific uses. (default=false)
* - `caller` (string): Optional name of calling function, for debugging purposes, i.e. "pages.count" (default=blank).
* - `include` (string): Optional inclusion mode of 'hidden', 'unpublished' or 'all'. (default=none). Typically you would specify this
@@ -342,7 +342,7 @@ class Pages extends Wire {
* @param string|array|Selectors $selector Selector to find page IDs.
* @param array|bool $options Options to modify behavior.
* - `verbose` (bool): Specify true to make return value array of associative arrays, each with verbose info.
* - The verbose option above can also be specified by providing boolean true as the $options argument.
* - The verbose option above can also be specified as alternative to the $options argument.
* - See `Pages::find()` $options argument for additional options.
* @return array Array of page IDs, or in verbose mode: array of arrays, each with id, parent_id and templates_id keys.
* @since 3.0.46
@@ -350,13 +350,20 @@ class Pages extends Wire {
*/
public function findIDs($selector, $options = array()) {
$verbose = false;
if($options === true) $verbose = true;
if(!is_array($options)) $options = array();
if(!is_array($options)) {
// verbose option specified in $options array
$verbose = $options;
$options = array();
}
if(isset($options['verbose'])) {
$verbose = $options['verbose'];
unset($options['verbose']);
}
$options['findIDs'] = $verbose ? true : 1;
if($verbose === 2 || $verbose === '*') {
$options['findIDs'] = 2;
} else {
$options['findIDs'] = $verbose ? true : 1;
}
/** @var array $ids */
$ids = $this->find($selector, $options);
return $ids;
@@ -389,7 +396,29 @@ class Pages extends Wire {
public function get($selector, $options = array()) {
return $this->loader->get($selector, $options);
}
/**
* Is there any page that matches the given $selector in the system? (with no exclusions)
*
* - This can be used as an “exists” or “getID” type of method.
* - Returns ID of first matching page if any exist, or 0 if none exist (returns array if `$verbose` is true).
* - Like with the `get()` method, no pages are excluded, so an `include=all` is not necessary in selector.
* - If you need to quickly check if something exists, this method is preferable to using a count() or get().
*
* When `$verbose` option is used, an array is returned instead. Verbose return array includes page `id`,
* `parent_id` and `templates_id` indexes.
*
* @param string|int|array|Selectors $selector
* @param bool $verbose Return verbose array with page id, parent_id, templates_id rather than just page id? (default=false)
* @return array|int
* @since 3.0.153
* @see Pages::count(), Pages::get()
*
*/
public function has($selector, $verbose = false) {
return $this->loader->has($selector, $verbose);
}
/**
* Save a page object and its fields to database.
*

View File

@@ -109,62 +109,44 @@ class PagesLoader extends Wire {
}
/**
* Helper for find() method to attempt to shortcut the find when possible
* Normalize a selector string
*
* @param string|array|Selectors $selector
* @param array $options
* @param array $loadOptions
* @return bool|Page|PageArray Returns boolean false when no shortcut available
* @param string $selector
* @param bool $convertIDs Normalize to integer ID or array of integer IDs when possible (default=true)
* @return array|int|string
*
*/
protected function findShortcut(&$selector, $options, $loadOptions) {
protected function normalizeSelectorString($selector, $convertIDs = true) {
if(empty($selector)) return $this->pages->newPageArray($loadOptions);
if(!empty($options['lazy'])) return false;
$value = false;
$filter = empty($options['findAll']);
if(is_string($selector)) {
$selector = trim($selector, ', ');
if(ctype_digit($selector)) {
// normalize to page ID (int)
$selector = (int) $selector;
} else if($selector === '/' || $selector === 'path=/') {
// normalize selectors that indicate homepage to just be ID 1
$selector = (int) $this->wire('config')->rootPageID;
} else if($selector[0] == '/') {
// if selector begins with a slash, it is referring to a path
$selector = "path=$selector";
}
}
$selector = trim($selector, ', ');
if(is_array($selector)) {
// array that is .... not associative .................. not selector array ........ consists of only numbers
if(ctype_digit(implode('', array_keys($selector))) && !is_array(reset($selector)) && ctype_digit(implode('', $selector))) {
// regular array of page IDs, we delegate that to getById() method, but with access/visibility control
foreach($selector as $k => $v) $selector[$k] = (int) $v;
$value = $this->getById($selector, $loadOptions);
$filter = true;
}
if(ctype_digit($selector)) {
// normalize to page ID (int)
$selector = (int) $selector;
} else if(is_int($selector)) {
// page ID integer
$value = $this->getById(array($selector), $loadOptions);
} else if($selector === '/' || $selector === 'path=/') {
// normalize selectors that indicate homepage to just be ID 1
$selector = (int) $this->wire('config')->rootPageID;
} else if($selector[0] === '/') {
// if selector begins with a slash, it is referring to a path
$selector = "path=$selector";
} else if(is_string($selector) && strpos($selector, ',') === false) {
// there is just one “key=value” or “value” selector
if(strpos($selector, 'id=') === 0) {
// string like id=123 or id=123|456|789
$s = substr($selector, 3); // skip over 'id='
if(ctype_digit($s)) {
// id=123
$value = $this->getById((int) $s, $loadOptions);
} else if(strpos($selector, '|') && ctype_digit(str_replace('|', '', $s))) {
// id=123|456|789
$a = explode('|', $s);
foreach($a as $k => $v) $a[$k] = (int) $v;
$value = $this->getById($a, $loadOptions);
} else if(strpos($selector, ',') === false) {
// there is just one “key=value” or “value” selector that needs further processing
if(strpos($selector, 'id=')) {
if($convertIDs) {
// string like id=123 or id=123|456|789 converted to int or int-array
$s = substr($selector, 3); // skip over 'id='
if(ctype_digit($s)) {
// id=123
$selector = (int) $s;
} else if(strpos($selector, '|') && ctype_digit(str_replace('|', '', $s))) {
// id=123|456|789
$a = explode('|', $s);
foreach($a as $k => $v) $a[$k] = (int) $v;
$selector = $a;
}
}
} else if(!Selectors::stringHasOperator($selector)) {
// no operator indicates this is just referring to a page name
@@ -176,6 +158,91 @@ class PagesLoader extends Wire {
}
}
}
if(is_int($selector) || ctype_digit("$selector")) {
// page ID integer
if($convertIDs) {
$selector = (int) $selector;
} else {
$selector = "id=$selector";
}
}
return $selector;
}
/**
* Normalize a selector
*
* @param string|int|array $selector
* @param bool $convertIDs Convert ID-only selectors to integers or arrays of integers?
* @return array|int|string
*
*/
protected function normalizeSelector($selector, $convertIDs = true) {
if(empty($selector)) return '';
if(is_int($selector)) {
if(!$convertIDs) $selector = "id=$selector";
} else if(is_string($selector)) {
$selector = $this->normalizeSelectorString($selector, $convertIDs);
} else if(is_array($selector)) {
// array that is not associative, not selector array, and consists of only numbers
if($this->isIdArray($selector)) {
if(!$convertIDs) $selector = 'id=' . implode('|', $selector);
}
}
return $selector;
}
/**
* Is this an array of IDs? Also sanitizes to all integers when true
*
* @param array $a
* @return bool
*
*/
protected function isIdArray(array &$a) {
if(ctype_digit(implode('', array_keys($a))) && !is_array(reset($a)) && ctype_digit(implode('', $a))) {
// regular array of page IDs, we delegate that to getById() method, but with access/visibility control
foreach($a as $k => $v) $a[$k] = (int) $v;
return true;
} else {
return false;
}
}
/**
* Helper for find() method to attempt to shortcut the find when possible
*
* @param string|array|Selectors $selector
* @param array $options
* @param array $loadOptions
* @return bool|Page|PageArray Returns boolean false when no shortcut available
*
*/
protected function findShortcut($selector, $options, $loadOptions) {
if(empty($selector)) {
return $this->pages->newPageArray($loadOptions);
}
$value = false;
$filter = empty($options['findAll']);
$selector = $this->normalizeSelector($selector, true);
if(is_array($selector)) {
if($this->isIdArray($selector)) {
$value = $this->getById($selector, $loadOptions);
$filter = true;
}
} else if(is_int($selector)) {
// page ID integer
$value = $this->getById(array($selector), $loadOptions);
}
if($value) {
if($filter) {
@@ -200,7 +267,7 @@ class PagesLoader extends Wire {
* @param array|string $options Optional one or more options that can modify certain behaviors. May be assoc array or key=value string.
* - findOne: boolean - apply optimizations for finding a single page
* - findAll: boolean - find all pages with no exclusions (same as include=all option)
* - findIDs: boolean|int - true=return array of [id, template_id, parent_id], or 1=return just page IDs.
* - findIDs: boolean|int - true=return array of [id, template_id, parent_id], or 1=return just page IDs, 2=return all columns (3.0.153+).
* - getTotal: boolean - whether to set returning PageArray's "total" property (default: true except when findOne=true)
* - cache: boolean - Allow caching of selectors and pages loaded (default=true). Also sets loadOptions[cache].
* - allowCustom: boolean - Whether to allow use of "_custom=new selector" in selectors (default=false).
@@ -239,6 +306,7 @@ class PagesLoader extends Wire {
if($selector instanceof Selectors) {
$selectors = $selector;
} else {
$selector = $this->normalizeSelector($selector, false);
$selectors = $this->wire(new Selectors());
$selectors->init($selector);
}
@@ -273,7 +341,10 @@ class PagesLoader extends Wire {
$pagesIDs = $pageFinder->findTemplateIDs($selectors, $options);
} else if($findIDs === 1) {
// [ pageID ]
$pagesIDs = $pageFinder->findIDs($selectors, $options);
$pagesIDs = $pageFinder->findIDs($selectors, $options);
} else if($findIDs === 2) {
// [ pageID => [ all pages columns ] ]
$pagesInfo = $pageFinder->findVerboseIDs($selectors, $options);
} else {
// [ [ 'id' => 3, 'templates_id' => 2, 'parent_id' => 1 ]
$pagesInfo = $pageFinder->find($selectors, $options);
@@ -504,6 +575,52 @@ class PagesLoader extends Wire {
if(!$page) $page = $this->pages->newNullPage();
return $page;
}
/**
* Is there any page that matches the given $selector in the system? (with no exclusions)
*
* - This can be used as an “exists” or “getID” type of method.
* - Returns ID of first matching page if any exist, or 0 if none exist (returns array if `$verbose` is true).
* - Like with the `get()` method, no pages are excluded, so an `include=all` is not necessary in selector.
* - If you need to quickly check if something exists, this method is preferable to using a count() or get().
*
* When `$verbose` option is used, an array is returned instead. Verbose return array includes page `id`,
* `parent_id` and `templates_id` indexes.
*
* @param string|int|array|Selectors $selector
* @param bool $verbose Return verbose array with all pages columns rather than just page id? (default=false)
* @return array|int
* @since 3.0.153
*
*/
public function has($selector, $verbose = false) {
$options = array(
'findOne' => true, // find only one page
'findAll' => true, // no exclusions
'findIDs' => $verbose ? 2 : 1, // 1=find IDs, true=find verbose all cols
'getTotal' => false, // don't count totals
'caller' => 'pages.has',
);
if(empty($selector)) return $verbose ? array() : 0;
if((is_string($selector) || is_int($selector)) && !$verbose) {
// see if any matching page is already in the cache
$page = $this->pages->getCache($selector);
if($page) return $page->id;
}
$items = $this->pages->find($selector, $options);
if($verbose) {
$value = count($items) ? reset($items) : array();
} else {
$value = count($items) ? (int) reset($items) : 0;
}
return $value;
}
/**
* Given an array or CSV string of Page IDs, return a PageArray