views_streaming_data-8.x-1.x-dev/src/StreamingViewExecutable.php
src/StreamingViewExecutable.php
<?php
namespace Drupal\views_streaming_data;
use Drupal\views\ViewExecutable;
use Drupal\views_streaming_data\Plugin\views\display\StreamingDisplayInterface;
/**
* Class StreamingViewExecutable.
*
* The $result property is an \Traversable instead of array.
*/
class StreamingViewExecutable extends ViewExecutable implements \IteratorAggregate {
/**
* Renders this view for a certain display.
*
* @param string $display_id
* The machine name of the display, which should be rendered.
*
* @return array|null
* A renderable array containing the view output or NULL if the build
* process failed.
*/
public function render($display_id = NULL) {
$success = $this->execute($display_id);
// Check to see if the build failed.
if (!$success || !empty($this->build_info['fail'])) {
return;
}
if (!empty($this->build_info['denied'])) {
return;
}
// Initialize the style plugin.
$this->initStyle();
$this->result = [];
return $this->display_handler->render();
}
/**
* Code copied from \Drupal\views\ViewExecutable::render()
*
* @param \Drupal\views\ResultRow[] $result
* An array of ResultRow objects returned from the query.
*/
protected function renderResult(array &$result) {
// Give field handlers the opportunity to perform additional queries
// using the entire resultset prior to rendering.
if ($this->style_plugin->usesFields()) {
foreach ($this->field as $id => $handler) {
if (!empty($this->field[$id])) {
$this->field[$id]->preRender($result);
}
}
}
$this->style_plugin->preRender($result);
}
/**
* Executes the view's query.
*
* @param string $display_id
* The machine name of the display, which should be executed.
*
* @return bool
* TRUE if the view execution was successful, FALSE otherwise. For example,
* an argument could stop the process.
*/
public function execute($display_id = NULL) {
if (empty($this->built)) {
if (!$this->build($display_id)) {
return FALSE;
}
}
// Never actually run the query when in live preview mode.
if (!empty($this->live_preview)) {
$this->result = [];
$this->executed = TRUE;
}
if (!empty($this->executed)) {
return TRUE;
}
// Don't allow deactivated displays, but display them on the live preview.
if (!$this->display_handler->isEnabled() && empty($this->live_preview)) {
$this->build_info['fail'] = TRUE;
return FALSE;
}
// Let modules modify the view just prior to executing it.
// @phpstan-ignore globalDrupalDependencyInjection.useDependencyInjection
$module_handler = \Drupal::moduleHandler();
$module_handler->invokeAll('views_pre_execute', [$this]);
$this->query->execute($this);
// We may not want to call this.
$this->_postExecute();
// Let modules modify the view just after executing it.
$module_handler->invokeAll('views_post_execute', [$this]);
return $this->executed = TRUE;
}
/**
* Get the iterator. Generator magic happens here.
*
* @throws \ReflectionException
*/
public function getIterator(): \Traversable {
$i = 0;
$chunk_size = $this->display_handler->getOption('chunk_size') ?: StreamingDisplayInterface::DEFAULT_CHUNK_SIZE;
do {
$result = [];
$j = 0;
do {
$row = $this->query->result->fetch();
if ($row) {
$row->index = $i;
$result[$i] = $row;
$i++;
}
$j++;
} while ($row && $j < $chunk_size);
// Load all entities contained in the results.
$this->query->loadEntities($result);
$this->renderResult($result);
$this->result = $result;
foreach ($result as $index => $row) {
yield $index => $row;
}
// This ugliness is because of the behavior in
// \Drupal\views\Entity\Render\EntityFieldRenderer::render() where
// it saves a build after rendering every row in the $view->result.
// We are trying to get chunks of 50 rendered at a time, so we need to
// force reinitialize the build.
foreach ($this->field as $obj) {
$reflected = new \ReflectionClass($obj);
if ($reflected->hasProperty('entityFieldRenderer')) {
$efr = $reflected->getProperty('entityFieldRenderer');
$efr->setAccessible(TRUE);
$efr->setValue($obj, NULL);
}
}
} while ($result);
}
}
