Completed
Pull Request — 2.0.x (#6)
by Andrew
02:09
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
/**
11
 * The TemplateCompiler takes a plain PHP template string and processes it to add automatic variable escaping within
12
 * PHP short echo tags, before returning the compiled template. You can optionally prefix your variables to mark that
13
 * they should not be escaped, or manually echo them from a full PHP code block.
14
 *
15
 * For example, the template:
16
 *
17
 *    <h1><?=$view->title;?></h1>
18
 *    <p><?=!$partial;?></p>
19
 *    <?php echo $stuff;?>
20
 *
21
 * Will compile to:
22
 *
23
 *    <h1><?=HTML::chars($view->title);?></h1>
24
 *    <p><?=$partial;?></p>
25
 *    <?php echo $stuff;?>
26
 *
27
 * The raw output prefix and escape method are configurable via the options array passed to the constructor.
28
 *
29
 * @package Ingenerator\KohanaView
30
 */
31
class TemplateCompiler
32
{
33
34
    /**
35
     * @var array
36
     */
37
    protected $options = [
38
        'raw_output_prefix' => '!',
39
        'escape_method'     => 'HTML::chars',
40
    ];
41
42
    /**
43
     * @param array $options
44
     */
45
    public function __construct(array $options = [])
46
    {
47
        $this->options = array_merge($this->options, $options);
48
    }
49
50
    /**
51
     * Compile a string containing a PHP template, automatically escaping variables that are echoed in PHP short tags,
52
     * and return the compiled PHP string.
53
     *
54
     * @param string $source
55
     *
56
     * @return string
57
     * @throws \InvalidArgumentException if the template is empty or invalid
58
     */
59
    public function compile($source)
60
    {
61
        if ( ! $source) {
62
            throw new \InvalidArgumentException('Cannot compile empty template');
63
        }
64
65
        return preg_replace_callback('/<\?=(.+?)(;|\?>)/s', [$this, 'compilePhpShortTag'], $source);
66
    }
67
68
    /**
69
     * @param string[] $matches
70
     *
71
     * @return string
72
     */
73
    protected function compilePhpShortTag($matches)
74
    {
75
        $var               = trim($matches[1]);
76
        $terminator        = $matches[2];
77
        $escape_method     = $this->options['escape_method'];
78
        $raw_output_prefix = $this->options['raw_output_prefix'];
79
80
        if ($this->startsWith($var, $raw_output_prefix)) {
81
            // Remove prefix and echo unescaped
82
            $compiled = '<?='.trim(substr($var, strlen($raw_output_prefix))).';';
83
        } elseif ($this->startsWith($var, '//')) {
84
            // Echo an empty string to prevent the comment causing a parse error
85
            $compiled = "<?='';$var;";
86
87
        } elseif ($this->startsWith($var, $escape_method)) {
88
            // Try to help user to avoid accidental double-escaping
89
            throw new \InvalidArgumentException(
90
                "Invalid implicit double-escape in template - remove $escape_method from {$matches[0]} or mark as raw"
91
            );
92
93
        } else {
94
            // Escape the value before echoing
95
            $compiled = "<?={$escape_method}($var);";
96
        }
97
98
        if ($terminator === '?>') {
99
            $compiled .= '?>';
100
        }
101
102
        return $compiled;
103
    }
104
105
    /**
106
     * @param string $string
107
     * @param string $prefix
108
     *
109
     * @return bool
110
     */
111
    protected function startsWith($string, $prefix)
112
    {
113
        return (strncmp($string, $prefix, strlen($prefix)) === 0);
114
    }
115
116
}
117