Skip to content

Commit

Permalink
Generics + better doc types PHPStan/psalm (#138)
Browse files Browse the repository at this point in the history
* introduce phpstan analizing and document generics

* phpstan 0.12.7

* rebase fixes + PHPStan update

* fixes
  • Loading branch information
marc-mabe authored Apr 26, 2020
1 parent 5c048f2 commit fba3870
Show file tree
Hide file tree
Showing 30 changed files with 609 additions and 332 deletions.
7 changes: 6 additions & 1 deletion .travis.yml
Original file line number Diff line number Diff line change
Expand Up @@ -53,9 +53,14 @@ script:
php -d 'zend.assertions=1' vendor/bin/phpunit --verbose;
fi

# run phpstan
- php vendor/bin/phpstan analyse --level max src/ tests/

# run psalm
- vendor/bin/psalm

# run benchmarks to make sure they are working fine
- php vendor/bin/phpbench run --no-interaction --revs=1 --retry-threshold=100
- vendor/bin/psalm

after_script:
- if [ "${CODE_COVERAGE}" == "1" ]; then
Expand Down
8 changes: 5 additions & 3 deletions composer.json
Original file line number Diff line number Diff line change
Expand Up @@ -21,9 +21,10 @@
"ext-reflection": "*"
},
"require-dev": {
"phpunit/phpunit": "^7.0",
"phpbench/phpbench": "^0.16.1",
"vimeo/psalm": "^3.10"
"phpbench/phpbench": "^0.16.10",
"phpstan/phpstan": "^0.12.19",
"phpunit/phpunit": "^7.5.20",
"vimeo/psalm": "^3.11.2"
},
"autoload": {
"psr-4": {
Expand All @@ -33,6 +34,7 @@
"autoload-dev": {
"psr-4": {
"MabeEnumTest\\": "tests/MabeEnumTest/",
"MabeEnumStaticAnalysis\\": "tests/MabeEnumStaticAnalysis/",
"MabeEnumBench\\": "bench/"
}
},
Expand Down
19 changes: 19 additions & 0 deletions phpstan.neon.dist
Original file line number Diff line number Diff line change
@@ -0,0 +1,19 @@
parameters:
ignoreErrors:
# EnumSet internally handles a bitset to be either an integer or a string
- message: '#Binary operation "[\|\&\^]" between int\|string and int\|string results in an error#'
path: %currentWorkingDirectory%/src/EnumSet.php

# EnumSerializableTrait
- message: '#Access to private property \$[a-z]+ of parent class MabeEnum\\Enum#'
path: %currentWorkingDirectory%/src/EnumSerializableTrait.php
- message: '#Access to an undefined static property MabeEnumTest\\TestAsset\\SerializableEnum::\$instances#'
path: %currentWorkingDirectory%/src/EnumSerializableTrait.php

# Tests
- message: '#Parameter \#\d \$[a-z]* of static method MabeEnum\\Enum::[^ ]* expects [^ ]*, .+ given#'
path: %currentWorkingDirectory%/tests/
- message: '#Parameter \#\d \$[a-z]* of method MabeEnum\\EnumSet<[^ ]*>::[^ ]* expects [^ ]*, .+ given#'
path: %currentWorkingDirectory%/tests/
- message: '#Parameter \#\d \$[a-z]* of class MabeEnum\\Enum(Set|Map) constructor expects class-string<T of MabeEnum\\Enum>, string given#'
path: %currentWorkingDirectory%/tests/
2 changes: 0 additions & 2 deletions phpunit.xml.dist
Original file line number Diff line number Diff line change
Expand Up @@ -7,10 +7,8 @@
stopOnFailure="false"
processIsolation="false"
backupGlobals="false"
syntaxCheck="true"
beStrictAboutTestsThatDoNotTestAnything="true"
beStrictAboutOutputDuringTests="true"
beStrictAboutTestSize="true"
beStrictAboutCoversAnnotation="true"
>
<testsuite name="php-enum Test-Suite">
Expand Down
81 changes: 56 additions & 25 deletions src/Enum.php
Original file line number Diff line number Diff line change
Expand Up @@ -22,7 +22,7 @@ abstract class Enum
/**
* The selected enumerator value
*
* @var null|bool|int|float|string|array
* @var null|bool|int|float|string|array<mixed>
*/
private $value;

Expand All @@ -36,29 +36,29 @@ abstract class Enum
/**
* A map of enumerator names and values by enumeration class
*
* @var array ["$class" => ["$name" => $value, ...], ...]
* @var array<class-string<Enum>, array<string, null|bool|int|float|string|array<mixed>>>
*/
private static $constants = [];

/**
* A List of available enumerator names by enumeration class
*
* @var array ["$class" => ["$name0", ...], ...]
* @var array<class-string<Enum>, string[]>
*/
private static $names = [];

/**
* Already instantiated enumerators
* A map of enumerator names and instances by enumeration class
*
* @var array ["$class" => ["$name" => $instance, ...], ...]
* @var array<class-string<Enum>, array<string, Enum>>
*/
private static $instances = [];

/**
* Constructor
*
* @param null|bool|int|float|string|array $value The value of the enumerator
* @param int|null $ordinal The ordinal number of the enumerator
* @param null|bool|int|float|string|array<mixed> $value The value of the enumerator
* @param int|null $ordinal The ordinal number of the enumerator
*/
final private function __construct($value, $ordinal = null)
{
Expand Down Expand Up @@ -111,7 +111,7 @@ final public function __wakeup()
/**
* Get the value of the enumerator
*
* @return null|bool|int|float|string|array
* @return null|bool|int|float|string|array<mixed>
*/
final public function getValue()
{
Expand All @@ -123,6 +123,7 @@ final public function getValue()
*
* @return string
*
* @phpstan-return string
* @psalm-return non-empty-string
*/
final public function getName()
Expand Down Expand Up @@ -157,7 +158,7 @@ final public function getOrdinal()
/**
* Compare this enumerator against another and check if it's the same.
*
* @param static|null|bool|int|float|string|array $enumerator An enumerator object or value
* @param static|null|bool|int|float|string|array<mixed> $enumerator An enumerator object or value
* @return bool
*/
final public function is($enumerator)
Expand All @@ -174,7 +175,7 @@ final public function is($enumerator)
/**
* Get an enumerator instance of the given enumerator value or instance
*
* @param static|null|bool|int|float|string|array $enumerator An enumerator object or value
* @param static|null|bool|int|float|string|array<mixed> $enumerator An enumerator object or value
* @return static
* @throws InvalidArgumentException On an unknown or invalid value
* @throws LogicException On ambiguous constant values
Expand All @@ -183,7 +184,15 @@ final public function is($enumerator)
*/
final public static function get($enumerator)
{
if ($enumerator instanceof static && \get_class($enumerator) === static::class) {
if ($enumerator instanceof static) {
if (\get_class($enumerator) !== static::class) {
throw new InvalidArgumentException(sprintf(
'Invalid value of type %s for enumeration %s',
\get_class($enumerator),
static::class
));
}

return $enumerator;
}

Expand All @@ -193,7 +202,7 @@ final public static function get($enumerator)
/**
* Get an enumerator instance by the given value
*
* @param null|bool|int|float|string|array $value Enumerator value
* @param null|bool|int|float|string|array<mixed> $value Enumerator value
* @return static
* @throws InvalidArgumentException On an unknown or invalid value
* @throws LogicException On ambiguous constant values
Expand All @@ -202,6 +211,8 @@ final public static function get($enumerator)
*/
final public static function byValue($value)
{
/** @var mixed $value */

$constants = self::$constants[static::class] ?? static::getConstants();

$name = \array_search($value, $constants, true);
Expand All @@ -215,8 +226,11 @@ final public static function byValue($value)
));
}

return self::$instances[static::class][$name]
/** @var static $instance */
$instance = self::$instances[static::class][$name]
?? self::$instances[static::class][$name] = new static($constants[$name]);

return $instance;
}

/**
Expand All @@ -232,7 +246,9 @@ final public static function byValue($value)
final public static function byName(string $name)
{
if (isset(self::$instances[static::class][$name])) {
return self::$instances[static::class][$name];
/** @var static $instance */
$instance = self::$instances[static::class][$name];
return $instance;
}

$const = static::class . "::{$name}";
Expand Down Expand Up @@ -271,15 +287,20 @@ final public static function byOrdinal(int $ordinal)
}

$name = self::$names[static::class][$ordinal];
return self::$instances[static::class][$name]

/** @var static $instance */
$instance = self::$instances[static::class][$name]
?? self::$instances[static::class][$name] = new static($constants[$name], $ordinal);

return $instance;
}

