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

feat(DowngradePhp73JsonConstRector): throw behavior #133

Merged
merged 4 commits into from
Jun 21, 2023

Conversation

sylver
Copy link
Contributor

@sylver sylver commented Jun 20, 2023

Add a throwing error behavior after any json_encode or json_decode function called with the JSON_THROW_ON_ERROR flag set, since it will be removed.
This is a partial improvement of removing the JSON_THROW_ON_ERROR flag altogether without a proper alternative behavior, but that only works when the flags are directly set in the function call.
If the flags are set from a variable, that would require a much more complex analysis to be 100% accurate, probably beyond Rector actual capabilities.

* add a code sample for `json_decode`
* Try to properly downgrade the throw behavior removed from `json_encode` and `json_decode`
  with an alternative code snippet added after the function node.
* This is a partial solution that only handles when the flags are directly set in the function call.
@sylver
Copy link
Contributor Author

sylver commented Jun 20, 2023

@samsonasik It seems there's a problem in your CI when the PR branch is not from the main repository, so the last check can't pass.

@samsonasik
Copy link
Member

You can run vendor/bin/rector && composer fix-cs to fix CI

@sylver
Copy link
Contributor Author

sylver commented Jun 20, 2023

@samsonasik following your advice, the code is fixed for the CI, but I had also to fix one file outside of the scope of this PR, which was not reported locally.
That's probably unexpected.

@samsonasik
Copy link
Member

You need to enable zend.assertions=1 to enable assertion error locally. It usually require set startLine and endLine attribute to new node, something like this PR:

@sylver
Copy link
Contributor Author

sylver commented Jun 20, 2023

It seems I'm unlucky today, everything was finally fine but some packages got an update and a lot of things are now broken (that are not related to this PR)

  - Upgrading phpstan/phpstan (1.10.19 => 1.10.20): Extracting archive
  - Upgrading rector/rector-src (dev-main ad41e8a => dev-main 3ed219b): Extracting archive
  - Upgrading rector/rector-symfony (dev-main f1020f6 => dev-main 100ca8c): Extracting archive
  - Upgrading rector/rector-phpunit (dev-main 5501d9d => dev-main 8288f71): Extracting archive
  - Upgrading symplify/easy-coding-standard (11.3.4 => 11.4.5): Extracting archive
  - Upgrading tomasvotruba/unused-public (0.1.10 => 0.1.12): Extracting archive

@samsonasik
Copy link
Member

It seems new phpstan cause error in rector-src as well:


There were 3 errors:

1) Rector\Tests\Php80\Rector\Switch_\ChangeSwitchToMatchRector\ChangeSwitchToMatchRectorTest::test with data set #11
Error: Call to undefined method Rector\Core\PhpParser\Printer\BetterStandardPrinter::pPHPStan_Node_AlwaysRememberedExpr()

I will check if that possibly related.

@samsonasik
Copy link
Member

I created PR:

for it.

@sylver
Copy link
Contributor Author

sylver commented Jun 20, 2023

Thanks @samsonasik and @TomasVotruba for the fix on rectorphp/rector-src
Tests are running green again locally for this PR, so I hope the CI should be ok too now.

@samsonasik
Copy link
Member

@TomasVotruba the error:

https://github.com/rectorphp/rector-downgrade-php/actions/runs/5322159313/jobs/9655820170?pr=133#step:5:14

"System error: "Call to undefined method                               
         Rector\Core\PhpParser\Printer\BetterStandardPrinter::pPHPStan_Node_AlwaysRememberedExpr()"                                   

because BetterStandardprinter doesn't have pPHPStan_Node_AlwaysRememberedExpr() method, so extends PHPStan printer may actually needed.

@samsonasik
Copy link
Member

I create new PR for extends PHPStan printer instead:

@samsonasik
Copy link
Member

Let see if rectorphp/rector-src#4310 resolve the rector build, let's restasrt build...

@samsonasik
Copy link
Member

CI seems green now 👍

@sylver
Copy link
Contributor Author

sylver commented Jun 21, 2023

@samsonasik awesome, thanks 🙏

I hope my contribution could be useful and merged if you think so.

