a12s-1.0.0-beta7/modules/page_context/src/RecordSet.php
modules/page_context/src/RecordSet.php
<?php
namespace Drupal\a12s_page_context;
use Drupal\a12s_page_context\Entity\PageContextFormInterface;
use Drupal\Core\Entity\EntityInterface;
/**
* Represents a set of records.
*/
class RecordSet implements \IteratorAggregate, \ArrayAccess, \Countable {
/**
* The list of records.
*
* @var \Drupal\a12s_page_context\Record[]
*/
protected array $records;
/**
* The compiled data.
*
* @var array
*/
protected array $compiledData;
/**
* Class constructor.
*
* @param \Drupal\a12s_page_context\Record[] $records
* The list of records.
* @param \Drupal\a12s_page_context\Entity\PageContextFormInterface $pageContextForm
* The related "page context" form.
* @param array $context
* The current context.
*/
public function __construct(array $records, protected PageContextFormInterface $pageContextForm, protected array $context = []) {
$settings = array_values($this->pageContextForm->getDisplaySettings());
usort($settings, fn(array $a, array $b) => $a['weight'] <=> $b['weight']);
$order = array_map(fn(array $setting) => $setting['plugin_id'], $settings);
// Order results by plugin weight, so the data compilation takes care of
// plugin priority.
usort($records, function (Record $a, Record $b) use ($order) {
$aPluginId = $a->getPluginId();
$bPluginId = $b->getPluginId();
if ($aPluginId !== $bPluginId) {
$aWeight = array_search($aPluginId, $order);
$bWeight = array_search($bPluginId, $order);
return $aWeight === FALSE ? 1 : ($bWeight === FALSE ? -1 : ($aWeight <=> $bWeight));
}
return 0;
});
$this->records = $records;
}
/**
* Load the stored records for the given context form and page/context.
*
* @param \Drupal\a12s_page_context\Entity\PageContextFormInterface $pageContextForm
* The Page Context Form instance.
* @param array $context
* The plugin context. Default values are calculated by the in-charge
* plugins, however it is possible to force some values if desired.
*
* @return \Drupal\a12s_page_context\RecordSet
* The record set.
*/
public static function load(PageContextFormInterface $pageContextForm, array $context = []): RecordSet {
static $sets = [];
$context['a12s_page_context_form'] = $pageContextForm;
ksort($context);
$contextClone = [];
foreach ($context as $key => $item) {
if ($item instanceof EntityInterface) {
$contextClone[$key] = $item->id();
}
}
$hash = md5(serialize($contextClone));
if (!isset($sets[$hash])) {
$connection = \Drupal::database();
$query = $connection->select('a12s_page_context_record')
->fields('a12s_page_context_record')
->condition('config_id', $pageContextForm->id());
$conditions = $connection->condition('OR')
// Ensure we have at least a condition which returns no results, just in
// case there are no enabled display plugins or nothing matching with
// the current route.
->where('1 = 0');
foreach ($pageContextForm->getDisplayPlugins() as $plugin) {
if ($condition = $plugin->addRecordQueryConditions($context)) {
$conditions->condition($condition);
}
}
$results = $query->condition($conditions)->execute()->fetchAll(\PDO::FETCH_CLASS, Record::class);
$sets[$hash] = new static($results, $pageContextForm, $context);
}
return $sets[$hash];
}
/**
* Get the data for the current Page Context Form and context.
*
* @return array
* The compiled data.
*/
public function getData(): array {
if (!isset($this->compiledData)) {
$this->compiledData = $this->compile();
}
return $this->compiledData;
}
/**
* Get the data as tree.
*
* @return array
* The nested data.
*/
public function getDataTree(): array {
return array_reduce($this->records, fn(array $tree, Record $record) => $this->addRecordToDataTree($record, $record->getData(), $tree), []);
}
/**
* Build recursively the data tree, with their related records.
*
* @param \Drupal\a12s_page_context\Record $record
* The record to add.
* @param array $data
* The current data.
* @param array $tree
* The current tree.
*
* @return array
* The data tree.
*/
public function addRecordToDataTree(Record $record, array $data, array $tree = []): array {
foreach ($data as $key => $item) {
if (str_ends_with($key, '_override')) {
continue;
}
if (is_array($item)) {
$tree[$key] = $this->addRecordToDataTree($record, $item, $tree[$key] ?? []);
}
else {
$overrideKey = $key . '_override';
if (!array_key_exists($key, $tree) || !isset($data[$overrideKey]) || !empty($data[$overrideKey])) {
$tree[$key][] = [
'record' => $record,
'value' => $item,
];
}
}
}
return $tree;
}
/**
* Compile the provided data with the current values.
*
* This is a recursive function, which takes care of calculating the final
* context values with the optional overrides.
*
* @param array $recordData
* The data from a database record.
* @param array $values
* The already calculated values.
*
* @return array
* The compiled data.
*/
public function compileRecordData(array $recordData, array $values = []): array {
foreach ($recordData as $key => $data) {
if (str_ends_with($key, '_override')) {
continue;
}
if (is_array($data)) {
$values[$key] = $this->compileRecordData($data, $values[$key] ?? []);
}
else {
$overrideKey = $key . '_override';
// Having an override key not set can mean that the parent record
// has been created after the children one. So this should lead
// to an override.
if (!array_key_exists($key, $values) || !isset($recordData[$overrideKey]) || !empty($recordData[$overrideKey])) {
$values[$key] = $data;
}
}
}
return $values;
}
/**
* Compile the raw data from all records recursively.
*
* @return array
* The compiled data.
*/
protected function compile(): array {
$records = $this->filter();
$recordsData = array_map(fn(Record $record) => $record->getData() ?? [], $records);
return array_reduce($recordsData, fn(array $values, array $data) => $this->compileRecordData($data, $values), []);
}
/**
* Filter the records using enabled plugins and current context.
*
* @return \Drupal\a12s_page_context\Record[]
* The filtered records.
*/
protected function filter(): array {
$records = $this->records;
foreach ($this->pageContextForm->getDisplayPlugins() as $plugin) {
$records = $plugin->filterRecords($records, $this->context);
}
return $records;
}
/**
* {@inheritdoc}
*/
public function offsetExists(mixed $offset): bool {
return array_key_exists($offset, $this->records);
}
/**
* {@inheritdoc}
*/
public function offsetGet(mixed $offset): ?Record {
return $this->records[$offset] ?? NULL;
}
/**
* {@inheritdoc}
*/
public function offsetSet(mixed $offset, mixed $value): void {
throw new \LogicException('Attempting to write to an immutable array');
}
/**
* {@inheritdoc}
*/
public function offsetUnset(mixed $offset): void {
throw new \LogicException('Attempting to write to an immutable array');
}
/**
* {@inheritdoc}
*/
public function count(): int {
return count($this->records);
}
/**
* {@inheritdoc}
*
* @return \Drupal\a12s_page_context\Record[]
* The list of records.
*/
public function getIterator(): \Traversable {
return new \ArrayIterator($this->records);
}
}
