Passed
Push — master ( 2dea8c...1a0534 )
by Sebastian
02:34
created

JSHelper::buildRegexStatement()   A

Complexity

Conditions 4
Paths 6

Size

Total Lines 43
Code Lines 25

Duplication

Lines 0
Ratio 0 %

Importance

Changes 1
Bugs 0 Features 0
Metric Value
cc 4
eloc 25
c 1
b 0
f 0
nc 6
nop 2
dl 0
loc 43
rs 9.52
1
<?php
2
/**
3
 * File containing the {@link JSHelper} class.
4
 * 
5
 * @package Application Utils
6
 * @subpackage JSHelper
7
 * @see JSHelper
8
 */
9
10
declare(strict_types=1);
11
12
namespace AppUtils;
13
14
/**
15
 * Simplifies building JavaScript statements from PHP variables.
16
 * Automatically converts variables to their JS equivalents, 
17
 * with different quote styles for usage within scripts or HTML
18
 * tag attributes.
19
 * 
20
 * Also offers a way to easily generate unique element IDs within
21
 * a single request.
22
 *
23
 * @package Application Utils
24
 * @subpackage Request
25
 * @author Sebastian Mordziol <[email protected]>
26
 */
27
class JSHelper
28
{
29
   /**
30
    * Quote style using single quotes.
31
    * @var integer
32
    */
33
    const QUOTE_STYLE_SINGLE = 1;
34
    
35
   /**
36
    * Quote style using double quotes.
37
    * @var integer
38
    */
39
    const QUOTE_STYLE_DOUBLE = 2;
40
41
   /**
42
    * @var array
43
    */
44
    protected static $variableCache = array();
45
    
46
   /**
47
    * @var integer
48
    */
49
    protected static $elementCounter = 0;
50
51
   /**
52
    * @var string
53
    */    
54
    protected static $idPrefix = 'E';
55
    
56
   /**
57
    * Builds a javascript statement. The first parameter is the
58
    * javascript function to call, any additional parameters are
59
    * used as arguments for the javascript function call. Variable
60
    * types are automagically converted to the javascript equivalents.
61
    *
62
    * Examples:
63
    *
64
    * // add an alert(); statement:
65
    * JSHelper::buildStatement('alert');
66
    *
67
    * // add an alert('Alert text'); statement
68
    * JSHelper::buildStatement('alert', 'Alert text');
69
    *
70
    */
71
    public static function buildStatement() : string
72
    {
73
        $args = func_get_args();
74
        array_unshift($args, self::QUOTE_STYLE_DOUBLE);
75
        return call_user_func_array(array(self::class, 'buildStatementQuoteStyle'), $args);
76
    }
77
    
78
   /**
79
    * Like {@link JSHelper::buildStatement()}, but using single quotes
80
    * to make it possible to use the statement in an HTML tag attribute.
81
    * 
82
    * @return string
83
    * @see JSHelper::buildStatement()
84
    */
85
    public static function buildStatementAttribute() : string
86
    {
87
        $args = func_get_args();
88
        array_unshift($args, self::QUOTE_STYLE_SINGLE);
89
        return call_user_func_array(array(self::class, 'buildStatementQuoteStyle'), $args);
90
    }
91
    
92
    protected static function buildStatementQuoteStyle()
93
    {
94
        $params = func_get_args();
95
        $quoteStyle = array_shift($params);
96
        $method = array_shift($params);
97
        
98
        $call = $method . '(';
99
        
100
        $total = count($params);
101
        if($total > 0) {
102
            for($i=0; $i < $total; $i++) 
103
            {
104
                $call .= self::phpVariable2JS($params[$i], $quoteStyle);
105
                if($i < ($total-1)) {
106
                    $call .= ',';
107
                }
108
            }
109
        }
110
        
111
        return $call . ');';
112
    }
113
114
   /**
115
    * Builds a set variable statement. The variable value is
116
    * automatically converted to the javascript equivalent.
117
    *
118
    * Examples:
119
    *
120
    * // foo = 'bar';
121
    * JSHelper::buildVariable('foo', 'bar');
122
    *
123
    * // foo = 42;
124
    * JSHelper::buildVariable('foo', 42);
125
    *
126
    * // foo = true;
127
    * JSHelper::buildVariable('foo', true);
128
    *
129
    * @param string $varName
130
    * @param mixed $varValue
131
    * @return string
132
    */
133
    public static function buildVariable(string $varName, $varValue) : string
134
    {
135
        return $varName . "=" . self::phpVariable2JS($varValue) . ';';
136
    }
137
    
138
   /**
139
    * Converts a PHP variable to its javascript equivalent. Note that
140
    * if a variable cannot be converted (like a PHP resource), this will
141
    * return a javascript "null".
142
    *
143
    * @param mixed $variable
144
    * @param int $quoteStyle The quote style to use for strings
145
    * @return string
146
    */
147
    public static function phpVariable2JS($variable, int $quoteStyle=self::QUOTE_STYLE_DOUBLE) : string
148
    {
149
        // after much profiling, this variant of the method offers
150
        // the best performance. Repeat scalar values are cached 
151
        // internally, others are likely not worth caching.
152
        
153
        $type = gettype($variable);
154
        $hash = null;
155
        if(is_scalar($variable) === true) 
156
        {
157
            $hash = $variable;
158
        
159
            if($hash === true) 
160
            { 
161
                $hash = 'true'; 
162
            } 
163
            else if($hash === false) 
164
            { 
165
                $hash = 'false'; 
166
            }
167
            
168
            $hash .= '-'.$quoteStyle.'-'.$type;
169
            
170
            if(isset(self::$variableCache[$hash])) {
171
                return self::$variableCache[$hash];
172
            }
173
        }
174
            
175
        $result = 'null';
176
177
        // one gettype call is better than a strict if-else.
178
        switch($type) 
179
        {
180
            case 'double':
181
            case 'string':
182
                $string = json_encode($variable);
183
                
184
                if($string === false) 
185
                {
186
                    $string = '';
187
                } 
188
                else if($quoteStyle === self::QUOTE_STYLE_SINGLE) 
189
                {
190
                    $string = mb_substr($string, 1, mb_strlen($string)-2);
191
                    $string = "'".str_replace("'", "\'", $string)."'";
192
                }
193
                
194
                $result = $string;
195
                break;
196
                
197
            case 'boolean':
198
                if($variable === true) {
199
                    $result = 'true';
200
                } else {
201
                    $result = 'false';
202
                }
203
                break;
204
205
            case 'integer':
206
                $result = (string)$variable;
207
                break;
208
209
            case 'object':
210
            case 'array':
211
                $result = json_encode($variable);
212
                break;
213
        }
214
215
        // cache cacheable values
216
        if($hash !== null) 
217
        {
218
            self::$variableCache[$hash] = $result;
219
        }
220
221
        return $result;
222
    }
223
    
224
   /**
225
    * Converts a variable to a JS string that can be 
226
    * used in an HTML attribute: it uses single quotes
227
    * instead of the default double quotes.
228
    * 
229
    * @param mixed $variable
230
    * @return string
231
    */
232
    public static function phpVariable2AttributeJS($variable) : string
233
    {
234
        return self::phpVariable2JS($variable, self::QUOTE_STYLE_SINGLE);
235
    }
236
237
   /**
238
    * Generates a dynamic element ID to be used with dynamically generated
239
    * HTML code to tie in with clientside javascript when compact but unique
240
    * IDs are needed in a  request.
241
    *
242
    * @return string
243
    */
244
    public static function nextElementID() : string
245
    {
246
        self::$elementCounter++;
247
248
        return self::$idPrefix . self::$elementCounter;
249
    }
250
    
251
   /**
252
    * Retrieves the ID prefix currently used.
253
    * 
254
    * @return string
255
    */
256
    public static function getIDPrefix() : string
257
    {
258
        return self::$idPrefix;
259
    }
260
    
261
   /**
262
    * Retrieves the value of the internal elements counter.
263
    * 
264
    * @return integer
265
    */
266
    public static function getElementCounter() : int
267
    {
268
        return self::$elementCounter;
269
    }
270
    
271
   /**
272
    * Sets the prefix that is added in front of all IDs
273
    * retrieved using the {@link nextElementID()} method.
274
    * 
275
    * @param string $prefix
276
    * @see JSHelper::nextElementID()
277
    */
278
    public static function setIDPrefix(string $prefix)
279
    {
280
        self::$idPrefix = $prefix;
281
    }
282
283
    const JS_REGEX_OBJECT = 'object';
284
    
285
    const JS_REGEX_JSON = 'json';
286
    
287
    /**
288
     * Takes a regular expression and attempts to convert it to
289
     * its javascript equivalent. Returns an array containing the
290
     * format string itself (without start and end characters),
291
     * and the modifiers.
292
     *
293
     * By default, the method returns a javascript statement
294
     * to create a RegExp object:
295
     *
296
     * ```php
297
     * <script>
298
     * var reg = <?php echo ConvertHelper::regex2js('/ab+c/i') ?>;
299
     * </script>
300
     * ```
301
     *
302
     * The second way allows accessing the format and the modifiers
303
     * separately, by storing these in a variable first:
304
     *
305
     * ```php
306
     * <script>
307
     * // define the regex details
308
     * var expression = <?php echo json_encode(ConvertHelper::regex2js('/ab+c/i', ConvertHelper::JS_REGEX_JSON)) ?>;
309
     *
310
     * // create the regex object
311
     * var reg = new RegExp(expression.format, expression.modifiers);
312
     * </script>
313
     * ```
314
     *
315
     * @param string $regex A PHP preg regex
316
     * @param string $statementType The statement type to generate: Default to a statement to create a RegExp object.
317
     * @return string
318
     *
319
     * @see JSHelper::JS_REGEX_OBJECT
320
     * @see JSHelper::JS_REGEX_JSON
321
     */
322
    public static function buildRegexStatement(string $regex, string $statementType=self::JS_REGEX_OBJECT) : string
323
    {
324
        $regex = trim($regex);
325
        $separator = substr($regex, 0, 1);
326
        $parts = explode($separator, $regex);
327
        array_shift($parts);
328
        
329
        $modifiers = array_pop($parts);
330
        if($modifiers == $separator) {
331
            $modifiers = '';
332
        }
333
        
334
        $modifierReplacements = array(
335
            's' => '',
336
            'U' => ''
337
        );
338
        
339
        $modifiers = str_replace(array_keys($modifierReplacements), array_values($modifierReplacements), $modifiers);
340
        
341
        $format = implode($separator, $parts);
342
        
343
        // convert the anchors that are not supported in js regexes
344
        $format = str_replace(array('\\A', '\\Z', '\\z'), array('^', '$', ''), $format);
345
        
346
        if($statementType==self::JS_REGEX_JSON)
347
        {
348
            return ConvertHelper::var2json(array(
349
                'format' => $format,
350
                'modifiers' => $modifiers
351
            ));
352
        }
353
        
354
        if(!empty($modifiers)) {
355
            return sprintf(
356
                'new RegExp(%s, %s)',
357
                ConvertHelper::var2json($format),
358
                ConvertHelper::var2json($modifiers)
359
            );
360
        }
361
        
362
        return sprintf(
363
            'new RegExp(%s)',
364
            ConvertHelper::var2json($format)
365
        );
366
    }
367
}
368