From 2f5fc4468e72331ee9070d031bd240e0cea3ed71 Mon Sep 17 00:00:00 2001
From: Tom H Anderson <tom.h.anderson@gmail.com>
Date: Mon, 17 Mar 2025 18:43:57 -0600
Subject: [PATCH 1/2] Laravel 12; remove psalm

---
 .github/workflows/static-analysis.yml | 26 -----------
 composer.json                         | 24 +++++-----
 psalm.xml                             | 15 ------
 test/ApiProblemTest.php               | 67 ++++++++++++---------------
 4 files changed, 41 insertions(+), 91 deletions(-)
 delete mode 100644 .github/workflows/static-analysis.yml
 delete mode 100644 psalm.xml

diff --git a/.github/workflows/static-analysis.yml b/.github/workflows/static-analysis.yml
deleted file mode 100644
index 4a3f43c..0000000
--- a/.github/workflows/static-analysis.yml
+++ /dev/null
@@ -1,26 +0,0 @@
-name: "Static analysis"
-
-on:
-  pull_request:
-    branches:
-      - "*.x"
-      - "main"
-  push:
-    branches:
-      - "*.x"
-      - "main"
-
-jobs:
-  psalm:
-    name: "Static Analysis"
-    runs-on: ubuntu-latest
-
-    steps:
-      - uses: shivammathur/setup-php@15c43e89cdef867065b0213be354c2841860869e
-        with:
-          php-version: '8.1'
-      - uses: actions/checkout@v2
-      - name: Install Dependencies
-        run: composer install -q --no-ansi --no-interaction --no-scripts --no-progress --prefer-dist
-      - name: Execute tests (Unit and Feature tests) via PHPUnit
-        run: vendor/bin/psalm
diff --git a/composer.json b/composer.json
index aaf7fc2..c8cb178 100644
--- a/composer.json
+++ b/composer.json
@@ -15,6 +15,17 @@
         "source": "https://github.com/api-skeletons/laravel-api-problem",
         "chat": "https://gitter.im/API-Skeletons/open-source"
     },
+    "require": {
+        "php": "^8.1",
+        "doctrine/instantiator": "^2.0",
+        "laravel/framework": "^8.0 || ^9.0 || ^10.0 || ^11.0 || ^12.0"
+    },
+    "require-dev": {
+        "doctrine/coding-standard": "^12.0",
+        "orchestra/testbench": "^v10.1",
+        "php-parallel-lint/php-parallel-lint": "^1.4",
+        "phpunit/phpunit": "^11.5"
+    },
     "config": {
         "sort-packages": true,
         "allow-plugins": {
@@ -48,20 +59,7 @@
         "test": [
             "vendor/bin/parallel-lint src test",
             "vendor/bin/phpcs",
-            "vendor/bin/psalm",
             "vendor/bin/phpunit"
         ]
-    },
-    "require": {
-        "php": "^8.1",
-        "doctrine/instantiator": "^2.0",
-        "laravel/framework": "^8.0 || ^9.0 || ^10.0 || ^11.0"
-    },
-    "require-dev": {
-        "doctrine/coding-standard": "^12.0",
-        "orchestra/testbench": "^7.41",
-        "php-parallel-lint/php-parallel-lint": "^1.4",
-        "phpunit/phpunit": "^9.5",
-        "vimeo/psalm": "^4.15"
     }
 }
diff --git a/psalm.xml b/psalm.xml
deleted file mode 100644
index 7c0333d..0000000
--- a/psalm.xml
+++ /dev/null
@@ -1,15 +0,0 @@
-<?xml version="1.0"?>
-<psalm
-    errorLevel="7"
-    resolveFromConfigFile="true"
-    xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
-    xmlns="https://getpsalm.org/schema/config"
-    xsi:schemaLocation="https://getpsalm.org/schema/config vendor/vimeo/psalm/config.xsd"
->
-    <projectFiles>
-        <directory name="src" />
-        <ignoreFiles>
-            <directory name="vendor" />
-        </ignoreFiles>
-    </projectFiles>
-</psalm>
diff --git a/test/ApiProblemTest.php b/test/ApiProblemTest.php
index 476c2a1..71190f6 100644
--- a/test/ApiProblemTest.php
+++ b/test/ApiProblemTest.php
@@ -7,7 +7,6 @@
 use ApiSkeletons\Laravel\ApiProblem\ApiProblem;
 use ApiSkeletons\Laravel\ApiProblem\Exception;
 use ApiSkeletons\Laravel\ApiProblem\Facades\ApiProblem as ApiProblemFacade;
