Passed
Push — master ( aadbf6...2ee406 )
by Caen
03:41 queued 12s
created

HydePage::__construct()   A

Complexity

Conditions 2
Paths 2

Size

Total Lines 8
Code Lines 5

Duplication

Lines 0
Ratio 0 %

Importance

Changes 0
Metric Value
eloc 5
dl 0
loc 8
rs 10
c 0
b 0
f 0
cc 2
nc 2
nop 2
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 readonly string $title;
70
71
    public static function make(string $identifier = '', FrontMatter|array $matter = []): static
72
    {
73
        return new static($identifier, $matter);
74
    }
75
76
    public function __construct(string $identifier = '', FrontMatter|array $matter = [])
77
    {
78
        $this->identifier = $identifier;
0 ignored issues
show
Bug introduced by
The property identifier is declared read-only in Hyde\Pages\Concerns\HydePage.
Loading history...
79
        $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...
80
        $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...
81
82
        $this->constructFactoryData();
83
        $this->constructMetadata();
84
    }
85
86
    // Section: State
87
88
    public static function isDiscoverable(): bool
89
    {
90
        return isset(static::$sourceDirectory, static::$outputDirectory, static::$fileExtension) && filled(static::$sourceDirectory);
91
    }
92
93
    // Section: Query
94
95
    /**
96
     * Get a page instance from the Kernel's page index by its identifier.
97
     *
98
     *
99
     * @throws \Hyde\Framework\Exceptions\FileNotFoundException If the page does not exist.
100
     */
101
    public static function get(string $identifier): HydePage
102
    {
103
        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

103
        return Pages::/** @scrutinizer ignore-call */ getPage(static::sourcePath($identifier));
Loading history...
104
    }
105
106
    /**
107
     * Parse a source file into a page model instance.
108
     *
109
     * @param  string  $identifier  The identifier of the page to parse.
110
     * @return static New page model instance for the parsed source file.
111
     *
112
     * @throws \Hyde\Framework\Exceptions\FileNotFoundException If the file does not exist.
113
     */
114
    public static function parse(string $identifier): HydePage
115
    {
116
        return (new SourceFileParser(static::class, $identifier))->get();
117
    }
118
119
    /**
120
     * Get an array of all the source file identifiers for the model.
121
     *
122
     * Note that the values do not include the source directory or file extension.
123
     *
124
     * @return array<string>
125
     */
126
    public static function files(): array
127
    {
128
        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

128
        return Files::/** @scrutinizer ignore-call */ getFiles(static::class)->map(function (SourceFile $file): string {
Loading history...
129
            return static::pathToIdentifier($file->getPath());
130
        })->values()->toArray();
131
    }
132
133
    /**
134
     * Get a collection of all pages, parsed into page models.
135
     *
136
     * @return \Hyde\Foundation\Kernel\PageCollection<static>
137
     */
138
    public static function all(): PageCollection
139
    {
140
        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

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