Skip to content
Merged
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
6 changes: 4 additions & 2 deletions composer.json
Original file line number Diff line number Diff line change
Expand Up @@ -105,7 +105,8 @@
"Rector\\PhpDeglobalize\\": "packages/PhpDeglobalize/src",
"Rector\\Phalcon\\": "packages/Phalcon/src",
"Rector\\DoctrineGedmoToKnplabs\\": "packages/DoctrineGedmoToKnplabs/src",
"Rector\\MinimalScope\\": "packages/MinimalScope/src"
"Rector\\MinimalScope\\": "packages/MinimalScope/src",
"Rector\\Polyfill\\": "packages/Polyfill/src"
}
},
"autoload-dev": {
Expand Down Expand Up @@ -166,7 +167,8 @@
"Rector\\PhpDeglobalize\\Tests\\": "packages/PhpDeglobalize/tests",
"Rector\\Phalcon\\Tests\\": "packages/Phalcon/tests",
"Rector\\DoctrineGedmoToKnplabs\\Tests\\": "packages/DoctrineGedmoToKnplabs/tests",
"Rector\\MinimalScope\\Tests\\": "packages/MinimalScope/tests"
"Rector\\MinimalScope\\Tests\\": "packages/MinimalScope/tests",
"Rector\\Polyfill\\Tests\\": "packages/Polyfill/tests"
},
"classmap": [
"packages/Symfony/tests/Rector/FrameworkBundle/AbstractToConstructorInjectionRectorSource",
Expand Down
2 changes: 2 additions & 0 deletions config/set/polyfill/unwrap-compat.yaml
Original file line number Diff line number Diff line change
@@ -0,0 +1,2 @@
services:
Rector\Polyfill\Rector\If_\UnwrapFutureCompatibleIfRector: null
9 changes: 9 additions & 0 deletions packages/Polyfill/config/config.yaml
Original file line number Diff line number Diff line change
@@ -0,0 +1,9 @@
services:
_defaults:
public: true
autowire: true

Rector\Polyfill\:
resource: '../src'
exclude:
- '../src/Rector/**/*Rector.php'
46 changes: 46 additions & 0 deletions packages/Polyfill/src/FeatureSupport/FunctionSupportResolver.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,46 @@
<?php

declare(strict_types=1);

namespace Rector\Polyfill\FeatureSupport;

use Rector\Php\PhpVersionProvider;

final class FunctionSupportResolver
{
/**
* @var string[][]
*/
private const FUNCTIONS_BY_VERSION = [
'5.6' => ['session_abort'],
];
Comment on lines +14 to +16
Copy link
Copy Markdown
Member Author

Choose a reason for hiding this comment

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

@staabm Here is just the code example you showed me. I need your help to complete list as far as possible.

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

I need to think about how to compile such method most easily

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

Copy link
Copy Markdown
Contributor

@staabm staabm Jan 6, 2020

Choose a reason for hiding this comment

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

Another thing I checked via github code-search
https://github.com/search?l=PHP&q=function_exists&type=Code

There we can see most examples are like

if( ! function_exists( 'supernova_comment' ) ){
  function supernova_comment($comment, $args, $depth) {
   // impl
  }
}

In these cases we could eleminate the code when we are sure that the function beeing checked is one from the php standard library (so semantically the code is polyfilling, not e.g. guarding against „function already exist“ error because of multiple including the same file)

Copy link
Copy Markdown
Member Author

Choose a reason for hiding this comment

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

After thinking about it I feel we could compile this list from exisiting polyfil libs, e.g.

Great!

Another thing I checked via github code-search

Try to keep issues separated, so we don't get stuck with complexity. This is another use case.

Copy link
Copy Markdown
Member Author

Choose a reason for hiding this comment

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

I see

Copy link
Copy Markdown
Member Author

Choose a reason for hiding this comment

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

There are many new PRs merged every day so there are new conflicts to rebase on.
I'll merge this now to prevent that.

You can send the function list as a new PR targeting this class 👍

Copy link
Copy Markdown
Member Author

Choose a reason for hiding this comment

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

@staabm Any update on this? The rule is pretty lonely with just 1 function now :D

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

Was on vacation till yesterday.. will need a few more days to catch up

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

here we go #2673


/**
* @var PhpVersionProvider
*/
private $phpVersionProvider;

public function __construct(PhpVersionProvider $phpVersionProvider)
{
$this->phpVersionProvider = $phpVersionProvider;
}

public function isFunctionSupported(string $desiredFunction): bool
{
foreach (self::FUNCTIONS_BY_VERSION as $version => $functions) {
foreach ($functions as $function) {
if ($desiredFunction !== $function) {
continue;
}

if (! $this->phpVersionProvider->isAtLeast($version)) {
Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

I guess it would also be helpfull to remove code when explicit php version checks are used (might be a separTe rector then?)

In codes like https://github.com/redaxo/redaxo/blob/591146a1dc60e8aacefd58dc9b7e9c307c0983b9/redaxo/src/addons/tests/vendor/phpspec/prophecy/src/Prophecy/Doubler/Generator/ClassCodeGenerator.php#L85

Copy link
Copy Markdown
Member Author

Choose a reason for hiding this comment

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

Sure! I though of that too.

Could you help by making a new issue with minimal code (3-lines) in diff a format?

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

See #2573 #2574

continue;
}

return true;
}
}

return false;
}
}
113 changes: 113 additions & 0 deletions packages/Polyfill/src/Rector/If_/UnwrapFutureCompatibleIfRector.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,113 @@
<?php

declare(strict_types=1);

namespace Rector\Polyfill\Rector\If_;

use PhpParser\Node;
use PhpParser\Node\Stmt\If_;
use Rector\PhpParser\Node\Manipulator\IfManipulator;
use Rector\Polyfill\FeatureSupport\FunctionSupportResolver;
use Rector\Rector\AbstractRector;
use Rector\RectorDefinition\CodeSample;
use Rector\RectorDefinition\RectorDefinition;

/**
* @see \Rector\Polyfill\Tests\Rector\If_\UnwrapFutureCompatibleIfRector\UnwrapFutureCompatibleIfRectorTest
*/
final class UnwrapFutureCompatibleIfRector extends AbstractRector
{
/**
* @var IfManipulator
*/
private $ifManipulator;

/**
* @var FunctionSupportResolver
*/
private $functionSupportResolver;

public function __construct(IfManipulator $ifManipulator, FunctionSupportResolver $functionSupportResolver)
{
$this->ifManipulator = $ifManipulator;
$this->functionSupportResolver = $functionSupportResolver;
}

public function getDefinition(): RectorDefinition
{
return new RectorDefinition('Remove functions exists if with else for always existing', [
new CodeSample(
<<<'PHP'
class SomeClass
{
public function run()
{
// session locking trough other addons
if (function_exists('session_abort')) {
session_abort();
} else {
session_write_close();
}
}
}
PHP
,
<<<'PHP'
class SomeClass
{
public function run()
{
// session locking trough other addons
session_abort();
}
}
PHP

),
]);
}

/**
* @return string[]
*/
public function getNodeTypes(): array
{
return [If_::class];
}

/**
* @param If_ $node
*/
public function refactor(Node $node): ?Node
{
$match = $this->ifManipulator->isIfElseWithFunctionCondition($node, 'function_exists');
if ($match === false) {
return null;
}

/** @var Node\Expr\FuncCall $funcCall */
$funcCall = $node->cond;

$functionToExistName = $this->getValue($funcCall->args[0]->value);
if (! is_string($functionToExistName)) {
return null;
}

if (! $this->functionSupportResolver->isFunctionSupported($functionToExistName)) {
return null;
}

foreach ($node->stmts as $key => $ifStmt) {
if ($key === 0) {
// move comment from if to first element to keep it
$ifStmt->setAttribute('comments', $node->getComments());
}

$this->addNodeAfterNode($ifStmt, $node);
}

$this->removeNode($node);

return null;
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,33 @@
<?php

namespace Rector\Polyfill\Tests\Rector\If_\UnwrapFutureCompatibleIfRector\Fixture;

class SomeClass
{
public function run()
{
// session locking trough other addons
if (function_exists('session_abort')) {
session_abort();
} else {
session_write_close();
}
}
}

?>
-----
<?php

namespace Rector\Polyfill\Tests\Rector\If_\UnwrapFutureCompatibleIfRector\Fixture;

class SomeClass
{
public function run()
{
// session locking trough other addons
session_abort();
}
}

?>
Original file line number Diff line number Diff line change
@@ -0,0 +1,30 @@
<?php

declare(strict_types=1);

namespace Rector\Polyfill\Tests\Rector\If_\UnwrapFutureCompatibleIfRector;

use Iterator;
use Rector\Polyfill\Rector\If_\UnwrapFutureCompatibleIfRector;
use Rector\Testing\PHPUnit\AbstractRectorTestCase;

final class UnwrapFutureCompatibleIfRectorTest extends AbstractRectorTestCase
{
/**
* @dataProvider provideDataForTest()
*/
public function test(string $file): void
{
$this->doTestFile($file);
}

public function provideDataForTest(): Iterator
{
return $this->yieldFilesFromDirectory(__DIR__ . '/Fixture');
}

protected function getRectorClass(): string
{
return UnwrapFutureCompatibleIfRector::class;
}
}
34 changes: 33 additions & 1 deletion src/PhpParser/Node/Manipulator/IfManipulator.php
Original file line number Diff line number Diff line change
Expand Up @@ -9,6 +9,7 @@
use PhpParser\Node\Expr\Assign;
use PhpParser\Node\Expr\BinaryOp\Identical;
use PhpParser\Node\Expr\BinaryOp\NotIdentical;
use PhpParser\Node\Expr\FuncCall;
use PhpParser\Node\Expr\Variable;
use PhpParser\Node\Stmt\Continue_;
use PhpParser\Node\Stmt\Do_;
Expand All @@ -18,6 +19,7 @@
use PhpParser\Node\Stmt\Throw_;
use PhpParser\Node\Stmt\While_;
use PhpParser\NodeTraverser;
use Rector\PhpParser\Node\Resolver\NameResolver;
use Rector\PhpParser\NodeTraverser\CallableNodeTraverser;
use Rector\PhpParser\Printer\BetterStandardPrinter;

Expand Down Expand Up @@ -53,16 +55,23 @@ final class IfManipulator
*/
private $stmtsManipulator;

/**
* @var NameResolver
*/
private $nameResolver;

public function __construct(
BetterStandardPrinter $betterStandardPrinter,
ConstFetchManipulator $constFetchManipulator,
CallableNodeTraverser $callableNodeTraverser,
StmtsManipulator $stmtsManipulator
StmtsManipulator $stmtsManipulator,
NameResolver $nameResolver
) {
$this->betterStandardPrinter = $betterStandardPrinter;
$this->constFetchManipulator = $constFetchManipulator;
$this->callableNodeTraverser = $callableNodeTraverser;
$this->stmtsManipulator = $stmtsManipulator;
$this->nameResolver = $nameResolver;
}

/**
Expand Down Expand Up @@ -233,6 +242,29 @@ public function isIfAndElseWithSameVariableAssignAsLastStmts(If_ $if, Expr $desi
return true;
}

/**
* Matches:
* if (<some_function>) {
* } else {
* }
*/
public function isIfElseWithFunctionCondition(If_ $if, string $functionName): bool
{
if (! $this->isIfWithElse($if)) {
return false;
}

if (! $if->cond instanceof FuncCall) {
return false;
}

if (! $this->nameResolver->isName($if->cond, $functionName)) {
return false;
}

return true;
}

private function matchComparedAndReturnedNode(NotIdentical $notIdentical, Return_ $returnNode): ?Expr
{
if ($this->betterStandardPrinter->areNodesEqual($notIdentical->left, $returnNode->expr)) {
Expand Down