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

[Types] Add JsonDecodeDynamicReturnTypeExtension #89

Open
wants to merge 9 commits into
base: 1.1.x
Choose a base branch
from

Conversation

TomasVotruba
Copy link
Contributor

@TomasVotruba TomasVotruba commented Jan 30, 2022

By default, the Nette\Utils\Json::decode() returns mixed, but in some cases we know there is more specific type.

We use this extension internally in private project to separate at array and stdClass, e.g.:

$values = Nette\Utils\Json::decode($values);
echo $values['item'];
// not possible - a stdClass type given

$values = Nette\Utils\Json::decode($values, Nette\Utils\Json::FORCE_ARRAY);
echo $values->item ;
// not possible - an array type given

It might be useful to have in Nette extension itself. What do you think?

cc @staabm @lulco @matthiasnoback

Based on https://twitter.com/VotrubaT/status/1487434178757074952

@TomasVotruba TomasVotruba force-pushed the tv-nette-json-return-type branch 2 times, most recently from 33460c9 to a4ca889 Compare January 30, 2022 10:35
phpunit.xml Outdated Show resolved Hide resolved
# in PHP 7.1, the json_decode() 2nd parameter requires bool, while PHP 7.2 it's null|int
- name: "Downgrade nette/utils"
if: matrix.php-version == '7.1'
run: "composer require --dev nette/utils:^2.3 nette/forms:^2.4 --update-with-dependencies"
Copy link
Contributor Author

@TomasVotruba TomasVotruba Jan 30, 2022

Choose a reason for hiding this comment

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

2nd arg of json_decode() takes different types in PHP 7.1 and PHP 7.2+

@TomasVotruba TomasVotruba changed the title [WIP] Add JsonDecodeDynamicReturnTypeExtension [Json] Add JsonDecodeDynamicReturnTypeExtension Jan 30, 2022
@TomasVotruba TomasVotruba changed the title [Json] Add JsonDecodeDynamicReturnTypeExtension [Types] Add JsonDecodeDynamicReturnTypeExtension Jan 30, 2022
Copy link
Member

@ondrejmirtes ondrejmirtes left a comment

Choose a reason for hiding this comment

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

What I'd like to see instead is to have this kind of logic for json_decode in phpstan-src. When that's done, this Nette extension can simply delegate the logic with $scope->getType(new FuncCall('json_decode', $args)). Of course $args would be composed based on the FORCE_ARRAY argument so that json_decode is called with $associative=true.

@TomasVotruba
Copy link
Contributor Author

I actually tried to add function extension first but PHPStan analysed only non-basic function. Not sure why.

Apart that, is the type resolution in tests here correct?

@TomasVotruba TomasVotruba marked this pull request as ready for review January 30, 2022 11:46
@ondrejmirtes
Copy link
Member

Instead of the whole resolveConstantStringType method you just should call this class https://github.com/phpstan/phpstan-src/blob/master/src/Type/ConstantTypeHelper.php, otherwise it looks fine.


// scalar types with stdClass
return new UnionType([
new ObjectType(stdClass::class),
Copy link
Member

Choose a reason for hiding this comment

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

It can still be an array here.

Copy link
Contributor Author

Choose a reason for hiding this comment

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

On what input?

Copy link
Contributor

Choose a reason for hiding this comment

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

I guess the stdclass could contain a array of other things?

Copy link
Member

Choose a reason for hiding this comment

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

You don't have to guess: https://3v4l.org/qsjVh

BTW I worry that these precise types would become really annoying. Right now people working with json_decode() get some errors on level 9, but with these union types it would be much sonner - on level 7. I think the best course of action is to return "MixedType without stdClass" with FORCE_ARRAY, and normal "MixedType" without FORCE_ARRAY.

MixedType is subtractable - new MixedType(true, new ObjectType(\stdClass::class)) means mixed without stdClass.

Copy link
Contributor Author

@TomasVotruba TomasVotruba Jan 30, 2022

Choose a reason for hiding this comment

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

MixedType is subtractable

Cool, that's a new trick to me.


I think the best course of action is to return "MixedType without stdClass" with FORCE_ARRAY, and normal "MixedType" without FORCE_ARRAY.

Do you mean like this?

if ($isForceArray) {
    return new MixedType(true, new ObjectType(\stdClass::class));
}

return new MixedType(true);

It might be change with more strict approach, but it would be more correct. The reason we made this extension for our project is to make sure there is only scalar value or stdClass. Mixed includes anything, objects, array of object etc.

Yet the DX is important, I understand. It might be useful to enable this only since level 9+.

@ondrejmirtes
Copy link
Member

Just to reiterate - I still want this to be submitted as a json_decode extension to phpstan-src first. Then we can continue here, keep all the tests but make the extension implementation much simpler, delegating to json_decode logic.

@TomasVotruba
Copy link
Contributor Author

@ondrejmirtes I understand that, I just want to make it work and confirm here, so we don't jump back and forth.
Then function extension port to core and update here.

@TomasVotruba
Copy link
Contributor Author

TomasVotruba commented Jan 30, 2022

@ondrejmirtes As for json_decode, is FunctionTypeSpecifyingExtension the right interface to implement?

@ondrejmirtes
Copy link
Member

No (https://phpstan.org/developing-extensions/dynamic-return-type-extensions):

functions using DynamicFunctionReturnTypeExtension interface and phpstan.broker.dynamicFunctionReturnTypeExtension service tag.

@TomasVotruba
Copy link
Contributor Author

TomasVotruba commented Jan 31, 2022

@ondrejmirtes Thanks 👍

I'm trying to add JsonDecodeDynamicReturnTypeExtension, but the only function that is found is PHPStan\Testing\assertType.
The json_decode is never passed.

What I'm doing wrong there?

Comment on lines +25 to +31
public function isFunctionSupported(FunctionReflection $functionReflection): bool
{
// @todo - finds only "assertType", but not "json_decode" :/
dump($functionReflection->getName());

return $functionReflection->getName() === 'json_decode';
}
Copy link
Contributor Author

Choose a reason for hiding this comment

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

Here the json_decode is never passed :/

Copy link
Contributor Author

@TomasVotruba TomasVotruba Jan 31, 2022

Choose a reason for hiding this comment

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

To reproduce localy, run PHPUnit:

vendor/bin/phpunit tests/Type/Php/JsonDecodeDynamicReturnTypeExtensionTest.php

image

Copy link
Member

Choose a reason for hiding this comment

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

Because there's already JsonThrowOnErrorDynamicReturnTypeExtension in phpstan-src whcih changes the result based on JSON_THROW_ON_ERROR. You need to modify it first.

Copy link
Contributor Author

Choose a reason for hiding this comment

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

I'll try it

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