PHP code example of shipmonk / phpstan-rules

1. Go to this page and download the library: Download shipmonk/phpstan-rules library. Choose the download type require.

2. Extract the ZIP file and open the index.php.

3. Add this code to the index.php.
    
        
<?php
require_once('vendor/autoload.php');

/* Start to develop here. Best regards https://php-download.com/ */

    

shipmonk / phpstan-rules example snippets


function example1(Money $fee1, Money $fee2) {
    if ($fee1 > $fee2) {} // comparing objects is denied
}

new DateTime() > '2040-01-02'; // comparing different types is denied
200 > '1e2'; // comparing different types is denied

enum MyEnum: string { // missing @implements tag
    case MyCase = 'case1';
}

/**
 * @param list<Entity> $entities
 * @return list<Uuid>
 */
public function getIds(array $entities): array {
    return array_map(
        function ($entity) { // missing native typehint; not reported with allowMissingTypeWhenInferred: true
            return $entity->id;
        },
        $entities
    );
}

enum MyEnum {
    case Foo;
    case Bar;
}

if ($enum === MyEnum::Foo) {
    // ...
} elseif ($enum === MyEnum::Bar) { // always true reported by phpstan (for versions 1.10.0 - 1.10.34)
    // ...
} else {
    throw new LogicException('Unknown case'); // phpstan knows it cannot happen
}

if ($enum === MyEnum::Foo) {
    // ...
} elseif ($enum === MyEnum::Bar) {
    // ...
}

if ($enum === MyEnum::Foo) {
    // ...
} else {
    // ...
}

$fn = function () {
    yield new stdClass => 1;
};

iterator_to_array($fn()); // denied, would fail

/**
 * @return array<string>
 */
public function returnList(): array // error, return phpdoc is generic array, but list is always returned
{
    return ['item'];
}

class NoNativeReturnTypehint {
    /**
     * @return list<string>
     */
    public function returnList() // error, missing array typehint
    {
        return ['item'];
    }
}

class EnforceReadonlyPublicPropertyRule {
    public int $foo; // fails, no readonly modifier
    public readonly int $bar;
}

function add(string $a, string $b) {
    return $a + $b; // denied, non-numeric types are allowed
}

$empty = (array) null; // denied cast
$notEmpty = (array) 0; // denied cast

class TransactionManager {
    /**
     * @param-immediately-invoked-callable $callback
     */
    public function transactional(callable $callback): void {
        // ...
        $callback();
        // ...
    }
}

class UserEditFacade
{
    /**
     * @throws UserNotFoundException
     */
    public function updateUserEmail(UserId $userId, Email $email): void
    {
        $this->transactionManager->transactional(function () use ($userId, $email) {
            $user = $this->userRepository->get($userId); // can throw checked UserNotFoundException
            $user->updateEmail($email);
        })
    }

    public function getUpdateEmailCallback(UserId $userId, Email $email): callable
    {
        return function () use ($userId, $email) {
            $user = $this->userRepository->get($userId); // this usage is denied, it throws checked exception, but you don't know when, thus it cannot be tracked by phpstan
            $user->updateEmail($email);
        };
    }
}

class Provider {
    /** @throws CheckedException */
    public static function generate(): iterable
    {
        yield 1;
        throw new CheckedException(); // denied, gets thrown once iterated
    }
}

new SomeClass(); // Class SomeClass is forbidden. Please use different class
(new AnotherClass())->someMethod(); // Method AnotherClass::someMethod() is forbidden. Please use anotherMethod

enum MyEnum: string {
    case MyCase = 'case1';
}

implode('', [MyEnum::MyCase]); // denied, would fail on implicit toString conversion

function example($unknown) {
    $unknown->property; // cannot fetch property on mixed
}

function isEqual(DateTimeImmutable $a, DateTimeImmutable $b): bool {
    return $a === $b;  // comparing denied classes
}

$value = '2e0';
$value++; // would be float(3), denied

match ($enum) {
    MyEnum::Case: 1;
    default: 2; // default arm forbidden
}

function example($unknown) {
    $unknown->call(); // cannot call method on mixed
}

/**
 * @return mixed|false   // denied, this is still just mixed
 */
public function getAttribute(string $name)
{
    return $this->attributes[$name] ?? false;
}

function getCost(int $cost, ?int $surcharge): int {
    $cost += $surcharge;  // denied, adding possibly-null value
    return $cost;
}

function getFullName(?string $firstName, string $lastName): string {
    return $firstName . ' ' . $lastName; // denied, null involved in binary operation
}

public function output(?string $name) {
    echo "Hello $name!"; // denied, possibly null value
}

/**
 * @param string $param
 */
public function sayHello(?string $param) {} // invalid phpdoc not containing null

enum MyEnum {
    protected function isOpen(): bool {} // protected enum method denied
}

class Get {
    public static function oneTwoThree(): iterable { // marked as iterable, caller cannot access the return value by Generator::getReturn
        yield 1;
        yield 2;
        return 3;
    }
}

iterator_to_array(Get::oneTwoThree()); // [1, 2] - see https://3v4l.org/Leu9j

$taxRates = [ // denied, float key gets casted to int (causing $taxRates to contain one item)
    1.15 => 'reduced',
    1.21 => 'standard',
];

function example(OrderId $id) {
    $id = $id->getStringValue(); // denied, type changed from object to string
}

function example(MyClass $class) {
    unset($class->field); // denied
}

public function example(int $foo): ?int { // null never returned
    if ($foo < 0) {
        return 0;
    }
    return $foo;
}

function validate(): void {
    new Exception(); // forgotten throw
}

match ($foo) { // unused match result
    case 'foo' => 1;
}

try {
    // some code
} catch (RuntimeException $e) {
    throw new LogicException('Cannot happen'); // $e not passed as previous
}

class MyException extends RuntimeException {
    public function __construct() {
        parent::__construct('My error');
    }
}

try {
    // some code
} catch (RuntimeException $e) {
    throw new MyException(); // reported even though MyException cannot accept it yet
}

class Example
{
    private ?int $field = null; // useless default value

    public function __construct()
    {
        $this->field = 1;
    }
}
neon
parameters:
    shipmonkRules:
        forbidCast:
            enabled: true
            blacklist!: ['(unset)'] # force the blacklist to be only (unset)