diff --git a/.github/workflows/continuous-integration.yaml b/.github/workflows/continuous-integration.yaml
index 08d55aa..faeca00 100644
--- a/.github/workflows/continuous-integration.yaml
+++ b/.github/workflows/continuous-integration.yaml
@@ -6,18 +6,20 @@ on:
jobs:
phpunit:
- runs-on: ubuntu-20.04
+ runs-on: ubuntu-24.04
strategy:
matrix:
include:
- - php: '7.4'
- phpunit: '8.5'
- - php: '8.0'
- phpunit: '8.5'
+ - php: '8.1'
+ psalm: true
- php: '8.1'
phpunit: '8.5'
+ - php: '8.5'
+ psalm: true
+ - php: '8.5'
+ phpunit: '8.5'
steps:
- - uses: actions/checkout@v2
+ - uses: actions/checkout@v6
- name: Install PHP
uses: shivammathur/setup-php@v2
@@ -26,7 +28,7 @@ jobs:
tools: 'composer:v2'
- name: Cache Composer dependencies
- uses: actions/cache@v2
+ uses: actions/cache@v5
with:
path: '~/.composer/cache'
key: ${{ runner.os }}-php${{ matrix.php }}-phpunit${{ matrix.phpunit }}-symfony${{ matrix.symfony }}-${{ hashFiles('**/composer.json') }}
@@ -37,7 +39,13 @@ jobs:
run: composer update --no-interaction --no-progress
- name: Run test suite
- uses: php-actions/phpunit@v3
+ uses: php-actions/phpunit@v4
+ if: ${{ matrix.phpunit != '' }}
with:
php_version: ${{ matrix.php }}
version: ${{ matrix.phpunit }}
+
+ - name: Run Psalm analysis
+ run: vendor/bin/psalm
+ if: ${{ matrix.psalm == true }}
+ continue-on-error: true
diff --git a/.gitignore b/.gitignore
index 58fb006..d338484 100644
--- a/.gitignore
+++ b/.gitignore
@@ -1,3 +1,4 @@
+*.cache
# Composer
vendor/
composer.lock
diff --git a/.travis.yml b/.travis.yml
deleted file mode 100644
index 6920f64..0000000
--- a/.travis.yml
+++ /dev/null
@@ -1,8 +0,0 @@
-language: php
-php:
- - '7.1'
- - '7.2'
- - '7.3'
- - '7.4'
-install:
- - composer install
diff --git a/composer.json b/composer.json
index a820f30..deb6741 100644
--- a/composer.json
+++ b/composer.json
@@ -3,7 +3,11 @@
"description": "An AutoMapper for PHP",
"license": "MIT",
"type": "library",
- "keywords": ["automapper", "converter", "object-to-object"],
+ "keywords": [
+ "automapper",
+ "converter",
+ "object-to-object"
+ ],
"authors": [
{
"name": "Mark Gerarts",
@@ -12,7 +16,7 @@
],
"autoload": {
"psr-4": {
- "AutoMapperPlus\\": "src/"
+ "AutoMapperPlus\\": "src/"
}
},
"autoload-dev": {
@@ -22,12 +26,13 @@
}
},
"require": {
- "php": ">=7.4.0"
+ "php": ">=8.1.0"
},
"require-dev": {
"symfony/var-dumper": "^3.3",
"symfony/debug": "^3.3",
"mark-gerarts/phpstan-automapper-plus": "^0.1.0",
- "phpunit/phpunit": "^7.0|^8.0"
+ "phpunit/phpunit": "^10.0|^11.0|12.0",
+ "vimeo/psalm": "^6.0"
}
}
diff --git a/psalm-baseline.xml b/psalm-baseline.xml
new file mode 100644
index 0000000..d2bdf8e
--- /dev/null
+++ b/psalm-baseline.xml
@@ -0,0 +1,26 @@
+
+
+
+
+ mapToObject
+
+
+
+
+ map
+ mapMultiple
+
+
+
+
+ array
+
+
+ $array
+
+
+ $array
+ $array
+
+
+
diff --git a/psalm.xml b/psalm.xml
new file mode 100644
index 0000000..72b4005
--- /dev/null
+++ b/psalm.xml
@@ -0,0 +1,16 @@
+
+
+
+
+
+
+
+
+
diff --git a/src/AutoMapper.php b/src/AutoMapper.php
index 09172cd..93a2d9c 100644
--- a/src/AutoMapper.php
+++ b/src/AutoMapper.php
@@ -51,7 +51,7 @@ public static function initialize(callable $configurator): AutoMapperInterface
return $mapper;
}
- private function push($key, $value, &$context)
+ private function push($key, $value, &$context): void
{
if (!array_key_exists($key, $context)) {
$stack = [];
@@ -62,15 +62,19 @@ private function push($key, $value, &$context)
$context[$key] = $stack;
}
- private function pop($key, &$context)
+ private function pop($key, &$context): void
{
array_pop($context[$key]);
}
/**
* @inheritdoc
+ *
+ * @psalm-suppress TooManyArguments
+ * Psalm borks on the missing $context on the interface, which is there
+ * because of backwards compatibility.
*/
- public function map($source, string $destinationClass, array $context = [])
+ public function map($source, string $targetClass, array $context = [])
{
if ($source === null) {
return null;
@@ -85,13 +89,13 @@ public function map($source, string $destinationClass, array $context = [])
}
}
- $context[self::DESTINATION_CLASS_CONTEXT] = $destinationClass;
+ $context[self::DESTINATION_CLASS_CONTEXT] = $targetClass;
- $mapping = $this->getMapping($sourceClass, $destinationClass);
+ $mapping = $this->getMapping($sourceClass, $targetClass);
if ($mapping->providesCustomMapper()) {
$customMapper = $this->getCustomMapper($mapping);
- return $customMapper->map($source, $destinationClass, $context);
+ return $customMapper->map($source, $targetClass, $context);
}
if ($mapping->hasCustomConstructor()) {
@@ -100,14 +104,14 @@ public function map($source, string $destinationClass, array $context = [])
$this,
$context
);
- } elseif (interface_exists($destinationClass)) {
+ } elseif (interface_exists($targetClass)) {
// If we're mapping to an interface a valid custom constructor has
// to be provided. Otherwise we can't know what to do.
$message = 'Mapping to an interface is not possible. Please '
. 'provide a concrete class or use mapToObject instead.';
throw new AutoMapperPlusException($message);
} else {
- $destinationObject = new $destinationClass;
+ $destinationObject = new $targetClass;
}
$context[self::DESTINATION_CONTEXT] = $destinationObject;
@@ -128,7 +132,7 @@ public function map($source, string $destinationClass, array $context = [])
*/
public function mapMultiple(
$sourceCollection,
- string $destinationClass,
+ string $targetClass,
array $context = []
): array
{
@@ -140,7 +144,7 @@ public function mapMultiple(
$mappedResults = [];
foreach ($sourceCollection as $source) {
- $mappedResults[] = $this->map($source, $destinationClass, $context);
+ $mappedResults[] = $this->map($source, $targetClass, $context);
}
return $mappedResults;
diff --git a/src/Configuration/Options.php b/src/Configuration/Options.php
index 5707201..ee40436 100644
--- a/src/Configuration/Options.php
+++ b/src/Configuration/Options.php
@@ -71,7 +71,8 @@ class Options
private $useSubstitution = true;
/**
- * @var string[]
+ * @var bool[]
+ * @psalm-var array
*/
private $objectCrates = [];
@@ -280,7 +281,7 @@ public function providesCustomMapper(): bool
}
/**
- * @param string $className
+ * @param class-string $className
*/
public function registerObjectCrate(string $className): void
{
diff --git a/src/CustomMapper/CustomMapper.php b/src/CustomMapper/CustomMapper.php
index e254b97..c8876d6 100644
--- a/src/CustomMapper/CustomMapper.php
+++ b/src/CustomMapper/CustomMapper.php
@@ -13,6 +13,10 @@ abstract class CustomMapper implements MapperInterface
{
/**
* @inheritdoc
+ *
+ * @psalm-suppress TooManyArguments
+ * Psalm borks on the missing $context on the interface, which is there
+ * because of backwards compatibility.
*/
public function map($source, string $targetClass)
{
diff --git a/src/MapperInterface.php b/src/MapperInterface.php
index 23a6df4..6c50ec8 100644
--- a/src/MapperInterface.php
+++ b/src/MapperInterface.php
@@ -15,10 +15,12 @@ interface MapperInterface
* Maps an object to an instance of class $to, provided a mapping is
* configured.
*
+ * @template T of object
* @param array|object $source
* The source object.
* @param string $targetClass
* The target classname.
+ * @psalm-param class-string $targetClass
* @param array $context
* An arbitrary array of values that will be passed to supporting
* mapping operations (e.g. MapFrom) to alter their behaviour based on
@@ -26,6 +28,7 @@ interface MapperInterface
* This is not explicitly required on the interface yet to preserve
* backwards compatibility, but will be added in version 2.0.
* @return mixed
+ * @psalm-return T
* An instance of class $to.
* @throws UnregisteredMappingException
*/
@@ -34,13 +37,16 @@ public function map($source, string $targetClass/**, array $context = [] */);
/**
* Maps properties of object $from to an existing object $to.
*
+ * @template T of object
* @param array|object $source
* The source object.
* @param object $destination
* The target object.
+ * @psalm-param T $destination
* @param array $context
* See MapperInterface::map()
* @return mixed
+ * @psalm-return T
* $to, with properties copied from $from.
* @throws UnregisteredMappingException
*/
diff --git a/src/MappingOperation/DefaultMappingOperation.php b/src/MappingOperation/DefaultMappingOperation.php
index 65eb568..eeed295 100644
--- a/src/MappingOperation/DefaultMappingOperation.php
+++ b/src/MappingOperation/DefaultMappingOperation.php
@@ -4,6 +4,7 @@
use AutoMapperPlus\Configuration\Options;
use AutoMapperPlus\NameResolver\NameResolverInterface;
+use AutoMapperPlus\PropertyAccessor\PropertyAccessorInterface;
use AutoMapperPlus\PropertyAccessor\PropertyReaderInterface;
use AutoMapperPlus\PropertyAccessor\PropertyWriterInterface;
diff --git a/src/MappingOperation/Implementations/MapToAnyOf.php b/src/MappingOperation/Implementations/MapToAnyOf.php
index 51ab1a7..b4c4b65 100644
--- a/src/MappingOperation/Implementations/MapToAnyOf.php
+++ b/src/MappingOperation/Implementations/MapToAnyOf.php
@@ -70,6 +70,11 @@ function ($value) use ($context) {
);
}
+ /**
+ * @psalm-suppress TooManyArguments
+ * Psalm borks on the missing $context on the interface, which is there
+ * because of backwards compatibility.
+ */
private function mapSingle($item, $context)
{
$destinationClass = $this->getDestinationClass($item);
diff --git a/src/PropertyAccessor/ArrayPropertyReader.php b/src/PropertyAccessor/ArrayPropertyReader.php
index 71bdfa9..6622564 100644
--- a/src/PropertyAccessor/ArrayPropertyReader.php
+++ b/src/PropertyAccessor/ArrayPropertyReader.php
@@ -29,6 +29,8 @@ public function getProperty($array, string $propertyName)
* @inheritdoc
*
* @param array $array
+ *
+ * @psalm-suppress ParamNameMismatch
*/
public function getPropertyNames($array): array
{
diff --git a/src/PropertyAccessor/PropertyReaderInterface.php b/src/PropertyAccessor/PropertyReaderInterface.php
index b938404..3ea494e 100644
--- a/src/PropertyAccessor/PropertyReaderInterface.php
+++ b/src/PropertyAccessor/PropertyReaderInterface.php
@@ -10,14 +10,14 @@
interface PropertyReaderInterface
{
/**
- * @param $object
+ * @param object|array $object
* @param string $propertyName
* @return bool
*/
public function hasProperty($object, string $propertyName): bool;
/**
- * @param $object
+ * @param object|array $object
* @param string $propertyName
* @return mixed
*/
@@ -26,7 +26,7 @@ public function getProperty($object, string $propertyName);
/**
* Returns a list of property names available on the object.
*
- * @param object $object
+ * @param object|array $object
* @return string[]
*/
public function getPropertyNames($object): array;