forrest79 / type-validator
TypeValidator provides functionality to check types with PHP Doc syntax in runtime and narrow types for PHPStan.
Installs: 1
Dependents: 0
Suggesters: 0
Security: 0
Stars: 6
Watchers: 1
Forks: 1
Open Issues: 0
Type:phpstan-extension
pkg:composer/forrest79/type-validator
Requires
- php: ^8.3
- nikic/php-parser: ^5.6
- phpstan/phpdoc-parser: ^2.3
Requires (Dev)
- forrest79/phpcs: ^2.3
- forrest79/phpcs-ignores: ^0.6
- nette/tester: ^2.5
- phpstan/phpstan: ^2.1
- phpstan/phpstan-strict-rules: ^2.0
- shipmonk/phpstan-rules: ^4.2
README
Introduction
Validates types using PHP Doc descriptions and narrows types for PHPStan.
Imagine you're loading data from some external source. For PHP, this is mostly mixed
(or some other common type like array
/object
), and PHPStan is unhappy with this. If data is some simple type, most of us will add something like:
assert(is_int($data)); // if we know, there will be always an int if (!is_int($data)) throw new InvalidDataException(); // if we want to check this also in runtime
Both make PHPStan happy, and you code is also tested (the first example mostly in dev environment, where the assertion is on).
But when the loaded data is a complex type like list<array{type: int, dates?: array<string, \DateTime>, validator: class-string<IValidator>}>
.
Checking this at runtime and making PHPStan happy is now harder. The goal of this library is to make this as simple as assert(is_int($data))
.
Use assert(is_type($data, 'list<array{type: int, dates?: array<string, \DateTime>, validator: class-string<IValidator>}>'))
and the variable is really checked for the correct type at runtime, and the type is also narrowed for PHPStan.
Code coverage is computed without PHPStan extension - only the PHP runtime part.
Installation
To use this extension, require it in Composer:
composer require --dev forrest79/type-validator
You probably only want this extension for development, but it can also be used in production (omit
--dev
).
Using
There is one global function is_type(mixed $var, string $type)
and static methods Forrest79\TypeValidator::isType(mixed $var, string $type): bool
or Forrest79\TypeValidator::checkType(mixed $var, string $type): void
.
All of them really check the data in $var
against the type description and there is corresponding PHPStan extension so PHPStan will understand, that $var
is in described type.
The function is_type(mixed $var, string $type)
and method Forrest79\TypeValidator::isType(mixed $var, string $type)
return a bool
- true if $var
matches the $type
, and false
otherwise.
The Method Forrest79\TypeValidator::checkType(mixed $var, string $type)
has no return, but it throws a CheckException
, if $var
does not match the $type
.
Example:
$arr = [3.14, 5, 10]; assert(is_type($arr, 'list<float|int>')); assert(Forrest79\TypeValidator::isType($arr, 'list<float|int>')); Forrest79\TypeValidator::checkType($arr, 'list<float|int>'));
With this you can replace your @var
annotations:
/** @var array<string|int, list<Db\Row>> $arr $arr = json_decode($data);
With:
$arr = json_decode($data); assert(is_type($arr, 'array<string|int, list<Db\Row>>'));
The benefit is that variable $arr
is checked for defined type.
Almost all PHPDoc types from PHPStan are supported (more information about supported types is provided later in the docs).
To use this library as PHPStan extension include extension.neon
in your project's PHPStan config:
includes: - vendor/forrest79/type-validator/extension.neon
Because of PHPStan, the type description must be a static stringβnothing can be generated dynamically.
Use in production
Typically, the assert
function is disabled in production, so checks are only performed in development/test environments, and there is no need to distribute this library in a production environment.
But you can use this for validation also in your production code. Parsing PHPDoc types is not too performance-intensive. This library depends on phpstan/phpdoc-parser
for parsing types and nikic/php-parser
for detection fully qualified class names.
FQN (Fully qualified names)
Correct fully qualified names are computed from the current namespace and use
statements, just like every other item in your PHP source files. However, if you use a use
statement only for this library, your IDE and PHPCS may mark it as unused because they don't know about this library:
Example:
namespace App; use App\Presenter; // this use is marked as unused assert($presenter, 'class-string<Presenter>'); // even though it is correctly used here
One solution is to concatenate the type string with ::class
such as assert($presenter, 'class-string<\\' . Presenter::class . '>')
. However, this looks very ugly. I prefer to use an FQN in the type description and omit the use
statement:
namespace App; assert($presenter, 'class-string<\App\Presenter>');
Supported PHPStan - PHPDoc Types
According to https://github.com/phpstan/phpstan/blob/2.1.x/website/src/writing-php-code/phpdoc-types.md
β supported π« not supported - doesn't make sense for variables β not supported
Basic types β /π«/β
int
,integer
βstring
,non-empty-string
,non-empty-lowercase-string
,non-empty-uppercase-string
,truthy-string
,non-falsy-string
,lowercase-string
,uppercase-string
βliteral-string
,non-empty-literal-string
βnumeric-string
β__stringandstringable
(string
or object implementingStringable
interface or object with__toString()
method) βarray-key
βbool
,boolean
,true
,false
βnull
βfloat
,double
βnumber
,numeric
βscalar
,empty-scalar
,non-empty-scalar
βarray
,associative-array
,non-empty-array
βlist
,non-empty-list
βiterable
βcallable
,callable-string
,callable-array
,callable-object
β ,pure-callable
βresource
,open-resource
,closed-resource
βobject
βempty
βmixed
,non-empty-mixed
βclass-string
,interface-string
,trait-string
,enum-string
βvoid
π«
Classes and interfaces β
Integer ranges β
positive-int
βnegative-int
βnon-positive-int
βnon-negative-int
βnon-zero-int
βint<0, 100>
βint<min, 100>
βint<50, max>
β
General arrays β
Type[]
βarray<Type>
βarray<int, Type>
βnon-empty-array<Type>
βnon-empty-array<int, Type>
β
Lists β
list<Type>
βnon-empty-list<Type>
β
Key and value types of arrays and iterables β
key-of<Type::ARRAY_CONST>
βvalue-of<Type::ARRAY_CONST>
βvalue-of<BackedEnum>
β
Iterables β (there can be some side effect while iterate in runtime to check correct type)
iterable<Type>
βCollection<Type>
βCollection<int, Type>
βCollection|Type[]
β
Union types β
Type1|Type2
β
Intersection types β
Type1&Type2
β
Parentheses β
(Type1&Type2)|Type3
β
self, static, parent and $this π«
self
,static
,parent
or$this
π«
Generics β /π«/β (some yes, some no, some doesn't make sense - concrete info can be found in the other types description)
Conditional return types π«
Utility types for generics β
template-type
βnew
β
class-string, interface-string β
class-string<Foo>
βinterface-string<Interface>
β
Global type aliases β
Local type aliases β
Array shapes β
array{'foo': int, "bar": string}
βarray{'foo': int, "bar"?: string}
βarray{int, int}
βarray{0: int, 1?: int}
βarray{foo: int, bar: string}
β
Object shapes β
object{'foo': int, "bar": string}
βobject{'foo': int, "bar"?: string}
βobject{foo: int, bar?: string}
βobject{foo: int, bar?: string}&\stdClass
β
Literals and constants β /β
234
β1.0
β'foo'|'bar'
βFoo::SOME_CONSTANT
βFoo::SOME_CONSTANT|Bar::OTHER_CONSTANT
βself::SOME_*
βFoo::*
β
Global constants β
SOME_CONSTANT
βSOME_CONSTANT|OTHER_CONSTANT
β
Callables β (only simple callable is supported)
callable(int, int): string
βcallable(int, int=): string
βcallable(int $foo, string $bar): void
βcallable(string &$bar): mixed
βcallable(float ...$floats): (int|null)
βcallable(float...): (int|null)
β\Closure(int, int): string
βpure-callable(int, int): string
βpure-Closure(int, int): string
β
Bottom type π«
never
π«never-return
π«never-returns
π«no-return
π«
Integer masks β /β
int-mask<1, 2, 4>
βint-mask-of<1|2|4>
βint-mask-of<Foo::INT_*>
β