* @link https://getkirby.com * @copyright Bastian Allgeier * @license https://opensource.org/licenses/MIT */ class Segments extends Collection { public function __construct( array $data = [], protected Query|null $parent = null, ) { parent::__construct($data); } /** * Split query string into segments by dot * but not inside (nested) parens */ public static function factory(string $query, Query $parent = null): static { $segments = static::parse($query); $position = 0; $segments = A::map( $segments, function ($segment) use (&$position) { // leave connectors as they are if (in_array($segment, ['.', '?.']) === true) { return $segment; } // turn all other parts into Segment objects // and pass their position in the chain (ignoring connectors) $position++; return Segment::factory($segment, $position - 1); } ); return new static($segments, $parent); } /** * Splits the string of a segment chaing into an * array of segments as well as conenctors (`.` or `?.`) * @internal */ public static function parse(string $string): array { return preg_split( '/(\??\.)|(\(([^()]+|(?2))*+\))(*SKIP)(*FAIL)/', trim($string), flags: PREG_SPLIT_DELIM_CAPTURE|PREG_SPLIT_NO_EMPTY ); } /** * Resolves the segments chain by looping through * each segment call to be applied to the value of * all previous segment calls, returning gracefully at * `?.` when current value is `null` */ public function resolve(array|object $data = []) { $value = null; foreach ($this->data as $segment) { // optional chaining: stop if current value is null if ($segment === '?.' && $value === null) { return null; } // for regular connectors, just skip if ($segment === '.') { continue; } // offer possibility to intercept on objects if ($value !== null) { $value = $this->parent?->intercept($value) ?? $value; } $value = $segment->resolve($value, $data); } return $value; } }