3 namespace BookStack\Api;
5 use BookStack\Entities\Models\Entity;
6 use BookStack\Entities\Models\Page;
8 class ApiEntityListFormatter
11 * The list to be formatted.
14 protected array $list = [];
17 * Whether to include related titles in the response.
19 protected bool $includeRelatedTitles = false;
22 * The fields to show in the formatted data.
23 * Can be a plain string array item for a direct model field (If existing on model).
24 * If the key is a string, with a callable value, the return value of the callable
25 * will be used for the resultant value. A null return value will omit the property.
26 * @var array<string|int, string|callable>
28 protected array $fields = [
41 public function __construct(array $list)
45 // Default dynamic fields
46 $this->withField('url', fn(Entity $entity) => $entity->getUrl());
50 * Add a field to be used in the formatter, with the property using the given
51 * name and value being the return type of the given callback.
53 public function withField(string $property, callable $callback): self
55 $this->fields[$property] = $callback;
60 * Show the 'type' property in the response reflecting the entity type.
61 * EG: page, chapter, bookshelf, book
62 * To be included in results with non-pre-determined types.
64 public function withType(): self
66 $this->withField('type', fn(Entity $entity) => $entity->getType());
71 * Include tags in the formatted data.
73 public function withTags(): self
75 $this->withField('tags', fn(Entity $entity) => $entity->tags);
80 * Enable the inclusion of related book and chapter titles in the response.
82 public function withRelatedTitles(): self
84 $this->includeRelatedTitles = true;
86 $this->withField('book_title', function (Entity $entity) {
87 if (method_exists($entity, 'book')) {
88 return $entity->book?->name;
93 $this->withField('chapter_title', function (Entity $entity) {
94 if ($entity instanceof Page && $entity->chapter_id) {
95 return optional($entity->getAttribute('chapter'))->name;
104 * Format the data and return an array of formatted content.
107 public function format(): array
109 if ($this->includeRelatedTitles) {
110 $this->loadRelatedTitles();
115 foreach ($this->list as $item) {
116 $results[] = $this->formatSingle($item);
123 * Eager load the related book and chapter data when needed.
125 protected function loadRelatedTitles(): void
127 $pages = collect($this->list)->filter(fn($item) => $item instanceof Page);
129 foreach ($this->list as $entity) {
130 if (method_exists($entity, 'book')) {
131 $entity->load('book');
133 if ($entity instanceof Page && $entity->chapter_id) {
134 $entity->load('chapter');
140 * Format a single entity item to a plain array.
142 protected function formatSingle(Entity $entity): array
145 $values = (clone $entity)->toArray();
147 foreach ($this->fields as $field => $callback) {
148 if (is_string($callback)) {
150 if (!isset($values[$field])) {
153 $value = $values[$field];
155 $value = $callback($entity);
156 if (is_null($value)) {
161 $result[$field] = $value;