]> BookStack Code Mirror - bookstack/blob - app/Api/ApiEntityListFormatter.php
respective book and chapter structure added.
[bookstack] / app / Api / ApiEntityListFormatter.php
1 <?php
2
3 namespace BookStack\Api;
4
5 use BookStack\Entities\Models\Entity;
6 use BookStack\Entities\Models\Page;
7
8 class ApiEntityListFormatter
9 {
10     /**
11      * The list to be formatted.
12      * @var Entity[]
13      */
14     protected array $list = [];
15
16     /**
17      * The fields to show in the formatted data.
18      * Can be a plain string array item for a direct model field (If existing on model).
19      * If the key is a string, with a callable value, the return value of the callable
20      * will be used for the resultant value. A null return value will omit the property.
21      * @var array<string|int, string|callable>
22      */
23     protected array $fields = [
24         'id',
25         'name',
26         'slug',
27         'book_id',
28         'chapter_id',
29         'draft',
30         'template',
31         'priority',
32         'created_at',
33         'updated_at',
34     ];
35
36     public function __construct(array $list)
37     {
38         $this->list = $list;
39
40         // Default dynamic fields
41         $this->withField('url', fn(Entity $entity) => $entity->getUrl());
42     }
43
44     /**
45      * Add a field to be used in the formatter, with the property using the given
46      * name and value being the return type of the given callback.
47      */
48     public function withField(string $property, callable $callback): self
49     {
50         $this->fields[$property] = $callback;
51         return $this;
52     }
53
54     /**
55      * Show the 'type' property in the response reflecting the entity type.
56      * EG: page, chapter, bookshelf, book
57      * To be included in results with non-pre-determined types.
58      */
59     public function withType(): self
60     {
61         $this->withField('type', fn(Entity $entity) => $entity->getType());
62         return $this;
63     }
64
65     /**
66      * Include tags in the formatted data.
67      */
68     public function withTags(): self
69     {
70         $this->withField('tags', fn(Entity $entity) => $entity->tags);
71         return $this;
72     }
73
74     /**
75      * Enable the inclusion of related book and chapter titles in the response.
76      */
77     public function withRelatedData(): self
78     {
79         $this->withField('book', function (Entity $entity) {
80             if (method_exists($entity, 'book')) {
81                 return $entity->book()->select(['id', 'name', 'slug'])->first();
82             }
83             return null;
84         });
85
86         $this->withField('chapter', function (Entity $entity) {
87             if ($entity instanceof Page && $entity->chapter_id) {
88                 return $entity->chapter()->select(['id', 'name', 'slug'])->first();
89             }
90             return null;
91         });
92
93         return $this;
94     }
95
96     /**
97      * Format the data and return an array of formatted content.
98      * @return array[]
99      */
100     public function format(): array
101     {
102         $this->loadRelatedData();
103
104         $results = [];
105
106         foreach ($this->list as $item) {
107             $results[] = $this->formatSingle($item);
108         }
109
110         return $results;
111     }
112
113     /**
114      * Eager load the related book and chapter data when needed.
115      */
116     protected function loadRelatedData(): void
117     {
118         $pages = collect($this->list)->filter(fn($item) => $item instanceof Page);
119
120         foreach ($this->list as $entity) {
121             if (method_exists($entity, 'book')) {
122                 $entity->load('book');
123             }
124             if ($entity instanceof Page && $entity->chapter_id) {
125                 $entity->load('chapter');
126             }
127         }
128     }
129
130     /**
131      * Format a single entity item to a plain array.
132      */
133     protected function formatSingle(Entity $entity): array
134     {
135         $result = [];
136         $values = (clone $entity)->toArray();
137
138         foreach ($this->fields as $field => $callback) {
139             if (is_string($callback)) {
140                 $field = $callback;
141                 if (!isset($values[$field])) {
142                     continue;
143                 }
144                 $value = $values[$field];
145             } else {
146                 $value = $callback($entity);
147                 if (is_null($value)) {
148                     continue;
149                 }
150             }
151
152             $result[$field] = $value;
153         }
154
155         return $result;
156     }
157 }