Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
17 changes: 15 additions & 2 deletions .github/workflows/ci.yml
Original file line number Diff line number Diff line change
Expand Up @@ -66,12 +66,25 @@ jobs:
mkdir -p build/logs
php vendor/bin/phpunit -c phpunit.xml.dist --coverage-clover=build/logs/clover.xml

# Infection 0.32.x requires PHP 8.3+, so keep the linked PHPStan/Infection checks on the basic 8.3 job.
- name: Run phpstan
continue-on-error: true
if: ${{ matrix.php == '8.0' }}
if: ${{ matrix.php == '8.3' && matrix.composer == 'basic' }}
run: |
php vendor/bin/phpstan analyse

- name: Install Infection
if: ${{ matrix.php == '8.3' && matrix.composer == 'basic' }}
run: |
curl --fail --silent --show-error --location \
--output infection.phar \
https://github.com/infection/infection/releases/download/0.32.7/infection.phar
chmod +x infection.phar

- name: Run Infection
if: ${{ matrix.php == '8.3' && matrix.composer == 'basic' }}
run: |
php infection.phar --threads=max --min-msi=0 --min-covered-msi=0 --no-progress

- name: Upload coverage results to Coveralls
env:
COVERALLS_REPO_TOKEN: ${{ secrets.GITHUB_TOKEN }}
Expand Down
3 changes: 2 additions & 1 deletion infection.json.dist
Original file line number Diff line number Diff line change
Expand Up @@ -8,8 +8,9 @@
"phpUnit": {
"customPath": "vendor\/bin\/phpunit"
},
"staticAnalysisTool": "phpstan",
"tmpDir": "build/infection/",
"logs": {
"text": "infection-log.txt"
}
}
}
12 changes: 12 additions & 0 deletions phpstan-fixtures.neon
Original file line number Diff line number Diff line change
@@ -0,0 +1,12 @@
parameters:
level: 8
reportUnmatchedIgnoredErrors: true
paths:
- %currentWorkingDirectory%/src/
- %currentWorkingDirectory%/tests/

services:
-
class: Arrayy\PHPStan\MetaDynamicStaticMethodReturnTypeExtension
tags:
- phpstan.broker.dynamicStaticMethodReturnTypeExtension
6 changes: 5 additions & 1 deletion phpstan.neon
Original file line number Diff line number Diff line change
Expand Up @@ -2,8 +2,12 @@ parameters:
level: 8
reportUnmatchedIgnoredErrors: true
paths:
# Keep the repo-wide CI pass focused on production code; fixture-style PHPStan tests run via phpstan-fixtures.neon.
- %currentWorkingDirectory%/src/
- %currentWorkingDirectory%/tests/
ignoreErrors:
-
message: '#^Parameter \#1 \$data of static method Arrayy\\Arrayy<.*>::create\(\) expects .*, .* given\.$#'
path: %currentWorkingDirectory%/src/Arrayy.php

services:
-
Expand Down
29 changes: 20 additions & 9 deletions src/Arrayy.php
Original file line number Diff line number Diff line change
Expand Up @@ -282,6 +282,7 @@
);
}

/* @phpstan-ignore argument.type */
$this->internalSet($key, $value);

return $this;
Expand Down Expand Up @@ -788,9 +789,11 @@
$value = null;

if ($this->offsetExists($offset)) {
/* @phpstan-ignore argument.type, argument.templateType */
$value = &$this->__get($offset);
}

/* @phpstan-ignore return.type */
return $value;
}