-use http\Exception\InvalidArgumentException;
 use Illuminate\Http\JsonResponse;
 use ReflectionObject;
 use TypeError;
@@ -15,7 +14,7 @@
 final class ApiProblemTest extends TestCase
 {
     /** @psalm-return array<string, array{0: int}> */
-    public function statusCodes(): array
+    public static function statusCodes(): array
     {
         return [
             '200' => [200],
@@ -42,25 +41,21 @@ public function testResponseWithFacade(): void
         $this->assertInstanceOf(JsonResponse::class, ApiProblemFacade::response('Testing', 500));
     }
 
-    /**
-     * @dataProvider statusCodes
-     */
+    /** @dataProvider statusCodes */
     public function testStatusIsUsedVerbatim(int $status): void
     {
         $apiProblem = new ApiProblem($status, 'foo');
-        $payload = $apiProblem->toArray();
+        $payload    = $apiProblem->toArray();
         $this->assertArrayHasKey('status', $payload);
         $this->assertEquals($status, $payload['status']);
     }
 
-    /**
-     * @requires PHP 7.0
-     */
+    /** @requires PHP 7.0 */
     public function testErrorAsDetails(): void
     {
-        $error = new TypeError('error message', 705);
+        $error      = new TypeError('error message', 705);
         $apiProblem = new ApiProblem(500, $error);
-        $payload = $apiProblem->toArray();
+        $payload    = $apiProblem->toArray();
 
         $this->assertArrayHasKey('title', $payload);
         $this->assertEquals('TypeError', $payload['title']);
@@ -72,9 +67,9 @@ public function testErrorAsDetails(): void
 
     public function testExceptionCodeIsUsedForStatus(): void
     {
-        $exception = new \Exception('exception message', 401);
+        $exception  = new \Exception('exception message', 401);
         $apiProblem = new ApiProblem('500', $exception);
-        $payload = $apiProblem->toArray();
+        $payload    = $apiProblem->toArray();
         $this->assertArrayHasKey('status', $payload);
         $this->assertEquals($exception->getCode(), $payload['status']);
     }
@@ -82,23 +77,23 @@ public function testExceptionCodeIsUsedForStatus(): void
     public function testDetailStringIsUsedVerbatim(): void
     {
         $apiProblem = new ApiProblem('500', 'foo');
-        $payload = $apiProblem->toArray();
+        $payload    = $apiProblem->toArray();
         $this->assertArrayHasKey('detail', $payload);
         $this->assertEquals('foo', $payload['detail']);
     }
 
     public function testExceptionMessageIsUsedForDetail(): void
     {
-        $exception = new \Exception('exception message');
+        $exception  = new \Exception('exception message');
         $apiProblem = new ApiProblem('500', $exception);
-        $payload = $apiProblem->toArray();
+        $payload    = $apiProblem->toArray();
         $this->assertArrayHasKey('detail', $payload);
         $this->assertEquals($exception->getMessage(), $payload['detail']);
     }
 
     public function testExceptionsCanTriggerInclusionOfStackTraceInDetails(): void
     {
-        $exception = new \Exception('exception message');
+        $exception  = new \Exception('exception message');
         $apiProblem = new ApiProblem('500', $exception);
         $apiProblem->setDetailIncludesStackTrace(true);
         $payload = $apiProblem->toArray();
@@ -109,7 +104,7 @@ public function testExceptionsCanTriggerInclusionOfStackTraceInDetails(): void
 
     public function testExceptionsCanTriggerInclusionOfNestedExceptions(): void
     {
-        $exceptionChild = new \Exception('child exception');
+        $exceptionChild  = new \Exception('child exception');
         $exceptionParent = new \Exception('parent exception', 0, $exceptionChild);
 
         $apiProblem = new ApiProblem('500', $exceptionParent);
@@ -130,13 +125,13 @@ public function testExceptionsCanTriggerInclusionOfNestedExceptions(): void
     public function testTypeUrlIsUsedVerbatim(): void
     {
         $apiProblem = new ApiProblem('500', 'foo', 'http://status.dev:8080/details.md');
-        $payload = $apiProblem->toArray();
+        $payload    = $apiProblem->toArray();
         $this->assertArrayHasKey('type', $payload);
         $this->assertEquals('http://status.dev:8080/details.md', $payload['type']);
     }
 
     /** @psalm-return array<string, array{0: int}> */
-    public function knownStatusCodes(): array
+    public static function knownStatusCodes(): array
     {
         return [
             '404' => [404],
@@ -146,14 +141,12 @@ public function knownStatusCodes(): array
         ];
     }
 
-    /**
-     * @dataProvider knownStatusCodes
-     */
+    /** @dataProvider knownStatusCodes */
     public function testKnownStatusResultsInKnownTitle(int $status): void
     {
         $apiProblem = new ApiProblem($status, 'foo');
-        $r = new ReflectionObject($apiProblem);
-        $p = $r->getProperty('problemStatusTitles');
+        $r          = new ReflectionObject($apiProblem);
+        $p          = $r->getProperty('problemStatusTitles');
         $p->setAccessible(true);
         $titles = $p->getValue($apiProblem);
 
@@ -165,7 +158,7 @@ public function testKnownStatusResultsInKnownTitle(int $status): void
     public function testUnknownStatusResultsInUnknownTitle(): void
     {
         $apiProblem = new ApiProblem(420, 'foo');
-        $payload = $apiProblem->toArray();
+        $payload    = $apiProblem->toArray();
         $this->assertArrayHasKey('title', $payload);
         $this->assertEquals('Unknown', $payload['title']);
     }
@@ -173,7 +166,7 @@ public function testUnknownStatusResultsInUnknownTitle(): void
     public function testProvidedTitleIsUsedVerbatim(): void
     {
         $apiProblem = new ApiProblem('500', 'foo', 'http://status.dev:8080/details.md', 'some title');
-        $payload = $apiProblem->toArray();
+        $payload    = $apiProblem->toArray();
         $this->assertArrayHasKey('title', $payload);
         $this->assertEquals('some title', $payload['title']);
     }
@@ -185,7 +178,7 @@ public function testCanPassArbitraryDetailsToConstructor(): void
             'Invalid input',
             'http://example.com/api/problem/400',
             'Invalid entity',
-            ['foo' => 'bar']
+            ['foo' => 'bar'],
         );
         $this->assertEquals('bar', $problem->foo);
     }
@@ -197,9 +190,9 @@ public function testArraySerializationIncludesArbitraryDetails(): void
             'Invalid input',
             'http://example.com/api/problem/400',
             'Invalid entity',
-            ['foo' => 'bar']
+            ['foo' => 'bar'],
         );
-        $array = $problem->toArray();
+        $array   = $problem->toArray();
         $this->assertArrayHasKey('foo', $array);
         $this->assertEquals('bar', $array['foo']);
     }
@@ -211,9 +204,9 @@ public function testArbitraryDetailsShouldNotOverwriteRequiredFieldsInArraySeria
             'Invalid input',
             'http://example.com/api/problem/400',
             'Invalid entity',
-            ['title' => 'SHOULD NOT GET THIS']
+            ['title' => 'SHOULD NOT GET THIS'],
         );
-        $array = $problem->toArray();
+        $array   = $problem->toArray();
         $this->assertArrayHasKey('title', $array);
         $this->assertEquals('Invalid entity', $array['title']);
     }
