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

Initial PHP 8 Support #67

Merged
merged 38 commits into from
Oct 5, 2020
Merged
Show file tree
Hide file tree
Changes from 26 commits
Commits
Show all changes
38 commits
Select commit Hold shift + click to select a range
251fce0
Support PHP 8
deleugpn Jul 11, 2020
b890211
Only run Reflection Closure 6 on PHP 8
deleugpn Jul 11, 2020
a1312e9
Revert phpunit version change
deleugpn Jul 11, 2020
a95a6a8
Attempt to expect exception for both new and old phpunit version
deleugpn Jul 11, 2020
fd66ae0
Support PHP 5.4
deleugpn Jul 11, 2020
1864f84
Use setExpectException instead of docblock
deleugpn Jul 11, 2020
710bbfb
Rename variable
deleugpn Jul 11, 2020
878c73a
Test against nightly
deleugpn Jul 11, 2020
920c191
Update tests.yml
GrahamCampbell Jul 11, 2020
7f26f1d
Update tests.yml
GrahamCampbell Jul 11, 2020
24e80e7
Update phpunit.xml
GrahamCampbell Jul 11, 2020
13590e2
Update composer.json
GrahamCampbell Jul 11, 2020
c484451
Update composer.json
GrahamCampbell Jul 11, 2020
6047099
Tweaks to allow the code to be updated easily if PHP 8.1 makes more c…
GrahamCampbell Jul 11, 2020
36f3d22
Added in array, callable, iterable
GrahamCampbell Jul 11, 2020
0f69f14
Added additional PHP8 pesudo-types
GrahamCampbell Jul 18, 2020
f453f32
Add more cases to assertion
deleugpn Jul 20, 2020
378c39e
Add more union type assertions
deleugpn Aug 12, 2020
c9eac8a
8.0.0-dev is not greater than 8.0.0
deleugpn Aug 12, 2020
47ad58b
GitHub Actions debug
deleugpn Aug 12, 2020
5f856e1
Namespace fix
deleugpn Aug 14, 2020
b116437
Fix qualified and unqualified namespace
deleugpn Aug 15, 2020
aba532d
Fix partially qualified namespace
deleugpn Aug 15, 2020
7a0d6ee
Instantiation with Partially Qualified Namespace
deleugpn Aug 15, 2020
f9e510d
Handle Different Cases of T_NAME_QUALIFIED
deleugpn Aug 23, 2020
baa435f
Short Closure only works on PHP 7.4
deleugpn Aug 23, 2020
3a8eb4a
Move constant definition to the start of the file
deleugpn Sep 2, 2020
a0676d9
Declaration inside namespace
deleugpn Sep 2, 2020
bb133ae
Add tests for Trailing Comma, Named Parameter & Nullsafe operator
deleugpn Sep 2, 2020
94745f7
Fix Named Parameter test
deleugpn Sep 2, 2020
7a7ff99
Don't need dev stability anymore
GrahamCampbell Sep 6, 2020
2274a2c
Code style
GrahamCampbell Sep 6, 2020
522c9b6
Code style
GrahamCampbell Sep 6, 2020
ed079ca
Code style
GrahamCampbell Sep 6, 2020
3b0ebd7
Code style
GrahamCampbell Sep 6, 2020
f61aa2a
Code style
GrahamCampbell Sep 6, 2020
ab833c4
Code style
GrahamCampbell Sep 6, 2020
caaf68c
Fixed typo
GrahamCampbell Sep 7, 2020
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
45 changes: 27 additions & 18 deletions .github/workflows/tests.yml
Original file line number Diff line number Diff line change
Expand Up @@ -8,36 +8,45 @@ on:
- '**.md'
jobs:
tests:
name: PHP ${{ matrix.php }}
runs-on: ubuntu-latest

strategy:
fail-fast: true
matrix:
php: [5.4, 5.5, 5.6, 7.0, 7.1, 7.2, 7.3, 7.4]
stability: [prefer-stable]

name: PHP ${{ matrix.php }}
php: ['5.4', '5.5', '5.6', '7.0', '7.1', '7.2', '7.3', '7.4', '8.0']

steps:
- name: Checkout code
- name: Checkout Code
uses: actions/checkout@v2

- name: Cache dependencies
uses: actions/cache@v1
with:
path: ~/.composer/cache/files
key: dependencies-php-${{ matrix.php }}-composer-${{ hashFiles('composer.json') }}

- name: Setup PHP
uses: shivammathur/setup-php@v2
with:
php-version: ${{ matrix.php }}
tools: composer:v2
coverage: none

