Passed
Push — master ( 25efb9...c70e91 )
by Caen
04:09 queued 12s
created

HydePage::toArray()   A

Complexity

Conditions 1
Paths 1

Size

Total Lines 11
Code Lines 9

Duplication

Lines 0
Ratio 0 %

Importance

Changes 2
Bugs 0 Features 0
Metric Value
cc 1
eloc 9
nc 1
nop 0
dl 0
loc 11
rs 9.9666
c 2
b 0
f 0
1
<?php
2
3
declare(strict_types=1);
4
5
namespace Hyde\Pages\Concerns;
6
7
use Hyde\Hyde;
8
use Hyde\Facades\Config;
9
use Hyde\Foundation\Facades;
10
use Hyde\Foundation\Facades\Files;
11
use Hyde\Foundation\Facades\Pages;
12
use Hyde\Foundation\Facades\Routes;
13
use Hyde\Foundation\Kernel\PageCollection;
14
use Hyde\Framework\Actions\SourceFileParser;
15
use Hyde\Framework\Concerns\InteractsWithFrontMatter;
16
use Hyde\Framework\Factories\Concerns\HasFactory;
17
use Hyde\Framework\Features\Metadata\PageMetadataBag;
18
use Hyde\Framework\Features\Navigation\NavigationData;
19
use Hyde\Markdown\Contracts\FrontMatter\PageSchema;
20
use Hyde\Markdown\Models\FrontMatter;
21
use Hyde\Support\Concerns\Serializable;
22
use Hyde\Support\Contracts\SerializableContract;
23
use Hyde\Support\Filesystem\SourceFile;
24
use Hyde\Support\Models\Route;
25
use Hyde\Support\Models\RouteKey;
26
use Illuminate\Support\Str;
27
use function unslash;
28
use function filled;
29
use function ltrim;
30
use function rtrim;
31
32
/**
33
 * The base class for all Hyde pages.
34
 *
35
 * To ensure compatibility with the Hyde Framework, all page models should extend this class.
36
 * Markdown-based pages can extend the BaseMarkdownPage class to get relevant helpers.
37
 *
38
 * Unlike other frameworks, in general you don't instantiate pages yourself in Hyde,
39
 * instead, the page models acts as blueprints defining information for Hyde to
40
 * know how to parse a file, and what data around it should be generated.
41
 *
42
 * To create a parsed file instance, you'd typically just create a source file,
43
 * and you can then access the parsed file from the HydeKernel's page index.
44
 * The source files are usually parsed by the SourceFileParser action.
45
 *
46
 * In Blade views, you can always access the current page instance being rendered using the $page variable.
47
 *
48
 * @see \Hyde\Pages\Concerns\BaseMarkdownPage
49
 * @see \Hyde\Framework\Testing\Feature\HydePageTest
50
 */