/**
* Get a list of enumerator instances ordered by ordinal number
*
* @return static[]
*
* @phpstan-return array<int, static>
* @psalm-return list<static>
* @psalm-pure
*/
Expand All @@ -288,14 +309,18 @@ final public static function getEnumerators()
if (!isset(self::$names[static::class])) {
static::getConstants();
}
return \array_map([static::class, 'byName'], self::$names[static::class]);

/** @var callable $byNameFn */
$byNameFn = [static::class, 'byName'];
return \array_map($byNameFn, self::$names[static::class]);
}

/**
* Get a list of enumerator values ordered by ordinal number
*
* @return mixed[]
* @return (null|bool|int|float|string|array)[]
*
* @phpstan-return array<int, null|bool|int|float|string|array>
* @psalm-return list<null|bool|int|float|string|array>
* @psalm-pure
*/
Expand All @@ -309,6 +334,7 @@ final public static function getValues()
*
* @return string[]
*
* @phpstan-return array<int, string>
* @psalm-return list<non-empty-string>
* @psalm-pure
*/
Expand All @@ -325,6 +351,7 @@ final public static function getNames()
*
* @return int[]
*
* @phpstan-return array<int, int>
* @psalm-return list<int>
* @psalm-pure
*/
Expand All @@ -337,9 +364,10 @@ final public static function getOrdinals()
/**
* Get all available constants of the called class
*
* @return mixed[]
* @return (null|bool|int|float|string|array)[]
* @throws LogicException On ambiguous constant values
*
* @phpstan-return array<string, null|bool|int|float|string|array>
* @psalm-return array<non-empty-string, null|bool|int|float|string|array>
* @psalm-pure
*/
Expand Down Expand Up @@ -375,7 +403,7 @@ final public static function getConstants()

