Skip to content

Commit 52b022f

Browse files
committed
Add conversion functions
- intval - floatval - strval - boolval
1 parent 84e948f commit 52b022f

File tree

7 files changed

+265
-0
lines changed

7 files changed

+265
-0
lines changed

README.md

+29
Original file line numberDiff line numberDiff line change
@@ -165,6 +165,35 @@ Adds tests known from PHP, so you can test a value for being:
165165
{% endif %}
166166
```
167167

168+
### Convert to type: intval, strval, floatval and boolval filters
169+
170+
Converting a variable to a specific type is not something Twig encourages and it probably should be avoided, if possible. Yet there are situations where you just want to convert something to an integer or string so you can be sure a comparison is type safe or that there is no unexpected behavior because one value has the wrong type.
171+
172+
```twig
173+
{% if '5'|intval === 5 %}
174+
Convert '5' to an integer - this if block is being executed
175+
{% endif %}
176+
177+
{% if 5.7|strval === '5.7' %}
178+
Convert 5.7 to a string - this if block is being executed
179+
{% endif %}
180+
181+
{% if 1|boolval === true %}
182+
Convert 1 to a boolean - this if block is being executed
183+
{% endif %}
184+
185+
{% if '5.7'|floatval === 5.7 %}
186+
Convert '5.7' to a float - this if block is being executed
187+
{% endif %}
188+
```
189+
190+
These filters mainly behave like the ones in PHP (and use the corresponding PHP functions internally), but there is some additional behavior to detect or avoid likely errors:
191+
192+
- only scalar values, null and objects with a __toString method are allowed, so if you use any of these filters with an array or an object that cannot be cast to a string it will throw an exception
193+
- null will return 0 for intval, '' for strval, false for boolval and 0.0 for floatval (just like in PHP)
194+
- objects with a __toString method will be converted to a string first (using the __toString method), and only after that intval, boolval and floatval will be used
195+
- boolval should be used with caution, as if you give it any non-numeric string it will return true, yet empty strings and "0" will return false. boolval is here more for completeness, as it is probably the least useful conversion function in PHP. The recommendation is to use the other three functions instead of using boolval if possible.
196+
168197
### && and ||
169198

170199
If you want to make expressions even more like PHP, you can use `&&` instead of `and` and `||` instead of `or`. This might be the least useful part of this library, as `and` and `or` are already short and clear, yet it is another easily remedied difference between Twig and PHP, and `&&` and `||` can be easier to spot in comparison to `and` and `or`.

src/PhpSyntaxExtension.php

+55
Original file line numberDiff line numberDiff line change
@@ -49,9 +49,64 @@ public function getFilters()
4949

5050
return $timestamp;
5151
}),
52+
new TwigFilter('intval', /** @param mixed $var */ function ($var): int {
53+
if (\is_int($var)) {
54+
return $var;
55+
}
56+
57+
$var = $this->validateType($var);
58+
59+
return \intval($var);
60+
}),
61+
new TwigFilter('floatval', /** @param mixed $var */ function ($var): float {
62+
if (\is_float($var)) {
63+
return $var;
64+
}
65+
66+
$var = $this->validateType($var);
67+
68+
return \floatval($var);
69+
}),
70+
new TwigFilter('strval', /** @param mixed $var */ function ($var): string {
71+
if (\is_string($var)) {
72+
return $var;
73+
}
74+
75+
$var = $this->validateType($var);
76+
77+
return \strval($var);
78+
}),
79+
new TwigFilter('boolval', /** @param mixed $var */ function ($var): bool {
80+
if (\is_bool($var)) {
81+
return $var;
82+
}
83+
84+
$var = $this->validateType($var);
85+
86+
return \boolval($var);
87+
}),
5288
];
5389
}
5490

91+
/**
92+
* @param mixed $var
93+
* @return string|int|float|bool|null
94+
*/
95+
private function validateType($var)
96+
{
97+
if (\is_object($var) && \method_exists($var, '__toString')) {
98+
return $var->__toString();
99+
}
100+
101+
if (!\is_scalar($var) && $var !== null) {
102+
throw new \InvalidArgumentException(
103+
'Non-scalar value given to intval/floatval/strval/boolval filter'
104+
);
105+
}
106+
107+
return $var;
108+
}
109+
55110
public function getTests(): array
56111
{
57112
return [

tests/Fixtures/boolval.test

+46
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,46 @@
1+
--TEST--
2+
"boolval" filter test
3+
--TEMPLATE--
4+
{% if 1|boolval === true %}
5+
1
6+
{% endif %}
7+
{% if '1'|boolval === true %}
8+
2
9+
{% endif %}
10+
{% if 1.0|boolval === true %}
11+
3
12+
{% endif %}
13+
{% if tostringobj|boolval === true %}
14+
4
15+
{% endif %}
16+
{% if 1.7|boolval === true %}
17+
5
18+
{% endif %}
19+
{% if false|boolval === false %}
20+
6
21+
{% endif %}
22+
{% if 'true'|boolval === true %}
23+
7
24+
{% endif %}
25+
{% if null|boolval === false %}
26+
8
27+
{% endif %}
28+
--DATA--
29+
return [
30+
'obj' => new \stdClass(),
31+
'tostringobj' => new class {
32+
public function __toString()
33+
{
34+
return '5';
35+
}
36+
},
37+
];
38+
--EXPECT--
39+
1
40+
2
41+
3
42+
4
43+
5
44+
6
45+
7
46+
8

tests/Fixtures/floatval.test

+41
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,41 @@
1+
--TEST--
2+
"floatval" filter test
3+
--TEMPLATE--
4+
{% if '1'|floatval === 1.0 %}
5+
1
6+
{% endif %}
7+
{% if '1'|floatval === '1' %}
8+
2
9+
{% endif %}
10+
{% if true|floatval === 1.0 %}
11+
3
12+
{% endif %}
13+
{% if tostringobj|floatval === 5.0 %}
14+
4
15+
{% endif %}
16+
{% if 1.7|floatval === 1.7 %}
17+
5
18+
{% endif %}
19+
{% if '5.7'|floatval === 5.7 %}
20+
6
21+
{% endif %}
22+
{% if null|floatval === 0.0 %}
23+
7
24+
{% endif %}
25+
--DATA--
26+
return [
27+
'obj' => new \stdClass(),
28+
'tostringobj' => new class {
29+
public function __toString()
30+
{
31+
return '5';
32+
}
33+
},
34+
];
35+
--EXPECT--
36+
1
37+
3
38+
4
39+
5
40+
6
41+
7

tests/Fixtures/intval.test

+41
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,41 @@
1+
--TEST--
2+
"intval" filter test
3+
--TEMPLATE--
4+
{% if '1'|intval === 1 %}
5+
1
6+
{% endif %}
7+
{% if '1'|intval === '1' %}
8+
2
9+
{% endif %}
10+
{% if true|intval === 1 %}
11+
3
12+
{% endif %}
13+
{% if tostringobj|intval === 5 %}
14+
4
15+
{% endif %}
16+
{% if 1.7|intval === 1 %}
17+
5
18+
{% endif %}
19+
{% if 5|intval === 5 %}
20+
6
21+
{% endif %}
22+
{% if null|intval === 0 %}
23+
7
24+
{% endif %}
25+
--DATA--
26+
return [
27+
'obj' => new \stdClass(),
28+
'tostringobj' => new class {
29+
public function __toString()
30+
{
31+
return '5';
32+
}
33+
},
34+
];
35+
--EXPECT--
36+
1
37+
3
38+
4
39+
5
40+
6
41+
7

tests/Fixtures/strval.test

+41
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,41 @@
1+
--TEST--
2+
"strval" filter test
3+
--TEMPLATE--
4+
{% if 1|strval === '1' %}
5+
1
6+
{% endif %}
7+
{% if 1|strval === 1 %}
8+
2
9+
{% endif %}
10+
{% if true|strval === '1' %}
11+
3
12+
{% endif %}
13+
{% if tostringobj|strval === '5' %}
14+
4
15+
{% endif %}
16+
{% if 1.7|strval === '1.7' %}
17+
5
18+
{% endif %}
19+
{% if 'dada'|strval === 'dada' %}
20+
6
21+
{% endif %}
22+
{% if null|strval === '' %}
23+
7
24+
{% endif %}
25+
--DATA--
26+
return [
27+
'obj' => new \stdClass(),
28+
'tostringobj' => new class {
29+
public function __toString()
30+
{
31+
return '5';
32+
}
33+
},
34+
];
35+
--EXPECT--
36+
1
37+
3
38+
4
39+
5
40+
6
41+
7

tests/Fixtures/strval_exception.test

+12
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,12 @@
1+
--TEST--
2+
"strtotime" filter exception test with non-scalar value
3+
--TEMPLATE--
4+
{% if obj|strval === '' %}
5+
3
6+
{% endif %}
7+
--DATA--
8+
return [
9+
'obj' => new \stdClass(),
10+
];
11+
--EXCEPTION--
12+
Twig\Error\RuntimeError: An exception has been thrown during the rendering of a template ("Non-scalar value given to intval/floatval/strval/boolval filter") in "index.twig" at line 2.

0 commit comments

Comments
 (0)