Remix.run Logo
▲ bel8 6 hours ago

PHP type system is lacking but I wouldn't call it minimal.

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.

[1] https://php.watch/versions/8.0/union-types

▲idoubtit 2 hours ago | parent | next [-]

Maybe "minimal" was not the right word. I hope the context made clear that I meant the type system was lacking even for basic uses. Here are 3 common cases.

Some 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.

▲someothherguyy 2 hours ago | parent [-]

> Phpdoc has tuples and (associative) array shapes, e.g. `@return list{int, \Error}`.

`list<Error>` a `list<T>` is `array<int<0, max>, T>` (some differences in phpstan vs psalm vs mago)

▲Timon3 5 hours ago | parent | prev | next [-]

> PHP type system is lacking but I wouldn't call it minimal.

PHP still doesn't have generics, right? IMO any type system without higher-order types is definitely on the minimal side.

▲eithed 4 hours ago | parent [-]

Natively: no. Via tools like phpstan and psalm: yes, per OP. Link to relevant docs: https://psalm.dev/docs/annotating_code/templated_annotations... or https://phpstan.org/blog/generics-by-examples

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(); // Door
▲someothherguyy 2 hours ago | parent [-]

yeah, it is a real pain in the ass. imagine if php 8 accepted the RFC for generics

▲someothherguyy 2 hours ago | parent | prev | next [-]

it has union and intersection types, but it is nothing like typescript.

▲wesammikhail 6 hours ago | parent | prev [-]

That's exactly it but PHP Core refuses to address the typed array issue in wait for a consensus on Generics even though those two things serve different audiences.

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.

▲hparadiz 5 hours ago | parent [-]

With how good PHPDoc and Phan are these days I don't really think about it anymore. You can sort of have typed array by implementing Iterator, Countable, ArrayAccess, and optionally Indexing but I assume you mean defining types of array values.

▲Tadpole9181 an hour ago | parent | next [-]

If I could take a tangent and talk about the infuriating implicit-casting behavior of arrays in PHP?

  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.
▲wesammikhail 7 minutes ago | parent [-]

I had this happen to me yesterday in spite of knowing about it because I was rushing. I added phone numbers as array keys to deduplicate the whole set. Some ended up as ints and some as strings.

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.

▲someothherguyy 2 hours ago | parent | prev [-]

pretty naive take, no run time guarantees, basically swinging on hope to keep your shit together. phpstan drops types on static analysis for deeply nested arrays/objects for example. or even those with many members.