Passed
Push — master ( 1f8afc...62c53d )
by Caen
03:16 queued 12s
created

InMemoryPage::__call()   A

Complexity

Conditions 2
Paths 2

Size

Total Lines 9
Code Lines 4

Duplication

Lines 0
Ratio 0 %

Importance

Changes 1
Bugs 0 Features 0
Metric Value
cc 2
eloc 4
nc 2
nop 2
dl 0
loc 9
rs 10
c 1
b 0
f 0
1
<?php
2
3
declare(strict_types=1);
4
5
namespace Hyde\Pages;
6
7
use BadMethodCallException;
8
use Closure;
9
use Hyde\Framework\Actions\AnonymousViewCompiler;
10
use Hyde\Markdown\Models\FrontMatter;
11
use Hyde\Pages\Concerns\HydePage;
12
use Illuminate\Support\Facades\View;
13
14
/**
15
 * Extendable class for in-memory (or virtual) Hyde pages that are not based on any source files.
16
 *
17
 * @experimental This feature is experimental and may change substantially before the 1.0.0 release.
18
 *
19
 * When used in a package, it's on the package developer to ensure that the virtual page is registered with Hyde,
20
 * usually within the boot method of the package's service provider, or a page collection callback in an extension.
21
 * This is because these pages cannot be discovered by the auto discovery process as there's no source files to parse.
22
 *
23
 * This class is especially useful for one-off custom pages. But if your usage grows, or if you want to utilize
24
 * Hyde autodiscovery, you may benefit from creating a custom page class instead, as that will give you full control.
25
 */
26
class InMemoryPage extends HydePage
27
{
28
    public static string $sourceDirectory;
29
    public static string $outputDirectory;
30
    public static string $fileExtension;
31
32
    protected string $contents;
33
    protected string $view;
34
35
    /** @var array<string, callable> */
36
    protected array $macros = [];
37
38
    /**
39
     * Static alias for the constructor.
40
     */
41
    public static function make(string $identifier = '', FrontMatter|array $matter = [], string $contents = '', string $view = ''): static
42
    {
43
        return new static($identifier, $matter, $contents, $view);
44
    }
45
46
    /**
47
     * Create a new in-memory/virtual page instance.
48
     *
49
     * The in-memory page class offers two content options. You can either pass a string to the $contents parameter,
50
     * Hyde will then save that literally as the page's contents. Alternatively, you can pass a view name to the $view parameter,
51
     * and Hyde will use that view to render the page contents with the supplied front matter during the static site build process.
52
     *
53
     * Note that $contents take precedence over $view, so if you pass both, only $contents will be used.
54
     * You can also register a macro with the name 'compile' to overload the default compile method.
55
     *
56
     * @param  string  $identifier  The identifier of the page. This is used to generate the route key which is used to create the output filename.
57
     *                              If the identifier for an in-memory page is "foo/bar" the page will be saved to "_site/foo/bar.html".
58
     *                              You can then also use the route helper to get a link to it by using the route key "foo/bar".
59
     * @param  \Hyde\Markdown\Models\FrontMatter|array  $matter  The front matter of the page. When using the Blade view rendering option,
60
     *                                                           all this data will be passed to the view rendering engine.
61
     * @param  string  $contents  The contents of the page. This will be saved as-is to the output file.
62
     * @param  string  $view  The view key or Blade file for the view to use to render the page contents.
63
     */
64
    public function __construct(string $identifier, FrontMatter|array $matter = [], string $contents = '', string $view = '')
65
    {
66
        parent::__construct($identifier, $matter);
67
68
        $this->contents = $contents;
69
        $this->view = $view;
70
    }
71
72
    /** Get the contents of the page. This will be saved as-is to the output file when this strategy is used. */
73
    public function getContents(): string
74
    {
75
        return $this->contents;
76
    }
77
78
    /** Get the view key or Blade file for the view to use to render the page contents when this strategy is used. */
79
    public function getBladeView(): string
80
    {
81
        return $this->view;
82
    }
83
84
    /**
85
     * Get the contents that will be saved to disk for this page.
86
     *
87
     * In order to make your virtual page easy to use we provide a few options for how the page can be compiled.
88
     * If you want even more control, you can register a macro with the name 'compile' to overload the method,
89
     * or simply extend the class and override the method yourself, either in a standard or anonymous class.
90
     */
91
    public function compile(): string
92
    {
93
        if ($this->hasMacro('compile')) {
94
            return $this->__call('compile', []);
95
        }
96
97
        if ($this->getBladeView() && ! $this->getContents()) {
98
            if (str_ends_with($this->getBladeView(), '.blade.php')) {
99
                // If the view key is for a Blade file path, we'll use the anonymous view compiler to compile it.
100
                // This allows you to use any arbitrary file, without needing to register its namespace or directory.
101
                return AnonymousViewCompiler::call($this->getBladeView(), $this->matter->toArray());
102
            }
103
104
            return View::make($this->getBladeView(), $this->matter->toArray())->render();
105
        }
106
107
        // If there's no macro or view configured, we'll just return the contents as-is.
108
        return $this->getContents();
109
    }
110
111
    /**
112
     * Register a macro for the instance.
113
     *
114
     * Unlike most macros you might be used to, these are not static, meaning they belong to the instance.
115
     * If you have the need for a macro to be used for multiple pages, you should create a custom page class instead.
116
     */
117
    public function macro(string $name, callable $macro): void
118
    {
119
        $this->macros[$name] = $macro;
120
    }
121
122
    /**
123
     * Determine if a macro with the given name is registered for the instance.
124
     */
125
    public function hasMacro(string $method): bool
126
    {
127
        return isset($this->macros[$method]);
128
    }
129
130
    /**
131
     * Dynamically handle macro calls to the class.
132
     */
133
    public function __call(string $method, array $parameters): mixed
134
    {
135
        if (! $this->hasMacro($method)) {
136
            throw new BadMethodCallException(sprintf(
137
                'Method %s::%s does not exist.', static::class, $method
138
            ));
139
        }
140
141
        return $this->callMacro($this->macros[$method], $parameters);
142
    }
143
144
    protected function callMacro(callable $macro, array $parameters): mixed
145
    {
146
        if ($macro instanceof Closure) {
147
            $macro = $macro->bindTo($this, static::class);
148
        }
149
150
        return $macro(...$parameters);
151
    }
152
}
153