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]]],