- name: Update composer
run: composer self-update
- name: Setup Problem Matchers
run: echo "::add-matcher::${{ runner.tool_cache }}/phpunit.json"

- name: Install PHP 5/7 Dependencies
uses: nick-invision/retry@v1
with:
timeout_minutes: 5
max_attempts: 5
command: composer update --no-interaction --no-progress
if: "matrix.php != '8.0'"

- name: Install dependencies
run: composer update --${{ matrix.stability }} --prefer-dist --no-interaction --no-progress
- name: Install PHP 8 Dependencies
uses: nick-invision/retry@v1
with:
timeout_minutes: 5
max_attempts: 5
command: |
composer require "phpunit/phpunit:^9.3" --no-update
composer update --no-interaction --no-progress --ignore-platform-req=php
php -v
if: "matrix.php == '8.0'"

- name: Execute tests
run: vendor/bin/phpunit --verbose
- name: Execute PHPUnit
run: vendor/bin/phpunit
12 changes: 9 additions & 3 deletions composer.json
Original file line number Diff line number Diff line change
Expand Up @@ -15,11 +15,11 @@
}
],
"require": {
"php": "^5.4 || ^7.0"
"php": "^5.4 || ^7.0 || ^8.0"
},
"require-dev": {
"jeremeamia/superclosure": "^2.0",
"phpunit/phpunit": "^4.0 || ^5.0 || ^6.0 || ^7.0"
deleugpn marked this conversation as resolved.
Show resolved Hide resolved
"phpunit/phpunit": "^4.0 || ^5.0 || ^6.0 || ^7.0 || ^8.0 || ^9.0"
},
"autoload": {
"psr-4": {
Expand All @@ -36,5 +36,11 @@
"branch-alias": {
"dev-master": "3.5.x-dev"
}
}
},
"config": {
"preferred-install": "dist",
"sort-packages": true
},
"minimum-stability": "dev",
"prefer-stable": true
}
16 changes: 13 additions & 3 deletions phpunit.xml
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
<phpunit bootstrap="./vendor/autoload.php">
<phpunit bootstrap="./vendor/autoload.php" verbose="true">
<testsuites>
<testsuite name="Main">
<file phpVersion="5.4.0" phpVersionOperator=">=">./tests/ClosureTest.php</file>
Expand All @@ -12,6 +12,12 @@
<testsuite name="Signed">
<file phpVersion="5.4.0" phpVersionOperator=">=">./tests/SignedClosureTest.php</file>
</testsuite>
<testsuite name="Namespace">
<file phpVersion="5.5.0" phpVersionOperator=">=">./tests/NamespaceTest.php</file>
<file phpVersion="5.5.0" phpVersionOperator=">=">./tests/NamespaceUnqualifiedTest.php</file>
<file phpVersion="5.5.0" phpVersionOperator=">=">./tests/NamespaceFullyQualifiedTest.php</file>
<file phpVersion="5.5.0" phpVersionOperator=">=">./tests/NamespacePartiallyQualifiedTest.php</file>
</testsuite>
<testsuite name="ReflectionClosure">
<file phpVersion="5.4.0" phpVersionOperator=">=">./tests/ReflectionClosureTest.php</file>
</testsuite>
Expand All @@ -25,7 +31,11 @@
<file phpVersion="7.2.0" phpVersionOperator=">=">./tests/ReflectionClosure4Test.php</file>
</testsuite>
<testsuite name="ReflectionClosure5">
<file phpVersion="7.4" phpVersionOperator=">=">./tests/ReflectionClosure5Test.php</file>
<file phpVersion="7.4.0" phpVersionOperator=">=">./tests/ReflectionClosure5Test.php</file>
<file phpVersion="7.4.0" phpVersionOperator=">=">./tests/NamespaceGroupTest.php</file>
</testsuite>
<testsuite name="ReflectionClosure6">
<file phpVersion="8.0.0-dev" phpVersionOperator=">=">./tests/ReflectionClosure6Test.php</file>
</testsuite>
</testsuites>
</phpunit>
</phpunit>
113 changes: 88 additions & 25 deletions src/ReflectionClosure.php
Original file line number Diff line number Diff line change
Expand Up @@ -36,6 +36,15 @@ class ReflectionClosure extends ReflectionFunction
*/
public function __construct(Closure $closure, $code = null)
{
// Not sure where to put this
sorinsarca marked this conversation as resolved.
Show resolved Hide resolved
if (! defined('T_NAME_QUALIFIED')) {
define('T_NAME_QUALIFIED', -4);
}

if (! defined('T_NAME_FULLY_QUALIFIED')) {
define('T_NAME_FULLY_QUALIFIED', -5);
}

$this->code = $code;
parent::__construct($closure);
}
Expand Down Expand Up @@ -84,29 +93,12 @@ public function getCode()
}

