Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Add available flag FORCE_OBJECT for Json::encode() #152

Open
wants to merge 4 commits into
base: master
Choose a base branch
from
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
5 changes: 4 additions & 1 deletion src/Utils/Json.php
Original file line number Diff line number Diff line change
Expand Up @@ -25,9 +25,11 @@ final class Json

public const ESCAPE_UNICODE = 0b0100;

const FORCE_OBJECT = 0b10000;


/**
* Returns the JSON representation of a value. Accepts flag Json::PRETTY.
* Returns the JSON representation of a value. Accepts flag Json::PRETTY and JSON::FORCE_OBJECT.
* @param mixed $value
* @throws JsonException
*/
Expand All @@ -36,6 +38,7 @@ public static function encode($value, int $flags = 0): string
$flags = ($flags & self::ESCAPE_UNICODE ? 0 : JSON_UNESCAPED_UNICODE)
| JSON_UNESCAPED_SLASHES
| ($flags & self::PRETTY ? JSON_PRETTY_PRINT : 0)
| ($flags & self::FORCE_OBJECT ? JSON_FORCE_OBJECT : 0)
| (defined('JSON_PRESERVE_ZERO_FRACTION') ? JSON_PRESERVE_ZERO_FRACTION : 0); // since PHP 5.6.6 & PECL JSON-C 1.3.7

