Skip to content

Commit ae1b1b2

Browse files
feature #416 PHP 8.3: json_validate polyfill (IonBazan)
This PR was merged into the 1.26-dev branch. Discussion ---------- PHP 8.3: json_validate polyfill This PR adds `json_validate()` polyfill - see: https://wiki.php.net/rfc/json_validate. Tests examples were taken from the php-src implementation. Test failures are unrelated. Float tests are fixed in #417, remaining PHP 8.2 mbstring test can be sorted out separately. Commits ------- 64c9121 Add json_validate polyfill
2 parents 1b5d247 + 64c9121 commit ae1b1b2

File tree

10 files changed

+224
-0
lines changed

10 files changed

+224
-0
lines changed

.github/workflows/tests.yml

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -24,6 +24,7 @@ jobs:
2424
- php: '8.0'
2525
- php: '8.1'
2626
- php: '8.2'
27+
- php: '8.3'
2728
mode: experimental
2829
fail-fast: false
2930

README.md

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -65,6 +65,7 @@ Polyfills are provided for:
6565
- the `AllowDynamicProperties` attribute introduced in PHP 8.2;
6666
- the `SensitiveParameter` attribute introduced in PHP 8.2;
6767
- the `SensitiveParameterValue` class introduced in PHP 8.2;
68+
- the `json_validate` function introduced in PHP 8.3;
6869

6970
It is strongly recommended to upgrade your PHP version and/or install the missing
7071
extensions whenever possible. This polyfill should be used only when there is no
@@ -96,6 +97,7 @@ should **not** `require` the `symfony/polyfill` package, but the standalone ones
9697
- `symfony/polyfill-php80` for using the PHP 8.0 functions,
9798
- `symfony/polyfill-php81` for using the PHP 8.1 functions,
9899
- `symfony/polyfill-php82` for using the PHP 8.2 functions,
100+
- `symfony/polyfill-php83` for using the PHP 8.3 functions,
99101
- `symfony/polyfill-iconv` for using the iconv functions,
100102
- `symfony/polyfill-intl-grapheme` for using the `grapheme_*` functions,
101103
- `symfony/polyfill-intl-idn` for using the `idn_to_ascii` and `idn_to_utf8` functions,

composer.json

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -32,6 +32,7 @@
3232
"symfony/polyfill-php80": "self.version",
3333
"symfony/polyfill-php81": "self.version",
3434
"symfony/polyfill-php82": "self.version",
35+
"symfony/polyfill-php83": "self.version",
3536
"symfony/polyfill-iconv": "self.version",
3637
"symfony/polyfill-intl-grapheme": "self.version",
3738
"symfony/polyfill-intl-icu": "self.version",

src/Php83/LICENSE

Lines changed: 19 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,19 @@
1+
Copyright (c) 2022 Fabien Potencier
2+
3+
Permission is hereby granted, free of charge, to any person obtaining a copy
4+
of this software and associated documentation files (the "Software"), to deal
5+
in the Software without restriction, including without limitation the rights
6+
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
7+
copies of the Software, and to permit persons to whom the Software is furnished
8+
to do so, subject to the following conditions:
9+
10+
The above copyright notice and this permission notice shall be included in all
11+
copies or substantial portions of the Software.
12+
13+
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
14+
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
15+
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
16+
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
17+
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
18+
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
19+
THE SOFTWARE.

src/Php83/Php83.php

