phparraysphpstan

How to make PHPStan understand a function asserts the existence of an array key


PHPStan can recognise we check if array key exists:

<?php declare(strict_types = 1);

/** @return array<mixed> */
function returnMixedArray(): array {
    return [];
}

/**
 * @return array{
 *     existing_key: mixed
 * }
 */
function narrowMixedArrayKeys(): array {
    $foo = returnMixedArray();

    assert(array_key_exists('existing_key', $foo));
    
    return $foo;
}

(This example in playground.)

I want to check multiple keys' existence, and make PHPStan realise what I'm doing.

I tried writing a generic function, but PHPStan doesn't understand what I'm trying to tell it:

<?php declare(strict_types = 1);

/**
 * @param string $keys
 * @param array<int|string> $array
 */
function assertKeys(array $array, string ...$keys): void {
    foreach ($keys as $key) {
        assert(array_key_exists($key, $array));
    }
}

Is there a way to make PHPStan understand a function asserts the existence of a key?

I realise I should be using objects instead of arrays, I'm forced to work with legacy code I want to secure for now.


Solution

  • As of version 1.11.7, that is not yet possible.

    The hypothetical feature would fall within Custom type-checking functions and methods, in Narrowing Types, introduced in 1.9.0. The update adds support for the custom PHPDoc tags @phpstan-assert, @phpstan-assert-if-true, @phpstan-assert-if-false, laying the foundations to what you (and I) are trying to achieve, but it is not quite there yet.

    Here are some examples to illustrate the current capabilities, which might give you some insights:

    /** @phpstan-assert-if-true T $object */
    public function isObjectOfClass(string $class, object $object): bool { ... }
    
    /** @phpstan-assert-if-true !null $this->getName() */
    public function hasName(): bool { ... }
    

    As one can always hope, maybe I'll update this post soon with news that this feature has been implemented.