Skip to content

ProxyGenerator: Added support for PHP 7.1 features #734

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

Merged
merged 2 commits into from
Oct 6, 2016

Conversation

Majkl578
Copy link
Contributor

@Majkl578 Majkl578 commented Aug 16, 2016

This was supposed to work without any change in the generator, but it's unfortunately not the case. There are multiple problems:

  1. generating nullable type with FQCN is broken - generated code results in parse error;
  2. reflection with nullable FQCN is broken - unable to generate proxy;
  3. it's impossible to distinguish nullable type with null default value and non-nullable type with null default value - may change function signature in the proxy.

Forwarded upstream for more discussion to the PR where the changes were originally discussed and where you can also find more discussion with some other concerns: php/php-src#2068 (comment)
In case of no activity there I'll forward it to the bugs tracker soon or eventually php.internals.

(Travis still has old PHP 7.1 version without the changes, they'll appear in beta3.)

@Majkl578 Majkl578 force-pushed the proxygenerator-php7.1 branch from 6a77931 to 8080153 Compare August 16, 2016 02:37
@Majkl578 Majkl578 force-pushed the proxygenerator-php7.1 branch from 8080153 to c58633f Compare August 31, 2016 19:57
@Majkl578
Copy link
Contributor Author

Majkl578 commented Aug 31, 2016

Updated for PHP 7.1.0RC1 (probably some refactoring would be good), but the Reflection API is still a mess IMHO. 😟
cc @Ocramius @trowski

@Majkl578
Copy link
Contributor Author

Added cd7fbb4 to show the inconsistent signature change for parameter without nullable type, but with null as default value. How should we handle this? I consider it a bug since the signature is different, but there is no easy way to support it properly (allowsNull returns true when default value is NULL).

@Ocramius
Copy link
Member

Ocramius commented Sep 1, 2016

Note: I stopped working on this unit 7.1 is in RC phase, since the
ReflectionType API is basically useless in its current state. Posted about
it on thr mailing list multiple times, btw.

On 1 Sep 2016 00:50, "Michael Moravec" [email protected] wrote:

Added cd7fbb4
cd7fbb4
to show the inconsistent signature change for parameter without nullable
type, but with null as default value. How should we handle this? I consider
it a bug since the signature is different, but there is no easy way to
support it properly (allowsNull returns true when default value is NULL).


You are receiving this because you were mentioned.
Reply to this email directly, view it on GitHub
#734 (comment), or mute
the thread
https://github.com/notifications/unsubscribe-auth/AAJakJ9SCkeHwEuYuyPXnRwq7APGYP2Xks5qlgVKgaJpZM4Jk9-T
.

@Ocramius
Copy link
Member

Finished working on zendframework/zend-code#87 - should be stable enough to be usable :-)

@Ocramius
Copy link
Member

Need checking of:

  • public function foo(int $bar = null) => public function foo(?int $bar = null)
  • public function foo(iterable $bar)
  • public function foo($bar) : iterable
  • public function foo($bar) : void

@Majkl578
Copy link
Contributor Author

Although 7.1 is RC2 already, Reflection is still a mess. Let's just wait for the resolution of php/php-src#2136 and see what happens. It may probably also fix the nonsense with broken allowsNull. 🙏

@Majkl578 Majkl578 force-pushed the proxygenerator-php7.1 branch from cd7fbb4 to 7818108 Compare September 30, 2016 14:02
@Majkl578 Majkl578 changed the title Added tests for nullable types (PHP 7.1) ProxyGenerator: Added support for PHP 7.1 features Sep 30, 2016
@Majkl578 Majkl578 force-pushed the proxygenerator-php7.1 branch from 7818108 to 8e3b4d4 Compare September 30, 2016 14:06
@Majkl578
Copy link
Contributor Author

Majkl578 commented Sep 30, 2016

PR updated, now also includes support for void return type and iterable pseudotype. Tested against PHP 7.1 RC3.

@Ocramius
Copy link
Member

@Majkl578 amazing! Reviewing now.

}