Lines changed: 41 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,41 @@
1+
<?php
2+
3+
/*
4+
* This file is part of the Symfony package.
5+
*
6+
* (c) Fabien Potencier <[email protected]>
7+
*
8+
* For the full copyright and license information, please view the LICENSE
9+
* file that was distributed with this source code.
10+
*/
11+
12+
namespace Symfony\Polyfill\Php83;
13+
14+
/**
15+
* @author Ion Bazan <[email protected]>
16+
*
17+
* @internal
18+
*/
19+
final class Php83
20+
{
21+
private const JSON_MAX_DEPTH = 0x7FFFFFFF; // see https://www.php.net/manual/en/function.json-decode.php
22+
23+
public static function json_validate(string $json, int $depth = 512, int $flags = 0): bool
24+
{
25+
if (0 !== $flags && \defined('JSON_INVALID_UTF8_IGNORE') && \JSON_INVALID_UTF8_IGNORE !== $flags) {
26+
throw new \ValueError('json_validate(): Argument #3 ($flags) must be a valid flag (allowed flags: JSON_INVALID_UTF8_IGNORE)');
27+
}
28+
29+
if ($depth <= 0) {
30+
throw new \ValueError('json_validate(): Argument #2 ($depth) must be greater than 0');
31+
}
32+
33+
if ($depth >= self::JSON_MAX_DEPTH) {
34+
throw new \ValueError(sprintf('json_validate(): Argument #2 ($depth) must be less than %d', self::JSON_MAX_DEPTH));
35+
}
36+
37+
json_decode($json, null, $depth, $flags);
38+
39+
return \JSON_ERROR_NONE === json_last_error();
40+
}
41+
}

src/Php83/README.md

