Code Coverage
 
Lines
Functions and Methods
Classes and Traits
Total
100.00% covered (success)
100.00%
23 / 23
100.00% covered (success)
100.00%
9 / 9
CRAP
100.00% covered (success)
100.00%
1 / 1
InMemoryPage
100.00% covered (success)
100.00%
23 / 23
100.00% covered (success)
100.00%
9 / 9
15
100.00% covered (success)
100.00%
1 / 1
 make
100.00% covered (success)
100.00%
1 / 1
100.00% covered (success)
100.00%
1 / 1
1
 __construct
100.00% covered (success)
100.00%
3 / 3
100.00% covered (success)
100.00%
1 / 1
1
 getContents
100.00% covered (success)
100.00%
1 / 1
100.00% covered (success)
100.00%
1 / 1
1
 getBladeView
100.00% covered (success)
100.00%
1 / 1
100.00% covered (success)
100.00%
1 / 1
1
 compile
100.00% covered (success)
100.00%
7 / 7
100.00% covered (success)
100.00%
1 / 1
5
 macro
100.00% covered (success)
100.00%
1 / 1
100.00% covered (success)
100.00%
1 / 1
1
 hasMacro
100.00% covered (success)
100.00%
1 / 1
100.00% covered (success)
100.00%
1 / 1
1
 __call
100.00% covered (success)
100.00%
5 / 5
100.00% covered (success)
100.00%
1 / 1
2
 callMacro
100.00% covered (success)
100.00%
3 / 3
100.00% covered (success)
100.00%
1 / 1
2
1<?php
2
3declare(strict_types=1);
4
5namespace Hyde\Pages;
6
7use BadMethodCallException;
8use Closure;
9use Hyde\Framework\Actions\AnonymousViewCompiler;
10use Hyde\Markdown\Models\FrontMatter;
11use Hyde\Pages\Concerns\HydePage;
12use Illuminate\Support\Facades\View;
13
14use function sprintf;
15
16/**
17 * Extendable class for in-memory (or virtual) Hyde pages that are not based on any source files.
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 */
26class 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     *                              Take note that the identifier must be unique to prevent overwriting other pages.
60     * @param  \Hyde\Markdown\Models\FrontMatter|array  $matter  The front matter of the page. When using the Blade view rendering option,
61     *                                                           all this data will be passed to the view rendering engine.
62     * @param  string  $contents  The contents of the page. This will be saved as-is to the output file.
63     * @param  string  $view  The view key or Blade file for the view to use to render the page contents.
64     */
65    public function __construct(string $identifier = '', FrontMatter|array $matter = [], string $contents = '', string $view = '')
66    {
67        parent::__construct($identifier, $matter);
68
69        $this->contents = $contents;
70        $this->view = $view;
71    }
72
73    /** Get the contents of the page. This will be saved as-is to the output file when this strategy is used. */
74    public function getContents(): string
75    {
76        return $this->contents;
77    }
78
79    /** Get the view key or Blade file for the view to use to render the page contents when this strategy is used. */
80    public function getBladeView(): string
81    {
82        return $this->view;
83    }
84
85    /**
86     * Get the contents that will be saved to disk for this page.
87     *
88     * In order to make your virtual page easy to use we provide a few options for how the page can be compiled.
89     * If you want even more control, you can register a macro with the name 'compile' to overload the method,
90     * or simply extend the class and override the method yourself, either in a standard or anonymous class.
91     */
92    public function compile(): string
93    {
94        if ($this->hasMacro('compile')) {
95            return $this->__call('compile', []);
96        }
97
98        if ($this->getBladeView() && ! $this->getContents()) {
99            if (str_ends_with($this->getBladeView(), '.blade.php')) {
100                // If the view key is for a Blade file path, we'll use the anonymous view compiler to compile it.
101                // This allows you to use any arbitrary file, without needing to register its namespace or directory.
102                return AnonymousViewCompiler::handle($this->getBladeView(), $this->matter->toArray());
103            }
104
105            return View::make($this->getBladeView(), $this->matter->toArray())->render();
106        }
107
108        // If there's no macro or view configured, we'll just return the contents as-is.
109        return $this->getContents();
110    }
111
112    /**
113     * Register a macro for the instance.
114     *
115     * Unlike most macros you might be used to, these are not static, meaning they belong to the instance.
116     * If you have the need for a macro to be used for multiple pages, you should create a custom page class instead.
117     */
118    public function macro(string $name, callable $macro): void
119    {
120        $this->macros[$name] = $macro;
121    }
122
123    /**
124     * Determine if a macro with the given name is registered for the instance.
125     */
126    public function hasMacro(string $method): bool
127    {
128        return isset($this->macros[$method]);
129    }
130
131    /**
132     * Dynamically handle macro calls to the class.
133     */
134    public function __call(string $method, array $parameters): mixed
135    {
136        if (! $this->hasMacro($method)) {
137            throw new BadMethodCallException(sprintf(
138                'Method %s::%s does not exist.', static::class, $method
139            ));
140        }
141
142        return $this->callMacro($this->macros[$method], $parameters);
143    }
144
145    protected function callMacro(callable $macro, array $parameters): mixed
146    {
147        if ($macro instanceof Closure) {
148            $macro = $macro->bindTo($this, static::class);
149        }
150
151        return $macro(...$parameters);
152    }
153}