JSHelper::nextElementID()   A
last analyzed

Complexity

Conditions 1
Paths 1

Size

Total Lines 5
Code Lines 2

Duplication

Lines 0
Ratio 0 %

Importance

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