typhoon/type

Typhoon Type System

0.4.4 2024-08-18 19:46 UTC

This package is auto-updated.

Last update: 2024-12-02 01:31:53 UTC


README

PHP Version Requirement GitHub Release Psalm Level Psalm Type Coverage Code Coverage Mutation testing badge

Typhoon Type is an object abstraction over the modern PHP type system. Use this library to build tools that work with sophisticated types.

Here are some examples of potential use-cases:

use Typhoon\Type\types;

$data = (new MyAwesomeJsonDecoder())->decode(
    json: '[1, 0.5, "213"]',
    type: types::list(types::numeric),
);

var_dump($data);
array(3) {
  [0] => int(1)
  [1] => float(0.5)
  [2] => string(3) "213"
}

Or:

use Typhoon\Type\types;

final readonly class GetUserResponse
{
    /**
     * @param non-empty-string $name
     * @param 'user'|'admin' $group
     */
    public function __construct(
        public Uuid $id,
        public string $name,
        public string $group,
    ) {}
}

echo (new MyAwesomeOpenApiGenerator())->generateSchema(types::object(GetUserResponse::class));
GetUserResponse:
  type: object
  properties:
    id:
      type: string
      format: uuid
      example: b609e6a9-bba6-4599-9faa-cc9977353bb4
    name:
      type: string
      example: Hello world!
    group:
      type: string
      enum: [ user, admin ]

Installation

composer require typhoon/type

Constructing types

Typhoon types can be constructed via the Typhoon\Type\types static factory. Let's express this monstrous type via the Typhoon DSL:

array{
    a: non-empty-string,
    b?: int|float,
    c: Traversable<numeric-string, false>,
    d: callable(PDO::*, TSend#Generator=, scalar...): void,
    ...
}
use Typhoon\Type\types;

$type = types::unsealedArrayShape([
    'a' => types::nonEmptyString,
    'b' => types::optional(types::union(types::int, types::float)),
    'c' => types::object(Traversable::class, [types::numericString, types::false]),
    'd' => types::callable(
        parameters: [
            types::classConstantMask(PDO::class),
            types::param(types::classTemplate(Generator::class, 'TSend'), hasDefault: true),
            types::param(types::scalar, variadic: true),
        ],
        return: types::void,
    ),
]);

As you can see, creating types in Typhoon is a lot of fun, especially if you work in IDE with autocompletion 😉

Design

Unlike other solutions, Typhoon Type does not expose concrete type classes in its API. Instead, it provides only a common Type interface, a type factory types, and a TypeVisitor with destructurization. This approach gives several advantages:

  1. The visitor has only a minimal subset of type methods that must be implemented when describing a type algebra. Complexity of the other types is hidden and can be completely ignored.
  2. Memory efficient enums can be used for all atomic types and for aliases of commonly used compound types.
  3. Using of downcasting via the instanceof operator is automatically discouraged, since all Type implementations are @internal ( see PHPStan: Why Is instanceof *Type Wrong and Getting Deprecated?).

Printing types

To cast any type to string, use the Typhoon\Type\stringify() function:

use Typhoon\Type\types;
use function Typhoon\Type\stringify;

var_dump(
   stringify(
       types::Generator(
           key: types::nonNegativeInt,
           value: types::classTemplate(Foo::class, 'T'),
           send: types::scalar,
       ),
   ),
); // Generator<int<0, max>, T#Foo, scalar, mixed>

Comparing types

Typhoon team is currently working on a type comparator. Until it is released, you can use DefaultTypeVisitor for simple checks:

use Typhoon\Type\Type;
use Typhoon\Type\types;
use Typhoon\Type\Visitor\DefaultTypeVisitor;

/**
 * @extends DefaultTypeVisitor<bool>
 */
final class BasicIntChecker extends DefaultTypeVisitor
{
    public function int(Type $type, Type $minType, Type $maxType): mixed
    {
        return true;
    }

    public function intValue(Type $type, int $value): mixed
    {
        return true;
    }

    public function intMask(Type $type, Type $ofType): bool
    {
        return true;
    }

    protected function default(Type $type): bool
    {
        return false;
    }
}

var_dump(types::positiveInt->accept(new BasicIntChecker())); // true
var_dump(types::callableString()->accept(new BasicIntChecker())); // false

Compatibility with Psalm and PHPStan

Native PHP types

Numbers

Strings

Constants

Arrays and iterables

Objects

Callables

Other