Skip to content

Commit eda6a3d

Browse files
committed
Added Psalm static analyzer
1 parent f6eefee commit eda6a3d

34 files changed

+207
-67
lines changed

.github/workflows/pipeline.yml

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -9,7 +9,7 @@ jobs:
99
fail-fast: false
1010
matrix:
1111
operating-system: [ubuntu-latest]
12-
php-versions: ['8.0', '8.1', '8.2']
12+
php-versions: ['8.0', '8.1', '8.2', '8.3', '8.4']
1313
steps:
1414
- name: Checkout
1515
uses: actions/checkout@v4
@@ -49,7 +49,7 @@ jobs:
4949
- name: Setup PHP
5050
uses: shivammathur/setup-php@v2
5151
with:
52-
php-version: 8.0
52+
php-version: 8.4
5353
tools: phpunit-bridge
5454
extensions: mbstring, json
5555
coverage: xdebug

README.md

Lines changed: 5 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -50,8 +50,11 @@ $result = a::marshalArray(
5050
a::toInt()
5151
)),
5252
a::toKey('fullname', a::pipe(
53-
a::get('[name]'),
54-
a::call(fn($v) => $v['firstname'] . ' ' . $v['lastname'])
53+
a::marshalNestedArray(
54+
a::toKey(0, a::get('[name][firstname]')),
55+
a::toKey(1, a::get('[name][lastname]')),
56+
),
57+
a::joinArray(' ')
5558
)),
5659
a::toKey('skills', a::pipe(
5760
a::get('[skills]'),

composer.json

Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -24,7 +24,8 @@
2424
"nikic/php-parser": "^4.15",
2525
"friendsofphp/php-cs-fixer": "^3.13",
2626
"respect/validation": "^2",
27-
"giggsey/libphonenumber-for-php": "*"
27+
"giggsey/libphonenumber-for-php": "*",
28+
"vimeo/psalm": "^5.26"
2829
},
2930
"suggest": {
3031
"respect/validation": "Enables respect-validation integration"
@@ -43,6 +44,7 @@
4344
},
4445
"bin": ["bin/generate-static-api"],
4546
"scripts": {
47+
"psalm": "psalm",
4648
"test": "phpunit",
4749
"generate-static-api": "generate-static-api",
4850
"php-cs-fixer": "php-cs-fixer"

psalm.xml

Lines changed: 18 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,18 @@
1+
<?xml version="1.0"?>
2+
<psalm
3+
errorLevel="1"
4+
resolveFromConfigFile="true"
5+
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
6+
xmlns="https://getpsalm.org/schema/config"
7+
xsi:schemaLocation="https://getpsalm.org/schema/config vendor/vimeo/psalm/config.xsd"
8+
findUnusedBaselineEntry="true"
9+
findUnusedCode="false"
10+
>
11+
<projectFiles>
12+
<directory name="src" />
13+
<ignoreFiles>
14+
<file name="src/Automapper.php" />
15+
<directory name="vendor" />
16+
</ignoreFiles>
17+
</projectFiles>
18+
</psalm>

src/Api/Helpers.php

Lines changed: 17 additions & 12 deletions
Original file line numberDiff line numberDiff line change
@@ -15,17 +15,18 @@ final class Helpers
1515
{
1616
public function joinArray(string $separator = ''): Pipeline
1717
{
18+
/** @psalm-suppress MixedArgumentTypeCoercion */
1819
return new Pipeline(
1920
new AssertType(AssertType::ARRAY),
20-
new Call(fn($value) => implode($separator, $value))
21+
new Call(fn(array $value) => implode($separator, $value))
2122
);
2223
}
2324

2425
public function sortArray(bool $descending = false, int $options = SORT_REGULAR): Pipeline
2526
{
2627
return new Pipeline(
2728
new AssertType(AssertType::ARRAY),
28-
new Call(function ($value) use ($descending, $options) {
29+
new Call(function (array $value) use ($descending, $options) {
2930
if ($descending) {
3031
rsort($value, $options);
3132
} else {
@@ -41,7 +42,7 @@ public function uniqueArray(bool $keepKeys = false, int $options = SORT_STRING):
4142
{
4243
return new Pipeline(
4344
new AssertType(AssertType::ARRAY),
44-
new Call(function ($value) use ($keepKeys, $options) {
45+
new Call(function (array $value) use ($keepKeys, $options) {
4546
$items = array_unique($value, $options);
4647

4748
return $keepKeys ? $items : array_values($items);
@@ -51,12 +52,12 @@ public function uniqueArray(bool $keepKeys = false, int $options = SORT_STRING):
5152

5253
public function ifNotFound(ProcessorInterface $true, ?ProcessorInterface $false = null): Condition
5354
{
54-
return new Condition(fn($value) => $value instanceof NotFoundValue, $true, $false ?? new Pass());
55+
return new Condition(fn(mixed $value) => $value instanceof NotFoundValue, $true, $false ?? new Pass());
5556
}
5657

5758
public function ifEmpty(ProcessorInterface $true, ?ProcessorInterface $false = null): Condition
5859
{
59-
return new Condition(fn($value) => empty($value), $true, $false ?? new Pass());
60+
return new Condition(fn(mixed $value) => empty($value), $true, $false ?? new Pass());
6061
}
6162

6263
public function ifNull(ProcessorInterface $true, ?ProcessorInterface $false = null): Condition
@@ -66,27 +67,31 @@ public function ifNull(ProcessorInterface $true, ?ProcessorInterface $false = nu
6667

6768
public function ifEqual(mixed $to, ProcessorInterface $true, ?ProcessorInterface $false = null, bool $strict = true): Condition
6869
{
69-
return new Condition(fn($value) => $strict ? $value === $to : $value == $to, $true, $false ?? new Pass());
70+
return new Condition(fn(mixed $value) => $strict ? $value === $to : $value == $to, $true, $false ?? new Pass());
7071
}
7172

7273
public function ifNotEqual(mixed $to, ProcessorInterface $true, ?ProcessorInterface $false = null, bool $strict = true): Condition
7374
{
74-
return $this->ifEqual($to, $false, $true, $strict);
75+
return new Condition(fn(mixed $value) => $strict ? $value !== $to : $value != $to, $true, $false ?? new Pass());
7576
}
7677

78+
/**
79+
* @param non-empty-string $separator
80+
* @return Pipeline
81+
*/
7782
public function explodeString(string $separator): Pipeline
7883
{
7984
return new Pipeline(
8085
new AssertType(AssertType::STRING),
81-
new Call(fn($value) => explode($separator, $value))
86+
new Call(fn(string $value) => explode($separator, $value))
8287
);
8388
}
8489

8590
public function trimString(string $characters = " \t\n\r\0\x0B"): Pipeline
8691
{
8792
return new Pipeline(
8893
new AssertType(AssertType::STRING),
89-
new Call(fn($value) => trim($value, $characters))
94+
new Call(fn(string $value) => trim($value, $characters))
9095
);
9196
}
9297

@@ -99,15 +104,15 @@ public function toFloat(): Pipeline
99104
{
100105
return new Pipeline(
101106
new AssertType(AssertType::NULL, AssertType::SCALAR),
102-
new Call(fn($value) => is_null($value) ? 0.0 : floatval($value))
107+
new Call(fn(mixed $value) => is_null($value) ? 0.0 : floatval($value))
103108
);
104109
}
105110

106111
public function toInt(): Pipeline
107112
{
108113
return new Pipeline(
109114
new AssertType(AssertType::NULL, AssertType::SCALAR),
110-
new Call(fn($value) => is_null($value) ? 0 : intval($value))
115+
new Call(fn(mixed $value) => is_null($value) ? 0 : intval($value))
111116
);
112117
}
113118

@@ -121,7 +126,7 @@ public function toString(): Pipeline
121126

122127
public function toArray(): Call
123128
{
124-
return new Call(function ($value) {
129+
return new Call(function (mixed $value) {
125130
if ($value instanceof Traversable) {
126131
return iterator_to_array($value);
127132
}

src/Api/Main.php

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -36,6 +36,7 @@ public function marshalArray(ContextInterface $context, mixed $source, ToArrayKe
3636
$mapper = $this->mapperFactory->create($context, ...$fields);
3737
$mapper->map($source, $target);
3838

39+
/** @var array<array-key, mixed> $target */
3940
return $target;
4041
}
4142

@@ -46,6 +47,7 @@ public function marshalObject(ContextInterface $context, mixed $source, ObjectFi
4647
$mapper = $this->mapperFactory->create($context, ...$fields);
4748
$mapper->map($source, $target);
4849

50+
/** @var \stdClass $target */
4951
return $target;
5052
}
5153
}

src/Api/Processors.php

Lines changed: 21 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -2,6 +2,7 @@
22

33
namespace Acelot\AutoMapper\Api;
44

5+
use Acelot\AutoMapper\ContextInterface;
56
use Acelot\AutoMapper\ExtractorResolver;
67
use Acelot\AutoMapper\Field\ToArrayKey;
78
use Acelot\AutoMapper\Field\ToObjectProp;
@@ -43,21 +44,41 @@ public function callCtx(callable $callable): CallWithContext
4344
return new CallWithContext($callable);
4445
}
4546

47+
/**
48+
* @param callable(mixed): bool $condition
49+
* @param ProcessorInterface $true
50+
* @param ProcessorInterface|null $false
51+
* @return Condition
52+
*/
4653
public function condition(callable $condition, ProcessorInterface $true, ?ProcessorInterface $false = null): Condition
4754
{
4855
return new Condition($condition, $true, $false ?? new Pass());
4956
}
5057

58+
/**
59+
* @param callable(ContextInterface, mixed): bool $condition
60+
* @param ProcessorInterface $true
61+
* @param ProcessorInterface|null $false
62+
* @return ConditionWithContext
63+
*/
5164
public function conditionCtx(callable $condition, ProcessorInterface $true, ?ProcessorInterface $false = null): ConditionWithContext
5265
{
5366
return new ConditionWithContext($condition, $true, $false ?? new Pass());
5467
}
5568

69+
/**
70+
* @param callable(mixed, int|string): bool $predicate
71+
* @return Find
72+
*/
5673
public function find(callable $predicate): Find
5774
{
5875
return new Find($predicate);
5976
}
6077

78+
/**
79+
* @param callable(ContextInterface, mixed, int|string): bool $predicate
80+
* @return FindWithContext
81+
*/
6182
public function findCtx(callable $predicate): FindWithContext
6283
{
6384
return new FindWithContext($predicate);

src/Context/Context.php

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -8,7 +8,7 @@
88
final class Context implements ContextInterface
99
{
1010
/**
11-
* @var array<int|string, mixed> $items
11+
* @param array<int|string, mixed> $items
1212
*/
1313
public function __construct(
1414
private array $items = []

src/Exception/UnknownPartException.php

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -9,13 +9,13 @@
99
final class UnknownPartException extends LogicException implements MapperExceptionInterface
1010
{
1111
public function __construct(
12-
private PartInterface $part
12+
private PartInterface|string $part
1313
)
1414
{
15-
parent::__construct(sprintf('Unknown part `%s`', $part::class));
15+
parent::__construct(sprintf('Unknown part `%s`', is_string($part) ? $part : $part::class));
1616
}
1717

18-
public function getPart(): PartInterface
18+
public function getPart(): PartInterface|string
1919
{
2020
return $this->part;
2121
}

src/Extractor/FromArrayKey.php

Lines changed: 12 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -4,13 +4,22 @@
44

55
use Acelot\AutoMapper\ExtractorInterface;
66

7+
/**
8+
* @implements ExtractorInterface<array|string>
9+
*/
710
final class FromArrayKey implements ExtractorInterface
811
{
12+
/**
13+
* @param array-key $key
14+
*/
915
public function __construct(
10-
private int|string $key
16+
private mixed $key
1117
) {}
1218

13-
public function getKey(): string
19+
/**
20+
* @return array-key
21+
*/
22+
public function getKey(): mixed
1423
{
1524
return $this->key;
1625
}
@@ -25,6 +34,6 @@ public function isExtractable(mixed $source): bool
2534

2635
public function extract(mixed $source): mixed
2736
{
28-
return $source[$this->key];
37+
return is_string($source) ? $source[(int) $this->key] : $source[$this->key];
2938
}
3039
}

0 commit comments

Comments
 (0)