@TomasVotruba TomasVotruba merged commit 9cfcb6a into rectorphp:main Jun 21, 2023
5 checks passed
@TomasVotruba
Copy link
Member

Thank you for contribution 👍 lets give this a try

@samsonasik
Copy link
Member

It seems cause downgrade build error

https://github.com/rectorphp/rector-src/actions/runs/5332525439/jobs/9664202507

I will look into it.

Comment on lines +143 to +149
$funcCall = match ($stmtExpression->expr::class) {
Assign::class => $stmtExpression->expr->expr instanceof FuncCall
? $stmtExpression->expr->expr
: null,
FuncCall::class => $stmtExpression->expr,
default => null,
};
Copy link
Member

Choose a reason for hiding this comment

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

Copy link
Member

Choose a reason for hiding this comment

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

The error seems happen on php 7.4 https://3v4l.org/75qT2#v7.4.33

Copy link
Member

Choose a reason for hiding this comment

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

I think it is because The scope of $stmtExpression->expr is already lost as parent re-printed, the better solution I think is refactor the code for now. I will create new PR.

@samsonasik
Copy link
Member

I think for inside try-catch, it should use the the catch stmts, see

rectorphp/rector@3236151#diff-3730c571f73f17d036e1d2177280cdb16a21772965d08ad48f23485036b9b03a

should be:

          try {
-                $resource = \json_encode($resource, \JSON_THROW_ON_ERROR);
+                $resource = \json_encode($resource, 0);
                if (\json_last_error() !== \JSON_ERROR_NONE) {
-                    throw new \Exception(\json_last_error_msg());
+                    $resource = \sprintf('resource of type "%s"', \get_debug_type($resource));
                }
            } catch (\JsonException $exception) {
                $resource = \sprintf('resource of type "%s"', \get_debug_type($resource));
            }

@samsonasik
Copy link
Member

or better, should be something like:

-          try {
-                $resource = \json_encode($resource, \JSON_THROW_ON_ERROR);
+                $resource = \json_encode($resource, 0);
                if (\json_last_error() !== \JSON_ERROR_NONE) {
-                    throw new \Exception(\json_last_error_msg());
+                    $resource = \sprintf('resource of type "%s"', \get_debug_type($resource));
                }
-            } catch (\JsonException $exception) {
-                $resource = \sprintf('resource of type "%s"', \get_debug_type($resource));
-            }

@samsonasik
Copy link
Member

Hm.., I think inside try catch can just be skipped for add if after Expression to avoid regression, I will create new PR for it.

@samsonasik
Copy link
Member

#135

Comment on lines +84 to +86
if (json_last_error() !== JSON_ERROR_NONE) {
throw new \Exception(json_last_error_msg());
}
Copy link
Member

Choose a reason for hiding this comment

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

this is incorrect, as it previously on \Exception to return false, so must be skipped instead to avoid regression, so basically if inside try catch, it must not add the if, here the example:

https://3v4l.org/k3U5V

with new behaviour, it throw exception, which should not.

Copy link
Contributor Author

@sylver sylver Jun 21, 2023

Choose a reason for hiding this comment

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

@samsonasik @TomasVotruba Not sure to follow you here, without the if statement nor the JSON_THROW_ON_ERROR, json_encode() / json_decode() does not raise any error, so the catch is not executed, that's exactly why this PR was made, you're actually removing the precise purpose of it.

The problem resides on the type of the exception thrown to match the one in the catch, you should not remove the if entirely or you defeat the purpose of this PR and we are back to square one.

In PHP < 7.3 that does not throw (initial behavior of DowngradePhp73JsonConstRector) :

https://3v4l.org/1Y2Bv#v7.2.34

try {
    $data = "\xB1\x31"; // Invalid UTF-8 sequence (binary data)
    $json = json_encode($data, 0);
    echo $json;
} catch (JsonException $e) {
    echo 'Error occurred during JSON encoding: ' . $e->getMessage();
}

While this replicate the same behavior as with JSON_THROW_ON_ERROR (goal of my PR) :

https://3v4l.org/lb86Z#v7.2.34