51
abstract class HydePage implements PageSchema, SerializableContract
52
{
53
    use InteractsWithFrontMatter;
54
    use Serializable;
55
    use HasFactory;
0 ignored issues
show
Bug introduced by
The trait Hyde\Framework\Factories\Concerns\HasFactory requires the property $markdown which is not provided by Hyde\Pages\Concerns\HydePage.
Loading history...
56
57
    public static string $sourceDirectory;
58
    public static string $outputDirectory;
59
    public static string $fileExtension;
60
    public static string $template;
61
62
    public readonly string $identifier;
63
    public readonly string $routeKey;
64
65
    public FrontMatter $matter;
66
    public PageMetadataBag $metadata;
67
    public NavigationData $navigation;
68
69
    public string $title;
70
    public ?string $canonicalUrl;
71
72
    public static function make(string $identifier = '', FrontMatter|array $matter = []): static
73
    {
74
        return new static($identifier, $matter);
75
    }
76
77
    public function __construct(string $identifier = '', FrontMatter|array $matter = [])
78
    {
79
        $this->identifier = $identifier;
0 ignored issues
show
Bug introduced by
The property identifier is declared read-only in Hyde\Pages\Concerns\HydePage.
Loading history...
80
        $this->routeKey = RouteKey::fromPage(static::class, $identifier)->get();
0 ignored issues
show
Bug introduced by
The property routeKey is declared read-only in Hyde\Pages\Concerns\HydePage.
Loading history...
81
        $this->matter = $matter instanceof FrontMatter ? $matter : new FrontMatter($matter);
0 ignored issues
show
introduced by
$matter is never a sub-type of Hyde\Markdown\Models\FrontMatter.
Loading history...
82
83
        $this->constructFactoryData();
84
        $this->constructMetadata();
85
    }
86
87
    // Section: State
88
89
    public static function isDiscoverable(): bool
90
    {
91
        return isset(static::$sourceDirectory, static::$outputDirectory, static::$fileExtension) && filled(static::$sourceDirectory);
92
    }
93
94
    // Section: Query
95
96
    /**
97
     * Get a page instance from the Kernel's page index by its identifier.
98
     *
99
     *
100
     * @throws \Hyde\Framework\Exceptions\FileNotFoundException If the page does not exist.
101
     */
102
    public static function get(string $identifier): HydePage
103
    {
104
        return Pages::getPage(static::sourcePath($identifier));
0 ignored issues
show
Bug introduced by
The method getPage() does not exist on Hyde\Foundation\Facades\Pages. Since you implemented __callStatic, consider adding a @method annotation. ( Ignorable by Annotation )

If this is a false-positive, you can also ignore this issue in your code via the ignore-call  annotation

104
        return Pages::/** @scrutinizer ignore-call */ getPage(static::sourcePath($identifier));
Loading history...
105
    }
106
107
    /**
108
     * Parse a source file into a page model instance.
109
     *
110
     * @param  string  $identifier  The identifier of the page to parse.
111
     * @return static New page model instance for the parsed source file.
112
     *
113
     * @throws \Hyde\Framework\Exceptions\FileNotFoundException If the file does not exist.
114
     */
115
    public static function parse(string $identifier): HydePage
116
    {
117
        return (new SourceFileParser(static::class, $identifier))->get();
118
    }
119
120
    /**
121
     * Get an array of all the source file identifiers for the model.
122
     *
123
     * Note that the values do not include the source directory or file extension.
124
     *
125
     * @return array<string>
126
     */
127
    public static function files(): array
128
    {
129
        return Files::getFiles(static::class)->map(function (SourceFile $file): string {
0 ignored issues
show
Bug introduced by
The method getFiles() does not exist on Hyde\Foundation\Facades\Files. Since you implemented __callStatic, consider adding a @method annotation. ( Ignorable by Annotation )

If this is a false-positive, you can also ignore this issue in your code via the ignore-call  annotation

129
        return Files::/** @scrutinizer ignore-call */ getFiles(static::class)->map(function (SourceFile $file): string {
Loading history...
130
            return static::pathToIdentifier($file->getPath());
131
        })->values()->toArray();
132
    }
133
134
    /**
135
     * Get a collection of all pages, parsed into page models.
136
     *
137
     * @return \Hyde\Foundation\Kernel\PageCollection<static>
138
     */
139
    public static function all(): PageCollection
140
    {
141
        return Facades\Pages::getPages(static::class);
0 ignored issues
show
Bug introduced by
The method getPages() does not exist on Hyde\Foundation\Facades\Pages. Since you implemented __callStatic, consider adding a @method annotation. ( Ignorable by Annotation )

If this is a false-positive, you can also ignore this issue in your code via the ignore-call  annotation

141
        return Facades\Pages::/** @scrutinizer ignore-call */ getPages(static::class);
Loading history...
142
    }
143
144
    // Section: Filesystem
145
146
    /**
147
     * Get the directory in where source files are stored.
148
     */
149
    public static function sourceDirectory(): string
150
    {
151
        return static::$sourceDirectory ?? Hyde::getSourceRoot();
152
    }
153
154
    /**
155
     * Get the output subdirectory to store compiled HTML.
156
     */
157
    public static function outputDirectory(): string
158
    {
159
        return static::$outputDirectory ?? '';
160
    }
161
162
    /**
163
     * Get the file extension of the source files.
164
     */
165
    public static function fileExtension(): string
166
    {
167
        return static::$fileExtension ?? '';
168
    }
169
170
    /**
171
     * Set the output directory for the HydePage class.
172
     */
173
    public static function setSourceDirectory(string $sourceDirectory): void
174
    {
175
        static::$sourceDirectory = unslash($sourceDirectory);
176
    }
177
178
    /**
179
     * Set the source directory for the HydePage class.
180
     */
181
    public static function setOutputDirectory(string $outputDirectory): void
182
    {
183
        static::$outputDirectory = unslash($outputDirectory);
184
    }
185
186
    /**
187
     * Set the file extension for the HydePage class.
188
     */
189
    public static function setFileExtension(string $fileExtension): void
190
    {
191
        static::$fileExtension = rtrim('.'.ltrim($fileExtension, '.'), '.');
192
    }
193
194
    /**
195
     * Qualify a page identifier into a local file path for the page source file relative to the project root.
196
     */
197
    public static function sourcePath(string $identifier): string
198
    {
199
        return unslash(static::sourceDirectory().'/'.unslash($identifier).static::fileExtension());
200
    }
201
202
    /**
203
     * Qualify a page identifier into a target output file path relative to the _site output directory.
204
     */
205
    public static function outputPath(string $identifier): string
206
    {
207
        return RouteKey::fromPage(static::class, $identifier).'.html';
208
    }
209
210
    /**
211
     * Get an absolute file path to the page's source directory, or a file within it.
212
     */
213
    public static function path(string $path = ''): string
214
    {
215
        return Hyde::path(unslash(static::sourceDirectory().'/'.unslash($path)));
216
    }
217
218
    /**
219
     * Format a filename to an identifier for a given model. Unlike the basename function, any nested paths
220
     * within the source directory are retained in order to satisfy the page identifier definition.
221
     *
222
     * @param  string  $path  Example: index.blade.php
223
     * @return string Example: index
224
     */
225
    public static function pathToIdentifier(string $path): string
226
    {
227
        return unslash(Str::between(Hyde::pathToRelative($path),
228
            static::sourceDirectory().'/',
229
            static::fileExtension())
230
        );
231
    }
232
233
    /**
234
     * Get the route key base for the page model.
235
     */
236
    public static function baseRouteKey(): string
237
    {
238
        return static::outputDirectory();
239
    }
240
241
    /**
242
     * Compile the page into static HTML.
243
     *
244
     * @return string The compiled HTML for the page.
245
     */
246
    abstract public function compile(): string;
247
248
    /**
249
     * Get the instance as an array.
250
     */
251
    public function toArray(): array
252
    {
253
        return [
254
            'class' => static::class,
255
            'identifier' => $this->identifier,
256
            'routeKey' => $this->routeKey,
257
            'matter' => $this->matter,
258
            'metadata' => $this->metadata,
259
            'navigation' => $this->navigation,
260
            'title' => $this->title,
261
            'canonicalUrl' => $this->canonicalUrl,
262
        ];
263
    }
264
265
    /**
266
     * Get the path to the instance source file, relative to the project root.
267
     */
268
    public function getSourcePath(): string
269
    {
270
        return unslash(static::sourcePath($this->identifier));
271
    }
272
273
    /**
274
     * Get the path where the compiled page will be saved.
275
     *
276
     * @return string Path relative to the site output directory.
277
     */
278
    public function getOutputPath(): string
279
    {
280
        return unslash(static::outputPath($this->identifier));
281
    }
282
283
    // Section: Routing
284
285
    /**
286
     * Get the route key for the page.
287
     *
288
     * The route key is the URL path relative to the site root.
289
     *
290
     * For example, if the compiled page will be saved to _site/docs/index.html,
291
     * then this method will return 'docs/index'. Route keys are used to
292
     * identify pages, similar to how named routes work in Laravel,
293
     * only that here the name is not just arbitrary,
294
     * but also defines the output location.
295
     *
296
     * @return string The page's route key.
297
     */
298
    public function getRouteKey(): string
299
    {
300
        return $this->routeKey;
301
    }
302
303
    /**
304
     * Get the route for the page.
305
     *
306
     * @return \Hyde\Support\Models\Route The page's route.
307
     */
308
    public function getRoute(): Route
309
    {
310
        return Routes::get($this->getRouteKey()) ?? new Route($this);
311
    }
312
313
    /**
314
     * Format the page instance to a URL path (relative to site root) with support for pretty URLs if enabled.
315
     */
316
    public function getLink(): string
317
    {
318
        return Hyde::formatLink($this->getOutputPath());
319
    }
320
321
    // Section: Getters
322
323
    /**
324
     * Get the page model's identifier property.
325
     *
326
     * The identifier is the part between the source directory and the file extension.
327
     * It may also be known as a 'slug', or previously 'basename'.
328
     *
329
     * For example, the identifier of a source file stored as '_pages/about/contact.md'
330
     * would be 'about/contact', and 'pages/about.md' would simply be 'about'.
331
     *
332
     * @return string The page's identifier.
333
     */
334
    public function getIdentifier(): string
335
    {
336
        return $this->identifier;
337
    }
338
339
    /**
340
     * Get the Blade template for the page.
341
     *
342
     * @return string Blade template/view key.
343
     */
344
    public function getBladeView(): string
345
    {
346
        return static::$template;
347
    }
348
349
    // Section: Accessors
350
351
    /**
352
     * Get the page title to display in HTML tags like <title> and <meta> tags.
353
     */
354
    public function title(): string
355
    {
356
        return Config::getString('hyde.name', 'HydePHP').' - '.$this->title;
357
    }
358
359
    public function metadata(): PageMetadataBag
360
    {
361
        return $this->metadata;
362
    }
363
364
    public function showInNavigation(): bool
365
    {
366
        return ! $this->navigation->hidden;
367
    }
368
369
    public function navigationMenuPriority(): int
370
    {
371
        return $this->navigation->priority;
372
    }
373
374
    public function navigationMenuLabel(): string
375
    {
376
        return $this->navigation->label;
377
    }
378
379
    public function navigationMenuGroup(): ?string
380
    {
381
        return $this->navigation->group;
382
    }
383
384
    protected function constructMetadata(): void
385
    {
386
        $this->metadata = new PageMetadataBag($this);
387
    }
388
}
389