diff --git a/.github/workflows/test-unit.yml b/.github/workflows/test-unit.yml index 0e9e3debee..4262066fdb 100644 --- a/.github/workflows/test-unit.yml +++ b/.github/workflows/test-unit.yml @@ -79,13 +79,13 @@ jobs: strategy: fail-fast: false matrix: - php: ['7.4', '8.0', '8.1', '8.2'] + php: ['7.4', '8.0', '8.1', '8.2', '8.3'] type: ['Phpunit', 'Phpunit Lowest'] include: - php: 'latest' type: 'Phpunit Burn' env: - LOG_COVERAGE: "${{ fromJSON('{true: \"1\", false: \"\"}')[matrix.php == '8.0' && matrix.type == 'Phpunit' && (github.event_name == 'pull_request' || (github.event_name == 'push' && (github.ref == 'refs/heads/develop' || github.ref == 'refs/heads/master')))] }}" + LOG_COVERAGE: "${{ fromJSON('{true: \"1\", false: \"\"}')[matrix.php == '8.2' && matrix.type == 'Phpunit' && (github.event_name == 'pull_request' || (github.event_name == 'push' && (github.ref == 'refs/heads/develop' || github.ref == 'refs/heads/master')))] }}" services: mysql: image: mysql:8 @@ -136,7 +136,7 @@ jobs: if [ "${{ matrix.type }}" != "Phpunit" ] && [ "${{ matrix.type }}" != "Phpunit Lowest" ] && [ "${{ matrix.type }}" != "Phpunit Burn" ]; then composer remove --no-interaction --no-update phpunit/phpunit johnkary/phpunit-speedtrap --dev; fi if [ "${{ matrix.type }}" != "CodingStyle" ]; then composer remove --no-interaction --no-update friendsofphp/php-cs-fixer --dev; fi if [ "${{ matrix.type }}" != "StaticAnalysis" ]; then composer remove --no-interaction --no-update phpstan/\* behat/\* --dev; fi - if [ -n "$LOG_COVERAGE" ]; then composer require --no-interaction --no-update phpunit/phpcov; fi + if [ -n "$LOG_COVERAGE" ]; then composer require --no-interaction --no-install phpunit/phpcov; fi composer update --ansi --prefer-dist --no-interaction --no-progress --optimize-autoloader if [ "${{ matrix.type }}" = "Phpunit Lowest" ]; then composer update --ansi --prefer-dist --prefer-lowest --prefer-stable --no-interaction --no-progress --optimize-autoloader; fi if [ "${{ matrix.type }}" = "Phpunit Burn" ]; then sed -i 's~ *public function runBare(): void~public function runBare(): void { gc_collect_cycles(); gc_collect_cycles(); $memDiffs = array_fill(0, '"$(if [ \"$GITHUB_EVENT_NAME\" == \"schedule\" ]; then echo 64; else echo 16; fi)"', 0); for ($i = -1; $i < count($memDiffs); ++$i) { $this->_runBare(); gc_collect_cycles(); gc_collect_cycles(); $mem = memory_get_usage(); if ($i !== -1) { $memDiffs[$i] = $mem - $memPrev; } $memPrev = $mem; rsort($memDiffs); if (array_sum($memDiffs) >= 4096 * 1024 || $memDiffs[2] > 0) { $this->onNotSuccessfulTest(new AssertionFailedError( "Memory leak detected! (" . implode(" + ", array_map(fn ($v) => number_format($v / 1024, 3, ".", " "), array_filter($memDiffs))) . " KB, " . ($i + 2) . " iterations)" )); } } } private function _runBare(): void~' vendor/phpunit/phpunit/src/Framework/TestCase.php && cat vendor/phpunit/phpunit/src/Framework/TestCase.php | grep '_runBare('; fi @@ -238,7 +238,7 @@ jobs: strategy: fail-fast: false matrix: - php: ['7.4', '8.0', '8.1', '8.2'] + php: ['7.4', '8.0', '8.1', '8.2', '8.3'] type: ['Chrome', 'Chrome Lowest'] include: - php: 'latest' @@ -246,7 +246,7 @@ jobs: - php: 'latest' type: 'Chrome Slow' env: - LOG_COVERAGE: "${{ fromJSON('{true: \"1\", false: \"\"}')[matrix.php == '8.0' && matrix.type == 'Chrome' && (github.event_name == 'pull_request' || (github.event_name == 'push' && (github.ref == 'refs/heads/develop' || github.ref == 'refs/heads/master')))] }}" + LOG_COVERAGE: "${{ fromJSON('{true: \"1\", false: \"\"}')[matrix.php == '8.2' && matrix.type == 'Chrome' && (github.event_name == 'pull_request' || (github.event_name == 'push' && (github.ref == 'refs/heads/develop' || github.ref == 'refs/heads/master')))] }}" services: mysql: image: mysql:8 @@ -353,7 +353,7 @@ jobs: composer remove --no-interaction --no-update phpunit/phpunit johnkary/phpunit-speedtrap --dev composer remove --no-interaction --no-update friendsofphp/php-cs-fixer --dev composer remove --no-interaction --no-update phpstan/\* --dev - if [ -n "$LOG_COVERAGE" ]; then composer require --no-interaction --no-update phpunit/phpcov; fi + if [ -n "$LOG_COVERAGE" ]; then composer require --no-interaction --no-install phpunit/phpcov; fi composer update --ansi --prefer-dist --no-interaction --no-progress --optimize-autoloader if [ "${{ matrix.type }}" = "Chrome Lowest" ]; then composer update --ansi --prefer-dist --prefer-lowest --prefer-stable --no-interaction --no-progress --optimize-autoloader; fi diff --git a/composer.json b/composer.json index df62ee2afe..8e2f8db800 100644 --- a/composer.json +++ b/composer.json @@ -51,7 +51,7 @@ ], "homepage": "https://github.com/atk4/ui", "require": { - "php": ">=7.4 <8.3", + "php": ">=7.4 <8.4", "atk4/data": "dev-develop", "nyholm/psr7": "^1.4", "nyholm/psr7-server": "^1.0", @@ -59,7 +59,7 @@ "symfony/http-foundation": "^4.4 || ^5.3 || ^6.0" }, "require-release": { - "php": ">=7.4 <8.3", + "php": ">=7.4 <8.4", "atk4/data": "~5.0.0", "nyholm/psr7": "^1.4", "nyholm/psr7-server": "^1.0", diff --git a/src/App.php b/src/App.php index d827d7fd29..880a03349d 100644 --- a/src/App.php +++ b/src/App.php @@ -204,7 +204,9 @@ public function __construct(array $defaults = []) throw new \ErrorException($msg, 0, $severity, $file, $line); }); - http_response_code(500); + if (\PHP_SAPI !== 'cli') { // for phpunit + http_response_code(500); + } } // always run app on shutdown @@ -1078,7 +1080,9 @@ function () { */ protected function emitResponse(): void { - http_response_code($this->response->getStatusCode()); + if (!headers_sent() || $this->response->getHeaders() !== []) { // avoid throwing late error in loop + http_response_code($this->response->getStatusCode()); + } foreach ($this->response->getHeaders() as $name => $values) { foreach ($values as $value) { diff --git a/src/App/SessionManager.php b/src/App/SessionManager.php index ee2834f27a..dac382f88c 100644 --- a/src/App/SessionManager.php +++ b/src/App/SessionManager.php @@ -57,9 +57,11 @@ protected function closeSession(bool $writeBeforeClose): void } /** - * @param \Closure(): mixed $fx + * @template T * - * @return mixed + * @param \Closure(): T $fx + * + * @return T */ public function atomicSession(\Closure $fx, bool $readAndCloseImmediately = false) { diff --git a/src/Callback.php b/src/Callback.php index 4ea428cc49..a29460d522 100644 --- a/src/Callback.php +++ b/src/Callback.php @@ -67,7 +67,7 @@ public function getUrlTrigger(): string * @param \Closure(mixed, mixed, mixed, mixed, mixed, mixed, mixed, mixed, mixed, mixed): T $fx * @param array $fxArgs * - * @phpstan-return T|null + * @return T|null */ public function set($fx = null, $fxArgs = null) { diff --git a/src/Console.php b/src/Console.php index 290b84bf01..f76e29c2ee 100644 --- a/src/Console.php +++ b/src/Console.php @@ -446,10 +446,8 @@ public function debug($message, array $context = []): void /** * @param 'emergency'|'alert'|'critical'|'error'|'warning'|'notice'|'info'|'debug' $level - * - * @phpstan-ignore-next-line */ - public function log($level, $message, array $context = []): void + public function log($level, $message, array $context = []): void // @phpstan-ignore-line { $this->{$level}($message, $context); } diff --git a/src/Form/Control.php b/src/Form/Control.php index 9d8f6665cc..467d481dbd 100644 --- a/src/Form/Control.php +++ b/src/Form/Control.php @@ -24,11 +24,7 @@ class Control extends View /** @var Form|null to which this field belongs */ public $form; - /** - * @var EntityFieldPair|null - * - * @phpstan-var EntityFieldPair|null - */ + /** @var EntityFieldPair|null */ public $entityField; /** @var string */ diff --git a/src/Form/Control/ScopeBuilder.php b/src/Form/Control/ScopeBuilder.php index 508b99fdcf..9031e6a256 100644 --- a/src/Form/Control/ScopeBuilder.php +++ b/src/Form/Control/ScopeBuilder.php @@ -692,7 +692,7 @@ protected function getConditionOption(string $type, $value, Condition $condition /** * Auto-detects a string delimiter based on list of predefined values in ScopeBuilder::$listDelimiters in order of priority. * - * @phpstan-return non-empty-string + * @return non-empty-string */ public function detectDelimiter(string $value): string { diff --git a/src/SessionTrait.php b/src/SessionTrait.php index 6015116cfc..aa15719cdb 100644 --- a/src/SessionTrait.php +++ b/src/SessionTrait.php @@ -19,9 +19,11 @@ private function getSessionManager(): App\SessionManager } /** - * @param \Closure(): mixed $fx + * @template T * - * @return mixed + * @param \Closure(): T $fx + * + * @return T */ public function atomicSession(\Closure $fx, bool $readAndCloseImmediately = false) { diff --git a/src/UserAction/JsCallbackExecutor.php b/src/UserAction/JsCallbackExecutor.php index 8dd1493ccf..4388e15fc6 100644 --- a/src/UserAction/JsCallbackExecutor.php +++ b/src/UserAction/JsCallbackExecutor.php @@ -53,10 +53,12 @@ public function setAction(Model\UserAction $action) } /** - * @param \Closure(): mixed $fx + * @template T + * + * @param \Closure(): T $fx * @param array $urlArgs * - * @return mixed + * @return T */ protected function invokeFxWithUrlArgs(\Closure $fx, array $urlArgs = []) { diff --git a/src/View.php b/src/View.php index c27d78d888..b7503a13e6 100644 --- a/src/View.php +++ b/src/View.php @@ -312,7 +312,7 @@ public function add($object, $region = null): AbstractView * * @param class-string $class * - * @phpstan-return T|null + * @return T|null */ public function getClosestOwner(string $class): ?self {