Completed
Push — update-to-php-71 ( f475af )
by Alexander
15:41 queued 04:41
created

View::init()   A

Complexity

Conditions 4
Paths 4

Size

Total Lines 12
Code Lines 8

Duplication

Lines 0
Ratio 0 %

Code Coverage

Tests 5
CRAP Score 5.4042

Importance

Changes 0
Metric Value
dl 0
loc 12
ccs 5
cts 9
cp 0.5556
rs 9.2
c 0
b 0
f 0
cc 4
eloc 8
nc 4
nop 0
crap 5.4042
1
<?php
2
/**
3
 * @link http://www.yiiframework.com/
4
 * @copyright Copyright (c) 2008 Yii Software LLC
5
 * @license http://www.yiiframework.com/license/
6
 */
7
8
namespace yii\base;
9
10
use Yii;
11
use yii\helpers\FileHelper;
12
use yii\widgets\Block;
13
use yii\widgets\ContentDecorator;
14
use yii\widgets\FragmentCache;
15
16
/**
17
 * View represents a view object in the MVC pattern.
18
 *
19
 * View provides a set of methods (e.g. [[render()]]) for rendering purpose.
20
 *
21
 * For more details and usage information on View, see the [guide article on views](guide:structure-views).
22
 *
23
 * @property string|bool $viewFile The view file currently being rendered. False if no view file is being
24
 * rendered. This property is read-only.
25
 *
26
 * @author Qiang Xue <[email protected]>
27
 * @since 2.0
28
 */