Expand Down Expand Up @@ -1052,7 +1055,7 @@
* @return $this
* <p>(Mutable) Return this Arrayy object, with the appended values.</p>
*
* @phpstan-param array<T> $values
* @phpstan-param array<T> $values
* @phpstan-param TKey|null $key
* @phpstan-return static
*/
Expand All @@ -1067,6 +1070,7 @@
\is_array($this->array[$key])
) {
foreach ($values as $value) {
/* @phpstan-ignore assign.propertyType */
$this->array[$key][] = $value;
}
} else {
Expand Down Expand Up @@ -7451,7 +7455,7 @@
*
* @return void
*
* @phpstan-param array<TKey,T>|null $currentOffset
* @phpstan-param array<array-key,mixed>|null $currentOffset
* @psalm-mutation-free
*/
protected function callAtPath($path, $callable, &$currentOffset = null)
Expand Down Expand Up @@ -7487,8 +7491,8 @@
/**
* Extracts the value of the given property or method from the object.
*
* @param static $object
* <p>The object to extract the value from.</p>
* @param mixed $object
* <p>The Arrayy instance, object, or other value from which to extract the property or method value.</p>
* @param string $keyOrPropertyOrMethod
* <p>The property or method for which the
* value should be extracted.</p>
Expand All @@ -7498,11 +7502,10 @@
* @return mixed
* <p>The value extracted from the specified property or method.</p>
*
* @phpstan-param self<TKey,T,TData> $object
*/
final protected function extractValue(self $object, string $keyOrPropertyOrMethod)
final protected function extractValue($object, string $keyOrPropertyOrMethod)

Check warning on line 7506 in src/Arrayy.php

View check run for this annotation

SonarQubeCloud / SonarCloud Code Analysis

This method has 4 returns, which is more than the 3 allowed.

See more on https://sonarcloud.io/project/issues?id=voku_Arrayy&issues=AZ4OhVTrHiNwq88hZnUE&open=AZ4OhVTrHiNwq88hZnUE&pullRequest=168
{
if (isset($object[$keyOrPropertyOrMethod])) {
if ($object instanceof self && isset($object[$keyOrPropertyOrMethod])) {
$return = $object->get($keyOrPropertyOrMethod);

if ($return instanceof self) {
Expand All @@ -7512,11 +7515,11 @@
return $return;
}

if (\property_exists($object, $keyOrPropertyOrMethod)) {
if (\is_object($object) && \property_exists($object, $keyOrPropertyOrMethod)) {
return $object->{$keyOrPropertyOrMethod};
}

if (\method_exists($object, $keyOrPropertyOrMethod)) {
if (\is_object($object) && \method_exists($object, $keyOrPropertyOrMethod)) {
return $object->{$keyOrPropertyOrMethod}();
}

Expand Down Expand Up @@ -8023,7 +8026,7 @@
*
* @return bool
*/
protected function internalRemove($key): bool

Check warning on line 8029 in src/Arrayy.php

View check run for this annotation

SonarQubeCloud / SonarCloud Code Analysis

This method has 4 returns, which is more than the 3 allowed.

See more on https://sonarcloud.io/project/issues?id=voku_Arrayy&issues=AZ4OkQ8EHiNwq88hZy79&open=AZ4OkQ8EHiNwq88hZy79&pullRequest=168
{
$this->generatorToArray();

Expand All @@ -8049,6 +8052,14 @@
$key = \array_shift($path);
}

if ($key === null) {
return false;
}

if (\is_float($key)) {
return false;
}

unset($this->array[$key]);

return true;
Expand Down
2 changes: 2 additions & 0 deletions src/Collection/AbstractCollection.php
Original file line number Diff line number Diff line change
Expand Up @@ -317,10 +317,12 @@ public static function createFromJsonMapper(string $json)
if (\is_array($jsonObject)) {
foreach ($jsonObject as $jsonObjectSingle) {
$collectionData = $mapper->map($jsonObjectSingle, $type);
/** @phpstan-var T $collectionData */
$return->add($collectionData);
}
} else {
$collectionData = $mapper->map($jsonObject, $type);
/** @phpstan-var T $collectionData */
$return->add($collectionData);
}
} else {
Expand Down
5 changes: 4 additions & 1 deletion src/Create.php
Original file line number Diff line number Diff line change
Expand Up @@ -17,7 +17,10 @@
*/
function create($data): Arrayy
{
return new Arrayy($data);
/** @var Arrayy<int|string,mixed,array<int|string,mixed>> $array */
$array = new Arrayy($data);

Check warning on line 21 in src/Create.php

View check run for this annotation

SonarQubeCloud / SonarCloud Code Analysis

Immediately return this expression instead of assigning it to the temporary variable "$array".

See more on https://sonarcloud.io/project/issues?id=voku_Arrayy&issues=AZ3ox5bsXiTxNghnB3W9&open=AZ3ox5bsXiTxNghnB3W9&pullRequest=168

return $array;
}
}

Expand Down
39 changes: 25 additions & 14 deletions src/Mapper/Json.php
Original file line number Diff line number Diff line change
Expand Up @@ -19,7 +19,7 @@ final class Json
* Override class names that JsonMapper uses to create objects.
* Useful when your setter methods accept abstract classes or interfaces.
*
* @var array
* @var array<string, class-string|callable(string|null, mixed): (class-string|string|null)>
*/
public $classMap = [];

Expand All @@ -33,22 +33,22 @@ final class Json
* 2. Name of the unknown JSON property
* 3. JSON value of the property
*
* @var callable
* @var null|callable(object, string, mixed): void
*/
public $undefinedPropertyHandler;

/**
* Runtime cache for inspected classes. This is particularly effective if
* mapArray() is called with a large number of objects
*
* @var array property inspection result cache
* @var array<string, array<string, array{0: bool, 1: \ReflectionMethod|\ReflectionProperty|string|null, 2: string|null}>> property inspection result cache
*/
private $arInspectedClasses = [];

/**
* Map data all data in $json into the given $object instance.
*
* @param object|iterable $json
* @param object|iterable<array-key,mixed> $json
* <p>JSON object structure from json_decode()</p>
* @param object|string $object
* <p>Object to map $json data into</p>
Expand All @@ -58,7 +58,8 @@ final class Json
*
* @see mapArray()
*
* @template TObject
* @template TObject of object
* @phpstan-param object|iterable<array-key,mixed> $json
* @phpstan-param TObject|class-string<TObject> $object
* <p>Object to map $json data into.</p>
* @phpstan-return TObject
Expand All @@ -79,6 +80,11 @@ public function map($json, $object)
$strClassName = \get_class($object);
$rc = new \ReflectionClass($object);
$strNs = $rc->getNamespaceName();

if (\is_object($json) && !($json instanceof \Traversable)) {
$json = \get_object_vars($json);
}

foreach ($json as $key => $jsonValue) {
$key = $this->getSafeName($key);

Expand All @@ -95,9 +101,8 @@ public function map($json, $object)
) = $this->arInspectedClasses[$strClassName][$key];

if (!$hasProperty) {
if (\is_callable($this->undefinedPropertyHandler)) {
\call_user_func(
$this->undefinedPropertyHandler,
if ($this->undefinedPropertyHandler !== null) {
($this->undefinedPropertyHandler)(
$object,
$key,
$jsonValue
Expand All @@ -111,7 +116,7 @@ public function map($json, $object)
continue;
}

if ($this->isNullable($type)) {
if ($type !== null && $this->isNullable($type)) {
if ($jsonValue === null) {
$this->setProperty($object, $accessor, null);

Expand Down Expand Up @@ -247,7 +252,7 @@ public function map($json, $object)
/**
* Map an array
*
* @param array $json JSON array structure from json_decode()
* @param array<array-key,mixed> $json JSON array structure from json_decode()
* @param mixed $array Array or ArrayObject that gets filled with
* data from $json
* @param string|null $class Class name for children objects.
Expand All @@ -261,6 +266,8 @@ public function map($json, $object)
* @pslam-param null|class-string $class
*
* @return mixed Mapped $array is returned
*
* @phpstan-param array<array-key,mixed> $json
*/
public function mapArray($json, $array, $class = null, $parent_key = '')
{
Expand Down Expand Up @@ -301,8 +308,12 @@ public function mapArray($json, $array, $class = null, $parent_key = '')
)
&&
\count($typesTmp->getTypes()) === 1
&&
\class_exists($typesTmp->getTypes()[0])
) {
$array[$key] = $this->map($jsonValue, $typesTmp->getTypes()[0]);
/** @var class-string<object> $mappedClass */
$mappedClass = $typesTmp->getTypes()[0];
$array[$key] = $this->map($jsonValue, $mappedClass);
$foundArrayy = true;

break;
Expand Down Expand Up @@ -404,7 +415,7 @@ private function getFullNamespace($type, $strNs)
* @param \ReflectionClass<object> $rc Reflection class to check
* @param string $name Property name
*
* @return array First value: if the property exists
* @return array{0: bool, 1: \ReflectionMethod|\ReflectionProperty|string|null, 2: string|null} First value: if the property exists
* Second value: the accessor to use (
* Array-Key-String or ReflectionMethod or ReflectionProperty, or null)
* Third value: type of the property
Expand Down Expand Up @@ -485,7 +496,7 @@ private function inspectProperty(\ReflectionClass $rc, $name): array
*
* @param string $docblock Full method docblock
*
* @return array
* @return array<string, list<string>>
*/
private static function parseAnnotations($docblock): array
{
Expand Down Expand Up @@ -739,7 +750,7 @@ private function removeNullable($type)
*
* @internal
*
* @template TClass
* @template TClass of object
* @phpstan-param TClass|class-string<TClass> $class
* @phpstan-return TClass
*/
Expand Down
1 change: 1 addition & 0 deletions src/PHPStan/MetaDynamicStaticMethodReturnTypeExtension.php
Original file line number Diff line number Diff line change
Expand Up @@ -38,6 +38,7 @@ public function getTypeFromStaticMethodCall(MethodReflection $methodReflection,
}

$className = $scope->resolveName($methodCall->class);
/* @phpstan-ignore phpstanApi.runtimeReflection */
if (!\is_a($className, Arrayy::class, true)) {
return null;
}
Expand Down
2 changes: 1 addition & 1 deletion src/Type/DetectFirstValueTypeCollection.php
Original file line number Diff line number Diff line change
Expand Up @@ -26,7 +26,7 @@ final class DetectFirstValueTypeCollection extends Collection implements TypeInt
* @param string $iteratorClass
* @param bool $checkPropertiesInConstructor
*
* @phpstan-param array<TKey,T>|Arrayy<TKey,T,array<TKey,T>> $data
* @phpstan-param array<TKey,T>|Arrayy<TKey,T,array<TKey,T>>|T $data
* @phpstan-param class-string<\Arrayy\ArrayyIterator<TKey,T>> $iteratorClass
*/
public function __construct(
Expand Down
2 changes: 2 additions & 0 deletions src/TypeCheck/TypeCheckCallback.php
Original file line number Diff line number Diff line change
Expand Up @@ -52,6 +52,8 @@ public function checkType(&$value): bool

/**
* @return array
*
* @phpstan-return list<never>
*/
public function getTypes(): array
{
Expand Down
Loading
Loading