@@ -223,7 +216,7 @@ public function testUsesTitleFromExceptionWhenProvided(): void
         $exception = new Exception\DomainException('exception message', 401);
         $exception->setTitle('problem title');
         $apiProblem = new ApiProblem('401', $exception);
-        $payload = $apiProblem->toArray();
+        $payload    = $apiProblem->toArray();
         $this->assertArrayHasKey('title', $payload);
         $this->assertEquals($exception->getTitle(), $payload['title']);
     }
@@ -233,7 +226,7 @@ public function testUsesTypeFromExceptionWhenProvided(): void
         $exception = new Exception\DomainException('exception message', 401);
         $exception->setType('http://example.com/api/help/401');
         $apiProblem = new ApiProblem('401', $exception);
-        $payload = $apiProblem->toArray();
+        $payload    = $apiProblem->toArray();
         $this->assertArrayHasKey('type', $payload);
         $this->assertEquals($exception->getType(), $payload['type']);
     }
@@ -243,13 +236,13 @@ public function testUsesAdditionalDetailsFromExceptionWhenProvided(): void
         $exception = new Exception\DomainException('exception message', 401);
         $exception->setAdditionalDetails(['foo' => 'bar']);
         $apiProblem = new ApiProblem('401', $exception);
-        $payload = $apiProblem->toArray();
+        $payload    = $apiProblem->toArray();
         $this->assertArrayHasKey('foo', $payload);
         $this->assertEquals('bar', $payload['foo']);
     }
 
     /** @psalm-return array<string, array{0: int}> */