/**
* Test that the given constants does not contain ambiguous values
* @param array $constants
* @param array<string, null|bool|int|float|string|array<mixed>> $constants
* @return bool
*/
private static function noAmbiguousValues($constants)
Expand All @@ -393,21 +421,24 @@ private static function noAmbiguousValues($constants)
/**
* Test if the given enumerator is part of this enumeration
*
* @param static|null|bool|int|float|string|array $enumerator
* @param static|null|bool|int|float|string|array<mixed> $enumerator
* @return bool
*
* @psalm-pure
*/
final public static function has($enumerator)
{
return ($enumerator instanceof static && \get_class($enumerator) === static::class)
|| static::hasValue($enumerator);
if ($enumerator instanceof static) {
return \get_class($enumerator) === static::class;
}

return static::hasValue($enumerator);
}

/**
* Test if the given enumerator value is part of this enumeration
*
* @param null|bool|int|float|string|array $value
* @param null|bool|int|float|string|array<mixed> $value
* @return bool
*
* @psalm-pure
Expand Down Expand Up @@ -436,8 +467,8 @@ final public static function hasName(string $name)
* This will be called automatically on calling a method
* with the same name of a defined enumerator.
*
* @param string $method The name of the enumerator (called as method)
* @param array $args There should be no arguments
* @param string $method The name of the enumerator (called as method)
* @param array<mixed> $args There should be no arguments
* @return static
* @throws InvalidArgumentException On an invalid or unknown name
* @throws LogicException On ambiguous constant values
Expand Down
Loading

0 comments on commit fba3870

Please sign in to comment.