Skip to content
This repository was archived by the owner on Jan 29, 2020. It is now read-only.

PHP 7.1 support #87

Merged
merged 60 commits into from
Oct 24, 2016
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
60 commits
Select commit Hold shift + click to select a range
ed00915
Test asset: class with default-null return parameter
Ocramius Aug 19, 2016
6f1c169
Test asset: class with default-null nullable method parameters
Ocramius Aug 19, 2016
f02e6e2
Test asset: class with nullable method parameters
Ocramius Aug 19, 2016
c6fd91c
Test asset: class with nullable return types
Ocramius Aug 19, 2016
63bbba6
Test case: method generation must work also with nullable return types
Ocramius Aug 19, 2016
d488f5d
Corrected test asset: using same class to distinguish usage of `self`…
Ocramius Aug 19, 2016
131ef0f
Supporting nullable type hints in the `TypeGenerator`
Ocramius Aug 19, 2016
b9ff80d
Making tests pass on 7.1: `self` hint to be properly recognized
Ocramius Aug 19, 2016
e89c054
Re-enabling nullable string type (now not crashing anymore)
Ocramius Aug 19, 2016
9198522
Adding `void` to the possible return type hints in the test assets
Ocramius Aug 19, 2016
427ca19
Testing for `void` as return type
Ocramius Aug 19, 2016
907b358
Void cannot be namespaced
Ocramius Aug 19, 2016
cac6dc2
`void` should just work
Ocramius Aug 19, 2016
c4781a2
`void` cannot be nullable
Ocramius Aug 19, 2016
6ac9d28
Better keys for errors in test case failures
Ocramius Aug 19, 2016
f68ee52
Better keys for errors in test case failures (for valid strings)
Ocramius Aug 19, 2016
820b0cf
Checking nullability of types
Ocramius Aug 19, 2016
e6df60f
Simplistic support of the `void` type
Ocramius Aug 19, 2016
6f8cf39
Correcting test expectation: nullability to be removed from expected …
Ocramius Aug 19, 2016
4ff2b36
Removed useless nullability matcher
Ocramius Aug 19, 2016
beabbe4
Corrected return type in docblock
Ocramius Aug 19, 2016
3f00d69
Removed ignored coverage rule
Ocramius Aug 19, 2016
dcf9934
`void` is not allowed to be nullable (`?` prefix rejected)
Ocramius Aug 19, 2016
eb3362e
Corrected parameter type to match same class
Ocramius Aug 19, 2016
5c5fbe7
Testing nullable parameter definitions
Ocramius Aug 19, 2016
9409b61
`ReflectionType#getName()` to be used instead of `__toString()`
Ocramius Aug 19, 2016
62a6303
Parameter generator test adjustments: must now assert the correct typ…
Ocramius Aug 19, 2016
d2ea9aa
Expecting generated code signature to match parameter types (with nul…
Ocramius Aug 19, 2016
480600b
Correcting method names to reflect correct class parameter name
Ocramius Aug 19, 2016
bd3da75
Correcting parameter type to use same class as defined one, testing n…
Ocramius Aug 19, 2016
94b518e
Reverting test expecations around `getType` calls, maintaining BC the…
Ocramius Aug 19, 2016
6e0df02
Implementing changes to make parameter reflection also expose nullabl…
Ocramius Aug 19, 2016
79a3cd9
expand `self` to class name
Sep 13, 2016
da9a02f
Re-enabling string hints on tests (now passing, as per PHP 7.1.0-RC1)
Ocramius Sep 13, 2016
f0e8621
Nullable return type parameters are not supported in PHP 7.0
Ocramius Sep 13, 2016
9b03825
Skipping `void` and nullable parameters from the `MethodGeneratorTest…
Ocramius Sep 13, 2016
07509d7
Adding empty class test asset
Ocramius Sep 13, 2016
e1b991b
Adding `parent` hint to `ReturnTypeHintedClass`
Ocramius Sep 13, 2016
6f2a368
Adding `parent` to the possible return types to be expanded to the ac…
Ocramius Sep 13, 2016
80efdfc
Adding `parent` nullable return type hint
Ocramius Sep 13, 2016
bba27ef
Expecting `parent` nullable return hint to be expanded to the actual …
Ocramius Sep 13, 2016
6bb8fcd
Expanding also `parent` type when looking at method return types
Ocramius Sep 13, 2016
9e90219
Nullable `parent` hint test asset
Ocramius Sep 13, 2016
23eb0f5
Testing against the `parentParameter` nullable hint
Ocramius Sep 13, 2016
69892eb
Testing against the `parentParameter` nullable hint on the parent par…
Ocramius Sep 13, 2016
1baa95f
Nullable defaulted `parent` parameter should be expaneded to the pare…
Ocramius Sep 13, 2016
8b339a2
Adding simple `parent` hint expansion scenarios
Ocramius Sep 13, 2016
cf1f743
Expanding `parent` parameter hint to the FQCN of the parent class
Ocramius Sep 13, 2016
b12085a
Minor CS fix - excessive spacing
Ocramius Sep 13, 2016
1d518df
Minor CS fix - excessive line length
Ocramius Sep 13, 2016
264bea4
Removing class docblock (not needed in this test asset, and genuinely…
Ocramius Sep 16, 2016
8495179
Test asset with the `iterable` hints (7.1 feature)
Ocramius Sep 16, 2016
5a5a441
The `iterable` PHP 7.1 built-in union-type should not be namespaced
Ocramius Sep 16, 2016
7f5c35a
Expectations around how the `iterable` PHP 7.1 built-in union type sh…
Ocramius Sep 16, 2016
b40bbd1
Adding `iterable` to the internal types recognized by `TypeGenerator`
Ocramius Sep 16, 2016
657e57a
`iterable` (PHP 7.1 internal union type) should be recognized as "spe…
Ocramius Sep 16, 2016
aa8e947
`iterable` (PHP 7.1 internal union type) should be recognized as "spe…
Ocramius Sep 16, 2016
bda0512
Tests around `iterable` to be skipped pre-PHP-7.1
Ocramius Sep 16, 2016
db63773
Adding PHP 7.1 to the build matrix
Ocramius Sep 20, 2016
c682384
`xdebug.ini` may not exist in all matrix entries
Ocramius Sep 20, 2016
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
11 changes: 10 additions & 1 deletion .travis.yml
Original file line number Diff line number Diff line change
Expand Up @@ -47,6 +47,15 @@ matrix:
- php: 7
env:
- DEPS=latest
- php: 7.1
env:
- DEPS=lowest
- php: 7.1
env:
- DEPS=locked
- php: 7.1
env:
- DEPS=latest
- php: hhvm
env:
- DEPS=lowest
Expand All @@ -65,7 +74,7 @@ notifications:

before_install:
- travis_retry composer self-update
- if [[ $TRAVIS_PHP_VERSION != "hhvm" && $TEST_COVERAGE != 'true' ]]; then phpenv config-rm xdebug.ini ; fi
- if [[ $TRAVIS_PHP_VERSION != "hhvm" && $TEST_COVERAGE != 'true' ]]; then phpenv config-rm xdebug.ini || true ; fi

install:
- if [[ $DEPS == 'latest' ]]; then travis_retry composer update $COMPOSER_ARGS ; fi
Expand Down
25 changes: 22 additions & 3 deletions src/Generator/MethodGenerator.php
Original file line number Diff line number Diff line change
Expand Up @@ -9,6 +9,7 @@

namespace Zend\Code\Generator;

use ReflectionMethod;
use Zend\Code\Reflection\MethodReflection;

class MethodGenerator extends AbstractMemberGenerator
Expand Down Expand Up @@ -389,12 +390,30 @@ private static function extractReturnTypeFromMethodReflection(MethodReflection $
return null;
}

$returnTypeString = (string) $returnType;
if (! method_exists($returnType, 'getName')) {
return self::expandLiteralType((string) $returnType, $methodReflection);
}

return ($returnType->allowsNull() ? '?' : '')
. self::expandLiteralType($returnType->getName(), $methodReflection);
}

if ('self' === strtolower($returnType)) {
/**
* @param string $literalReturnType
* @param ReflectionMethod $methodReflection
*
* @return string
*/
private static function expandLiteralType($literalReturnType, ReflectionMethod $methodReflection)
Copy link
Member

Choose a reason for hiding this comment

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

Am I reading this correctly in that it pulls the type from reflecting the docblock in this case? If so, should it also consider static and $this? (If it's strictly from declared return type, ignore.)

Copy link
Member Author

Choose a reason for hiding this comment

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

If so, should it also consider static and $this? (If it's strictly from declared return type, ignore.)

static is not a valid type hint (for good reasons). static is used when resolving methods to call inside an instance, but it is not possible to use it as a hint due to its natural non-LSP-compliance.

$this is not a type

In general, reflecting from docblocks is now a deprecated feature (there are explicit tests that verify that docblocks aren't used for reflecting anymore)

{
if ('self' === strtolower($literalReturnType)) {
return $methodReflection->getDeclaringClass()->getName();
}

return $returnTypeString;
if ('parent' === strtolower($literalReturnType)) {
return $methodReflection->getDeclaringClass()->getParentClass()->getName();
}

return $literalReturnType;
}
}
30 changes: 24 additions & 6 deletions src/Generator/ParameterGenerator.php
Original file line number Diff line number Diff line change
Expand Up @@ -9,6 +9,7 @@

namespace Zend\Code\Generator;

use ReflectionParameter;
use Zend\Code\Reflection\ParameterReflection;

class ParameterGenerator extends AbstractGenerator
Expand Down Expand Up @@ -334,14 +335,12 @@ private static function extractFQCNTypeFromReflectionType(ParameterReflection $r
return null;
}

$typeString = (string) $type;

if ('self' === strtolower($typeString)) {
// exceptional case: `self` must expand to the reflection type declaring class
return $reflectionParameter->getDeclaringClass()->getName();
if (! method_exists($type, 'getName')) {
return self::expandLiteralParameterType((string) $type, $reflectionParameter);
}

return $typeString;
return ($type->allowsNull() ? '?' : '')
. self::expandLiteralParameterType($type->getName(), $reflectionParameter);
}

/**
Expand All @@ -368,6 +367,25 @@ private static function prePhp7ExtractFQCNTypeFromReflectionType(ParameterReflec
return null;
}

/**
* @param string $literalParameterType
* @param ReflectionParameter $reflectionParameter
*
* @return string
*/
private static function expandLiteralParameterType($literalParameterType, ReflectionParameter $reflectionParameter)
Copy link
Member

Choose a reason for hiding this comment

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

Same comment as with MethodGenerator.

Copy link
Member Author

Choose a reason for hiding this comment

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

Same comment as with MethodGenerator

{
if ('self' === strtolower($literalParameterType)) {
return $reflectionParameter->getDeclaringClass()->getName();
}

if ('parent' === strtolower($literalParameterType)) {
return $reflectionParameter->getDeclaringClass()->getParentClass()->getName();
}

return $literalParameterType;
}

/**
* @param string|null $type
*
Expand Down
47 changes: 37 additions & 10 deletions src/Generator/TypeGenerator.php
Original file line number Diff line number Diff line change
Expand Up @@ -23,19 +23,23 @@ final class TypeGenerator implements GeneratorInterface
*/
private $type;

/**
* @var bool
*/
private $nullable;

/**
* @var string[]
*
* @link http://php.net/manual/en/functions.arguments.php#functions.arguments.type-declaration
*/
private static $internalPhpTypes = ['int', 'float', 'string', 'bool', 'array', 'callable'];
private static $internalPhpTypes = ['void', 'int', 'float', 'string', 'bool', 'array', 'callable', 'iterable'];

// @codingStandardsIgnoreStart
/**
* @var string a regex pattern to match valid class names or types
*/
private static $validIdentifierMatcher = '/^[a-zA-Z_\x80-\xff][a-zA-Z0-9_\x80-\xff]*(\\\\[a-zA-Z_\x80-\xff][a-zA-Z0-9_\x80-\xff]*)*$/';
// @codingStandardsIgnoreEnd
private static $validIdentifierMatcher = '/^[a-zA-Z_\x80-\xff][a-zA-Z0-9_\x80-\xff]*'
. '(\\\\[a-zA-Z_\x80-\xff][a-zA-Z0-9_\x80-\xff]*)*$/';

/**
* @param string $type
Expand All @@ -46,7 +50,8 @@ final class TypeGenerator implements GeneratorInterface
*/
public static function fromTypeString($type)
{
list($wasTrimmed, $trimmedType) = self::trimType($type);
list($nullable, $trimmedNullable) = self::trimNullable($type);
list($wasTrimmed, $trimmedType) = self::trimType($trimmedNullable);

if (! preg_match(self::$validIdentifierMatcher, $trimmedType)) {
throw new InvalidArgumentException(sprintf(
Expand All @@ -65,9 +70,14 @@ public static function fromTypeString($type)
));
}

if ($nullable && $isInternalPhpType && 'void' === strtolower($trimmedType)) {
throw new InvalidArgumentException(sprintf('Provided type "%s" cannot be nullable', $type));
}

$instance = new self();

$instance->type = $trimmedType;
$instance->nullable = $nullable;
$instance->isInternalPhpType = self::isInternalPhpType($trimmedType);

return $instance;
Expand All @@ -82,26 +92,43 @@ private function __construct()
*/
public function generate()
{
$nullable = $this->nullable ? '?' : '';

if ($this->isInternalPhpType) {
return strtolower($this->type);
return $nullable . strtolower($this->type);
}

return '\\' . $this->type;
return $nullable . '\\' . $this->type;
}

/**
* @return string the cleaned type string
*/
public function __toString()
{
return ltrim($this->generate(), '\\');
return ltrim($this->generate(), '?\\');
}

/**
* @param string $type
*
* @return bool[]|string[] ordered tuple, first key represents whether the type is nullable, second is the
* trimmed string
*/
private static function trimNullable($type)
{
if (0 === strpos($type, '?')) {
return [true, substr($type, 1)];
}

return [false, $type];
}

/**
* @param string $type
*
* @return bool[]|int[] ordered tuple, first key represents whether the values was trimmed, second is the
* trimmed string
* @return bool[]|string[] ordered tuple, first key represents whether the values was trimmed, second is the
* trimmed string
*/
private static function trimType($type)
{
Expand Down
30 changes: 29 additions & 1 deletion test/Generator/MethodGeneratorTest.php
Original file line number Diff line number Diff line change
Expand Up @@ -14,7 +14,10 @@
use Zend\Code\Generator\ValueGenerator;
use Zend\Code\Reflection\MethodReflection;
use ZendTest\Code\TestAsset\ClassWithByRefReturnMethod;
use ZendTest\Code\TestAsset\EmptyClass;
use ZendTest\Code\TestAsset\InternalHintsClass;
use ZendTest\Code\TestAsset\IterableHintsClass;
use ZendTest\Code\TestAsset\NullableReturnTypeHintedClass;
use ZendTest\Code\TestAsset\ReturnTypeHintedClass;

/**
Expand Down Expand Up @@ -338,17 +341,42 @@ public function testFrom(string $className, string $methodName, string $expected

public function returnTypeHintClassesProvider()
{
return [
$parameters = [
[ReturnTypeHintedClass::class, 'voidReturn', 'void'],
[ReturnTypeHintedClass::class, 'arrayReturn', 'array'],
[ReturnTypeHintedClass::class, 'callableReturn', 'callable'],
[ReturnTypeHintedClass::class, 'intReturn', 'int'],
[ReturnTypeHintedClass::class, 'floatReturn', 'float'],
[ReturnTypeHintedClass::class, 'stringReturn', 'string'],
[ReturnTypeHintedClass::class, 'boolReturn', 'bool'],
[ReturnTypeHintedClass::class, 'selfReturn', '\\' . ReturnTypeHintedClass::class],
[ReturnTypeHintedClass::class, 'parentReturn', '\\' . EmptyClass::class],
[ReturnTypeHintedClass::class, 'classReturn', '\\' . ReturnTypeHintedClass::class],
[ReturnTypeHintedClass::class, 'otherClassReturn', '\\' . InternalHintsClass::class],
[NullableReturnTypeHintedClass::class, 'arrayReturn', '?array'],
[NullableReturnTypeHintedClass::class, 'callableReturn', '?callable'],
[NullableReturnTypeHintedClass::class, 'intReturn', '?int'],
[NullableReturnTypeHintedClass::class, 'floatReturn', '?float'],
[NullableReturnTypeHintedClass::class, 'stringReturn', '?string'],
[NullableReturnTypeHintedClass::class, 'boolReturn', '?bool'],
[NullableReturnTypeHintedClass::class, 'selfReturn', '?\\' . NullableReturnTypeHintedClass::class],
[NullableReturnTypeHintedClass::class, 'parentReturn', '?\\' . EmptyClass::class],
[NullableReturnTypeHintedClass::class, 'classReturn', '?\\' . NullableReturnTypeHintedClass::class],
[NullableReturnTypeHintedClass::class, 'otherClassReturn', '?\\' . InternalHintsClass::class],
[IterableHintsClass::class, 'iterableReturnValue', 'iterable'],
[IterableHintsClass::class, 'nullableIterableReturnValue', '?iterable'],
];

return array_filter(
$parameters,
function (array $parameter) {
return PHP_VERSION_ID >= 70100
|| (
false === strpos($parameter[2], '?')
&& ! in_array(strtolower($parameter[2]), ['void', 'iterable'])
);
}
);
}

/**
Expand Down
Loading