-    public function invalidStatusCodes(): array
+    public static function invalidStatusCodes(): array
     {
         return [
             '-1' => [-1],
@@ -266,7 +259,7 @@ public function invalidStatusCodes(): array
      */
     public function testInvalidHttpStatusCodesAreCastTo500(int $code): void
     {
-        $e = new \Exception('Testing', $code);
+        $e       = new \Exception('Testing', $code);
         $problem = new ApiProblem($code, $e);
         $this->assertEquals(500, $problem->status);
     }

From 51ab9338f4b68a752cebd4e7e4699abd31b3f70e Mon Sep 17 00:00:00 2001
From: Tom H Anderson <tom.h.anderson@gmail.com>
Date: Mon, 17 Mar 2025 18:47:50 -0600
Subject: [PATCH 2/2] Adjust action versions

---
 .github/workflows/continuous-integration.yml | 2 +-
 .github/workflows/php.yml                    | 4 ++--
 .gitignore                                   | 1 +
 3 files changed, 4 insertions(+), 3 deletions(-)

diff --git a/.github/workflows/continuous-integration.yml b/.github/workflows/continuous-integration.yml
index c362142..f970da5 100644
--- a/.github/workflows/continuous-integration.yml
+++ b/.github/workflows/continuous-integration.yml
@@ -67,7 +67,7 @@ jobs:
         run: "vendor/bin/phpunit --coverage-clover=coverage.xml"
 
       - name: "Upload coverage file"
-        uses: "actions/upload-artifact@v2"
+        uses: "actions/upload-artifact@v4"
         with:
           name: "phpunit-${{ matrix.php-version }}-${{ matrix.dependencies }}-${{ matrix.dbal-version }}.coverage"
           path: "coverage.xml"
diff --git a/.github/workflows/php.yml b/.github/workflows/php.yml
index 39335a6..b188595 100644
--- a/.github/workflows/php.yml
+++ b/.github/workflows/php.yml
@@ -12,14 +12,14 @@ jobs:
     runs-on: ubuntu-latest
 
     steps:
-    - uses: actions/checkout@v2
+    - uses: actions/checkout@v4
 
     - name: Validate composer.json and composer.lock
       run: composer validate --strict
 
     - name: Cache Composer packages
       id: composer-cache
-      uses: actions/cache@v2
+      uses: actions/cache@v4
       with:
         path: vendor
         key: ${{ runner.os }}-php-${{ hashFiles('**/composer.lock') }}
diff --git a/.gitignore b/.gitignore
index 89d0f12..d4950c8 100644
--- a/.gitignore
+++ b/.gitignore
@@ -4,3 +4,4 @@ composer.lock
 /.phpcs-cache
 /coverage/
 /.phpunit.cache/
+.phpunit.result.cache