$className = null;
$fn = false;


if (null !== $className = $this->getClosureScopeClass()) {
$className = '\\' . trim($className->getName(), '\\');
}


if($php7 = PHP_MAJOR_VERSION === 7){
switch (PHP_MINOR_VERSION){
case 0:
$php7_types = array('string', 'int', 'bool', 'float');
break;
case 1:
$php7_types = array('string', 'int', 'bool', 'float', 'void');
break;
case 2:
default:
$php7_types = array('string', 'int', 'bool', 'float', 'void', 'object');
}
$fn = PHP_MINOR_VERSION === 4;
}

$builtin_types = self::getBuiltinTypes();
$class_keywords = ['self', 'static', 'parent'];

$ns = $this->getNamespaceName();
Expand Down Expand Up @@ -143,7 +135,7 @@ public function getCode()
if ($token[0] === T_FUNCTION || $token[0] === T_STATIC) {
$code .= $token[1];
$state = $token[0] === T_FUNCTION ? 'function' : 'static';
} elseif ($fn && $token[0] === T_FN) {
} elseif (\PHP_VERSION_ID >= 70400 && $token[0] === T_FN) {
sorinsarca marked this conversation as resolved.
Show resolved Hide resolved
$isShortClosure = true;
$code .= $token[1];
$state = 'closure_args';
Expand All @@ -155,7 +147,7 @@ public function getCode()
if ($token[0] === T_FUNCTION) {
$state = 'function';
}
} elseif ($fn && $token[0] === T_FN) {
} elseif (\PHP_VERSION_ID >= 70400 && $token[0] === T_FN) {
$isShortClosure = true;
$code .= $token[1];
$state = 'closure_args';
Expand All @@ -182,14 +174,20 @@ public function getCode()
if($token[0] === T_FUNCTION || $token[0] === T_STATIC){
$code = $token[1];
$state = $token[0] === T_FUNCTION ? 'function' : 'static';
} elseif ($fn && $token[0] === T_FN) {
} elseif (\PHP_VERSION_ID >= 70400 && $token[0] === T_FN) {
$isShortClosure = true;
$code .= $token[1];
$state = 'closure_args';
}
break;
case 'closure_args':
switch ($token[0]){
case T_NAME_QUALIFIED:
list($id_start, $id_start_ci, $id_name) = $this->parseNameQualified($token[1]);
$context = 'args';
$state = 'id_name';
$lastState = 'closure_args';
break;
case T_NS_SEPARATOR:
case T_STRING:
$id_start = $token[1];
Expand Down Expand Up @@ -258,6 +256,12 @@ public function getCode()
$state = 'id_name';
$lastState = 'return';
break 2;
case T_NAME_QUALIFIED:
list($id_start, $id_start_ci, $id_name) = $this->parseNameQualified($token[1]);
$context = 'return_type';
$state = 'id_name';
$lastState = 'return';
break 2;
case T_DOUBLE_ARROW:
$code .= $token[1];
if ($isShortClosure) {
Expand Down Expand Up @@ -364,6 +368,12 @@ public function getCode()
$state = 'id_name';
$lastState = 'closure';
break 2;
case T_NAME_QUALIFIED:
list($id_start, $id_start_ci, $id_name) = $this->parseNameQualified($token[1]);
$context = 'root';
$state = 'id_name';
$lastState = 'closure';
break 2;
case T_NEW:
$code .= $token[1];
$context = 'new';
Expand Down Expand Up @@ -463,13 +473,18 @@ public function getCode()
$code .= $token[1];
break;
case T_NS_SEPARATOR:
case T_NAME_FULLY_QUALIFIED:
case T_STRING:
case T_STATIC:
$id_start = $token[1];
$id_start_ci = strtolower($id_start);
$id_name = '';
$state = 'id_name';
break 2;
case T_NAME_QUALIFIED:
list($id_start, $id_start_ci, $id_name) = $this->parseNameQualified($token[1]);
$state = 'id_name';
break 2;
case T_VARIABLE:
$code .= $token[1];
$state = $lastState;
Expand All @@ -485,10 +500,9 @@ public function getCode()
break;
case 'id_name':
switch ($token[0]){
case T_NAME_QUALIFIED:
case T_NS_SEPARATOR:
case T_STRING:
$id_name .= $token[1];
break;
case T_WHITESPACE:
case T_COMMENT:
case T_DOC_COMMENT:
Expand Down Expand Up @@ -538,7 +552,7 @@ public function getCode()
if (!$inside_structure) {
$isUsingScope = $token[0] === T_DOUBLE_COLON;
}
} elseif (!($php7 && in_array($id_start_ci, $php7_types))){
} elseif (!(\PHP_MAJOR_VERSION >= 7 && in_array($id_start_ci, $builtin_types))){
if ($classes === null) {
$classes = $this->getClasses();
}
Expand Down Expand Up @@ -588,7 +602,7 @@ public function getCode()
if (!$inside_structure && !$id_start_ci === 'static') {
$isUsingScope = true;
}
} elseif (!($php7 && in_array($id_start_ci, $php7_types))){
} elseif (!(\PHP_MAJOR_VERSION >= 7 && in_array($id_start_ci, $builtin_types))){
if($classes === null){
$classes = $this->getClasses();
}
Expand Down Expand Up @@ -646,6 +660,32 @@ public function getCode()
return $this->code;
}

/**
* @return array
*/
private static function getBuiltinTypes()
{
// PHP 5
if (\PHP_MAJOR_VERSION === 5) {
return ['array', 'callable'];
}

// PHP 8
if (\PHP_MAJOR_VERSION === 8) {
return ['array', 'callable', 'string', 'int', 'bool', 'float', 'iterable', 'void', 'object', 'mixed', 'false', 'null'];
}

// PHP 7
switch (\PHP_MINOR_VERSION) {
case 0:
return ['array', 'callable', 'string', 'int', 'bool', 'float'];
case 1:
return ['array', 'callable', 'string', 'int', 'bool', 'float', 'iterable', 'void'];
default:
return ['array', 'callable', 'string', 'int', 'bool', 'float', 'iterable', 'void', 'object'];
}
}

/**
* @return array
*/
Expand Down Expand Up @@ -901,6 +941,11 @@ protected function fetchItems()
$name .= $token[1];
$alias = $token[1];
break;
case T_NAME_QUALIFIED:
Copy link
Contributor

Choose a reason for hiding this comment

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

There are more tokens than this to consider. ;)

Copy link
Contributor Author

Choose a reason for hiding this comment

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

This one fixes one specific test that I wrote that was passing on 7.4. It's 23:00 here so I'm heading to bed for today and will continue tomorrow

Copy link
Contributor

Choose a reason for hiding this comment

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

This is still unresolved.

Copy link
Contributor Author

Choose a reason for hiding this comment

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

Can you be more specific?

$name .= $token[1];
$pieces = explode('\\', $token[1]);
$alias = end($pieces);
break;
case T_AS:
$lastState = 'use';
$state = 'alias';
Expand Down Expand Up @@ -935,6 +980,11 @@ protected function fetchItems()
case T_NS_SEPARATOR:
$name .= $token[1];
break;
case T_NAME_QUALIFIED:
$name .= $token[1];
$pieces = explode('\\', $token[1]);
$alias = end($pieces);
break;
case T_STRING:
$name .= $token[1];
$alias = $token[1];
Expand Down Expand Up @@ -1035,4 +1085,17 @@ protected function fetchItems()
static::$constants[$key] = $constants;
static::$structures[$key] = $structures;
}

private function parseNameQualified($token)
{
$pieces = explode('\\', $token);

$id_start = array_shift($pieces);

$id_start_ci = strtolower($id_start);

$id_name = '\\' . implode('\\', $pieces);

return [$id_start, $id_start_ci, $id_name];
}
}
1 change: 1 addition & 0 deletions tests/ClosureTest.php
Original file line number Diff line number Diff line change
Expand Up @@ -230,6 +230,7 @@ public function testClosureNested()
$n = function ($b) {
return !$b;
};

$ns = unserialize(serialize(new SerializableClosure($n)));

return $ns(false);
Expand Down
22 changes: 22 additions & 0 deletions tests/NamespaceFullyQualifiedTest.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,22 @@
<?php

namespace Opis\Closure\Test;

use Closure;
use Opis\Closure\ReflectionClosure;

final class NamespaceGroupTest extends \PHPUnit\Framework\TestCase
{
public function test_namespace_fully_qualified()
sorinsarca marked this conversation as resolved.
Show resolved Hide resolved
{
$f = function (){ new \A; };
$e = 'function (){ new \A; }';
$this->assertEquals($e, $this->c($f));
}

protected function c(Closure $closure)
{
$r = new ReflectionClosure($closure);
return $r->getCode();
}
}
Loading