if (method_exists($parameter, 'hasType') && $parameter->hasType() && $parameter->getType()->isBuiltin()) {
return (string) $parameter->getType();
if (method_exists($parameter, 'hasType')) {
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This block is to be moved to a private method

@@ -935,25 +939,49 @@ private function buildParametersString(ClassMetadata $class, \ReflectionMethod $
*/
private function getParameterType(ClassMetadata $class, \ReflectionMethod $method, \ReflectionParameter $parameter)
{
$typeDeclaration = '';
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This assignment and subsequent block is to be moved to a private method

@@ -1018,20 +1046,43 @@ private function getMethodReturnType(\ReflectionMethod $method)

$returnType = $method->getReturnType();

$returnDeclaration = ': ';
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

To be moved to a private method (probably same method as block above)


$classCode = file_get_contents(__DIR__ . '/generated/__CG__DoctrineTestsCommonProxyVoidReturnTypeClass.php');

$this->assertEquals(1, substr_count($classCode, 'function returnsVoid(): void'));
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

We should also verify that calling the method produces no return value. Without actually evaluating the code, we can't be sure that we support void correctly. Not sure if we should put it here or in https://github.com/doctrine/common/blob/670d814b6c8b5e8f39a2764d090463d39b3502c9/tests/Doctrine/Tests/Common/Proxy/ProxyLogicTest.php

Copy link
Contributor Author

@Majkl578 Majkl578 Oct 3, 2016

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

What exactly do you mean? Since we can't put any return value into the method that is to be proxied (otherwise it's a fatal error), just testing that such method returns NULL, i.e. assertNull($proxy->returnsVoid())?

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Something like $proxy->increment(10); and then self::assertSame(10, $proxy->counter). Simply verifies that we're passing parameters.

@Majkl578 Majkl578 force-pushed the proxygenerator-php7.1 branch 2 times, most recently from 3032832 to fbf9791 Compare October 3, 2016 18:24
Copy link
Contributor Author

@Majkl578 Majkl578 left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Updated, refactored most of the code to separate private method and added few more tests.

{
}

public function nullableTypeHintWithDefaultNull(?int $param = null)
Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

There is still no way to detect this correctly at runtime (makes one test fail).

$this->assertEquals(1, substr_count($classCode, 'function nullableTypeHintObject(?\stdClass $param)'));
$this->assertEquals(1, substr_count($classCode, 'function nullableTypeHintSelf(?\\' . $className . ' $param)'));
$this->assertEquals(1, substr_count($classCode, 'function nullableTypeHintWithDefault(?int $param = 123)'));
$this->assertEquals(1, substr_count($classCode, 'function nullableTypeHintWithDefaultNull(?int $param = NULL)'));
Copy link
Contributor Author

@Majkl578 Majkl578 Oct 3, 2016

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This fails in 7.1 because Reflection API still doesn't provide proper way to detect this case (see method declaration above) and I don't see any straightforward workaround that wouldn't break generated code in 7.0 (allowsNull exists there too so method_exists doesn't help).
Any advice how to handle this?

Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

ReflectionParameter::isOptional() and ReflectionParameter::getDefaultValue()

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

@trowski Thanks, unfortunately that's not enough either, I used isDefaultValueAvailable which should be the same. See https://github.com/doctrine/common/pull/734/files#diff-ce03ab9b396edcbb313c54234c20e0deR1081

The following:

public function foo(int $param = null)

would still generate:

public function foo(?int $param = null)

which is not correct (at least in <7.1). And since we don't want any version checks, it's tricky...

Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Is it needed for 7.1 to generate 7.0 compatible code? If so, you could special case nullable parameters with a null default value to always omit the preceding ?. It may not be exactly as written, but it will be compatible.

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

@trowski That's a good question, probably not, but then someone could use 7.1 on CI server to warm-up cache (and generate proxies) and then 7.0 in production...

I already added special case for null default value, but then it has opposite effect.
I actually thought that this code would throw an error due to incompatible signatures:

class Foo {
    function x(?int $x = NULL) {}
}
class Bar extends Foo {
    function x(int $x = NULL) {}
}

But surprisingly it doesn't, even though subclass removes nullability (question mark) from the type, something I'd not really expect. This is super-confusing, really something not well-thought-out in my opinion... 😳

Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Bar::x(int $x = null) is not is not removing nullability from $x, but rather just using older syntax to declare nullability. ?int $x = null is semantically identical to int $x = null, so the definitions are compatible.

@Majkl578 Majkl578 force-pushed the proxygenerator-php7.1 branch 2 times, most recently from 4e46fbb to 464507f Compare October 5, 2016 03:58
@Majkl578 Majkl578 force-pushed the proxygenerator-php7.1 branch from 464507f to 87dc24e Compare October 5, 2016 04:20
@Majkl578
Copy link
Contributor Author

Majkl578 commented Oct 5, 2016

Updated:

  • When default value is null, question mark is not added to ensure PHP 7.0 compatibility (see inline discussion above).
  • Added test for void and calling parent with/without parameters.
  • Hopefully fixed most of the CS issues (no code sniffer here to rescue me).

@@ -22,4 +22,12 @@ public function combinationOfTypeHintsAndNormal(\stdClass $a, $b, int $c)
public function typeHintsWithVariadic(int ...$foo)
{
}

public function withDefaultValue(int $foo = 123)
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Oooh, shiny! I'll have to test this on my libs too.

@Ocramius Ocramius added this to the 2.7.0 milestone Oct 6, 2016
@Ocramius Ocramius self-assigned this Oct 6, 2016
@Ocramius
Copy link
Member

Ocramius commented Oct 6, 2016

@Majkl578 excellent work! The only blocker for getting this merged is having 7.1.0-RC3 on travis, since there were some changes in the API there.

@Ocramius
Copy link
Member

Ocramius commented Oct 6, 2016

Actually, never mind, tested locally, and everything works! Merging, thanks!

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Projects
None yet
Development

Successfully merging this pull request may close these issues.

3 participants