<?php
namespace Jackalope;
use ArrayAccess;
use Countable;
use InvalidArgumentException;
use SeekableIterator;
/**
* @license http://www.apache.org/licenses Apache License Version 2.0, January 2004
* @license http://opensource.org/licenses/MIT MIT License
*/
class NodePathIterator implements SeekableIterator, ArrayAccess, Countable
{
/**
* @var int
*/
protected $position = 0;
/**
* @var array
*/
protected $nodes = [];
/**
* @var array
*/
protected $paths;
protected $typeFilter;
protected $class;
/**
* @var int
*/
protected $count = 0;
protected $batchSize;
public function __construct(
ObjectManager $objectManager,
$paths,
$class = Node::class,
$typeFilter = [],
$batchSize = 50
) {
$this->objectManager = $objectManager;
$this->paths = array_values((array) $paths); // ensure paths are indexed numerically
$this->batchSize = $batchSize;
$this->typeFilter = $typeFilter;
$this->class = $class;
$this->loadBatch();
}
/**
* Return the batchSize
*
* @return integer
*/
public function getBatchSize()
{
return $this->batchSize;
}
/**
* Return the type filter
*
* @return string
*/
public function getTypeFilter()
{
return $this->typeFilter;
}
/**
* {@inheritDoc}
*/
public function current()
{
return $this->nodes[$this->paths[$this->position]];
}
/**
* {@inheritDoc}
*/
public function next()
{
$this->position++;
}
/**
* {@inheritDoc}
*/
public function rewind()
{
$this->position = 0;
}
/**
* {@inheritDoc}
*/
public function valid()
{
if (!isset($this->paths[$this->position])) {
return false;
}
$path = $this->paths[$this->position];
// skip any paths which have been filtered in userland
// and move on
if ($path === null) {
$this->position++;
return $this->valid();
}
if (!array_key_exists($path, $this->nodes)) {
$this->loadBatch();
}
if (empty($this->nodes[$path])) {
$this->position++;
return $this->valid();
}
return true;
}
/**
* {@inheritDoc}
*/
public function key()
{
return $this->paths[$this->position];
}
/**
* Load a batch of records according to the
* batch size.
*
* @param integer $position - Optional position to start from
*/
protected function loadBatch($position = null)
{
if (0 === count($this->paths)) {
return;
}
$paths = array_slice(
$this->paths,
$position ? $position : $this->position,
$this->batchSize
);
$nodes = $this->objectManager->getNodesByPathAsArray(
$paths,
$this->class,
$this->typeFilter
);
foreach ($paths as $path) {
if (isset($nodes[$path]) && $nodes[$path] !== '') {
$this->nodes[$path] = $nodes[$path];
$this->count++;
} else {
$this->nodes[$path] = null;
}
}
}
/**
* Ensure that the given path is loaded from the database.
* We will iterate over the batches until we either get to
* the end or we find the node we are looking for.
*
* Subsequent calls will start loading from the first path
* which does not have a corresponding array key in the nodes array
* - if the node is indeed not already loaded.
*
* @param integer $offset
*/
protected function ensurePathLoaded($offset)
{
if (count($this->paths) > 0) {
if (!array_key_exists($offset, $this->nodes)) {
// start loading batches from the position of the first
// "missing" node
$position = null;
foreach ($this->paths as $position => $path) {
if (!array_key_exists($path, $this->nodes)) {
break;
}
}
while (isset($this->paths[$position])) {
// keep loading batches until we get to the end of the paths
// or we find the one we want.
$this->loadBatch($position);
$position += $this->batchSize;
if (array_key_exists($offset, $this->nodes)) {
break;
}
}
}
}
// if it wasn't found, it doesn't exist, set it to null
if (!array_key_exists($offset, $this->nodes)) {
$this->nodes[$offset] = null;
}
}
/**
* {@inheritDoc}
*/
public function offsetExists($offset)
{
$this->ensurePathLoaded($offset);
return $this->nodes[$offset] === null ? false : true;
}
/**
* {@inheritDoc}
*/
public function offsetGet($offset)
{
$this->ensurePathLoaded($offset);
return $this->nodes[$offset];
}
/**
* {@inheritDoc}
*/
public function offsetSet($offset, $value)
{
throw new InvalidArgumentException('Node path collection is read only');
}
/**
* {@inheritDoc}
*/
public function offsetUnset($offset)
{
throw new InvalidArgumentException('Node path collection is read only');
}
/**
* {@inheritDoc}
*/
public function seek($position)
{
$this->position = $position;
}
/**
* {@inheritDoc}
*/
public function count()
{
$this->ensurePathLoaded(count($this->paths));
return $this->count;
}
}