phpstan / phpstan-webmozart-assert Goto Github PK
View Code? Open in Web Editor NEWPHPStan extension for webmozart/assert
PHPStan extension for webmozart/assert
It should return an array shape that is the same as the input one + the key passed as parameter.
This can probably be achieved with PhpParser\Node\Expr\FuncCall
(simulate a function call to array_key_exists()
?
I don't quite understand why it calls both isset
and array_key_exists
though: https://github.com/webmozart/assert/blob/7956178940dc33ff9b2ef42b2e057d85dc55a6ab/src/Assert.php#L1842
EDIT: performance reasons
With this code:
final class PdfRenderer
{
/**
* @var TranslatorInterface|LocaleAwareInterface
*/
private $translator;
public function __construct(TranslatorInterface $translator)
{
$this->translator = $translator;
Assert::isInstanceOf($this->translator, LocaleAwareInterface::class);
}
public function renderPdf(string $template, array $parameters, ?string $locale = null): string
{
// ...
$fallbackLocale = $this->translator->getLocale();
// ...
}
}
I will have:
Call to an undefined method Symfony\Contracts\Translation\LocaleAwareInterface|Symfony\Contracts\Translation\TranslatorInterface::getLocale().
But if I move the assert call under the renderPdf
method:
final class PdfRenderer
{
/**
* @var TranslatorInterface|LocaleAwareInterface
*/
private $translator;
public function __construct(TranslatorInterface $translator)
{
$this->translator = $translator;
}
public function renderPdf(string $template, array $parameters, ?string $locale = null): string
{
Assert::isInstanceOf($this->translator, LocaleAwareInterface::class);
// ...
$fallbackLocale = $this->translator->getLocale();
// ...
}
}
No error.
As the non-static methods can not be used without class instance and the class cannot be instantiated if the assert throw an exception, using it on the constructor should not add any error on PHPStan.
I bumped from 1.0.9 to 1.1.2 ;
I assume it's related to 56d7b27 @herndlm
class Foo
{
/** @var array<int, array{id: string, num_order: string}> */
protected array $orders = [];
public function setOrders(array $orders): self
{
Assert::allCount($orders, 2);
Assert::allKeyExists($orders, 'id');
Assert::allKeyExists($orders, 'num_order');
$this->orders = $orders;
return $this;
}
}
Hello I just updated phpstan-webmozart-assert to v1.0.7 and my codebase started reporting this error:
Call to static method Webmozart\Assert\Assert::upper() with non-empty-string will always evaluate to true.
It's failing in the following class construct
final class CityCode
{
private function __construct(private string $cityCode)
{
}
public static function fromString(string $cityCode) : self
{
Assert::length($cityCode, 3);
Assert::upper($cityCode);
return new self($cityCode);
}
}
I'm not using the non-empty-string type assertion anywhere so it must be resolved from the length assertion, however that doesn't ensure what upper is doing.
Thank you for looking into this, and all your great work.
With the following code
foreach ($data['sniffs'] as $sniffName) {
Assert::string($sniffName);
Assert::classExists($sniffName);
Assert::implementsInterface($sniffName, SniffInterface::class);
$ruleSet->addSniff(new $sniffName());
}
I get
Call to static method Webmozart\Assert\Assert::implementsInterface() with class-string and 'TwigCsFixer\\Sniff\\SniffInterface' will always evaluate to false.
i tried to install
"phpstan/phpstan": "1.9.x-dev as 1.9.0",
"phpstan/phpstan-webmozart-assert": "1.2.x-dev as 1.3",
but it doesn't solved anything
I saw you recently work on this method @herndlm (#144), any idea what could be wrong here ?
I'm trying to check if a method exist on a third party library using Assert::methodExists
$response = $this->client->request(
'GET',
$this->getUrl($module, $id, $subModule, $subId),
$this->defaultOptions,
);
Assert::same(200, $response->getStatusCode());
// not every client is stream able so we need to check if its possible to stream the content
Assert::methodExists($response, 'toStream');
$response->toStream();
But this currently fails with:
Call to an undefined method Symfony\Contracts\HttpClient\ResponseInterface::toStream()
Playground looks like this (Assert is not possible there maybe it could be added):
https://phpstan.org/r/130e8b7a-7b51-460f-b884-6a532ae935c6
Would be nice if Assert::methodExists
could also be supported by phpstan.
This code used to work fine, until I upgraded from 0.12.82
to 0.12.83
class Draft {
public function getEvent(): ?Event {}
}
class Listing {
public function setEvent(Event $event): void {}
}
private function createListing(Draft $draft) : Listing
{
Assert::notNull($draft->getEvent());
$listing = new Listing();
// Parameter #1 $event of method Listing::setEvent() expects Event, Event|null given.
$listing->setEvent($draft->getEvent());
return $listing;
}
Tried to reproduce it but I cannot: https://phpstan.org/r/c0ef8412-208b-441d-95c5-e3a9df165e55
I've noticed that the instanceOf assertions do not work with generics. Looking at the extension's code it makes sense; we don't know the type of T
yet so we can't tell PHPStan what the type is going to be. We do however know that $value
is an object
, so some narrowing should be possible.
/**
* @template T of object
*/
final class Example
{
/**
* @param non-empty-array<non-empty-string, class-string<T>> $classMap
* @param class-string<T> $className
*/
public function __construct(
private array $classMap,
private string $className,
) {
}
public function example(mixed $value): void
{
\PHPStan\Testing\assertType('mixed', $value);
Assert::isInstanceOf($value, $this->className);
\PHPStan\Testing\assertType('object', $value);
Assert::isInstanceOfAny($value, $this->classMap);
\PHPStan\Testing\assertType('object', $value);
}
}
Expected:
Actual output:
------ -------------------------------------
Line src/Example.php
------ -------------------------------------
29 Expected type object, actual: mixed
31 Expected type object, actual: mixed
------ -------------------------------------
Hello,
today, I am observing a strange behavior that I cannot really understand, maybe it is expected, but it feels like a bug.
I have this code:
private function getTimePeriod(VacationRequestInterface $request): DatePeriod
{
$startDate = $request->getStartDate();
$endDate = $request->getEndDate();
Assert::allIsInstanceOf([$startDate, $endDate], \DateTimeImmutable::class);
Assert::allNotNull([$startDate, $endDate]);
return new DatePeriod(
$startDate,
new DateInterval('P1D'),
$endDate,
DatePeriod::INCLUDE_END_DATE,
);
}
So, I'd expect that both $startDate
and $endDate
would be evaluated to be DateTimeImmutable
(the allNotNull
check is only there for a sanity check and not really required technically). However, once I run phpstan, I receive this error:
85 Parameter #1 $start of class DatePeriod constructor expects DateTimeInterface, DateTimeInterface|null given.
87 Parameter #3 $end of class DatePeriod constructor expects DateTimeInterface, DateTimeInterface|null given.
Now, if I do singular checks with isInstanceOf
instead of allIsInstanceOf
, the results are as expected.
Is it happening because I create a new array inside the allIsInstanceOf
, which is later not used to get the individual - and evaluated - elements? Maybe people think it make sense to allow evaluation like this, and we could adapt the module.
Hello,
I found out that the release 0.11 does not have this commit a5d6fa7
Considering the tag is 1 month younger than this commit, is there any reason for that ?
Thanks.
Currently it results in array
.
Example
function foo(array $input) {
Assert::isNonEmptyList($input);
Assert::allString($input);
\PHPStan\dumpType($input);
}
function bar(array $input) {
Assert::isList($input);
Assert::allString($input);
\PHPStan\dumpType($input);
}
Output
Dumped type: array<string>
Dumped type: array<string>
Expected output
Dumped type: non-empty-list<string>
Dumped type: list<string>
I guess the list type in general is not implemented here.
The function implementsInterface($value, $interface, $message = '') from the docs
Check that a class implements an interface.
Here is the code:
<?php declare(strict_types=1);
namespace App;
use DateTimeInterface;
use Webmozart\Assert\Assert;
Assert::implementsInterface(\DateTime::class, DateTimeInterface::class);
Assert::implementsInterface(\DateTimeZone::class, DateTimeInterface::class);
Here is phpstan error:
Call to static method Webmozart\Assert\Assert::implementsInterface() with 'DateTime' and 'DateTimeInterface' will always evaluate to false.
Call to static method Webmozart\Assert\Assert::implementsInterface() with 'DateTimeZone' and 'DateTimeInterface' will always evaluate to false.
Just for the record and since reporting on Mastodon is not optimal I guess :)
But see https://phpc.social/@herndlm/112179238683506275 for reference
didn't work the last 2 times with the same error that looked like some base URL config is missing or so. and it seems to work for some other repos.
I was surprised to see that Assert::length($value, 26);
did not result in a non-empty-string. It seems redundant to do:
Assert::stringNotEmpty($value);
Assert::length($value, 26);
Same goes for Assert::minLength()
.
$encryptedValue = "some value";
$valueParts = explode(':', $encryptedValue);
Assert::count(
$valueParts,
2,
'Encrypted secret parameter was expected to consist of 2 parts separated by a colon'
);
// Call to static method Webmozart\Assert\Assert::count() with arguments non-empty-array<int, string>, 2 and 'error' will always evaluate to true
Since version 1.1.0
function test(float $value): void
{
Assert::range($value, 0, 1);
}
reports Call to static method Webmozart\Assert\Assert::range() with arguments float, 0 and 1 will always evaluate to true.
I released 1.4.0 with some new assertions that should be added here.
I think that #50 is not working as it should.
final class DateFilter
{
public const DATE_FILTER_TONIGHT = 'tonight';
public const DATE_FILTER_TOMORROW = 'tomorrow';
public const ALLOWED_FILTERS = [
self::DATE_FILTER_TONIGHT,
self::DATE_FILTER_TOMORROW
];
public function __construct(string $dateFilter)
{
Assert::inArray(
$dateFilter,
self::ALLOWED_FILTERS,
);
}
}
Call to static method Webmozart\Assert\Assert::inArray() with string and array('tonight', 'tomorrow', 'this_weekend', 'this_week', 'this_month', 'next_weekend', 'next_week', 'next_month', ...) will always evaluate to true.
/cc @ntzm
class A {
public function __construct(private string $val) {
}
}
function foo(A $a) {
Assert::inArray($a, [new A('a')]);
}
gives:
Call to static method Webmozart\Assert\Assert::inArray() with arguments A and array(A) will always evaluate to true
Assert::implementsInterface()
works on object|string
, not just object
, so it should be translated to something like assert($foo instanceof $type || in_array($interface, \class_implements($value)))
(see https://3v4l.org/VdIbR).
Not sure what the correct translation (understood by PHPStan) would be.
For reference, this is the current implementation:
This check is used to assert non-empty-string.
Assert::minLength($value, 1);
phpstan considers it as a string
only.
Having something like
final class PhpStanTest
{
/**
* @return stdClass[]
*/
public function foo(bool $bar): array
{
$result = $bar ? [new stdClass(), new stdClass()] : false;
Assert::allIsInstanceOf($result, stdClass::class);
return $result;
}
}
falsely reports
Method PhpStanTest::foo() should return array<stdClass> but returns array<int, stdClass>|false.
It cannot be false though because the all*
methods are internally checking for iterables in https://github.com/webmozarts/assert/blob/master/src/Assert.php#L1947
I would love to help out here but do not know yet where to start. Feel free to point me there or to any starter resources if you like :)
When calling Webmozart\Assert\Assert::stringNotEmpty()
with a string
variable, the following error is reported:
Call to static method Webmozart\Assert\Assert::stringNotEmpty() with string will always evaluate to true.
As a string may be empty, the error is unexpected.
This also occurs with a hardcoded non-numeric argument, i.e. Assert::stringNotEmpty('')
:
Call to static method Webmozart\Assert\Assert::stringNotEmpty() with '' will always evaluate to true.
Here the error is expected as the value is hardcoded, though the message is wrong: it should be always false instead of always true.
Hello,
public function foo(?string $bar)
{
Assert::nullOrStringNotEmpty($bar);
}
The above example leads to the following error:
Call to static method Webmozart\Assert\Mixin::nullOrStringNotEmpty() with string|null will always evaluate to true.
The problem is, that it's totally possible for $bar
to be an empty string.
Unfortunately I can't provide a playground example, because Webmozart\Assert
can't be used there.
See discussion in #128
A nested array like:
/**
* @param mixed[] $requestData
*
* @return array{
* accountId: int,
* errorColor: string|null,
* theme: array{
* backgroundColor: string|null,
* textColor: string|null,
* headerImage: array{id: int}|null,
* },
* }
*/
https://phpstan.org/r/652f74f8-c926-4802-99b9-bab73da329dd
As requested the none webmozart assert implementation: https://phpstan.org/r/f535c1bb-82e3-4e49-a1d6-ee31c25339fe / https://phpstan.org/r/371acae7-1694-46bc-9d4e-2aa2c653134e (more dumps)
Is not possible to be validated as it seems to lost type after:
Assert::keyExists($requestData['theme'], 'headerImage');
Assert::nullOrIsArray($requestData['theme']['headerImage']);
\Phpstan\dumpType($requestData);
\Phpstan\dumpType($requestData['theme']);
\Phpstan\dumpType($requestData['theme']['headerImage']);
4 Dumped type: array&hasOffset('errorColor')&hasOffset('theme')
5 Dumped type: mixed
6 Dumped type: mixed
Not sure if this is even possible. I see the complexness and also know that my solution is not the best. And should maybe go with json validation or something like that instead of validating array manually. Still I thought about reporting it.
PS: any possibility to enable Webmozart/Assert
on https://phpstan.org/ ?
Now that we can narrow types, it would be quite handy to be able to do this:
/**
* @var string
* @phpstan-var EntityType::TYPE_*
*/
private $inputType;
public function __construct(string $inputType)
{
Assert::oneOf($inputType, EntityType::ALL);
- assert(in_array($inputType, EntityType::ALL, true));
$this->inputType = $inputType;
}
Phpstan does not understand that Assert::true($var === 'x')
means the $var
is of type 'x'
while with assert($var === 'x')
it does.
The behaviour should be the same.
with this plugin, phpstan should be aware about the array shape of the variable involved:
/**
* @param array{password: mixed, email: mixed} $arr
*/
function a($arr) {
}
$data = $_POST;
Assert::keyExists($data, 'password');
Assert::keyExists($data, 'email');
a($data);
it would be even better if we could verify that the keys are of a certain type, e.g. string in my above example.
though it seems this is not even possible with Assert
itself, without additional asserts.
The same issue applies probably to more checks than just this.
In this case $a is never going to be null but phpstan won't show an error
$a = 1;
Assert::notNull($a);
When calling Webmozart\Assert\Assert::numeric()
with a string variable, the following error is reported:
Call to static method Webmozart\Assert\Assert::numeric() with string will always evaluate to true.
As a string may be a numeric value, the error is unexpected.
This also occurs with a hardcoded non-numeric argument, i.e. Assert::numeric('foo')
:
Call to static method Webmozart\Assert\Assert::numeric() with 'foo' will always evaluate to true.
Here the error is expected as the value is hardcoded, though the message is wrong: it should be always false instead of always true.
The error is not reported anymore when calling is_numeric()
directly:
if (!is_numeric($value)) {
throw new \InvalidArgumentException();
}
The readme could describe in a sentence or two what the actual use case of this extension is and when/why its usefull.
As it is right now it feels only be usable for phpstan experts.
Since version 1.1.0
function test(float $value): void
{
Assert::greaterThan($value, 0);
}
reports Call to static method Webmozart\Assert\Assert::greaterThan() with float and 0 will always evaluate to true.
After updating my dependencies I'm experiencing problems with analysis. Array types seem to be converted internally to iterable types when calling webmozart/assert methods. I've added screenshots and my system information. Hopefully this helps, if you need more information, ping me :-)
class Logic
{
/**
* @param array<int, array<string, mixed>> $items
*/
public function execute(array $items): void
{
\Webmozart\Assert\Assert::allKeyExists($items, 'id');
// ...
$this->executeSomethingElse($items); // ERROR: Now phpstan says that $items has become an iterable.
}
/**
* @param array<int, array<string, mixed>> $items
*/
public function executeSomethingElse(array $items): void
{
}
}
System information:
bash-5.1$ php -v
PHP 7.4.16 (cli) (built: Mar 25 2021 23:24:45) ( NTS )
Copyright (c) The PHP Group
Zend Engine v3.4.0, Copyright (c) Zend Technologies
with Zend OPcache v7.4.16, Copyright (c), by Zend Technologies
with Xdebug v3.0.3, Copyright (c) 2002-2021, by Derick Rethans
This issue lists Renovate updates and detected dependencies. Read the Dependency Dashboard docs to learn more.
These updates have all been created already. Click a checkbox below to force a retry/rebase of any.
nikic/php-parser
, phpunit/phpunit
)composer.json
php ^7.2 || ^8.0
phpstan/phpstan ^1.10
nikic/php-parser ^4.13.0
php-parallel-lint/php-parallel-lint ^1.2
phpstan/phpstan-deprecation-rules ^1.1
phpstan/phpstan-phpunit ^1.0
phpstan/phpstan-strict-rules ^1.0
phpunit/phpunit ^9.5
webmozart/assert ^1.11.0
.github/workflows/build.yml
actions/checkout v4
shivammathur/setup-php v2
actions/checkout v4
actions/checkout v4
shivammathur/setup-php v2
actions/checkout v4
shivammathur/setup-php v2
actions/checkout v4
shivammathur/setup-php v2
.github/workflows/create-tag.yml
actions/checkout v4
WyriHaximus/github-action-get-previous-tag v1
WyriHaximus/github-action-next-semvers v1
rickstaa/action-create-tag v1
rickstaa/action-create-tag v1
.github/workflows/lock-closed-issues.yml
dessant/lock-threads v5
.github/workflows/release-toot.yml
cbrgm/mastodon-github-action v2
.github/workflows/release-tweet.yml
Eomm/why-don-t-you-tweet v1
.github/workflows/release.yml
actions/checkout v4
metcalfc/changelog-generator v4.3.1
actions/create-release v1
What I did in phpstan-beberlei-assert in this commit should also be done here in phpstan-webmozart-assert. Instead of hacking together a really burdensome way of testing type inference, the new modern way takes advantage of TypeInferenceTest
that's in current dev-master of phpstan/phpstan
.
I also wrote a short documentation on this: https://phpstan.org/developing-extensions/testing#type-inference
If anyone is feeling up to this, please comment on this issue so that there aren't multiple people doing the same thing :) Thanks!
Not sure if this is an issue or not, and if it's related to phpstan-webmozart-assert or just webmozart-assert.
When upgrading from 0.12.8 to 0.12.9 I get this error:
Parameter #1 $input of class Command constructor expects array<string>, iterable<string> given.
This is coming from:
$input = $request->request->get('input');
Assert::isList($input);
Assert::allString($input);
$command = new Command($input)
My Command
has the following constructor:
/**
* @param string[] $input
*/
public function __construct(array $input)
Assert's allString
method defines:
/**
* @psalm-pure
* @psalm-assert iterable<string> $value
*
* @param mixed $value
* @param string $message
*
* @throws InvalidArgumentException
*/
public static function allString($value, $message = '');
It seems that PHPStan no longer accepts passing an iterable<string>
into a string[]
.
Calling Assert::integerish(1.000)
with a float or numeric the following error is reported:
Call to static method Webmozart\Assert\Assert::integerish() with float will always evaluate to false.
Assert::integerish(1.000); // OK
Assert::integerish(1.001); // not OK
If I do:
$opLimit = $input->getOption('operation-limit');
$timeLimit = $input->getOption('time-limit');
Assert::numeric($opLimit);
Assert::numeric($timeLimit);
$opLimit = (int) $opLimit;
$timeLimit = (int) $timeLimit;
No error outputted. But if I do:
$opLimit = $input->getOption('operation-limit');
$timeLimit = $input->getOption('time-limit');
Assert::allNumeric($opLimit, $timeLimit);
$opLimit = (int) $opLimit;
$timeLimit = (int) $timeLimit;
I have:
src/AppBundle/Command/OperationExecuteCommand.php:105:Cannot cast array<string>|bool|string|null to int.
src/AppBundle/Command/OperationExecuteCommand.php:106:Cannot cast array<string>|bool|string|null to int.
It seems the all*
methods are ignored by the plugin.
A declarative, efficient, and flexible JavaScript library for building user interfaces.
๐ Vue.js is a progressive, incrementally-adoptable JavaScript framework for building UI on the web.
TypeScript is a superset of JavaScript that compiles to clean JavaScript output.
An Open Source Machine Learning Framework for Everyone
The Web framework for perfectionists with deadlines.
A PHP framework for web artisans
Bring data to life with SVG, Canvas and HTML. ๐๐๐
JavaScript (JS) is a lightweight interpreted programming language with first-class functions.
Some thing interesting about web. New door for the world.
A server is a program made to process requests and deliver data to clients.
Machine learning is a way of modeling and interpreting data that allows a piece of software to respond intelligently.
Some thing interesting about visualization, use data art
Some thing interesting about game, make everyone happy.
We are working to build community through open source technology. NB: members must have two-factor auth.
Open source projects and samples from Microsoft.
Google โค๏ธ Open Source for everyone.
Alibaba Open Source for everyone
Data-Driven Documents codes.
China tencent open source team.