29
class View extends Component
30
{
31
    /**
32
     * @event Event an event that is triggered by [[beginPage()]].
33
     */
34
    const EVENT_BEGIN_PAGE = 'beginPage';
35
    /**
36
     * @event Event an event that is triggered by [[endPage()]].
37
     */
38
    const EVENT_END_PAGE = 'endPage';
39
    /**
40
     * @event ViewEvent an event that is triggered by [[renderFile()]] right before it renders a view file.
41
     */
42
    const EVENT_BEFORE_RENDER = 'beforeRender';
43
    /**
44
     * @event ViewEvent an event that is triggered by [[renderFile()]] right after it renders a view file.
45
     */
46
    const EVENT_AFTER_RENDER = 'afterRender';
47
48
    /**
49
     * @var ViewContextInterface the context under which the [[renderFile()]] method is being invoked.
50
     */
51
    public $context;
52
    /**
53
     * @var mixed custom parameters that are shared among view templates.
54
     */
55
    public $params = [];
56
    /**
57
     * @var array a list of available renderers indexed by their corresponding supported file extensions.
58
     * Each renderer may be a view renderer object or the configuration for creating the renderer object.
59
     * For example, the following configuration enables both Smarty and Twig view renderers:
60
     *
61
     * ```php
62
     * [
63
     *     'tpl' => ['class' => \yii\smarty\ViewRenderer::class],
64
     *     'twig' => ['class' => \yii\twig\ViewRenderer::class],
65
     * ]
66
     * ```
67
     *
68
     * If no renderer is available for the given view file, the view file will be treated as a normal PHP
69
     * and rendered via [[renderPhpFile()]].
70
     */
71
    public $renderers;
72
    /**
73
     * @var string the default view file extension. This will be appended to view file names if they don't have file extensions.
74
     */
75
    public $defaultExtension = 'php';
76
    /**
77
     * @var Theme|array|string the theme object or the configuration for creating the theme object.
78
     * If not set, it means theming is not enabled.
79
     */
80
    public $theme;
81
    /**
82
     * @var array a list of named output blocks. The keys are the block names and the values
83
     * are the corresponding block content. You can call [[beginBlock()]] and [[endBlock()]]
84
     * to capture small fragments of a view. They can be later accessed somewhere else
85
     * through this property.
86
     */
87
    public $blocks;
88
    /**
89
     * @var array a list of currently active fragment cache widgets. This property
90
     * is used internally to implement the content caching feature. Do not modify it directly.
91
     * @internal
92
     */
93
    public $cacheStack = [];
94
    /**
95
     * @var array a list of placeholders for embedding dynamic contents. This property
96
     * is used internally to implement the content caching feature. Do not modify it directly.
97
     * @internal
98
     */
99
    public $dynamicPlaceholders = [];
100
101
    /**
102
     * @var array the view files currently being rendered. There may be multiple view files being
103
     * rendered at a moment because one view may be rendered within another.
104
     */
105
    private $_viewFiles = [];
106
107
108
    /**
109
     * Initializes the view component.
110
     */
111 143
    public function init()
112
    {
113 143
        parent::init();
114 143
        if (is_array($this->theme)) {
115
            if (!isset($this->theme['class'])) {
116
                $this->theme['class'] = Theme::class;
117
            }
118
            $this->theme = Yii::createObject($this->theme);
119 143
        } elseif (is_string($this->theme)) {
120
            $this->theme = Yii::createObject($this->theme);
121
        }
122 143
    }
123
124
    /**
125
     * Renders a view.
126
     *
127
     * The view to be rendered can be specified in one of the following formats:
128
     *
129
     * - [path alias](guide:concept-aliases) (e.g. "@app/views/site/index");
130
     * - absolute path within application (e.g. "//site/index"): the view name starts with double slashes.
131
     *   The actual view file will be looked for under the [[Application::viewPath|view path]] of the application.
132
     * - absolute path within current module (e.g. "/site/index"): the view name starts with a single slash.
133
     *   The actual view file will be looked for under the [[Module::viewPath|view path]] of the [[Controller::module|current module]].
134
     * - relative view (e.g. "index"): the view name does not start with `@` or `/`. The corresponding view file will be
135
     *   looked for under the [[ViewContextInterface::getViewPath()|view path]] of the view `$context`.
136
     *   If `$context` is not given, it will be looked for under the directory containing the view currently
137
     *   being rendered (i.e., this happens when rendering a view within another view).
138
     *
139
     * @param string $view the view name.
140
     * @param array $params the parameters (name-value pairs) that will be extracted and made available in the view file.
141
     * @param object $context the context to be assigned to the view and can later be accessed via [[context]]
142
     * in the view. If the context implements [[ViewContextInterface]], it may also be used to locate
143
     * the view file corresponding to a relative view name.
144
     * @return string the rendering result
145
     * @throws ViewNotFoundException if the view file does not exist.
146
     * @throws InvalidCallException if the view cannot be resolved.
147
     * @see renderFile()
148
     */
149 32
    public function render($view, $params = [], $context = null)
150
    {
151 32
        $viewFile = $this->findViewFile($view, $context);
152 32
        return $this->renderFile($viewFile, $params, $context);
0 ignored issues
show
Bug introduced by
It seems like $viewFile defined by $this->findViewFile($view, $context) on line 151 can also be of type boolean; however, yii\base\View::renderFile() does only seem to accept string, maybe add an additional type check?

If a method or function can return multiple different values and unless you are sure that you only can receive a single value in this context, we recommend to add an additional type check:

/**
 * @return array|string
 */
function returnsDifferentValues($x) {
    if ($x) {
        return 'foo';
    }

    return array();
}

$x = returnsDifferentValues($y);
if (is_array($x)) {
    // $x is an array.
}

If this a common case that PHP Analyzer should handle natively, please let us know by opening an issue.

Loading history...
153
    }
154
155
    /**
156
     * Finds the view file based on the given view name.
157
     * @param string $view the view name or the [path alias](guide:concept-aliases) of the view file. Please refer to [[render()]]
158
     * on how to specify this parameter.
159
     * @param object $context the context to be assigned to the view and can later be accessed via [[context]]
160
     * in the view. If the context implements [[ViewContextInterface]], it may also be used to locate
161
     * the view file corresponding to a relative view name.
162
     * @return string the view file path. Note that the file may not exist.
163
     * @throws InvalidCallException if a relative view name is given while there is no active context to
164
     * determine the corresponding view file.
165
     */
166 32
    protected function findViewFile($view, $context = null)
167
    {
168 32
        if (strncmp($view, '@', 1) === 0) {
169
            // e.g. "@app/views/main"
170 21
            $file = Yii::getAlias($view);
171 11
        } elseif (strncmp($view, '//', 2) === 0) {
172
            // e.g. "//layouts/main"
173
            $file = Yii::$app->getViewPath() . DIRECTORY_SEPARATOR . ltrim($view, '/');
174 11
        } elseif (strncmp($view, '/', 1) === 0) {
175
            // e.g. "/site/index"
176
            if (Yii::$app->controller !== null) {
177
                $file = Yii::$app->controller->module->getViewPath() . DIRECTORY_SEPARATOR . ltrim($view, '/');
178
            } else {
179
                throw new InvalidCallException("Unable to locate view file for view '$view': no active controller.");
180
            }
181 11
        } elseif ($context instanceof ViewContextInterface) {
182 6
            $file = $context->getViewPath() . DIRECTORY_SEPARATOR . $view;
183 5
        } elseif (($currentViewFile = $this->getViewFile()) !== false) {
184 5
            $file = dirname($currentViewFile) . DIRECTORY_SEPARATOR . $view;
185
        } else {
186
            throw new InvalidCallException("Unable to resolve view file for view '$view': no active view context.");
187
        }
188
189 32
        if (pathinfo($file, PATHINFO_EXTENSION) !== '') {
190 20
            return $file;
191
        }
192 12
        $path = $file . '.' . $this->defaultExtension;
193 12
        if ($this->defaultExtension !== 'php' && !is_file($path)) {
194
            $path = $file . '.php';
195
        }
196
197 12
        return $path;
198
    }
199
200
    /**
201
     * Renders a view file.
202
     *
203
     * If [[theme]] is enabled (not null), it will try to render the themed version of the view file as long
204
     * as it is available.
205
     *
206
     * The method will call [[FileHelper::localize()]] to localize the view file.
207
     *
208
     * If [[renderers|renderer]] is enabled (not null), the method will use it to render the view file.
209
     * Otherwise, it will simply include the view file as a normal PHP file, capture its output and
210
     * return it as a string.
211
     *
212
     * @param string $viewFile the view file. This can be either an absolute file path or an alias of it.
213
     * @param array $params the parameters (name-value pairs) that will be extracted and made available in the view file.
214
     * @param object $context the context that the view should use for rendering the view. If null,
215
     * existing [[context]] will be used.
216
     * @return string the rendering result
217
     * @throws ViewNotFoundException if the view file does not exist
218
     */
219 71
    public function renderFile($viewFile, $params = [], $context = null)
220
    {
221 71
        $viewFile = Yii::getAlias($viewFile);
222
223 71
        if ($this->theme !== null) {
224
            $viewFile = $this->theme->applyTo($viewFile);
225
        }
226 71
        if (is_file($viewFile)) {
227 70
            $viewFile = FileHelper::localize($viewFile);
228
        } else {
229 1
            throw new ViewNotFoundException("The view file does not exist: $viewFile");
230
        }
231
232 70
        $oldContext = $this->context;
233 70
        if ($context !== null) {
234 23
            $this->context = $context;
235
        }
236 70
        $output = '';
237 70
        $this->_viewFiles[] = $viewFile;
238
239 70
        if ($this->beforeRender($viewFile, $params)) {
240 70
            Yii::trace("Rendering view file: $viewFile", __METHOD__);
241 70
            $ext = pathinfo($viewFile, PATHINFO_EXTENSION);
242 70
            if (isset($this->renderers[$ext])) {
243
                if (is_array($this->renderers[$ext]) || is_string($this->renderers[$ext])) {
244
                    $this->renderers[$ext] = Yii::createObject($this->renderers[$ext]);
245
                }
246
                /* @var $renderer ViewRenderer */
247
                $renderer = $this->renderers[$ext];
248
                $output = $renderer->render($this, $viewFile, $params);
249
            } else {
250 70
                $output = $this->renderPhpFile($viewFile, $params);
251
            }
252 70
            $this->afterRender($viewFile, $params, $output);
253
        }
254
255 70
        array_pop($this->_viewFiles);
256 70
        $this->context = $oldContext;
257
258 70
        return $output;
259
    }
260
261
    /**
262
     * @return string|bool the view file currently being rendered. False if no view file is being rendered.
263
     */
264 5
    public function getViewFile()
265
    {
266 5
        return end($this->_viewFiles);
267
    }
268
269
    /**
270
     * This method is invoked right before [[renderFile()]] renders a view file.
271
     * The default implementation will trigger the [[EVENT_BEFORE_RENDER]] event.
272
     * If you override this method, make sure you call the parent implementation first.
273
     * @param string $viewFile the view file to be rendered.
274
     * @param array $params the parameter array passed to the [[render()]] method.
275
     * @return bool whether to continue rendering the view file.
276
     */
277 70
    public function beforeRender($viewFile, $params)
278
    {
279 70
        $event = new ViewEvent([
280 70
            'viewFile' => $viewFile,
281 70
            'params' => $params,
282
        ]);
283 70
        $this->trigger(self::EVENT_BEFORE_RENDER, $event);
284
285 70
        return $event->isValid;
286
    }
287
288
    /**
289
     * This method is invoked right after [[renderFile()]] renders a view file.
290
     * The default implementation will trigger the [[EVENT_AFTER_RENDER]] event.
291
     * If you override this method, make sure you call the parent implementation first.
292
     * @param string $viewFile the view file being rendered.
293
     * @param array $params the parameter array passed to the [[render()]] method.
294
     * @param string $output the rendering result of the view file. Updates to this parameter
295
     * will be passed back and returned by [[renderFile()]].
296
     */
297 70
    public function afterRender($viewFile, $params, &$output)
298
    {
299 70
        if ($this->hasEventHandlers(self::EVENT_AFTER_RENDER)) {
300
            $event = new ViewEvent([
301
                'viewFile' => $viewFile,
302
                'params' => $params,
303
                'output' => $output,
304
            ]);
305
            $this->trigger(self::EVENT_AFTER_RENDER, $event);
306
            $output = $event->output;
307
        }
308 70
    }
309
310
    /**
311
     * Renders a view file as a PHP script.
312
     *
313
     * This method treats the view file as a PHP script and includes the file.
314
     * It extracts the given parameters and makes them available in the view file.
315
     * The method captures the output of the included view file and returns it as a string.
316
     *
317
     * This method should mainly be called by view renderer or [[renderFile()]].
318
     *
319
     * @param string $_file_ the view file.
320
     * @param array $_params_ the parameters (name-value pairs) that will be extracted and made available in the view file.
321
     * @return string the rendering result
322
     */
323 70
    public function renderPhpFile($_file_, $_params_ = [])
324
    {
325 70
        $_obInitialLevel_ = ob_get_level();
326 70
        ob_start();
327 70
        ob_implicit_flush(false);
328 70
        extract($_params_, EXTR_OVERWRITE);
329
        try {
330 70
            require($_file_);
331 70
            return ob_get_clean();
332 1
        } catch (\Throwable $e) {
0 ignored issues
show
Bug introduced by
The class Throwable does not exist. Did you forget a USE statement, or did you not list all dependencies?

Scrutinizer analyzes your composer.json/composer.lock file if available to determine the classes, and functions that are defined by your dependencies.

It seems like the listed class was neither found in your dependencies, nor was it found in the analyzed files in your repository. If you are using some other form of dependency management, you might want to disable this analysis.

Loading history...
333 1
            while (ob_get_level() > $_obInitialLevel_) {
334 1
                if (!@ob_end_clean()) {
335
                    ob_clean();
336
                }
337
            }
338 1
            throw $e;
339
        }
340
    }
341
342
    /**
343
     * Renders dynamic content returned by the given PHP statements.
344
     * This method is mainly used together with content caching (fragment caching and page caching)
345
     * when some portions of the content (called *dynamic content*) should not be cached.
346
     * The dynamic content must be returned by some PHP statements. You can optionally pass
347
     * additional parameters that will be available as variables in the PHP statement:
348
     *
349
     * ```php
350
     * <?= $this->renderDynamic('return foo($myVar);', [
351
     *     'myVar' => $model->getMyComplexVar(),
352
     * ]) ?>
353
     * ```
354
     * @param string $statements the PHP statements for generating the dynamic content.
355
     * @param array $params the parameters (name-value pairs) that will be extracted and made
356
     * available in the $statement context. The parameters will be stored in the cache and be reused
357
     * each time $statement is executed. You should make sure, that these are safely serializable.
358
     * @throws \yii\base\ErrorException if the statement throws an exception in eval()
359
     * @return string the placeholder of the dynamic content, or the dynamic content if there is no
360
     * active content cache currently.
361
     */
362 21
    public function renderDynamic($statements, array $params = [])
363
    {
364 21
        if (!empty($params)) {
365 1
            $statements = 'extract(unserialize(\'' . serialize($params) . '\'));' . $statements;
366
        }
367 21
        if (!empty($this->cacheStack)) {
368 16
            $n = count($this->dynamicPlaceholders);
369 16
            $placeholder = "<![CDATA[YII-DYNAMIC-$n]]>";
370 16
            $this->addDynamicPlaceholder($placeholder, $statements);
371
372 16
            return $placeholder;
373
        }
374 5
        return $this->evaluateDynamicContent($statements);
375
    }
376
377
    /**
378
     * Adds a placeholder for dynamic content.
379
     * This method is internally used.
380
     * @param string $placeholder the placeholder name
381
     * @param string $statements the PHP statements for generating the dynamic content
382
     */
383 16
    public function addDynamicPlaceholder($placeholder, $statements)
384
    {
385 16
        foreach ($this->cacheStack as $cache) {
386 16
            $cache->dynamicPlaceholders[$placeholder] = $statements;
387
        }
388 16
        $this->dynamicPlaceholders[$placeholder] = $statements;
389 16
    }
390
391
    /**
392
     * Evaluates the given PHP statements.
393
     * This method is mainly used internally to implement dynamic content feature.
394
     * @param string $statements the PHP statements to be evaluated.
395
     * @return mixed the return value of the PHP statements.
396
     */
397 20
    public function evaluateDynamicContent($statements)
398
    {
399 20
        return eval($statements);
400
    }
401
402
    /**
403
     * Begins recording a block.
404
     * This method is a shortcut to beginning [[Block]]
405
     * @param string $id the block ID.
406
     * @param bool $renderInPlace whether to render the block content in place.
407
     * Defaults to false, meaning the captured block will not be displayed.
408
     * @return Block the Block widget instance
409
     */
410
    public function beginBlock($id, $renderInPlace = false)
411
    {
412
        return Block::begin([
413
            'id' => $id,
414
            'renderInPlace' => $renderInPlace,
415
            'view' => $this,
416
        ]);
417
    }
418
419
    /**
420
     * Ends recording a block.
421
     */
422
    public function endBlock()
423
    {
424
        Block::end();
425
    }
426
427
    /**
428
     * Begins the rendering of content that is to be decorated by the specified view.
429
     * This method can be used to implement nested layout. For example, a layout can be embedded
430
     * in another layout file specified as '@app/views/layouts/base.php' like the following:
431
     *
432
     * ```php
433
     * <?php $this->beginContent('@app/views/layouts/base.php'); ?>
434
     * //...layout content here...
435
     * <?php $this->endContent(); ?>
436
     * ```
437
     *
438
     * @param string $viewFile the view file that will be used to decorate the content enclosed by this widget.
439
     * This can be specified as either the view file path or [path alias](guide:concept-aliases).
440
     * @param array $params the variables (name => value) to be extracted and made available in the decorative view.
441
     * @return ContentDecorator the ContentDecorator widget instance
442
     * @see ContentDecorator
443
     */
444
    public function beginContent($viewFile, $params = [])
445
    {
446
        return ContentDecorator::begin([
447
            'viewFile' => $viewFile,
448
            'params' => $params,
449
            'view' => $this,
450
        ]);
451
    }
452
453
    /**
454
     * Ends the rendering of content.
455
     */
456
    public function endContent()
457
    {
458
        ContentDecorator::end();
459
    }
460
461
    /**
462
     * Begins fragment caching.
463
     * This method will display cached content if it is available.
464
     * If not, it will start caching and would expect an [[endCache()]]
465
     * call to end the cache and save the content into cache.
466
     * A typical usage of fragment caching is as follows,
467
     *
468
     * ```php
469
     * if ($this->beginCache($id)) {
470
     *     // ...generate content here
471
     *     $this->endCache();
472
     * }
473
     * ```
474
     *
475
     * @param string $id a unique ID identifying the fragment to be cached.
476
     * @param array $properties initial property values for [[FragmentCache]]
477
     * @return bool whether you should generate the content for caching.
478
     * False if the cached version is available.
479
     */
480 7
    public function beginCache($id, $properties = [])
481
    {
482 7
        $properties['id'] = $id;
483 7
        $properties['view'] = $this;
484
        /* @var $cache FragmentCache */
485 7
        $cache = FragmentCache::begin($properties);
486 7
        if ($cache->getCachedContent() !== false) {
487 4
            $this->endCache();
488
489 4
            return false;
490
        }
491 7
        return true;
492
    }
493
494
    /**
495
     * Ends fragment caching.
496
     */
497 7
    public function endCache()
498
    {
499 7
        FragmentCache::end();
500 7
    }
501
502
    /**
503
     * Marks the beginning of a page.
504
     */
505 48
    public function beginPage()
506
    {
507 48
        ob_start();
508 48
        ob_implicit_flush(false);
509
510 48
        $this->trigger(self::EVENT_BEGIN_PAGE);
511 48
    }
512
513
    /**
514
     * Marks the ending of a page.
515
     */
516
    public function endPage()
517
    {
518
        $this->trigger(self::EVENT_END_PAGE);
519
        ob_end_flush();
520
    }
521
}
522