Giter Site home page Giter Site logo

phpstan-webmozart-assert's Introduction

PHPStan webmozart/assert extension

Build Latest Stable Version License

Description

The main scope of this extension is to help PHPStan to detect the type of object after the Webmozart\Assert\Assert validation.

<?php declare(strict_types = 1);

use Webmozart\Assert\Assert;

function demo(?int $a) {
	// ...

	Assert::integer($a);
	// PHPStan is now aware that $a can no longer be `null` at this point

	return ($a === 10);
}

Installation

To use this extension, require it in Composer:

composer require --dev phpstan/phpstan-webmozart-assert

If you also install phpstan/extension-installer then you're all set!

Manual installation

If you don't want to use phpstan/extension-installer, include extension.neon in your project's PHPStan config:

includes:
    - vendor/phpstan/phpstan-webmozart-assert/extension.neon

phpstan-webmozart-assert's People

Contributors

acasademont avatar boesing avatar clxmstaab avatar dependabot[bot] avatar fbuchlak avatar greg0ire avatar herndlm avatar kocal avatar localheinz avatar lookyman avatar mavimo avatar ntzm avatar ondrejmirtes avatar r4c00n avatar renovate-bot avatar renovate[bot] avatar ruudk avatar simpod avatar staabm avatar tomasvotruba avatar tomaszhanc avatar vhenzl avatar villfa avatar

Stargazers

 avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar

Watchers

 avatar  avatar  avatar  avatar  avatar  avatar  avatar

phpstan-webmozart-assert's Issues

Assert::keyExists() should be supported

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.

Issue with `implementsInterface`

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 ?

Describe use-case in Readme

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.

Assert::stringNotEmpty() with string will always evaluate to true

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.

Assert::all* not handle

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.

Assert::length() should result in non-empty-string

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().

Support the new methods in 1.4

I released 1.4.0 with some new assertions that should be added here.

  • Assert::ip()
  • Assert::ipv4()
  • Assert::ipv6()
  • Assert::notRegex()
  • Assert::interfaceExists()
  • Assert::isList()
  • Assert::isMap()

Asserts done on class constructor should be considered for methods

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.

Add support for Assert::methodExists

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.

False positive: inArray with string and array('value1', 'value2') will always evaluate to true.

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

Assert::numeric() with string will always evaluate to true

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();
}

Validating nested arrays ends in mixed

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/ ?

After upgrading PHPStan from `0.12.82` to `0.12.83`: `Assert::notNull($object->getEvent())` no longer removes nullability

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

Assert::nullOrStringNotEmpty() with string|null will always evaluate to 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.

Internal type conversion from array to iterable.

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
    {
    }
}

image

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

image

Call for help: Modernize AssertTypeSpecifyingExtensionTest

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!

Assert::isNonEmptyList should result in non-empty-list

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.

Add FQCN support in Assert::implementsInterface

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.  

False positive: Call to static method Webmozart\Assert\Assert::count() with arguments non-empty-array<int, string>, 2 and 'error' will always evaluate to true

Related to #62 /cc @villfa

$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

False positive on `Assert::upper()` always evaluates as true

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.

`Assert::implementsInterface($foo, <Type>)` is incorrectly being translated to `assert($foo instanceof <Type>)`

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:

'implementsInterface' => function (Scope $scope, Arg $expr, Arg $class): ?\PhpParser\Node\Expr {
$classType = $scope->getType($class->value);
if (!$classType instanceof ConstantStringType) {
return null;
}
return new \PhpParser\Node\Expr\Instanceof_(
$expr->value,
new \PhpParser\Node\Name($classType->getValue())
);
},

Since 0.12.9: `expects array<string>, iterable<string> given`

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[].

allIsInstanceOf / allNotNull does not tell phpstan that its evaluated as expected

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.

Dependency Dashboard

This issue lists Renovate updates and detected dependencies. Read the Dependency Dashboard docs to learn more.

Open

These updates have all been created already. Click a checkbox below to force a retry/rebase of any.

Detected dependencies

composer
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-actions
.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

  • Check this box to trigger a request for Renovate to run again on this repository

Add support for Assert::oneOf()

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;
 
     }

Assert::allIsInstanceOf evaluates to non iterables

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 :)

Recommend Projects

  • React photo React

    A declarative, efficient, and flexible JavaScript library for building user interfaces.

  • Vue.js photo Vue.js

    ๐Ÿ–– Vue.js is a progressive, incrementally-adoptable JavaScript framework for building UI on the web.

  • Typescript photo Typescript

    TypeScript is a superset of JavaScript that compiles to clean JavaScript output.

  • TensorFlow photo TensorFlow

    An Open Source Machine Learning Framework for Everyone

  • Django photo Django

    The Web framework for perfectionists with deadlines.

  • D3 photo D3

    Bring data to life with SVG, Canvas and HTML. ๐Ÿ“Š๐Ÿ“ˆ๐ŸŽ‰

Recommend Topics

  • javascript

    JavaScript (JS) is a lightweight interpreted programming language with first-class functions.

  • web

    Some thing interesting about web. New door for the world.

  • server

    A server is a program made to process requests and deliver data to clients.

  • Machine learning

    Machine learning is a way of modeling and interpreting data that allows a piece of software to respond intelligently.

  • Game

    Some thing interesting about game, make everyone happy.

Recommend Org

  • Facebook photo Facebook

    We are working to build community through open source technology. NB: members must have two-factor auth.

  • Microsoft photo Microsoft

    Open source projects and samples from Microsoft.

  • Google photo Google

    Google โค๏ธ Open Source for everyone.

  • D3 photo D3

    Data-Driven Documents codes.