Code Coverage |
||||||||||
Lines |
Functions and Methods |
Classes and Traits |
||||||||
Total | |
100.00% |
60 / 60 |
|
100.00% |
23 / 23 |
CRAP | |
100.00% |
1 / 1 |
PublicationType | |
100.00% |
60 / 60 |
|
100.00% |
23 / 23 |
28 | |
100.00% |
1 / 1 |
get | |
100.00% |
1 / 1 |
|
100.00% |
1 / 1 |
1 | |||
fromFile | |
100.00% |
6 / 6 |
|
100.00% |
1 / 1 |
2 | |||
__construct | |
100.00% |
10 / 10 |
|
100.00% |
1 / 1 |
1 | |||
toArray | |
100.00% |
13 / 13 |
|
100.00% |
1 / 1 |
2 | |||
toJson | |
100.00% |
1 / 1 |
|
100.00% |
1 / 1 |
1 | |||
getIdentifier | |
100.00% |
1 / 1 |
|
100.00% |
1 / 1 |
1 | |||
getSchemaFile | |
100.00% |
1 / 1 |
|
100.00% |
1 / 1 |
1 | |||
getDirectory | |
100.00% |
1 / 1 |
|
100.00% |
1 / 1 |
1 | |||
getMetadata | |
100.00% |
1 / 1 |
|
100.00% |
1 / 1 |
1 | |||
setMetadata | |
100.00% |
1 / 1 |
|
100.00% |
1 / 1 |
1 | |||
getFields | |
100.00% |
1 / 1 |
|
100.00% |
1 / 1 |
1 | |||
getFieldDefinition | |
100.00% |
1 / 1 |
|
100.00% |
1 / 1 |
1 | |||
getCanonicalFieldDefinition | |
100.00% |
3 / 3 |
|
100.00% |
1 / 1 |
2 | |||
getPublications | |
100.00% |
1 / 1 |
|
100.00% |
1 / 1 |
1 | |||
getPaginator | |
100.00% |
5 / 5 |
|
100.00% |
1 / 1 |
1 | |||
getListPage | |
100.00% |
1 / 1 |
|
100.00% |
1 / 1 |
1 | |||
usesPagination | |
100.00% |
1 / 1 |
|
100.00% |
1 / 1 |
2 | |||
save | |
100.00% |
3 / 3 |
|
100.00% |
1 / 1 |
1 | |||
parseSchemaFile | |
100.00% |
1 / 1 |
|
100.00% |
1 / 1 |
1 | |||
getRelativeDirectoryEntry | |
100.00% |
1 / 1 |
|
100.00% |
1 / 1 |
1 | |||
parseFieldData | |
100.00% |
3 / 3 |
|
100.00% |
1 / 1 |
1 | |||
withoutNullValues | |
100.00% |
1 / 1 |
|
100.00% |
1 / 1 |
1 | |||
validateSchemaFile | |
100.00% |
2 / 2 |
|
100.00% |
1 / 1 |
2 |
1 | <?php |
2 | |
3 | declare(strict_types=1); |
4 | |
5 | namespace Hyde\Publications\Models; |
6 | |
7 | use Exception; |
8 | use Hyde\Framework\Concerns\InteractsWithDirectories; |
9 | use Hyde\Hyde; |
10 | use Hyde\Publications\Actions\PublicationSchemaValidator; |
11 | use Hyde\Publications\Pages\PublicationListPage; |
12 | use Hyde\Publications\Publications; |
13 | use Hyde\Support\Concerns\Serializable; |
14 | use Hyde\Support\Contracts\SerializableContract; |
15 | use Hyde\Support\Paginator; |
16 | use Illuminate\Support\Collection; |
17 | use Illuminate\Support\Str; |
18 | use RuntimeException; |
19 | |
20 | use function array_filter; |
21 | use function array_merge; |
22 | use function dirname; |
23 | use function file_get_contents; |
24 | use function file_put_contents; |
25 | use function is_null; |
26 | use function json_decode; |
27 | use function json_encode; |
28 | |
29 | /** |
30 | * @see \Hyde\Publications\Testing\Feature\PublicationTypeTest |
31 | */ |
32 | class PublicationType implements SerializableContract |
33 | { |
34 | use Serializable; |
35 | use InteractsWithDirectories; |
36 | |
37 | /** The "pretty" name of the publication type */ |
38 | public string $name; |
39 | |
40 | /** |
41 | * The field name that is used as the canonical (or identifying) field of publications. |
42 | * |
43 | * It's used primarily for generating filenames, and the publications must thus be unique by this field. |
44 | */ |
45 | public string $canonicalField = '__createdAt'; |
46 | |
47 | /** The Blade filename or view identifier used for rendering a single publication */ |
48 | public string $detailTemplate = 'detail.blade.php'; |
49 | |
50 | /** The Blade filename or view identifier used for rendering the index page (or index pages, when using pagination) */ |
51 | public string $listTemplate = 'list.blade.php'; |
52 | |
53 | /** The field that is used for sorting publications. */ |
54 | public string $sortField = '__createdAt'; |
55 | |
56 | /** Whether the sort field should be sorted in ascending order. */ |
57 | public bool $sortAscending = true; |
58 | |
59 | /** The number of publications to show per paginated page. Set to 0 to disable pagination. */ |
60 | public int $pageSize = 0; |
61 | |
62 | /** Generic array field which can be used to store additional data as needed. */ |
63 | public array $metadata = []; |
64 | |
65 | /** |
66 | * The front matter fields used for the publications. |
67 | * |
68 | * @var \Illuminate\Support\Collection<string, \Hyde\Publications\Models\PublicationFieldDefinition> |
69 | */ |
70 | public Collection $fields; |
71 | |
72 | /** The directory of the publication files */ |
73 | protected string $directory; |
74 | |
75 | public static function get(string $name): static |
76 | { |
77 | return static::fromFile("$name/schema.json"); |
78 | } |
79 | |
80 | public static function fromFile(string $schemaFile): static |
81 | { |
82 | try { |
83 | return new static(...array_merge( |
84 | static::parseSchemaFile($schemaFile), |
85 | static::getRelativeDirectoryEntry($schemaFile)) |
86 | ); |
87 | } catch (Exception $exception) { |
88 | throw new RuntimeException("Could not parse schema file $schemaFile", 0, $exception); |
89 | } |
90 | } |
91 | |
92 | /** @param array<array<string, string>> $fields */ |
93 | public function __construct( |
94 | string $name, // todo get from directory name if not set in schema? |
95 | string $canonicalField = '__createdAt', |
96 | string $detailTemplate = 'detail.blade.php', |
97 | string $listTemplate = 'list.blade.php', |
98 | string $sortField = '__createdAt', |
99 | bool $sortAscending = true, |
100 | int $pageSize = 0, |
101 | array $fields = [], |
102 | array $metadata = [], |
103 | ?string $directory = null |
104 | ) { |
105 | $this->name = $name; // todo get from directory name if not set in schema? |
106 | $this->canonicalField = $canonicalField; |
107 | $this->detailTemplate = $detailTemplate; |
108 | $this->listTemplate = $listTemplate; |
109 | $this->fields = $this->parseFieldData($fields); |
110 | $this->directory = $directory ?? Str::slug($name); |
111 | $this->sortField = $sortField; |
112 | $this->sortAscending = $sortAscending; |
113 | $this->pageSize = $pageSize; |
114 | $this->metadata = $metadata; |
115 | } |
116 | |
117 | public function toArray(): array |
118 | { |
119 | $array = $this->withoutNullValues([ |
120 | 'name' => $this->name, |
121 | 'canonicalField' => $this->canonicalField, |
122 | 'detailTemplate' => $this->detailTemplate, |
123 | 'listTemplate' => $this->listTemplate, |
124 | 'sortField' => $this->sortField, |
125 | 'sortAscending' => $this->sortAscending, |
126 | 'pageSize' => $this->pageSize, |
127 | 'fields' => $this->fields->toArray(), |
128 | ]); |
129 | |
130 | if ($this->metadata) { |
131 | $array['metadata'] = $this->metadata; |
132 | } |
133 | |
134 | return $array; |
135 | } |
136 | |
137 | /** @param int $options */ |
138 | public function toJson($options = JSON_PRETTY_PRINT): string |
139 | { |
140 | return json_encode($this->toArray(), $options); |
141 | } |
142 | |
143 | /** Get the publication type's identifier */ |
144 | public function getIdentifier(): string |
145 | { |
146 | return $this->directory ?? Str::slug($this->name); |
147 | } |
148 | |
149 | public function getSchemaFile(): string |
150 | { |
151 | return "$this->directory/schema.json"; |
152 | } |
153 | |
154 | public function getDirectory(): string |
155 | { |
156 | return $this->directory; |
157 | } |
158 | |
159 | public function getMetadata(): array |
160 | { |
161 | return $this->metadata; |
162 | } |
163 | |
164 | public function setMetadata(array $metadata): array |
165 | { |
166 | return $this->metadata = $metadata; |
167 | } |
168 | |
169 | /** |
170 | * Get the publication fields, deserialized to PublicationFieldDefinition objects. |
171 | * |
172 | * @return \Illuminate\Support\Collection<string, \Hyde\Publications\Models\PublicationFieldDefinition> |
173 | */ |
174 | public function getFields(): Collection |
175 | { |
176 | return $this->fields; |
177 | } |
178 | |
179 | public function getFieldDefinition(string $fieldName): PublicationFieldDefinition |
180 | { |
181 | return $this->getFields()->filter(fn (PublicationFieldDefinition $field): bool => $field->name === $fieldName)->firstOrFail(); |
182 | } |
183 | |
184 | public function getCanonicalFieldDefinition(): PublicationFieldDefinition |
185 | { |
186 | if ($this->canonicalField === '__createdAt') { |
187 | return new PublicationFieldDefinition('string', $this->canonicalField); |
188 | } |
189 | |
190 | return $this->getFields()->filter(fn (PublicationFieldDefinition $field): bool => $field->name === $this->canonicalField)->first(); |
191 | } |
192 | |
193 | /** @return \Illuminate\Support\Collection<\Hyde\Publications\Pages\PublicationPage> */ |
194 | public function getPublications(): Collection |
195 | { |
196 | return Publications::getPublicationsForType($this); |
197 | } |
198 | |
199 | public function getPaginator(int $currentPageNumber = null): Paginator |
200 | { |
201 | return new Paginator($this->getPublications(), |
202 | $this->pageSize, |
203 | $currentPageNumber, |
204 | $this->getIdentifier() |
205 | ); |
206 | } |
207 | |
208 | public function getListPage(): PublicationListPage |
209 | { |
210 | return new PublicationListPage($this); |
211 | } |
212 | |
213 | public function usesPagination(): bool |
214 | { |
215 | return ($this->pageSize > 0) && ($this->pageSize < $this->getPublications()->count()); |
216 | } |
217 | |
218 | public function save(?string $path = null): void |
219 | { |
220 | $path ??= $this->getSchemaFile(); |
221 | $this->needsParentDirectory($path); |
222 | file_put_contents(Hyde::path($path), json_encode($this->toArray(), JSON_PRETTY_PRINT)); |
223 | } |
224 | |
225 | protected static function parseSchemaFile(string $schemaFile): array |
226 | { |
227 | return json_decode(file_get_contents(Hyde::path($schemaFile)), true, 512, JSON_THROW_ON_ERROR); |
228 | } |
229 | |
230 | protected static function getRelativeDirectoryEntry(string $schemaFile): array |
231 | { |
232 | return ['directory' => Hyde::pathToRelative(dirname($schemaFile))]; |
233 | } |
234 | |
235 | protected function parseFieldData(array $fields): Collection |
236 | { |
237 | return Collection::make($fields)->map(function (array $data): PublicationFieldDefinition { |
238 | return new PublicationFieldDefinition(...$data); |
239 | }); |
240 | } |
241 | |
242 | protected function withoutNullValues(array $array): array |
243 | { |
244 | return array_filter($array, fn (mixed $value): bool => ! is_null($value)); |
245 | } |
246 | |
247 | /** |
248 | * Validate the schema.json file is valid. |
249 | * |
250 | * @internal This method is experimental and may be removed without notice |
251 | */ |
252 | public function validateSchemaFile(bool $throw = true): ?array |
253 | { |
254 | $method = $throw ? 'validate' : 'errors'; |
255 | |
256 | return PublicationSchemaValidator::call($this->getIdentifier(), $throw)->$method(); |
257 | } |
258 | } |