BookStack/app/Api/ApiEntityListFormatter.php

158 lines
4.1 KiB
PHP

<?php
namespace BookStack\Api;
use BookStack\Entities\Models\Entity;
use BookStack\Entities\Models\Page;
class ApiEntityListFormatter
{
/**
* The list to be formatted.
* @var Entity[]
*/
protected array $list = [];
/**
* The fields to show in the formatted data.
* Can be a plain string array item for a direct model field (If existing on model).
* If the key is a string, with a callable value, the return value of the callable
* will be used for the resultant value. A null return value will omit the property.
* @var array<string|int, string|callable>
*/
protected array $fields = [
'id',
'name',
'slug',
'book_id',
'chapter_id',
'draft',
'template',
'priority',
'created_at',
'updated_at',
];
public function __construct(array $list)
{
$this->list = $list;
// Default dynamic fields
$this->withField('url', fn(Entity $entity) => $entity->getUrl());
}
/**
* Add a field to be used in the formatter, with the property using the given
* name and value being the return type of the given callback.
*/
public function withField(string $property, callable $callback): self
{
$this->fields[$property] = $callback;
return $this;
}
/**
* Show the 'type' property in the response reflecting the entity type.
* EG: page, chapter, bookshelf, book
* To be included in results with non-pre-determined types.
*/
public function withType(): self
{
$this->withField('type', fn(Entity $entity) => $entity->getType());
return $this;
}
/**
* Include tags in the formatted data.
*/
public function withTags(): self
{
$this->withField('tags', fn(Entity $entity) => $entity->tags);
return $this;
}
/**
* Enable the inclusion of related book and chapter titles in the response.
*/
public function withRelatedData(): self
{
$this->withField('book', function (Entity $entity) {
if (method_exists($entity, 'book')) {
return $entity->book()->select(['id', 'name', 'slug'])->first();
}
return null;
});
$this->withField('chapter', function (Entity $entity) {
if ($entity instanceof Page && $entity->chapter_id) {
return $entity->chapter()->select(['id', 'name', 'slug'])->first();
}
return null;
});
return $this;
}
/**
* Format the data and return an array of formatted content.
* @return array[]
*/
public function format(): array
{
$this->loadRelatedData();
$results = [];
foreach ($this->list as $item) {
$results[] = $this->formatSingle($item);
}
return $results;
}
/**
* Eager load the related book and chapter data when needed.
*/
protected function loadRelatedData(): void
{
$pages = collect($this->list)->filter(fn($item) => $item instanceof Page);
foreach ($this->list as $entity) {
if (method_exists($entity, 'book')) {
$entity->load('book');
}
if ($entity instanceof Page && $entity->chapter_id) {
$entity->load('chapter');
}
}
}
/**
* Format a single entity item to a plain array.
*/
protected function formatSingle(Entity $entity): array
{
$result = [];
$values = (clone $entity)->toArray();
foreach ($this->fields as $field => $callback) {
if (is_string($callback)) {
$field = $callback;
if (!isset($values[$field])) {
continue;
}
$value = $values[$field];
} else {
$value = $callback($entity);
if (is_null($value)) {
continue;
}
}
$result[$field] = $value;
}
return $result;
}
}