Lines changed: 14 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,14 @@
1+
Symfony Polyfill / Php83
2+
========================
3+
4+
This component provides features added to PHP 8.3 core:
5+
6+
- [`json_validate`](https://wiki.php.net/rfc/json_validate)
7+
8+
More information can be found in the
9+
[main Polyfill README](https://github.com/symfony/polyfill/blob/main/README.md).
10+
11+
License
12+
=======
13+
14+
This library is released under the [MIT license](LICENSE).

src/Php83/bootstrap.php

Lines changed: 20 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,20 @@
1+
<?php
2+
3+
/*
4+
* This file is part of the Symfony package.
5+
*
6+
* (c) Fabien Potencier <[email protected]>
7+
*
8+
* For the full copyright and license information, please view the LICENSE
9+
* file that was distributed with this source code.
10+
*/
11+
12+
use Symfony\Polyfill\Php83 as p;
13+
14+
if (\PHP_VERSION_ID >= 80300) {
15+
return;
16+
}
17+
18+
if (!function_exists('json_validate')) {
19+
function json_validate(string $json, int $depth = 512, int $flags = 0): bool { return p\Php83::json_validate($json, $depth, $flags); }
20+
}

src/Php83/composer.json

Lines changed: 36 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,36 @@
1+
{
2+
"name": "symfony/polyfill-php83",
3+
"type": "library",
4+
"description": "Symfony polyfill backporting some PHP 8.3+ features to lower PHP versions",
5+
"keywords": ["polyfill", "shim", "compatibility", "portable"],
6+
"homepage": "https://symfony.com",
7+
"license": "MIT",
8+
"authors": [
9+
{
10+
"name": "Nicolas Grekas",
11+
"email": "[email protected]"
12+
},
13+
{
14+
"name": "Symfony Community",
15+
"homepage": "https://symfony.com/contributors"
16+
}
17+
],
18+
"require": {
19+
"php": ">=7.1",
20+
"symfony/polyfill-php80": "^1.14"
21+
},
22+
"autoload": {
23+
"psr-4": { "Symfony\\Polyfill\\Php83\\": "" },
24+
"files": [ "bootstrap.php" ],
25+
},
26+
"minimum-stability": "dev",
27+
"extra": {
28+
"branch-alias": {
29+
"dev-main": "1.26-dev"
30+
},
31+
"thanks": {
32+
"name": "symfony/polyfill",
33+
"url": "https://github.com/symfony/polyfill"
34+
}
35+
}
36+
}

src/bootstrap.php

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -32,3 +32,7 @@
3232
if (\PHP_VERSION_ID < 80200) {
3333
require __DIR__.'/Php82/bootstrap.php';
3434
}
35+
36+
if (\PHP_VERSION_ID < 80300) {
37+
require __DIR__.'/Php83/bootstrap.php';
38+
}

tests/Php83/Php83Test.php

Lines changed: 86 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,86 @@
1+
<?php
2+
3+
/*
4+
* This file is part of the Symfony package.
5+
*
6+
* (c) Fabien Potencier <[email protected]>
7+
*
8+
* For the full copyright and license information, please view the LICENSE
9+
* file that was distributed with this source code.
10+
*/
11+
12+
namespace Symfony\Polyfill\Tests\Php83;
13+
14+
use PHPUnit\Framework\TestCase;
15+
16+
class Php83Test extends TestCase
17+
{
18+
/**
19+
* @covers \Symfony\Polyfill\Php83\Php83::json_validate
20+
* @dataProvider jsonDataProvider
21+
*/
22+
public function testJsonValidate(bool $valid, string $json, string $errorMessage = 'No error', int $depth = 512, int $options = 0)
23+
{
24+
$this->assertSame($valid, json_validate($json, $depth, $options));
25+
$this->assertSame($errorMessage, json_last_error_msg());
26+
}
27+
28+
/**
29+
* @return iterable<array{0: bool, 1: string, 2?: string, 3?: int, 4?: int}>
30+
*/
31+
public function jsonDataProvider(): iterable
32+
{
33+
yield [false, '', 'Syntax error'];
34+
yield [false, '.', 'Syntax error'];
35+
yield [false, '<?>', 'Syntax error'];
36+
yield [false, ';', 'Syntax error'];
37+
yield [false, 'руссиш', 'Syntax error'];
38+
yield [false, 'blah', 'Syntax error'];
39+
yield [false, '{ "": "": "" } }', 'Syntax error'];
40+
yield [false, '{ "test": {} "foo": "bar" }, "test2": {"foo" : "bar" }, "test2": {"foo" : "bar" } }', 'Syntax error'];
41+
yield [true, '{ "test": { "foo": "bar" } }'];
42+
yield [true, '{ "test": { "foo": "" } }'];
43+
yield [true, '{ "": { "foo": "" } }'];
44+
yield [true, '{ "": { "": "" } }'];
45+
yield [true, '{ "test": {"foo": "bar"}, "test2": {"foo" : "bar" }, "test2": {"foo" : "bar" } }'];
46+
yield [true, '{ "test": {"foo": "bar"}, "test2": {"foo" : "bar" }, "test3": {"foo" : "bar" } }'];
47+
yield [false, '{"key1":"value1", "key2":"value2"}', 'Maximum stack depth exceeded', 1];
48+
yield [false, "\"a\xb0b\"", 'Malformed UTF-8 characters, possibly incorrectly encoded'];
49+
50+
if (\defined('JSON_INVALID_UTF8_IGNORE')) {
51+
yield [true, "\"a\xb0b\"", 'No error', 512, \JSON_INVALID_UTF8_IGNORE];
52+
} else {
53+
// The $options should not be validated when JSON_INVALID_UTF8_IGNORE is not defined (PHP 7.1)
54+
yield [true, '{}', 'No error', 512, 1];
55+
}
56+
}
57+
58+
/**
59+
* @covers \Symfony\Polyfill\Php83\Php83::json_validate
60+
*
61+
* @dataProvider invalidOptionsProvider
62+
*/
63+
public function testInvalidOptionsProvided(int $depth, int $flags, string $expectedError)
64+
{
65+
$this->expectException(\ValueError::class);
66+
$this->expectErrorMessage($expectedError);
67+
json_validate('{}', $depth, $flags);
68+
}
69+
70+
/**
71+
* @return iterable<array{0: int, 1: int, 2: string}>
72+
*/
73+
public function invalidOptionsProvider(): iterable
74+
{
75+
yield [0, 0, 'json_validate(): Argument #2 ($depth) must be greater than 0'];
76+
yield [\PHP_INT_MAX, 0, 'json_validate(): Argument #2 ($depth) must be less than 2147483647'];
77+
78+
if (\defined('JSON_INVALID_UTF8_IGNORE')) {
79+
yield [
80+
512,
81+
\JSON_BIGINT_AS_STRING,
82+
'json_validate(): Argument #3 ($flags) must be a valid flag (allowed flags: JSON_INVALID_UTF8_IGNORE)',
83+
];
84+
}
85+
}
86+
}

0 commit comments

Comments
 (0)