try {
    $data = "\xB1\x31"; // Invalid UTF-8 sequence (binary data)
    $json = json_encode($data, 0);
    if (json_last_error() !== JSON_ERROR_NONE) {
        throw new \Exception(json_last_error_msg());
    }
    echo $json;
} catch (Exception $e) {
    echo 'Error occurred during JSON encoding: ' . $e->getMessage();
}

Also the exception JsonException does not exist in PHP < 7.3 so your example is not valid in this context, if the catch specify a JsonException, it should be replaced by a standard Exception to be compatible with PHP < 7.3 anyway.

I could have worked that out if you didn't act so fast, with your last merge this PR is basically useless.
It was just needed a little bit of improvement on the exception type (and actually also handle the downgrade of JsonException that I overlooked, thanks for pointing that out)

Copy link
Member

@samsonasik samsonasik Jun 21, 2023

Choose a reason for hiding this comment

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

See https://3v4l.org/k3U5V vs https://3v4l.org/gJkgE

Better approach to use catch stmts instead probably the real solution, but the quick fix is not add if to avoid regression.

Copy link
Contributor Author

@sylver sylver Jun 21, 2023

Choose a reason for hiding this comment

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

sorry, I don't get it, it's like you didn't read what I just said.

The 2 examples you just provided are not valid PHP 7.2.x (since JsonException does not exist for that version)

https://www.php.net/manual/en/class.jsonexception.php

Copy link
Member

Choose a reason for hiding this comment

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

Exception can be used, but need to map for what defined inside catch, or it will fatal error that mean regression.

Copy link
Contributor Author

Choose a reason for hiding this comment

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

For rector use case, the downgrade is used to allow installed in php 7.2, to upgrade code from php 5 until php 8.2, so the downgraded code should keep working even opened in php 8.

I thought a "Downgrade" rule could only be applied.. well, for a downgrade. That wouldn't make much sense to apply it the other way around during an upgrade, but again I don't know all the subtlety of Rector.
Would be curious to understand it more, when you'll have the time, I have a few other PRs coming up 😅

I'll go with a custom local rule for now, since with your last changes it does not fix my downgrade problem anymore.

Copy link
Contributor Author

Choose a reason for hiding this comment

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

as a side note, this can be rework with more carefull approach, with check if catch Exception or Throwable, or JsonException directly, then map the code below catch inside if, then remove the try catch itself.

See my comment above #133 (comment)

Yeah, I didn't get it at first tbh, but thing is you're trying to avoid the throw/catch entirely, while this is the initial/expected behavior of the code that should be retained, no matter what, else you get a discrepancy between the versions of your code.

I'll try to come up with something else if I have the time, unless you do it first. Thanks for your help and the discussion/debate 😉

Copy link
Member

Choose a reason for hiding this comment

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

No worries, improvement is welcome, just need more carefull approach to avoid regression ;)

I have on list of downgrade improvement, one of them is patch invalid the docblock during downgrade, see rectorphp/rector#8004 but that's not our priority right now as docblock doesn't cause fatal error ;)

Copy link
Contributor Author

Choose a reason for hiding this comment

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

No worries, improvement is welcome, just need more carefull approach to avoid regression ;)

btw, how could I have caught locally the regression issue you got before pushing the PR ?
Since all was green both locally and eventually in the CI, I don't know where to look for to reproduce it ?

Copy link
Member

Choose a reason for hiding this comment

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

For this rule itself, for example, we have the following code:

try {
    $data = "\xB1\x31"; // Invalid UTF-8 sequence (binary data)
    $json = json_encode($data, JSON_THROW_ON_ERROR);
    echo $json;
} catch (JsonException $e) {
    echo 'something is wrong';
}

Currently, in php 7.3, it output something is wrong, see https://3v4l.org/GkRKc#v7.3.33

When downgraded to php 7.2, it should keep show something is wrong, see https://3v4l.org/XSJ4W#v7.2.34

This should limit per use-case to avoid general change,eg: Try, then catch JsonException, no finally, no anything else, if that works, we can improve to another improvement :)

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

Successfully merging this pull request may close these issues.

3 participants