$json = json_encode($value, $flags);
Expand Down
35 changes: 35 additions & 0 deletions src/Utils/Reflection.php
Original file line number Diff line number Diff line change
Expand Up @@ -113,6 +113,7 @@ public static function getPropertyDeclaringClass(\ReflectionProperty $prop): \Re
{
foreach ($prop->getDeclaringClass()->getTraits() as $trait) {
if ($trait->hasProperty($prop->name)
// doc-comment guessing as workaround for insufficient PHP reflection
&& $trait->getProperty($prop->name)->getDocComment() === $prop->getDocComment()
) {
return self::getPropertyDeclaringClass($trait->getProperty($prop->name));
Expand All @@ -122,6 +123,40 @@ public static function getPropertyDeclaringClass(\ReflectionProperty $prop): \Re
}


/**
* Returns declaring class or trait.
*/
public static function getMethodDeclaringMethod(\ReflectionMethod $method): \ReflectionMethod
{
// file & line guessing as workaround for insufficient PHP reflection
$decl = $method->getDeclaringClass();
if ($decl->getFileName() === $method->getFileName()
&& $decl->getStartLine() <= $method->getStartLine()
&& $decl->getEndLine() >= $method->getEndLine()
) {
return $method;
}

$hash = [$method->getFileName(), $method->getStartLine(), $method->getEndLine()];
if (($alias = $decl->getTraitAliases()[$method->name] ?? null)
&& ($m = new \ReflectionMethod($alias))
&& [$m->getFileName(), $m->getStartLine(), $m->getEndLine()] === $hash
) {
return self::getMethodDeclaringMethod($m);
}

foreach ($decl->getTraits() as $trait) {
if ($trait->hasMethod($method->name)
&& ($m = $trait->getMethod($method->name))
&& [$m->getFileName(), $m->getStartLine(), $m->getEndLine()] === $hash
) {
return self::getMethodDeclaringMethod($m);
}
}
return $method;
}


/**
* Are documentation comments available?
*/
Expand Down
20 changes: 13 additions & 7 deletions src/Utils/Validators.php
Original file line number Diff line number Diff line change
Expand Up @@ -301,12 +301,15 @@ public static function isEmail(string $value): bool
{
$atom = "[-a-z0-9!#$%&'*+/=?^_`{|}~]"; // RFC 5322 unquoted characters in local-part
$alpha = "a-z\x80-\xFF"; // superset of IDN
return (bool) preg_match("(^
(\"([ !#-[\\]-~]*|\\\\[ -~])+\"|$atom+(\\.$atom+)*) # quoted or unquoted
return (bool) preg_match(<<<XX
(^
("([ !#-[\\]-~]*|\\\\[ -~])+"|$atom+(\\.$atom+)*) # quoted or unquoted
@
([0-9$alpha]([-0-9$alpha]{0,61}[0-9$alpha])?\\.)+ # domain - RFC 1034
[$alpha]([-0-9$alpha]{0,17}[$alpha])? # top domain
$)Dix", $value);
([0-9$alpha]([-0-9$alpha]{0,61}[0-9$alpha])?\\.)+ # domain - RFC 1034
[$alpha]([-0-9$alpha]{0,17}[$alpha])? # top domain
$)Dix
XX
, $value);
}


Expand All @@ -316,7 +319,8 @@ public static function isEmail(string $value): bool
public static function isUrl(string $value): bool
{
$alpha = "a-z\x80-\xFF";
return (bool) preg_match("(^
return (bool) preg_match(<<<XX
(^
https?://(
(([-_0-9$alpha]+\\.)* # subdomain
[0-9$alpha]([-0-9$alpha]{0,61}[0-9$alpha])?\\.)? # domain
Expand All @@ -327,7 +331,9 @@ public static function isUrl(string $value): bool
(/\\S*)? # path
(\?\\S*)? # query
(\#\\S*)? # fragment
$)Dix", $value);
$)Dix
XX
, $value);
}


Expand Down
2 changes: 2 additions & 0 deletions tests/Utils/Json.encode().phpt
Original file line number Diff line number Diff line change
Expand Up @@ -41,6 +41,8 @@ Assert::same('"\u2028\u2029"', Json::encode("\u{2028}\u{2029}", Json::ESCAPE_UNI
// JSON_PRETTY_PRINT
Assert::same("[\n 1,\n 2,\n 3\n]", Json::encode([1, 2, 3], Json::PRETTY));

Assert::same('[]', JSON::encode([]));
Assert::same('{}', JSON::encode([], Json::FORCE_OBJECT));

Assert::exception(function () {
Json::encode(NAN);
Expand Down
112 changes: 112 additions & 0 deletions tests/Utils/Reflection.getDeclaringMethod.alias.phpt
Original file line number Diff line number Diff line change
@@ -0,0 +1,112 @@
<?php

/**
* Test: Nette\Utils\Reflection::getDeclaringMethod
*/

declare(strict_types=1);

use Nette\Utils\Reflection;
use Tester\Assert;


require __DIR__ . '/../bootstrap.php';


trait A
{
public function foo()
{
}
}

trait B
{
use A {
A::foo as foo2;
}
}

trait B2
{
use A {
A::foo as foo2;
}


public function foo2()
{
}
}

class E1
{
use B {
B::foo2 as alias;
}
}

class E2
{
use B {
B::foo2 as alias;
}


public function foo2()
{
}


public function alias()
{
}
}

class E3
{
use B2 {
B2::foo as foo3;
}
}


function get(ReflectionMethod $m)
{
$res = Reflection::getMethodDeclaringMethod($m);
return $res->getDeclaringClass()->name . '::' . $res->name;
}


// new ReflectionMethod and getMethod returns different method names, PHP #79636

// Method in trait
Assert::same('A::foo', get((new ReflectionClass('E3'))->getMethod('foo3')));
Assert::same('A::foo', get(new ReflectionMethod('E3', 'foo3')));

Assert::same('B2::foo2', get((new ReflectionClass('E3'))->getMethod('foo2')));
Assert::same('B2::foo2', get(new ReflectionMethod('E3', 'foo2')));

Assert::same('A::foo', get((new ReflectionClass('E3'))->getMethod('foo')));
Assert::same('A::foo', get(new ReflectionMethod('E3', 'foo')));

// Method in class
Assert::same('E2::alias', get((new ReflectionClass('E2'))->getMethod('alias')));
Assert::same('E2::alias', get(new ReflectionMethod('E2', 'alias')));

Assert::same('E2::foo2', get((new ReflectionClass('E2'))->getMethod('foo2')));
Assert::same('E2::foo2', get(new ReflectionMethod('E2', 'foo2')));

// Method in trait
Assert::same('A::foo', get((new ReflectionClass('E1'))->getMethod('alias')));
Assert::same('A::foo', get(new ReflectionMethod('E1', 'alias')));

// Method in trait
Assert::same('B2::foo2', get((new ReflectionClass('B2'))->getMethod('foo2')));
Assert::same('B2::foo2', get(new ReflectionMethod('B2', 'foo2')));

Assert::same('A::foo', get((new ReflectionClass('B'))->getMethod('foo2')));
Assert::same('A::foo', get(new ReflectionMethod('B', 'foo2')));

Assert::same('A::foo', get((new ReflectionClass('A'))->getMethod('foo')));
Assert::same('A::foo', get(new ReflectionMethod('A', 'foo')));
67 changes: 67 additions & 0 deletions tests/Utils/Reflection.getDeclaringMethod.insteadof.phpt
Original file line number Diff line number Diff line change
@@ -0,0 +1,67 @@
<?php

/**
* Test: Nette\Utils\Reflection::getDeclaringMethod
*/

declare(strict_types=1);

use Nette\Utils\Reflection;
use Tester\Assert;


require __DIR__ . '/../bootstrap.php';


trait A
{
public function foo()
{
}
}

trait B
{
public function foo()
{
}
}

class C
{
use A, B {
B::foo insteadof A;
}
}

class D
{
use A, B {
B::foo insteadof A;
}


public function foo()
{
}
}


function get(ReflectionMethod $m)
{
$res = Reflection::getMethodDeclaringMethod($m);
return $res->getDeclaringClass()->name . '::' . $res->name;
}


// Method in class
Assert::same('D::foo', get(new ReflectionMethod('D', 'foo')));

// Method in trait - uses doccomment & file-line workaround
Assert::same('B::foo', get(new ReflectionMethod('C', 'foo')));

// Method in trait
Assert::same('B::foo', get(new ReflectionMethod('B', 'foo')));

// Method in trait
Assert::same('A::foo', get(new ReflectionMethod('A', 'foo')));
66 changes: 66 additions & 0 deletions tests/Utils/Reflection.getDeclaringMethod.overwrite.phpt
Original file line number Diff line number Diff line change
@@ -0,0 +1,66 @@
<?php

/**
* Test: Nette\Utils\Reflection::getDeclaringMethod
*/

declare(strict_types=1);

use Nette\Utils\Reflection;
use Tester\Assert;


require __DIR__ . '/../bootstrap.php';


trait A
{
public function foo()
{
}
}

trait B
{
use A;

public function foo()
{
}
}

class C
{
use B;

public function foo()
{
}
}

class D extends C
{
public function foo()
{
}
}


function get(ReflectionMethod $m)
{
$res = Reflection::getMethodDeclaringMethod($m);
return $res->getDeclaringClass()->name . '::' . $res->name;
}


// Method in class
Assert::same('D::foo', get(new ReflectionMethod('D', 'foo')));

// Method in class - uses doccomment & file-line workaround
Assert::same('C::foo', get(new ReflectionMethod('C', 'foo')));

// Method in trait - uses doccomment & file-line workaround
Assert::same('B::foo', get(new ReflectionMethod('B', 'foo')));

// Method in trait
Assert::same('A::foo', get(new ReflectionMethod('A', 'foo')));
Loading