Psalm 7 release: PHP security&static analysis software
psalm.dev
psalm.dev
/**
* @param array<string, MyRecord> $input
* @return list<int>
*/
function foo(array $input): array
{
foreach ($input as $string => $record) {
// IDE knows $record is an instance of MyRecord
The static analysis will use both type systems, and it's the only way to check that phpdoc are consistent. Psalm has various levels of strictness, and even at a very lax level, it can help detect errors or code smells.There are two major static analysis tools for PHP, Psalm and PHPstan, both written in PHP. The latter used to be much slower, but last time I tried it there was little difference (about 20 s for 100k lines + dependencies).
My guess is that a new contender, Mago, written in Rust, will probably soon replace those other tools. Its commands (analyze, lint) are already almost as complete as psalm/phpstan. Though the migration won't be easy because it does not have a notion of levels.
PHP even has union types, just like TypeScript.
The major issue is the lack of array types and it's 90% of the reason I see projects using extra linting tools.
I haven't actually run into a single issue with PHP's type system in the last few years. Literally the only thing that remains is typed arrays but that seems to be ways off due to fragmentation in how the core devs want it handled.
declare(strict_types=1);
/\* @var array<string, int> \*/
function foo(string $key) {
echo $key;
}
$foo = [
'a' => 10,
'1' => 15,
];
foreach ($foo as $key => $_) {
foo($key);
}
The static analyzer will say this is safe (sans a bleeding edge setting in PHPStan), but it will throw a type error when it runs. Because the string '1' is, despite having strict types enabled, still implicitly cast to the number 1.PHP arrays are the most versatile part of the language. They're what actually makes the language so good in so many ways.
But typed arrays are a freaking requirement at this point and I've been screaming about it for years now... alas no response.
I wish that the implicit casting you describe only happened in the two first case. In your example PHP already knows that $foo is a hashmap, so I'd want it to keep '1' as is!
Unfortunately for all of us, for every person surprised by this behavior, there's someone who relies on it. So it'll never change.
Best we can hope is to have better array types eventually (no, the SPL isn't it).
PHP still doesn't have generics, right? IMO any type system without higher-order types is definitely on the minimal side.
One can declare:
final class Door {}
/**
* @template T
*/
final class Box
{
/** @param T $value */
public function __construct(private mixed $value) {}
/** @return T */
public function get(): mixed
{
return $this->value;
}
}
final class DoorInspector
{
/** @param Box<Door> $box */
public function __construct(private Box $box) {}
public function inspect(): void
{
$this->box->get()->open(); // analyser knows this is a Door
}
}
new DoorInspector(new Box(new Door())); // OK
new DoorInspector(new Box('not a door')); // error: Box<string> given
You can even bind a template by a generic type with "of": final class Door {}
/**
* @template T
*/
class Box
{
/** @param T $value */
public function __construct(private mixed $value) {}
/** @return T */
public function get(): mixed
{
return $this->value;
}
}
/** @extends Box<Door> */
final class DoorBox extends Box {}
/**
* @template T
* @template F of Box<T>
*/
final class Shelf
{
/** @param F $box */
public function __construct(private Box $box) {}
/** @return F */
public function box(): Box
{
return $this->box;
}
/** @return T */
public function item(): mixed
{
return $this->box->get();
}
}
$shelf = new Shelf(new DoorBox(new Door()));
$shelf->box(); // DoorBox
$shelf->item(); // DoorSome PHP functions (e.g. opening a file) return a `resource` type, but PHP has no official way to declare it. So the right way is to declare `mixed` as an official type (which means `any`) and fix that declaration with a phpdoc comment.
Phpdoc provides generics (named "templates", à la C++), e.g. `iterable<Something>` where `iterable` is an official PHP type (which is awfully imprecise without phpdoc).
Phpdoc has tuples and (associative) array shapes, e.g. `@return list{int, \Error}`.
In past PHP versions, the type system was worse because the internal types were ambiguous: types were implicitly nullable, i.e. `string` meant `null|string`. When PHP gradually added sum types, they fixed that ambiguity.
`list<Error>` a `list<T>` is `array<int<0, max>, T>` (some differences in phpstan vs psalm vs mago)
> An official Discord community!
That's a regression.So as much as people on this forum love to shit on it, PHP is already very fast and memory efficient.
The gains from the ongoing rust rewrite are (in order):
1. The author's ego-stroking
2. Claude's agentic capabilities show-case
3. Memory safety (to be confirmed)
"Strange; seems like all I hear about these days are projects funded by the world's largest advertising corporation."
Come on PHP has so many shitty things, but even something basic like this is still not in the lanuage itself?
Because if yes, thats just ugly and a hack/workaround.
I was not precise enough about the exact version of it, fair, but my correction did not change my opinion on this topic.
And the PHPDoc stuff is old, it was normal to write them everywere 11 years ago and still today you have to write them for some cases.
Its still inconsistent and stupid.
Including generic type arrays which is not supported.
ooh wait, you couldn't be more wrong. Pikachuface.png
You remember when HHVM / hack came out from Facebook? Besides the fact that it was quite a dick move from Facebook to bring it out and then stop supporting it, it pushed PHP to take performance temporarily serious and there was so much air in it.
But the standard library is still inconsistent as hell, type system is still garbage especially mixxing strict and non strict.
The concurrency implementation? Has to be fixed again by frameworks.
And yeah i mean the critisism of my original comment stands as well and thats just an eye sore.