Psalm: Is the intention of psalm to replace 'strict_types=1' and PHP Lint, or augment it?

Created on 28 Jun 2020  路  10Comments  路  Source: vimeo/psalm

This isn't an "issue" with psalm, and I couldn't find a forum with knowledgeable users to post this question to, so forgive me if I should not be asking my question here.

Assuming you are using a new codebase without any legacy code to worry about, do people using psalm declare strict_types=1 and use PHP Linting?

See https://psalm.dev/r/319e45f823

Declaring it allows you to use PHP Lint to check your code for errors, in addition to psalm, but at the expense of having to declare types in 2 places: psalm types in the comments, and, actual types inline. Moreover, sometimes those types will differ.

Not declaring it allows you to only specify types in one place (in the comments), but then PHP Linting will fail.

In general, what is the best practice?

Most helpful comment

Psalm will work with what you give to it. Whether it's in signature, in standard phpdoc tags or specific phpdoc tags.

That means you don't HAVE to follow any strict rules. However, Psalm will work better with more precise types than what can be declared in signature (the most obvious case would be arrays, they don't describe their contents in signature).
Psalm also has a tool to edit code automatically called Psalter. This tool has a mode where it will only trust types in signature when it tries to add Missing Return Types.

The conclusion of that is that it's generally better to have both types in phpdoc and in signature. Psalm will check coherence between the two when both are provided.
My preferred practice is to remove phpdoc tags when they don't provide more information than the signature. Moreover, when the phpdoc tag is needed, I try to keep a syntax understood by PhpStorm in them (for autocompletion purposes) and I use specific psalm tags when the PhpStorm syntax is not enough to fully express a type.

Which means I sometimes end up with something like:

<?php

/**
 * @param int[] $param
 * @psalm-param list<int> $param
 */
function test(array $param){}

That can be very verbose but you sometimes have to compose with different tools...

All 10 comments

I found these snippets:


https://psalm.dev/r/319e45f823

<?php
// Should we be declaring strict_types when using psalm?
declare(strict_types=1);

// If yes, then which of the below is best?

// Not declaring a type for $k causes php lint to fail
/** @param string|int $k */
function func1($k) : bool
{
  return $k===1;
}

// PHP7 doesn't support declaring a type of 'string|int' so to allow PHP lint to
// pass, we must declare the type as 'mixed'.  But then we are declaring
// $k's type in two differnt places, and the two types being declared are different.
/** @param string|int $k */
function func2(mixed $k) : bool
{
  return $k===1;
}
Psalm output (using commit c95ebfe):

No issues!

Psalm will work with what you give to it. Whether it's in signature, in standard phpdoc tags or specific phpdoc tags.

That means you don't HAVE to follow any strict rules. However, Psalm will work better with more precise types than what can be declared in signature (the most obvious case would be arrays, they don't describe their contents in signature).
Psalm also has a tool to edit code automatically called Psalter. This tool has a mode where it will only trust types in signature when it tries to add Missing Return Types.

The conclusion of that is that it's generally better to have both types in phpdoc and in signature. Psalm will check coherence between the two when both are provided.
My preferred practice is to remove phpdoc tags when they don't provide more information than the signature. Moreover, when the phpdoc tag is needed, I try to keep a syntax understood by PhpStorm in them (for autocompletion purposes) and I use specific psalm tags when the PhpStorm syntax is not enough to fully express a type.

Which means I sometimes end up with something like:

<?php

/**
 * @param int[] $param
 * @psalm-param list<int> $param
 */
function test(array $param){}

That can be very verbose but you sometimes have to compose with different tools...

@ignacvucko I'm not sure what you mean by "Not declaring a type for $k causes php lint to fail" . I downloaded your snippet as foo.php and linted it using PHP 7.3:

$ php -l foo.php 
No syntax errors detected in foo.php

I do always declare strict types in the code that is check with Psalm. I'm not sure exactly how much this changes the behavior of Psalm - I expect it does change it substantially - but the checks that this makes the PHP engine do at runtime are also important to me.

The project I'm working on now doesn't have psalm running in its totallyTyped mode, and there are a lot of mixed values that come from HTTP requests or the database, so there are a lot of things that Psalm check that need to be checked at run time.

// pass, we must declare the type as 'mixed'. But then we are declaring

Aside: PHP 8.0 added both union types and the mixed types. You'll be able to declare function func2(int|string $k) : bool in php 8. In php 7, mixed $k would look for an object of type class mixed.

https://wiki.php.net/rfc/mixed_type_v2 https://wiki.php.net/rfc/union_types_v2

Not a maintainer of Psalm: There are multiple projects called "PHP Lint", which one are you referring to? I'd assume php lint is a linter which checks for simple syntax rules. Psalm is a full type checker (which can be used in combination with whatever tools you're already existing), and that psalm would aim to focus on type checking instead of linting (not checking for whitespace coding style guidelines)

Yes, thanks, I had heard that PHP8 will support union types and a mixed type, and that it's planned release date is end of year.
I was referring to the default built in "php -l " that you issue from the command line.
I think there is probably still some usefulness to running the lint check (especially with strict_types enabled) on top of the psalm check, which is why I was struggling with things: it requires you to define types in two places, both inline and in comments.

I'd assume the intent is to augment it - PHP has hundreds of syntax and semantic checks in --syntax-check, and it's probably not worth the effort for the maintainers to reproduce all of them in pure PHP unless it matters for forward/backwards compatibility (the end result would be less efficient for some checks and make the code much longer).

Other static analyzers have taken the approach of writing a plugin or config setting that would run php --syntax-check with a configurable list of php binaries (e.g. this plugin for a different analyzer)

  • Adding a plugin would help with Psalm's language server, e.g. to check compatibility with multiple php versions.

For example, there's not much reason to check for https://psalm.dev/r/d7ab9d79a5 since it's extremely rare and there are other tools to run --syntax-check on the desired set of php files. Those tools such as parallel lint would likely do a better job of managing parallelism (e.g. running exactly 8 php --syntax-check --no-php-ini in parallel until finished)

I found these snippets:


https://psalm.dev/r/d7ab9d79a5

<?php
goto my_label;
for ($i = 0; $i < 10; $i++) {
    my_label:
}
Psalm output (using commit c95ebfe):

No issues!

Psalm is not a replacement for php -l.

The parser Psalm uses, nikic/php-parser, emits some of the same error messages that php -l does when encountering bad code, but it allows newly-introduced features (like allowing trailing commas and union types) that break earlier versions of PHP.

The strict_types directive is a little more complex - in a codebase with 100% Psalm type coverage (and no errors when you run Psalm) there should also be no runtime type errors. In practice most codebases don鈥檛 meet that high bar, so I recommend using strict_types=1 wherever it鈥檚 feasible.

Was this page helpful?
0 / 5 - 0 ratings