diff --git a/Dockerfile b/Dockerfile
index d876ab3..2e84790 100644
--- a/Dockerfile
+++ b/Dockerfile
@@ -1,4 +1,4 @@
-FROM php:7.4-cli
+FROM php:8.4-cli
# Install system dependencies
RUN apt-get update && apt-get install -y \
diff --git a/composer.json b/composer.json
index 8706b2f..223d7f6 100644
--- a/composer.json
+++ b/composer.json
@@ -10,20 +10,20 @@
}
],
"require": {
- "php": "^7.1"
+ "php": "^8.4"
},
"require-dev": {
"ergebnis/composer-normalize": "^2.47",
- "nikic/php-parser": "^4.1",
- "phpstan/phpstan": "^0.11",
- "phpunit/phpunit": "^8.0"
+ "nikic/php-parser": "^5.6",
+ "phpstan/phpstan": "^2.1",
+ "phpunit/phpunit": "^12.3"
},
"replace": {
"gabrielelana/precious": "self.version"
},
"suggest": {
- "nikic/php-parser": "^4.1",
- "phpstan/phpstan": "^0.11"
+ "nikic/php-parser": "^5.6",
+ "phpstan/phpstan": "^2.1"
},
"minimum-stability": "stable",
"autoload": {
diff --git a/phpstan.neon b/phpstan.neon
index 2cff866..c2f884a 100644
--- a/phpstan.neon
+++ b/phpstan.neon
@@ -3,7 +3,10 @@ includes:
parameters:
level: 7
- excludes_analyse:
+ paths:
+ - src
+ - tests
+ excludePaths:
- %currentWorkingDirectory%/vendor/
- %currentWorkingDirectory%/tests/*/data/*
- %currentWorkingDirectory%/tests/PreciousTest.php
diff --git a/phpunit.xml b/phpunit.xml
index fc2b8a9..0acb3d2 100644
--- a/phpunit.xml
+++ b/phpunit.xml
@@ -1,24 +1,22 @@
-
-
-
-
-
-
-
-
- tests
-
-
-
-
-
- src
-
-
+ displayDetailsOnTestsThatTriggerDeprecations="true"
+ displayDetailsOnPhpunitDeprecations="true"
+ bootstrap="vendor/autoload.php" cacheDirectory=".phpunit.cache">
+
+
+
+
+
+ tests
+
+
+
+
+ src
+
+
diff --git a/src/Field.php b/src/Field.php
index a54cd28..89ba2c5 100644
--- a/src/Field.php
+++ b/src/Field.php
@@ -6,17 +6,14 @@ interface Field
{
/**
* Returns the name of the field
- *
- * @returns string
*/
- public function name();
+ public function name(): string;
/**
* Returns the value of the field picked from an array of values
*
+ * @param array $parameters
* @throws MissingRequiredFieldException
- *
- * @returns mixed
*/
- public function pickIn(array $parameters);
+ public function pickIn(array $parameters): mixed;
}
diff --git a/src/Fields.php b/src/Fields.php
index 81a2bba..9411a6d 100644
--- a/src/Fields.php
+++ b/src/Fields.php
@@ -4,55 +4,50 @@
use Iterator;
+/**
+ * @implements Iterator
+ */
class Fields implements Iterator
{
- /**
- * @var int
- */
- private $position;
-
- /**
- * @var array
- */
- private $fields;
+ private int $position;
/**
- * @var array $fields
+ * @param array $fields
* @throws NameClashFieldException
* @returns self
*/
- public function __construct(array $fields) {
+ public function __construct(private readonly array $fields) {
$this->position = 0;
- $this->fields = $fields;
self::ensureUniqueNames(
array_map(function($field) { return $field->name(); }, $fields)
);
}
- public function rewind() {
+ public function rewind(): void {
$this->position = 0;
}
- public function current() {
+ public function current(): Field {
return $this->fields[$this->position];
}
- public function key() {
+ public function key(): int {
return $this->position;
}
- public function next() {
+ public function next(): void {
++$this->position;
}
- public function valid() {
+ public function valid(): bool {
return isset($this->fields[$this->position]);
}
/**
+ * @param array $declaredNames;
* @throws NameClashFieldException
*/
- private static function ensureUniqueNames(array $declaredNames) : void
+ private static function ensureUniqueNames(array $declaredNames): void
{
$uniqueNames = array_unique($declaredNames);
if (count($declaredNames) !== count($uniqueNames)) {
diff --git a/src/OptionalField.php b/src/OptionalField.php
index 0d44b48..9f689cd 100644
--- a/src/OptionalField.php
+++ b/src/OptionalField.php
@@ -6,19 +6,9 @@
class OptionalField extends RequiredField
{
- /**
- * @var mixed $defaultValue
- */
- private $defaultValue;
+ private mixed $defaultValue;
- /**
- * @var string $name
- * @var Type $type
- * @var mixed $defaultValue
- *
- * @returns self
- */
- public function __construct(string $name, Type $type, $defaultValue)
+ public function __construct(string $name, Type $type, mixed $defaultValue)
{
parent::__construct($name, $type);
$this->defaultValue = $defaultValue;
@@ -27,12 +17,12 @@ public function __construct(string $name, Type $type, $defaultValue)
/**
* Returns the value of the field picked from an array of values
*
+ * @param array $parameters
* @throws WrongTypeFieldException
- * @throws MissingRequiredFieldException
*
* @returns mixed
*/
- public function pickIn(array $parameters)
+ public function pickIn(array $parameters): mixed
{
try {
return parent::pickIn($parameters);
@@ -42,13 +32,11 @@ public function pickIn(array $parameters)
return null;
}
return $this->cast($this->defaultValue);
-
} catch (WrongTypeFieldException $e) {
if (null === $this->defaultValue) {
return null;
}
throw $e;
-
}
}
}
diff --git a/src/PHPStan/Reflection/PreciousPropertiesClassReflectionExtension.php b/src/PHPStan/Reflection/PreciousPropertiesClassReflectionExtension.php
index 86f02c1..7987a24 100644
--- a/src/PHPStan/Reflection/PreciousPropertiesClassReflectionExtension.php
+++ b/src/PHPStan/Reflection/PreciousPropertiesClassReflectionExtension.php
@@ -3,29 +3,17 @@
namespace Precious\PHPStan\Reflection;
use PHPStan\Broker\Broker;
-use PHPStan\Reflection\BrokerAwareExtension;
use PHPStan\Reflection\ClassReflection;
use PHPStan\Reflection\PropertiesClassReflectionExtension;
use PHPStan\Reflection\PropertyReflection;
+use PHPStan\Reflection\ReflectionProvider;
use Precious\Precious;
-class PreciousPropertiesClassReflectionExtension implements PropertiesClassReflectionExtension, BrokerAwareExtension
+class PreciousPropertiesClassReflectionExtension implements PropertiesClassReflectionExtension
{
- /** @var Broker */
- private $broker;
-
/** @var array> */
- private $properties;
+ private array $properties;
- /**
- * @param Broker $broker Class reflection broker
- * @return void
- */
- public function setBroker(Broker $broker) : void
- {
- $this->broker = $broker;
- $this->properties = [];
- }
/**
* @param ClassReflection $classReflection
diff --git a/src/PHPStan/Reflection/PropertiesDetector.php b/src/PHPStan/Reflection/PropertiesDetector.php
index 2cc1c84..943a847 100644
--- a/src/PHPStan/Reflection/PropertiesDetector.php
+++ b/src/PHPStan/Reflection/PropertiesDetector.php
@@ -3,6 +3,7 @@
namespace Precious\PHPStan\Reflection;
use Exception;
+use PHPStan\ShouldNotHappenException;
use PHPStan\Type\ArrayType;
use PHPStan\Type\BooleanType;
use PHPStan\Type\FloatType;
@@ -35,29 +36,26 @@
class PropertiesDetector extends NodeVisitorAbstract
{
/** @var array> */
- private $properties;
+ private array $properties;
- /** @var ?Node */
- private $inPreciousClass;
+ private ?Node $inPreciousClass;
/** @var ?Node */
- private $inPreciousClassInitMethod;
+ private ?Node $inPreciousClassInitMethod;
- /** @var array */
- private $names;
+ /** @var array */
+ private array $names;
- /** @var string */
- private $namespace;
+ private string $namespace;
/**
* Properties defined per classes in file
*
- * @var string $filePath
- * @returns array>
+ * @return array>
*/
- public static function inFile(string $filePath) : array
+ public static function inFile(string $filePath): array
{
- $parser = (new ParserFactory)->create(ParserFactory::PREFER_PHP7);
+ $parser = new ParserFactory()->createForHostVersion();
$fileContent = file_get_contents($filePath);
if (!$fileContent) {
return [];
@@ -72,10 +70,10 @@ public static function inFile(string $filePath) : array
/**
* Properties defined per classes in ast
*
- * @var array $ast
- * @returns array>
+ * @param array $ast
+ * @return array>
*/
- public static function inAst(array $ast) : array
+ public static function inAst(array $ast): array
{
$nameResolver = new NameResolver();
$propertiesDetector = new self();
@@ -94,7 +92,11 @@ public function __construct()
// echo PHP_EOL;
}
- public function enterNode(Node $node)
+ /**
+ * @throws ShouldNotHappenException
+ * @throws Exception
+ */
+ public function enterNode(Node $node): null
{
if ($node instanceof Namespace_) {
$this->namespace = (string) $node->name;
@@ -114,22 +116,26 @@ public function enterNode(Node $node)
if ($node instanceof Class_ && Precious::class === (string) $node->extends) {
$this->inPreciousClass = $node;
// echo '> Precious class ' . $node->namespacedName . PHP_EOL;
- return;
+ return null;
}
if ($node instanceof ClassMethod && $this->inPreciousClass && $node->isProtected() && 'init' === (string) $node->name) {
$this->inPreciousClassInitMethod = $node;
// echo '> Precious init method' . PHP_EOL;
- return;
+ return null;
}
if ($node instanceof StaticCall && $this->inPreciousClassInitMethod) {
if ($node->name instanceof Identifier && ('required' === (string) $node->name || 'optional' == (string) $node->name)) {
// TODO: throw exception if arguments are not what we expect
$isOptional = ((string) $node->name) === 'optional';
$hasDefault = count($node->args) === 3;
- assert($node->args[0]->value instanceof String_);
- $propertyName = $this->extractPropertyName($node->args[0]->value);
- assert($node->args[1]->value instanceof StaticCall);
- $propertyType = $this->extractPropertyType($node->args[1]->value);
+ assert($node->args[0] instanceof Arg);
+ $value = $node->args[0]->value;
+ assert($value instanceof String_);
+ $propertyName = $this->extractPropertyName($value);
+ assert($node->args[1] instanceof Arg);
+ $value = $node->args[1]->value;
+ assert($value instanceof StaticCall);
+ $propertyType = $this->extractPropertyType($value);
if ($isOptional && !$hasDefault) {
$propertyType = new UnionType([new NullType(), $propertyType]);
}
@@ -142,9 +148,11 @@ public function enterNode(Node $node)
// echo '= Precious property ' . $propertyName . ':' . get_class($propertyType) . PHP_EOL;
}
}
+
+ return null;
}
- public function leaveNode(Node $node)
+ public function leaveNode(Node $node): null
{
if ($node instanceof Namespace_) {
$this->namespace = '';
@@ -152,13 +160,13 @@ public function leaveNode(Node $node)
if ($node instanceof Class_ && Precious::class === (string) $node->extends) {
$this->inPreciousClass = null;
// echo '< Precious class ' . $node->namespacedName . PHP_EOL;
- return;
+ return null;
}
if ($node instanceof ClassMethod && $this->inPreciousClass && $node->isProtected() && 'init' === (string) $node->name) {
$this->inPreciousClassInitMethod = null;
// echo '< Precious init method' . PHP_EOL;
- return;
}
+ return null;
}
private function extractPropertyName(String_ $node) : string
@@ -166,6 +174,9 @@ private function extractPropertyName(String_ $node) : string
return $node->value;
}
+ /**
+ * @throws Exception
+ */
private function extractPropertyType(StaticCall $node) : Type
{
assert($node->name instanceof Identifier);
@@ -184,16 +195,12 @@ private function extractPropertyType(StaticCall $node) : Type
return new NullType();
case 'instanceOf':
assert($node->args[0] instanceof Arg);
- switch (get_class($node->args[0]->value)) {
- case String_::class:
- assert($node->args[0]->value instanceof String_);
- return new ObjectType($node->args[0]->value->value);
- case ClassConstFetch::class:
- assert($node->args[0]->value instanceof ClassConstFetch);
- return new ObjectType($this->fullyQualifiedNameOf($node->args[0]->value));
- default:
- return new MixedType();
- }
+ $value = $node->args[0]->value;
+ return match (get_class($value)) {
+ String_::class => new ObjectType($value->value),
+ ClassConstFetch::class => new ObjectType($this->fullyQualifiedNameOf($value)),
+ default => new MixedType(),
+ };
default:
return new MixedType();
}
diff --git a/src/PHPStan/Reflection/Property.php b/src/PHPStan/Reflection/Property.php
index b461e19..cef9a7a 100644
--- a/src/PHPStan/Reflection/Property.php
+++ b/src/PHPStan/Reflection/Property.php
@@ -4,22 +4,14 @@
use PHPStan\Reflection\ClassReflection;
use PHPStan\Reflection\PropertyReflection;
+use PHPStan\TrinaryLogic;
use PHPStan\Type\Type;
class Property implements PropertyReflection
{
- /** @var string */
- private $name;
+ private ClassReflection $class;
- /** @var Type */
- private $type;
-
- /** @var ClassReflection */
- private $class;
-
- public function __construct(string $name, Type $type) {
- $this->name = $name;
- $this->type = $type;
+ public function __construct(private readonly string $name, private readonly Type $type) {
}
public function inClass(ClassReflection $class) : void
@@ -27,7 +19,22 @@ public function inClass(ClassReflection $class) : void
$this->class = $class;
}
+ public function getName(): string
+ {
+ return $this->name;
+ }
+
public function getType() : Type
+ {
+ return $this->type;
+ }
+
+ public function getReadableType(): Type
+ {
+ return $this->type;
+ }
+
+ public function getWritableType(): Type
{
return $this->type;
}
@@ -61,4 +68,30 @@ public function isWritable() : bool
{
return false;
}
+
+ public function isInternal() : TrinaryLogic
+ {
+ return TrinaryLogic::createMaybe();
+ }
+
+ public function isDeprecated() : TrinaryLogic
+ {
+ return TrinaryLogic::createNo();
+ }
+
+ public function canChangeTypeAfterAssignment() : bool
+ {
+ return false;
+ }
+
+ public function getDeprecatedDescription() : ?string
+ {
+ return null;
+ }
+
+ public function getDocComment() : ?string
+ {
+ // TODO: implement
+ return null;
+ }
}
diff --git a/src/PHPStan/Rule/NullRule.php b/src/PHPStan/Rule/NullRule.php
index 41ad17c..c0a4f97 100644
--- a/src/PHPStan/Rule/NullRule.php
+++ b/src/PHPStan/Rule/NullRule.php
@@ -2,24 +2,18 @@
namespace Precious\PHPStan\Rule;
-use PHPStan\Analyser\Scope;
-use PHPStan\Broker\Broker;
-use PHPStan\Rules\Rule;
-use PHPStan\ShouldNotHappenException;
use PhpParser\Node;
use PhpParser\Node\Stmt\Class_;
-use Precious\Precious;
+use PHPStan\Analyser\Scope;
+use PHPStan\Reflection\ReflectionProvider;
+use PHPStan\Rules\IdentifierRuleError;
+use PHPStan\Rules\Rule;
+/**
+ * @implements Rule
+ */
class NullRule implements Rule
{
- /** @var Broker */
- private $broker;
-
- public function __construct(Broker $broker)
- {
- $this->broker = $broker;
- }
-
/**
* @return string Node we are interested in
*/
@@ -31,8 +25,8 @@ public function getNodeType(): string
/**
* @param Node $node
* @param Scope $scope
- * @return array errors
- */
+ * @return array errors
+ */
public function processNode(Node $node, Scope $scope): array
{
return [];
diff --git a/src/PHPStan/Rule/PreciousClassMustBeFinalRule.php b/src/PHPStan/Rule/PreciousClassMustBeFinalRule.php
index 744169d..9eb95e2 100644
--- a/src/PHPStan/Rule/PreciousClassMustBeFinalRule.php
+++ b/src/PHPStan/Rule/PreciousClassMustBeFinalRule.php
@@ -3,21 +3,22 @@
namespace Precious\PHPStan\Rule;
use PHPStan\Analyser\Scope;
-use PHPStan\Broker\Broker;
+use PHPStan\Reflection\ReflectionProvider;
use PHPStan\Rules\Rule;
+use PHPStan\Rules\RuleError;
+use PHPStan\Rules\RuleErrorBuilder;
use PHPStan\ShouldNotHappenException;
use PhpParser\Node;
use PhpParser\Node\Stmt\Class_;
use Precious\Precious;
-class PreciousClassMustBeFinalRule implements Rule
+/**
+ * @implements Rule
+ */
+readonly class PreciousClassMustBeFinalRule implements Rule
{
- /** @var Broker */
- private $broker;
-
- public function __construct(Broker $broker)
+ public function __construct(private ReflectionProvider $broker)
{
- $this->broker = $broker;
}
/**
@@ -32,7 +33,7 @@ public function getNodeType(): string
* @param Node $node
* @param Scope $scope
* @throws ShouldNotHappenException
- * @return array errors
+ * @return array errors
*/
public function processNode(Node $node, Scope $scope): array
{
@@ -49,6 +50,7 @@ public function processNode(Node $node, Scope $scope): array
if ($currentClassReflection->isFinal()) {
return [];
}
- return ['A subclass of Precious\Precious must be declared final'];
+
+ return [RuleErrorBuilder::message('A subclass of Precious\Precious must be declared final')->build()];
}
}
diff --git a/src/Precious.php b/src/Precious.php
index 690947d..8e4701e 100644
--- a/src/Precious.php
+++ b/src/Precious.php
@@ -10,52 +10,50 @@
abstract class Precious implements JsonSerializable
{
/**
- * @var array
+ * @var array
*/
- private $parameters = [];
+ private readonly array $parameters;
/**
* @var array
*/
- private static $fields = [];
+ private static array $fields = [];
/**
* Returns a new instance of a value object
*
+ * @param array $candidateParameters
* @throws NameClashFieldException
* @throws MissingRequiredFieldException
* @throws MissingRequiredFieldException
* @throws WrongTypeFieldException
* @returns self
*/
- public function __construct(array $parameters = [])
+ public final function __construct(array $candidateParameters = [])
{
if (!array_key_exists(static::class, self::$fields)) {
self::$fields[static::class] = new Fields($this->init());
}
+ $parameters = [];
/** @var Field $field */
foreach (self::$fields[static::class] as $field) {
- $this->parameters[$field->name()] = $field->pickIn($parameters);
+ $parameters[$field->name()] = $field->pickIn($candidateParameters);
}
+
+ $this->parameters = $parameters;
}
/**
- * @var string $name
- * @var mixed $value
* @throws MissingRequiredFieldException
* @throws WrongTypeFieldException
- * @returns static
+ * @throws NameClashFieldException
*/
- public function set(string $name, $value)
+ public function set(string $name, mixed $value): static
{
return new static(array_merge($this->parameters, [$name => $value]));
}
- /**
- * @var string $name
- * @returns bool
- */
- public function has(string $name) : bool
+ public function has(string $name): bool
{
return array_key_exists($name, $this->parameters);
}
@@ -63,10 +61,9 @@ public function has(string $name) : bool
/**
* Unsafe version, use only as last resource
*
- * @var string $name
* @returns mixed The field value or null if missing
*/
- public function get(string $name)
+ public function get(string $name): mixed
{
if (!array_key_exists($name, $this->parameters)) {
return null;
@@ -74,7 +71,7 @@ public function get(string $name)
return $this->parameters[$name];
}
- public function __get($name)
+ public function __get(string $name): mixed
{
if (!array_key_exists($name, $this->parameters)) {
throw new UnknownFieldException("Unknown field `$name`");
@@ -82,7 +79,7 @@ public function __get($name)
return $this->parameters[$name];
}
- public function __set($name, $value)
+ public function __set(string $name, mixed $value): void
{
if (!array_key_exists($name, $this->parameters)) {
throw new UnknownFieldException("Unknown field `$name`");
@@ -90,11 +87,14 @@ public function __set($name, $value)
throw new ReadOnlyFieldException("Cannot write field `$name`");
}
- public function __isset($name): bool
+ public function __isset(string $name): bool
{
return array_key_exists($name, $this->parameters);
}
+ /**
+ * @return array
+ */
public function jsonSerialize(): array
{
return $this->parameters;
@@ -102,12 +102,14 @@ public function jsonSerialize(): array
/**
* Returns a new instance where the given fields replace existing ones.
+ * @param array $parameters
+ * @throws MissingRequiredFieldException
+ * @throws WrongTypeFieldException
+ * @throws NameClashFieldException
*/
- public function with(array $parameters = [])
+ public function with(array $parameters = []): static
{
- $class = static::class;
-
- return new $class(array_merge(
+ return new static(array_merge(
$this->parameters,
$parameters
));
@@ -118,7 +120,7 @@ protected static function required(string $fieldName, Type $fieldType) : Field
return new RequiredField($fieldName, $fieldType);
}
- protected static function optional(string $fieldName, Type $fieldType, $defaultValue = null) : Field
+ protected static function optional(string $fieldName, Type $fieldType, mixed $defaultValue = null): Field
{
return new OptionalField($fieldName, $fieldType, $defaultValue);
}
diff --git a/src/RequiredField.php b/src/RequiredField.php
index fe5bb03..ebdb54c 100644
--- a/src/RequiredField.php
+++ b/src/RequiredField.php
@@ -7,20 +7,8 @@
class RequiredField implements Field
{
- /**
- * @var string $name
- */
- private $name;
-
- /**
- * @var Type $type
- */
- private $type;
-
- public function __construct(string $name, Type $type)
+ public function __construct(private readonly string $name, private readonly Type $type)
{
- $this->name = $name;
- $this->type = $type;
}
/**
@@ -28,7 +16,7 @@ public function __construct(string $name, Type $type)
*
* @returns string
*/
- public function name()
+ public function name(): string
{
return $this->name;
}
@@ -36,11 +24,11 @@ public function name()
/**
* Returns the value of the field picked from an array of values
*
+ * @param array $parameters
* @throws WrongTypeFieldException
* @throws MissingRequiredFieldException
- * @returns mixed
*/
- public function pickIn(array $parameters)
+ public function pickIn(array $parameters): mixed
{
if (!array_key_exists($this->name, $parameters)) {
throw new MissingRequiredFieldException(
@@ -51,11 +39,9 @@ public function pickIn(array $parameters)
}
/**
- * @var mixed $value
* @throws WrongTypeFieldException
- * @returns mixed
*/
- protected function cast($value)
+ protected function cast(mixed $value): mixed
{
try {
return $this->type->cast($value);
diff --git a/src/Singleton.php b/src/Singleton.php
index 13e0c2b..9365487 100644
--- a/src/Singleton.php
+++ b/src/Singleton.php
@@ -4,8 +4,5 @@
interface Singleton
{
- /**
- * @returns mixed
- */
- public static function instance();
+ public static function instance(): self;
}
diff --git a/src/SingletonScaffold.php b/src/SingletonScaffold.php
index 9b1b6c2..12c7050 100644
--- a/src/SingletonScaffold.php
+++ b/src/SingletonScaffold.php
@@ -6,32 +6,38 @@
trait SingletonScaffold
{
- protected static $instance;
+ protected static ?self $instance = null;
private function __construct()
{
// nothing is good
}
+ /**
+ * @throws Exception
+ */
final public function __clone()
{
throw new Exception('You can not clone a singleton');
}
+ /**
+ * @throws Exception
+ */
final public function __sleep()
{
throw new Exception('You can not serialize a singleton');
}
+ /**
+ * @throws Exception
+ */
final public function __wakeup()
{
throw new Exception('You can not deserialize a singleton');
}
- /**
- * @returns static
- */
- public static function instance()
+ public static function instance(): self
{
if (!self::$instance) {
self::$instance = new self();
diff --git a/src/Type/ArrayType.php b/src/Type/ArrayType.php
index c6fafdd..bcfbee4 100644
--- a/src/Type/ArrayType.php
+++ b/src/Type/ArrayType.php
@@ -9,13 +9,10 @@ class ArrayType extends PrimitiveType
use SingletonScaffold;
/**
- * @var mixed $value
- *
+ * @return array
* @throws WrongTypeException
- *
- * @returns array
*/
- public function cast($value)
+ public function cast(mixed $value): array
{
if (!is_array($value)) {
self::throwWrongTypeFor($value, 'array');
diff --git a/src/Type/BooleanType.php b/src/Type/BooleanType.php
index 3d0a156..d6c7b2f 100644
--- a/src/Type/BooleanType.php
+++ b/src/Type/BooleanType.php
@@ -8,14 +8,7 @@ class BooleanType extends PrimitiveType
{
use SingletonScaffold;
- /**
- * @var mixed $value
- *
- * @throws WrongTypeException
- *
- * @returns bool
- */
- public function cast($value)
+ public function cast(mixed $value): bool
{
return (bool) $value;
}
diff --git a/src/Type/ClassType.php b/src/Type/ClassType.php
index ae3e41e..8153e78 100644
--- a/src/Type/ClassType.php
+++ b/src/Type/ClassType.php
@@ -4,13 +4,11 @@
use Exception;
-class ClassType implements Type
+readonly class ClassType implements Type
{
/**
- * @var string
+ * @throws Exception
*/
- private $class;
-
public static function instanceOf(string $class) : self
{
if (!class_exists($class) && !interface_exists($class)) {
@@ -19,19 +17,14 @@ public static function instanceOf(string $class) : self
return new self($class);
}
- private function __construct(string $class)
+ private function __construct(private string $class)
{
- $this->class = $class;
}
/**
- * @var mixed $value
- *
* @throws WrongTypeException
- *
- * @return ?object
*/
- public function cast($value)
+ public function cast(mixed $value): ?object
{
if (is_null($value)) {
return null;
diff --git a/src/Type/FloatType.php b/src/Type/FloatType.php
index a96ee45..1dca6e7 100644
--- a/src/Type/FloatType.php
+++ b/src/Type/FloatType.php
@@ -9,13 +9,9 @@ class FloatType extends PrimitiveType
use SingletonScaffold;
/**
- * @var mixed $value
- *
* @throws WrongTypeException
- *
- * @returns float
*/
- public function cast($value)
+ public function cast(mixed $value): float
{
if (!is_numeric($value)) {
self::throwWrongTypeFor($value, 'float');
diff --git a/src/Type/IntegerType.php b/src/Type/IntegerType.php
index e56eacc..358ab2d 100644
--- a/src/Type/IntegerType.php
+++ b/src/Type/IntegerType.php
@@ -9,13 +9,9 @@ class IntegerType extends PrimitiveType
use SingletonScaffold;
/**
- * @var mixed $value
- *
* @throws WrongTypeException
- *
- * @returns int
*/
- public function cast($value)
+ public function cast(mixed $value): int
{
if (!is_numeric($value)) {
self::throwWrongTypeFor($value, 'integer');
diff --git a/src/Type/MixedType.php b/src/Type/MixedType.php
index 011a125..63d0cf8 100644
--- a/src/Type/MixedType.php
+++ b/src/Type/MixedType.php
@@ -8,14 +8,7 @@ class MixedType extends PrimitiveType
{
use SingletonScaffold;
- /**
- * @var mixed $value
- *
- * @throws WrongTypeException
- *
- * @returns mixed
- */
- public function cast($value)
+ public function cast(mixed $value): mixed
{
return $value;
}
diff --git a/src/Type/NullType.php b/src/Type/NullType.php
index 8707508..10e025f 100644
--- a/src/Type/NullType.php
+++ b/src/Type/NullType.php
@@ -9,13 +9,9 @@ class NullType extends PrimitiveType
use SingletonScaffold;
/**
- * @var mixed $value
- *
* @throws WrongTypeException
- *
- * @returns null
*/
- public function cast($value)
+ public function cast(mixed $value): null
{
if (!is_null($value)) {
self::throwWrongTypeFor($value, 'null');
diff --git a/src/Type/PrimitiveType.php b/src/Type/PrimitiveType.php
index a496041..f61c3b0 100644
--- a/src/Type/PrimitiveType.php
+++ b/src/Type/PrimitiveType.php
@@ -42,11 +42,9 @@ public static function mixedType() : PrimitiveType
}
/**
- * @var mixed $value
- *
* @throws WrongTypeException
*/
- protected static function throwWrongTypeFor($value, $expectedType) : void
+ protected static function throwWrongTypeFor(mixed $value, string $expectedType): void
{
$currentType = gettype($value);
throw new WrongTypeException(
diff --git a/src/Type/StringType.php b/src/Type/StringType.php
index 10bff00..e081a71 100644
--- a/src/Type/StringType.php
+++ b/src/Type/StringType.php
@@ -9,13 +9,9 @@ class StringType extends PrimitiveType
use SingletonScaffold;
/**
- * @var mixed $value
- *
* @throws WrongTypeException
- *
- * @returns string
*/
- public function cast($value)
+ public function cast(mixed $value): string
{
if (is_string($value)) {
return $value;
@@ -26,9 +22,11 @@ public function cast($value)
if (is_bool($value)) {
return $value ? 'true' : 'false';
}
- if (method_exists($value, '__toString')) {
+ if ($value instanceof \Stringable) {
return $value->__toString();
}
self::throwWrongTypeFor($value, 'string');
+
+ return ''; // Unreachable but PHPStan cannot detect this
}
}
diff --git a/src/Type/Type.php b/src/Type/Type.php
index 63e4093..97270db 100644
--- a/src/Type/Type.php
+++ b/src/Type/Type.php
@@ -7,12 +7,8 @@ interface Type
/**
* Cast a value in another value of a specific type
*
- * @var mixed $value
*
* @throws WrongTypeException
- *
- * @returns mixed
*/
- public function cast($value);
-
+ public function cast(mixed $value): mixed;
}
diff --git a/tests/PHPStan/Reflection/PropertiesDetectorTest.php b/tests/PHPStan/Reflection/PropertiesDetectorTest.php
index d884fe1..39c9bb2 100644
--- a/tests/PHPStan/Reflection/PropertiesDetectorTest.php
+++ b/tests/PHPStan/Reflection/PropertiesDetectorTest.php
@@ -20,7 +20,7 @@
class PropertiesDetectorTest extends TestCase
{
- public function testOneRequiredProperty()
+ public function testOneRequiredProperty(): void
{
$properties = PropertiesDetector::inFile(__DIR__ . '/data/OneRequiredProperty.php');
$this->assertCount(1, $properties);
@@ -31,7 +31,7 @@ public function testOneRequiredProperty()
$this->assertEquals($properties['a'], new Property('a', new IntegerType()));
}
- public function testOneOptionalProperty()
+ public function testOneOptionalProperty(): void
{
$properties = PropertiesDetector::inFile(__DIR__ . '/data/OneOptionalProperty.php');
$this->assertCount(1, $properties);
@@ -42,7 +42,7 @@ public function testOneOptionalProperty()
$this->assertEquals($properties['a'], new Property('a', new IntegerType()));
}
- public function testOneOptionalNullableProperty()
+ public function testOneOptionalNullableProperty(): void
{
$properties = PropertiesDetector::inFile(__DIR__ . '/data/OneOptionalNullableProperty.php');
$this->assertCount(1, $properties);
@@ -53,7 +53,7 @@ public function testOneOptionalNullableProperty()
$this->assertEquals($properties['a'], new Property('a', new UnionType([new NullType(), new IntegerType()])));
}
- public function testOneRequiredPropertyPerType()
+ public function testOneRequiredPropertyPerType(): void
{
$properties = PropertiesDetector::inFile(__DIR__ . '/data/OneRequiredPropertyPerType.php');
$this->assertCount(1, $properties);
@@ -71,7 +71,7 @@ public function testOneRequiredPropertyPerType()
$this->assertEquals($properties['h'], new Property('h', new ObjectType('Precious\Precious')));
}
- public function testFullyQualifiedClassNameBarrage()
+ public function testFullyQualifiedClassNameBarrage(): void
{
$properties = PropertiesDetector::inFile(__DIR__ . '/data/FullyQualifiedClassNameBarrage.php');
$this->assertCount(1, $properties);
diff --git a/tests/PHPStan/Rule/PreciousClassMustBeFinalRuleTest.php b/tests/PHPStan/Rule/PreciousClassMustBeFinalRuleTest.php
index 8bea1b8..ccafe3b 100644
--- a/tests/PHPStan/Rule/PreciousClassMustBeFinalRuleTest.php
+++ b/tests/PHPStan/Rule/PreciousClassMustBeFinalRuleTest.php
@@ -6,11 +6,14 @@
use PHPStan\Testing\RuleTestCase;
use PHPStan\Rules\Rule;
+/**
+ * @extends RuleTestCase
+ */
class PreciousClassMustBeFinalRuleTest extends RuleTestCase
{
protected function getRule(): Rule
{
- $broker = $this->createBroker();
+ $broker = $this->createReflectionProvider();
return new PreciousClassMustBeFinalRule($broker);
}
diff --git a/tests/PreciousTest.php b/tests/PreciousTest.php
index af46b52..a15a571 100644
--- a/tests/PreciousTest.php
+++ b/tests/PreciousTest.php
@@ -2,6 +2,7 @@
namespace Precious;
+use PHPUnit\Framework\Attributes\DataProvider;
use PHPUnit\Framework\TestCase;
use Precious\MissingRequiredFieldException;
use Precious\UnknownFieldException;
@@ -76,9 +77,7 @@ public function testWrongTypeMessage()
$a = new A(['a1' => null, 'a2' => 'aaa', 'a3' => 2]);
}
- /**
- * @dataProvider wrongTypes
- */
+ #[DataProvider('wrongTypes')]
public function testWrongType($parameters)
{
$this->expectException(WrongTypeFieldException::class);
@@ -86,7 +85,7 @@ public function testWrongType($parameters)
$b = new B($parameters);
}
- public function wrongTypes()
+ public static function wrongTypes(): array
{
return [
// [['integer' => 1, 'float' => 1.1, 'boolean' => true, 'string' => 'foo', 'null' => null, 'mixed' => 'whatever', 'array' => [1, 2, 3]]],