Completed
Branch 2.0.x (e30486)
by Andrew
02:20
created

TemplateCompiler::__construct()   A

Complexity

Conditions 1
Paths 1

Size

Total Lines 4
Code Lines 2

Duplication

Lines 0
Ratio 0 %

Importance

Changes 1
Bugs 0 Features 1
Metric Value
c 1
b 0
f 1
dl 0
loc 4
rs 10
cc 1
eloc 2
nc 1
nop 1
1
<?php
2
/**
3
 * @author     Andrew Coulton <[email protected]>
4
 * @copyright  2015 inGenerator Ltd
5
 * @license    http://kohanaframework.org/license
6
 */
7
8
namespace Ingenerator\KohanaView;
9
10
use Ingenerator\KohanaView\Exception\InvalidTemplateContentException;
11
12
/**
13
 * The TemplateCompiler takes a plain PHP template string and processes it to add automatic variable escaping within
14
 * PHP short echo tags, before returning the compiled template. You can optionally prefix your variables to mark that
15
 * they should not be escaped, or manually echo them from a full PHP code block.
16
 *
17
 * For example, the template:
18
 *
19
 *    <h1><?=$view->title;?></h1>
20
 *    <p><?=!$partial;?></p>
21
 *    <?php echo $stuff;?>
22
 *
23
 * Will compile to:
24
 *
25
 *    <h1><?=HTML::chars($view->title);?></h1>
26
 *    <p><?=$partial;?></p>
27
 *    <?php echo $stuff;?>
28
 *
29
 * The raw output prefix and escape method are configurable via the options array passed to the constructor.
30
 *
31
 * @package Ingenerator\KohanaView
32
 */
33
class TemplateCompiler
34
{
35
36
    /**
37
     * @var array
38
     */
39
    protected $options = [
40
        'raw_output_prefix' => '!',
41
        'escape_method'     => 'HTML::chars',
42
    ];
43
44
    /**
45
     * @param array $options
46
     */
47
    public function __construct(array $options = [])
48
    {
49
        $this->options = array_merge($this->options, $options);
50
    }
51
52
    /**
53
     * Compile a string containing a PHP template, automatically escaping variables that are echoed in PHP short tags,
54
     * and return the compiled PHP string.
55
     *
56
     * @param string $source
57
     *
58
     * @return string
59
     * @throws \InvalidArgumentException if the template is empty or invalid
60
     */
61
    public function compile($source)
62
    {
63
        if ( ! $source) {
64
            throw InvalidTemplateContentException::forEmptyTemplate();
65
        }
66
67
        return preg_replace_callback('/<\?=(.+?)(;|\?>)/s', [$this, 'compilePhpShortTag'], $source);
68
    }
69
70
    /**
71
     * @param string[] $matches
72
     *
73
     * @return string
74
     */
75
    protected function compilePhpShortTag($matches)
76
    {
77
        $var               = trim($matches[1]);
78
        $terminator        = $matches[2];
79
        $escape_method     = $this->options['escape_method'];
80
        $raw_output_prefix = $this->options['raw_output_prefix'];
81
82
        if ($this->startsWith($var, $raw_output_prefix)) {
83
            // Remove prefix and echo unescaped
84
            $compiled = '<?='.trim(substr($var, strlen($raw_output_prefix))).';';
85
        } elseif ($this->startsWith($var, '//')) {
86
            // Echo an empty string to prevent the comment causing a parse error
87
            $compiled = "<?='';$var;";
1 ignored issue
show
Coding Style Best Practice introduced by
As per coding-style, please use concatenation or sprintf for the variable $var instead of interpolation.

It is generally a best practice as it is often more readable to use concatenation instead of interpolation for variables inside strings.

// Instead of
$x = "foo $bar $baz";

// Better use either
$x = "foo " . $bar . " " . $baz;
$x = sprintf("foo %s %s", $bar, $baz);
Loading history...
88
89
        } elseif ($this->startsWith($var, $escape_method)) {
90
            throw InvalidTemplateContentException::containsImplicitDoubleEscape($escape_method, $matches[0]);
91
92
        } else {
93
            // Escape the value before echoing
94
            $compiled = "<?={$escape_method}($var);";
2 ignored issues
show
Coding Style Best Practice introduced by
As per coding-style, please use concatenation or sprintf for the variable $escape_method instead of interpolation.

It is generally a best practice as it is often more readable to use concatenation instead of interpolation for variables inside strings.

// Instead of
$x = "foo $bar $baz";

// Better use either
$x = "foo " . $bar . " " . $baz;
$x = sprintf("foo %s %s", $bar, $baz);
Loading history...
Coding Style Best Practice introduced by
As per coding-style, please use concatenation or sprintf for the variable $var instead of interpolation.

It is generally a best practice as it is often more readable to use concatenation instead of interpolation for variables inside strings.

// Instead of
$x = "foo $bar $baz";

// Better use either
$x = "foo " . $bar . " " . $baz;
$x = sprintf("foo %s %s", $bar, $baz);
Loading history...
95
        }
96
97
        if ($terminator === '?>') {
98
            $compiled .= '?>';
99
        }
100
101
        return $compiled;
102
    }
103
104
    /**
105
     * @param string $string
106
     * @param string $prefix
107
     *
108
     * @return bool
109
     */
110
    protected function startsWith($string, $prefix)
111
    {
112
        return (strncmp($string, $prefix, strlen($prefix)) === 0);
113
    }
114
115
}
116