Completed
Branch develop (85a9c8)
by Anton
05:44
created

Isolator::isolatePHP()   B

Complexity

Conditions 6
Paths 7

Size

Total Lines 33
Code Lines 19

Duplication

Lines 0
Ratio 0 %

Importance

Changes 1
Bugs 0 Features 0
Metric Value
c 1
b 0
f 0
dl 0
loc 33
rs 8.439
cc 6
eloc 19
nc 7
nop 1
1
<?php
2
/**
3
 * Spiral Framework.
4
 *
5
 * @license   MIT
6
 * @author    Anton Titov (Wolfy-J)
7
 */
8
namespace Spiral\Tokenizer;
9
10
use Spiral\Tokenizer\Exceptions\IsolatorException;
11
12
/**
13
 * Isolators used to find and replace php blocks in given source. Can
14
 * be used by view processors, or to remove php code from some string.
15
 */
16
class Isolator
17
{
18
    /**
19
     * Found PHP blocks to be replaced.
20
     *
21
     * @var array
22
     */
23
    private $phpBlocks = [];
24
25
    /**
26
     * Isolation prefix. Use any values that will not corrupt HTML or
27
     * other source.
28
     *
29
     * @var string
30
     */
31
    private $prefix = '';
32
33
    /**
34
     * Isolation postfix. Use any values that will not corrupt HTML
35
     * or other source.
36
     *
37
     * @var string
38
     */
39
    private $postfix = '';
40
41
    /**
42
     * @param string $prefix  Replaced block prefix, -php by default.
43
     * @param string $postfix Replaced block postfix, block- by default.
44
     */
45
    public function __construct($prefix = '-php-', $postfix = '-block-')
46
    {
47
        $this->prefix = $prefix;
48
        $this->postfix = $postfix;
49
    }
50
51
    /**
52
     * Isolates all returned PHP blocks with a defined pattern. Method uses
53
     * token_get_all function. Resulted source have all php blocks replaced
54
     * with non executable placeholder.
55
     *
56
     * @param string $source
57
     * @return string
58
     */
59
    public function isolatePHP($source)
60
    {
61
        $phpBlock = false;
62
63
        $isolated = '';
64
        foreach (token_get_all($source) as $token) {
65
            if ($this->isOpenTag($token)) {
66
                $phpBlock = $token[1];
67
68
                continue;
69
            }
70
71
            if ($this->isCloseTag($token)) {
72
                $blockID = $this->uniqueID();
73
74
                $this->phpBlocks[$blockID] = $phpBlock . $token[1];
75
                $isolated .= $this->placeholder($blockID);
76
77
                $phpBlock = '';
78
79
                continue;
80
            }
81
82
            $tokenContent = is_array($token) ? $token[1] : $token;
83
            if (!empty($phpBlock)) {
84
                $phpBlock .= $tokenContent;
85
            } else {
86
                $isolated .= $tokenContent;
87
            }
88
        }
89
90
        return $isolated;
91
    }
92
93
    /**
94
     * Set block content by id.
95
     *
96
     * @param string $blockID
97
     * @param string $source
98
     * @return $this
99
     * @throws IsolatorException
100
     */
101
    public function setBlock($blockID, $source)
102
    {
103
        if (!isset($this->phpBlocks[$blockID])) {
104
            throw new IsolatorException("Undefined block {$blockID}");
105
        }
106
107
        $this->phpBlocks[$blockID] = $source;
108
109
        return $this;
110
    }
111
112
    /**
113
     * Replace every isolated block.
114
     *
115
     * @deprecated Use setBlock instead!
116
     * @param array $blocks
117
     * @return $this
118
     */
119
    public function setBlocks(array $blocks)
120
    {
121
        $this->phpBlocks = $blocks;
122
123
        return $this;
124
    }
125
126
    /**
127
     * List of all found and replaced php blocks.
128
     *
129
     * @return array
130
     */
131
    public function getBlocks()
132
    {
133
        return $this->phpBlocks;
134
    }
135
136
    /**
137
     * Restore PHP blocks position in isolated source (isolatePHP() must
138
     * be already called).
139
     *
140
     * @param string $source
141
     * @return string
142
     */
143
    public function repairPHP($source)
144
    {
145
        return preg_replace_callback(
146
            $this->blockRegex(),
147
            function ($match) {
148
                if (!isset($this->phpBlocks[$match['id']])) {
149
                    return $match[0];
150
                }
151
152
                return $this->phpBlocks[$match['id']];
153
            },
154
            $source
155
        );
156
    }
157
158
    /**
159
     * Remove PHP blocks from isolated source (isolatePHP() must be
160
     * already called).
161
     *
162
     * @param string $isolatedSource
163
     * @return string
164
     */
165
    public function removePHP($isolatedSource)
166
    {
167
        return preg_replace($this->blockRegex(), '', $isolatedSource);
168
    }
169
170
    /**
171
     * Reset isolator state.
172
     */
173
    public function reset()
174
    {
175
        $this->phpBlocks = [];
176
    }
177
178
    /**
179
     * @return string
180
     */
181
    private function blockRegex()
182
    {
183
        return '/' .
184
        preg_quote($this->prefix)
185
        . '(?P<id>[0-9a-z]+)'
186
        . preg_quote($this->postfix)
187
        . '/';
188
    }
189
190
    /**
191
     * @return string
192
     */
193
    private function uniqueID()
194
    {
195
        return md5(count($this->phpBlocks) . uniqid(true));
196
    }
197
198
    /**
199
     * @param int $blockID
200
     * @return string
201
     */
202
    private function placeholder($blockID)
203
    {
204
        return $this->prefix . $blockID . $this->postfix;
205
    }
206
207
    /**
208
     * @param mixed $token
209
     * @return bool
210
     */
211
    private function isOpenTag($token)
212
    {
213
        if (!is_array($token)) {
214
            return false;
215
        }
216
217
        if ($token[0] == T_ECHO && $token[1] == '<?=') {
218
            //todo Find out why HHVM behaves differently or create issue
219
            return true;
220
        }
221
222
        return $token[0] == T_OPEN_TAG || $token[0] == T_OPEN_TAG_WITH_ECHO;
223
    }
224
225
    /**
226
     * @param mixed $token
227
     * @return bool
228
     */
229
    public function isCloseTag($token)
230
    {
231
        if (!is_array($token)) {
232
            return false;
233
        }
234
235
        return $token[0] == T_CLOSE_TAG;
236
    }
237
}