1
0
mirror of https://github.com/processwire/processwire.git synced 2025-08-08 07:47:00 +02:00

Add a new $pages->findIDs() method, plus some experimental options in PageFinder

This commit is contained in:
Ryan Cramer
2016-12-19 13:38:07 -05:00
parent e34918c73a
commit 381293e0c7
3 changed files with 145 additions and 37 deletions

View File

@@ -130,6 +130,18 @@ class PageFinder extends Wire {
*/
'allowCustom' => false,
/**
* Use sortsAfter feature where PageFinder lets you perform the sorting manually after the find()
*
* When in use, you can access the PageFinder::getSortsAfter() method to retrieve an array of sort
* fields that should be sent to PageArray::sort()
*
* So far this option seems to add more overhead in most cases (rather than save it) so recommend not
* using it. Kept for further experimenting.
*
*/
'useSortsAfter' => false,
);
protected $fieldgroups;
@@ -144,6 +156,7 @@ class PageFinder extends Wire {
protected $getQueryNumChildren = 0; // number of times the function has been called
protected $lastOptions = array();
protected $extraOrSelectors = array(); // one from each field must match
protected $sortsAfter = array(); // apply these sorts after pages loaded
// protected $extraSubSelectors = array(); // subselectors that are added in after getQuery()
// protected $extraJoins = array();
@@ -342,6 +355,7 @@ class PageFinder extends Wire {
* template ID and score. When false, returns only an array of page IDs. True is required by most usage from
* Pages class. False is only for specific cases.
* - `allowCustom` (bool): Whether or not to allow _custom='selector string' type values (default=false).
* - `useSortsAfter` (bool): When true, PageFinder may ask caller to perform sort manually in some cases (default=false).
* @return array|DatabaseQuerySelect
* @throws PageFinderException
*
@@ -357,8 +371,6 @@ class PageFinder extends Wire {
$this->fieldgroups = $this->wire('fieldgroups');
$options = array_merge($this->defaultOptions, $options);
$this->start = 0; // reset for new find operation
$this->limit = 0;
$this->parent_id = null;
$this->templates_id = null;
$this->checkAccess = true;
@@ -490,16 +502,62 @@ class PageFinder extends Wire {
*
*/
protected function preProcessSelectors(Selectors $selectors, $options = array()) {
if(!empty($options['allowCustom'])) {
foreach($selectors as $selector) {
$field = $selector->field;
if(!is_string($field) || $field !== '_custom') continue;
$sortSelectors = array();
$start = null;
$limit = null;
foreach($selectors as $selector) {
$field = $selector->field;
if($field === '_custom') {
$selectors->remove($selector);
$_selectors = $this->wire(new Selectors($selector->value()));
/** @var Selectors $_selectors */
foreach($_selectors as $s) $selectors->add($s);
if(!empty($options['allowCustom'])) {
$_selectors = $this->wire(new Selectors($selector->value()));
/** @var Selectors $_selectors */
foreach($_selectors as $s) $selectors->add($s);
}
} else if($field === 'sort') {
if(!empty($options['useSortsAfter']) && $selector->operator == '=' && strpos($selector->value, '.') === false) {
$sortSelectors[] = $selector;
}
} else if($field === 'limit') {
$limit = (int) $selector->value;
} else if($field === 'start') {
$start = (int) $selector->value;
}
}
if(!$limit && !$start && count($sortSelectors)
&& $options['returnVerbose'] && !empty($options['useSortsAfter'])
&& empty($options['startAfterID']) && empty($options['stopBeforeID'])) {
// the `useSortsAfter` option is enabled and potentially applicable
$sortsAfter = array();
foreach($sortSelectors as $n => $selector) {
if(!$n && $this->wire('pages')->loader()->isNativeColumn($selector->value)) {
// first iteration only, see if it's a native column and prevent sortsAfter if so
break;
}
if(strpos($selector->value, '.') !== false) {
// we don't supports sortsAfter for subfields, so abandon entirely
$sortsAfter = array();
break;
}
if($selector->operator != '=') {
// sort property being used for something else that we don't recognize
continue;
}
$sortsAfter[] = $selector->value;
$selectors->remove($selector);
}
$this->sortsAfter = $sortsAfter;
}
$this->limit = $limit;
$this->start = $start;
}
@@ -802,8 +860,8 @@ class PageFinder extends Wire {
if(count($fields) > 1) $fields = $this->arrangeFields($fields);
$fieldsStr = ':' . implode(':', $fields) . ':'; // for strpos
$field = reset($fields); // first field
$subfield = '';
if(strpos($field, '.')) list($field, $subfield) = explode('.', $field);
else $subfield = '';
// TODO Make native fields and path/url multi-field and multi-value aware
if($field == 'sort' && $selector->operator === '=' && !$subfield) {
@@ -811,7 +869,7 @@ class PageFinder extends Wire {
continue;
} else if($field == 'limit' || $field == 'start') {
if(!$startLimit) $this->getQueryStartLimit($query, $selectors);
if(!$startLimit) $this->getQueryStartLimit($query);
$startLimit = true;
continue;
@@ -1488,20 +1546,13 @@ class PageFinder extends Wire {
}
}
protected function getQueryStartLimit(DatabaseQuerySelect $query, $selectors) {
protected function getQueryStartLimit(DatabaseQuerySelect $query) {
$start = null;
$limit = null;
$sql = '';
foreach($selectors as $selector) {
if($selector->field == 'start') $start = (int) $selector->value;
else if($selector->field == 'limit') $limit = (int) $selector->value;
}
$start = $this->start;
$limit = $this->limit;
if($limit) {
$this->limit = $limit;
$sql = '';
if(is_null($start) && ($input = $this->wire('input'))) {
// if not specified in the selector, assume the 'start' property from the default page's pageNum
@@ -1511,15 +1562,13 @@ class PageFinder extends Wire {
if(!is_null($start)) {
$sql .= "$start,";
$this->start = $start;
}
$sql .= "$limit";
if($this->getTotal && $this->getTotalType != 'count') $query->select("SQL_CALC_FOUND_ROWS");
if($this->getTotal && $this->getTotalType != 'count') $query->select("SQL_CALC_FOUND_ROWS");
if($sql) $query->limit($sql);
}
if($sql) $query->limit($sql);
}
@@ -1951,7 +2000,7 @@ class PageFinder extends Wire {
*
*/
public function getLimit() {
return $this->limit;
return $this->limit === null ? 0 : $this->limit;
}
/**
@@ -1961,7 +2010,7 @@ class PageFinder extends Wire {
*
*/
public function getStart() {
return $this->start;
return $this->start === null ? 0 : $this->start;
}
/**
@@ -1994,6 +2043,20 @@ class PageFinder extends Wire {
return $this->lastOptions;
}
/**
* Returns array of sortfields that should be applied to resulting PageArray after loaded
*
* See the `useSortsAfter` option which must be enabled to use this.
*
* #pw-internal
*
* @return array
*
*/
public function getSortsAfter() {
return $this->sortsAfter;
}
/**
* Does the given field or fieldName resolve to a field that uses Page or PageArray values?
*

View File

@@ -298,6 +298,34 @@ class Pages extends Wire {
return $matches;
}
/**
* Like $pages->find() except returns array of IDs rather than Page objects.
*
* This is a faster method to use when you only need to know the matching page IDs.
*
* #pw-group-retrieval
*
* @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 arrays with [ id, parent_id, templates_id ] for each page.
* - The verbose option above can also be specified by providing boolean true as the $options argument.
* - See `Pages::find()` $options argument for additional options.
* @return array Array of page IDs, or in verbose mode: array of arrays with id, parent_id and templates_id.
* @since 3.0.46
*
*/
public function findIDs($selector, $options = array()) {
$verbose = false;
if($options === true) $verbose = true;
if(!is_array($options)) $options = array();
if(isset($options['verbose'])) {
$verbose = $options['verbose'];
unset($options['verbose']);
}
$options['findIDs'] = $verbose ? true : 1;
return $this->find($selector, $options);
}
/**
* Returns the first page matching the given selector with no exclusions
*

View File

@@ -173,7 +173,8 @@ class PagesLoader 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 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 exculsions (same as include=all option)
* - 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.
* - 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).
@@ -199,11 +200,12 @@ class PagesLoader extends Wire {
$loadPages = array_key_exists('loadPages', $options) ? (bool) $options['loadPages'] : true;
$caller = isset($options['caller']) ? $options['caller'] : 'pages.find';
$lazy = empty($options['lazy']) ? false : true;
$findIDs = isset($options['findIDs']) ? $options['findIDs'] : false;
$debug = $this->debug && !$lazy;
$cachePages = isset($options['cache']) ? (bool) $options['cache'] : true;
if(!$cachePages && !isset($loadOptions['cache'])) $loadOptions['cache'] = false;
if($loadPages) {
if($loadPages && !$lazy && !$findIDs) {
$pages = $this->findShortcut($selector, $options, $loadOptions);
if($pages) return $pages;
}
@@ -222,10 +224,12 @@ class PagesLoader extends Wire {
$selectorString = is_string($selector) ? $selector : (string) $selectors;
// see if this has been cached and return it if so
$pages = $this->pages->cacher()->getSelectorCache($selectorString, $options);
if(!is_null($pages)) {
if($debug) $this->pages->debugLog('find', $selectorString, $pages . ' [from-cache]');
return $pages;
if($loadPages && !$findIDs && !$lazy) {
$pages = $this->pages->cacher()->getSelectorCache($selectorString, $options);
if(!is_null($pages)) {
if($debug) $this->pages->debugLog('find', $selectorString, $pages . ' [from-cache]');
return $pages;
}
}
$pageFinder = $this->pages->getPageFinder();
@@ -236,8 +240,9 @@ class PagesLoader extends Wire {
$profiler = $this->wire('profiler');
$profilerEvent = $profiler ? $profiler->start("$caller($selectorString)", "Pages") : null;
if($lazy) {
if(strpos($selectorString, 'limit=') === false) $options['getTotal'] = false;
if(($lazy || $findIDs) && strpos($selectorString, 'limit=') === false) $options['getTotal'] = false;
if($lazy || $findIDs === 1) {
$pagesIDs = $pageFinder->findIDs($selectors, $options);
} else {
$pagesInfo = $pageFinder->find($selectors, $options);
@@ -261,7 +266,7 @@ class PagesLoader extends Wire {
$loadPages = false;
$cachePages = false;
$template = null;
foreach($pagesIDs as $id) {
$page = $this->pages->newPage();
$page->_lazy($id);
@@ -272,6 +277,12 @@ class PagesLoader extends Wire {
$pages->setDuplicateChecking(true);
if(count($pagesIDs)) $pages->_lazy(true);
} else if($findIDs) {
$loadPages = false;
$cachePages = false;
$pages = $this->pages->newPageArray($loadOptions); // only for hooks to see
} else if($loadPages) {
// parent_id is null unless a single parent was specified in the selectors
$parent_id = $pageFinder->getParentID();
@@ -288,6 +299,7 @@ class PagesLoader extends Wire {
if(count($idsByTemplate) > 1) {
// perform a load for each template, which results in unsorted pages
// @todo use $idsUnsorted array rather than $unsortedPages PageArray
$unsortedPages = $this->pages->newPageArray($loadOptions);
foreach($idsByTemplate as $tpl_id => $ids) {
$opt = $loadOptions;
@@ -315,6 +327,9 @@ class PagesLoader extends Wire {
$opt['parent_id'] = $parent_id;
$pages->import($this->getById($idsSorted, $opt));
}
$sortsAfter = $pageFinder->getSortsAfter();
if(count($sortsAfter)) $pages->sort($sortsAfter);
} else {
$pages = $this->pages->newPageArray($loadOptions);
@@ -353,6 +368,8 @@ class PagesLoader extends Wire {
'pagesInfo' => $pagesInfo,
'options' => $options
));
if($findIDs) return $findIDs === 1 ? $pagesIDs : $pagesInfo;
return $pages;
}