Completed
Pull Request — master (#25)
by Alexander
01:37
created

Page::setContentType()   A

Complexity

Conditions 1
Paths 1

Size

Total Lines 4
Code Lines 2

Duplication

Lines 0
Ratio 0 %

Importance

Changes 0
Metric Value
cc 1
eloc 2
nc 1
nop 1
dl 0
loc 4
rs 10
c 0
b 0
f 0
1
<?php
2
3
namespace alkemann\h2l\response;
4
5
use alkemann\h2l\exceptions\InvalidUrl;
6
use alkemann\h2l\exceptions\ConfigMissing;
7
use alkemann\h2l\Log;
8
use alkemann\h2l\Message;
9
use alkemann\h2l\Request;
10
use alkemann\h2l\Response;
11
use alkemann\h2l\Environment;
12
use alkemann\h2l\util\Http;
13
14
/**
15
 * Class Page
16
 *
17
 * @package alkemann\h2l
18
 */
19
class Page extends Response
20
{
21
    /**
22
     * Overwrite this in view templates to set layout, i.e. `$this->layout = 'slim';`
23
     *
24
     * @var string
25
     */
26
    public $layout = 'default';
27
    /**
28
     * @var Request
29
     */
30
    protected $request;
31
    /**
32
     * @var array
33
     */
34
    protected $data = [];
35
    /**
36
     * @var string
37
     */
38
    private $template = 'error';
39
    /**
40
     * @var string
41
     */
42
    private $content_type = Http::CONTENT_HTML;
43
    /**
44
     * @var int
45
     */
46
    private $code = 200;
47
    /**
48
     * @var array
49
     */
50
    protected $config = [];
51
52
    public function __construct($data = [], array $config = [])
53
    {
54
        $this->data = $data;
55
        foreach (['request', 'content_type', 'code'] as $key) {
56
            if (isset($config[$key])) {
57
                $this->{$key} = $config[$key];
58
                unset($config[$key]);
59
            }
60
        }
61
        if (isset($config['template'])) {
62
            $this->setTemplate($config['template']);
63
        }
64
        $this->config = $config;
65
66
        $this->message = (new Message())
67
            ->withCode($this->code)
68
            ->withHeader('Content-Type', $this->content_type)
69
        ;
70
    }
71
72
    /**
73
     * Analyze the request url to convert to a view template
74
     *
75
     * @param Request $request
76
     * @param array $config
77
     * @return Page
78
     */
79
    public static function fromRequest(Request $request, array $config = []): Page
80
    {
81
        $config += [
82
            'request' => $request,
83
            'content_type' => $request->acceptType(),
84
        ];
85
        $page = new static([], $config);
86
        $route = $request->route();
87
        $url = $route ? $route->url() : $request->url();
88
        $page->template = $config['template'] ?? $page->templateFromUrl($url);
89
        return $page;
90
    }
91
92
    /**
93
     * @return bool
94
     * @throws InvalidUrl
95
     */
96
    public function isValid(): bool
97
    {
98
        $file = $this->getContentFile($this->template);
99
        if (!file_exists($file)) {
100
            throw new InvalidUrl("Missing view file: [ $file ]");
101
        }
102
        return true;
103
    }
104
105
    /**
106
     * Provide data (variables) that are to be extracted into the view (and layout) templates
107
     *
108
     * @param string|array $key an array of data or the name for $value
109
     * @param mixed $value if $key is a string, this can be the value of that var
110
     */
111
    public function setData($key, $value = null): void
112
    {
113
        if (is_array($key)) {
114
            foreach ($key as $k => $v) {
115
                $this->data[$k] = $v;
116
            }
117
        } else {
118
            $this->data[$key] = $value;
119
        }
120
    }
121
122
    public function request(): ?Request
123
    {
124
        return $this->request;
125
    }
126
127
    // @TODO refactor, and cache
128
    private function head(): string
129
    {
130
        ob_start();
131
        try {
132
            $headfile = $this->getLayoutFile('head');
133
            if (file_exists($headfile)) {
134
                (function ($sldkfjlksejflskjflskdjflskdfj) {
135
                    extract($this->data);
136
                    include $sldkfjlksejflskjflskdjflskdfj;
137
                })($headfile);
138
            }
139
140
            $neckfile = $this->getLayoutFile('neck');
141
            if (file_exists($neckfile)) {
142
                (function ($lidsinqjhsdfytqkwjkasjdksadsdg) {
143
                    extract($this->data);
144
                    include $lidsinqjhsdfytqkwjkasjdksadsdg;
145
                })($neckfile);
146
            }
147
        } finally {
148
            $ret = ob_get_contents();
149
            ob_end_clean();
150
        }
151
        return $ret;
152
    }
153
154
    private function getLayoutFile(string $name): string
155
    {
156
        $path = $this->config['layout_path'] ?? Environment::get('layout_path');
157
        if (is_null($path)) {
158
            Log::debug("As there is no configured `layout_path` in Environment, layouts are skipped");
159
        }
160
        $ending = Http::fileEndingFromType($this->content_type);
161
        return $path . $this->layout . DIRECTORY_SEPARATOR . $name . '.' . $ending . '.php';
162
    }
163
164
    private function getContentFile(string $view): string
165
    {
166
        $path = $this->config['content_path'] ?? Environment::get('content_path');
167
        if (is_null($path)) {
168
            throw new ConfigMissing("Page requires a `content_path` in Environment");
169
        }
170
        return $path . $view . '.php';
171
    }
172
173
    // @TODO refactor, and cache
174
    private function foot(): string
175
    {
176
        $footfile = $this->getLayoutFile('foot');
177
        if (!file_exists($footfile)) {
178
            return '';
179
        }
180
181
        ob_start();
182
        try {
183
            (function ($ldkfoskdfosjicyvutwehkshfskjdf) {
184
                extract($this->data);
185
                include $ldkfoskdfosjicyvutwehkshfskjdf;
186
            })($footfile);
187
        } finally {
188
            $ret = ob_get_contents();
189
            ob_end_clean();
190
        }
191
        return $ret;
192
    }
193
194
    // @TODO refactor, and cache
195
196
    /**
197
     * @param string $view
198
     * @return string
199
     * @throws InvalidUrl
200
     */
201
    public function view(string $view): string
202
    {
203
        $file = $this->getContentFile($view);
204
        ob_start();
205
        try {
206
            // or another way to hide the file variable?
207
            (function ($dsfjskdfjsdlkfjsdkfjsdkfjsdlkfjsd) {
208
                extract($this->data);
209
                include $dsfjskdfjsdlkfjsdkfjsdkfjsdlkfjsd;
210
            })($file);
211
        } finally {
212
            $ret = ob_get_contents();
213
            ob_end_clean();
214
        }
215
        return $ret;
216
    }
217
218
    /**
219
     * Set header type, render the view, then optionally render layouts and wrap the template
220
     *
221
     * @return string fully rendered string, ready to be echo'ed
222
     * @throws InvalidUrl if the view template does not exist
223
     */
224
    public function render(): string
225
    {
226
        $this->setHeaders();
227
        $view = $this->view($this->template);
228
        $response = $this->head();
229
        $response .= $view;
230
        $response .= $this->foot();
231
        $this->message = $this->message->withBody($response);
232
        return $this->message->body();
0 ignored issues
show
Bug Best Practice introduced by
The expression return $this->message->body() could return the type null which is incompatible with the type-hinted return string. Consider adding an additional type-check to rule them out.
Loading history...
233
    }
234
235
    /**
236
     * @param string $template
237
     */
238
    public function setTemplate(string $template): void
239
    {
240
        $ending = Http::fileEndingFromType($this->content_type);
241
        $this->template = "{$template}.{$ending}";
242
    }
243
244
    /**
245
     * @param null|string $url
246
     * @return string
247
     */
248
    private function templateFromUrl(?string $url = null): string
249
    {
250
        $parts = \explode('/', $url);
251
        $last = \array_slice($parts, -1, 1, true);
252
        unset($parts[key($last)]);
253
        $view = current($last);
254
        $period = strrpos($view, '.');
255
        if ($period) {
256
            $ending = substr($view, $period + 1);
257
            if ($type = Http::contentTypeFromFileEnding($ending)) {
258
                $this->setContentType($type);
259
                $view = substr($view, 0, $period);
260
            }
261
        }
262
        $ending = Http::fileEndingFromType($this->content_type);
263
        $ret = join(DIRECTORY_SEPARATOR, $parts) . DIRECTORY_SEPARATOR . $view . '.' . $ending;
264
        return trim($ret, DIRECTORY_SEPARATOR);
265
    }
266
267
    private function setContentType($type)
268
    {
269
        $this->content_type = $type;
270
        $this->message = $this->message->withHeader('Content-Type', $this->content_type);
271
    }
272
}
273