Passed
Push — master ( 1e39e8...4523d8 )
by Caen
03:01 queued 12s
created

BladeMatterParser   A

Complexity

Total Complexity 12

Size/Duplication

Total Lines 91
Duplicated Lines 0 %

Importance

Changes 14
Bugs 0 Features 0
Metric Value
eloc 26
c 14
b 0
f 0
dl 0
loc 91
rs 10
wmc 12

9 Methods

Rating   Name   Duplication   Size   Complexity  
A get() 0 3 1
A normalizeValue() 0 9 2
A parse() 0 13 3
A __construct() 0 3 1
A parseString() 0 3 1
A parseFile() 0 3 1
A extractKey() 0 10 1
A extractValue() 0 16 1
A lineMatchesFrontMatter() 0 3 1
1
<?php
2
3
namespace Hyde\Framework\Actions;
4
5
use Hyde\Framework\Hyde;
6
7
/**
8
 * Parse the front matter in a Blade file.
9
 *
10
 * Accepts a string to make it easier to mock when testing.
11
 *
12
 * @see \Hyde\Framework\Testing\Feature\BladeMatterParserTest
13
 *
14
 * === DOCUMENTATION (draft) ===
15
 *
16
 * ## Front Matter in Markdown
17
 *
18
 * HydePHP uses a special syntax called BladeMatter that allows you to define variables in a Blade file,
19
 * and have Hyde statically parse them into the front matter of the page model. This allows metadata
20
 * in your Blade pages to be used when Hyde generates dynamic data like page titles and SEO tags.
21
 *
22
 * ### Syntax
23
 *
24
 * Any line following the syntax below will be added to the parsed page object's front matter.
25
 *
26
 * @example `@php($title = 'BladeMatter Test')`
27
 * This would then be parsed into the following array in the page model: ['title' => 'BladeMatter Test']
28
 *
29
 * ### Limitations
30
 * Each directive must be on its own line, and start with `@php($.`. Arrays are currently not supported.
31
 */
32
class BladeMatterParser
33
{
34
    protected string $contents;
35
    protected array $matter;
36
37
    /** The directive signature used to determine if a line should be parsed. */
38
    protected const SEARCH = '@php($';
39
40
    public static function parseFile(string $localFilePath): array
41
    {
42
        return static::parseString(file_get_contents(Hyde::path($localFilePath)));
43
    }
44
45
    public static function parseString(string $contents): array
46
    {
47
        return (new static($contents))->parse()->get();
48
    }
49
50
    public function __construct(string $contents)
51
    {
52
        $this->contents = $contents;
53
    }
54
55
    public function get(): array
56
    {
57
        return $this->matter;
58
    }
59
60
    public function parse(): static
61
    {
62
        $this->matter = [];
63
64
        $lines = explode("\n", $this->contents);
65
66
        foreach ($lines as $line) {
67
            if (static::lineMatchesFrontMatter($line)) {
68
                $this->matter[static::extractKey($line)] = static::normalizeValue(static::extractValue($line));
69
            }
70
        }
71
72
        return $this;
73
    }
74
75
    /** @internal */
76
    public static function lineMatchesFrontMatter(string $line): bool
77
    {
78
        return str_starts_with($line, static::SEARCH);
79
    }
80
81
    /** @internal */
82
    public static function extractKey(string $line): string
83
    {
84
        // Remove search prefix
85
        $key = substr($line, strlen(static::SEARCH));
86
87
        // Remove everything after the first equals sign
88
        $key = substr($key, 0, strpos($key, '='));
89
90
        // Return trimmed line
91
        return trim($key);
92
    }
93
94
    /** @internal */
95
    public static function extractValue(string $line): string
96
    {
97
        // Trim any trailing spaces and newlines
98
        $key = trim($line);
99
100
        // Remove everything before the first equals sign
101
        $key = substr($key, strpos($key, '=') + 1);
102
103
        // Remove closing parenthesis
104
        $key = substr($key, 0, strlen($key) - 1);
105
106
        // Remove any quotes so we can normalize the value
107
        $key = trim($key, ' "\'');
108
109
        // Return trimmed line
110
        return trim($key);
111
    }
112
113
    /** @internal */
114
    public static function normalizeValue($value): mixed
115
    {
116
        if ($value === 'null') {
117
            return null;
118
        }
119
120
        // This will cast integers, floats, and booleans to their respective types
121
        // Still working on a way to handle arrays and objects
122
        return json_decode($value) ?? $value;
123
    }
124
}
125