diff --git a/CHANGELOG.md b/CHANGELOG.md index 069a23a1c..2009671dd 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -12,6 +12,12 @@ The format is based on Keep a Changelog. ### Changed - The `DemoWeb` and `Toolkit` module templates redirect back with `back()` instead of `redirect(get_referrer() ?? base_url())` and `redirectWith(get_referrer() ?? base_url(), $data)`, so generated code gets the same-origin check. Modules generated before keep their code (#541) - Twig is a required dependency again: `twig/twig` `^3.27` is in `require` and no longer only suggested, which reverses the 3.0.3 change that made it optional while the framework supported PHP 8.0 (#532). Twig `^3.27` needs PHP 8.1, the minimum since 3.1.0. An application that pins an older Twig must allow `^3.27`. Removed `RendererException::twigNotInstalled()` and the check in `TwigAdapter` that threw it when Twig was missing, and the Twig tests no longer skip (#626) +- The test suite runs on PHPUnit `^10.5` (a development dependency) and `phpunit.xml` uses its schema. It now sets `error_reporting` to `-1`: it was the text `E_ALL & ~E_DEPRECATED`, which PHP reads as `0`, so every PHP warning was hidden in the tests (#622) + +### Fixed +- `php qt core:env` no longer prints `.env is successfully generated` and no longer tries to copy the file when `.env.example` is missing: it stops after the error message (#622) +- `flush()` of the session adapters no longer raises a warning when no session is active, so calling it twice, or before a session was started, no longer throws (#622) +- `listDirectory()` of the Dropbox and Google Drive adapters returns `false`, as its contract says, when the API response has no `entries` or `files`. It could return `null` when PHP warnings were not reported (#622) ## [3.1.0] - 2026-10-24 diff --git a/composer.json b/composer.json index 9858c62f4..a1350314f 100644 --- a/composer.json +++ b/composer.json @@ -56,7 +56,7 @@ "twig/twig": "^3.27" }, "require-dev": { - "phpunit/phpunit": "^9.0", + "phpunit/phpunit": "^10.5", "mockery/mockery": "^1.4", "mikey179/vfsstream": "^1.6", "rector/rector": "^2.3", diff --git a/phpunit.xml b/phpunit.xml index 3b2122c21..2c5efdfa3 100644 --- a/phpunit.xml +++ b/phpunit.xml @@ -4,13 +4,12 @@ xsi:noNamespaceSchemaLocation="./vendor/phpunit/phpunit/phpunit.xsd" bootstrap="tests/bootstrap.php" colors="true" - verbose="true" stderr="true" stopOnFailure="false" - convertDeprecationsToExceptions="false" + cacheDirectory=".phpunit.cache" > - + @@ -19,9 +18,9 @@ - + ./src - - \ No newline at end of file + + diff --git a/src/Console/Commands/EnvCommand.php b/src/Console/Commands/EnvCommand.php index 171bf7ea7..5c7194ba3 100644 --- a/src/Console/Commands/EnvCommand.php +++ b/src/Console/Commands/EnvCommand.php @@ -64,6 +64,7 @@ public function exec(): void if (!$fs->exists(App::getBaseDir() . DS . '.env.example')) { $this->error('.env.example file not found'); + return; } if ($fs->exists(App::getBaseDir() . DS . '.env') && !$this->getOption('yes') && !$this->confirm('The operation will overwrite values of the existing .env and will create new one from .env.example. Continue?')) { diff --git a/src/Debugger/Debugger.php b/src/Debugger/Debugger.php index a4765b25d..583f2f6e3 100644 --- a/src/Debugger/Debugger.php +++ b/src/Debugger/Debugger.php @@ -249,7 +249,7 @@ protected function routeInfo(): array $info['View'] = request()->getCurrentModule() . '/Views/' . $viewFile; } - return array_filter($info, fn ($value): bool => $value !== null && $value !== []); + return array_filter($info, fn (string|array|null $value): bool => $value !== null && $value !== []); } /** diff --git a/src/Session/Traits/SessionTrait.php b/src/Session/Traits/SessionTrait.php index 31f584297..480d8e5ad 100644 --- a/src/Session/Traits/SessionTrait.php +++ b/src/Session/Traits/SessionTrait.php @@ -103,7 +103,10 @@ public function delete(string $key): void public function flush(): void { self::$storage = []; - session_destroy(); + + if (session_status() === PHP_SESSION_ACTIVE) { + session_destroy(); + } } /** diff --git a/src/Storage/Adapters/Dropbox/DropboxFileSystemAdapter.php b/src/Storage/Adapters/Dropbox/DropboxFileSystemAdapter.php index a70ebbd87..480701bea 100644 --- a/src/Storage/Adapters/Dropbox/DropboxFileSystemAdapter.php +++ b/src/Storage/Adapters/Dropbox/DropboxFileSystemAdapter.php @@ -210,7 +210,7 @@ public function listDirectory(string $dirname) { try { $response = (array) $this->dropboxApp->rpcRequest(DropboxApp::ENDPOINT_LIST_FOLDER, $this->dropboxApp->path($dirname)); - return $response['entries']; + return $response['entries'] ?? false; } catch (Exception) { return false; } diff --git a/src/Storage/Adapters/GoogleDrive/GoogleDriveFileSystemAdapter.php b/src/Storage/Adapters/GoogleDrive/GoogleDriveFileSystemAdapter.php index d49298298..1f18e061e 100644 --- a/src/Storage/Adapters/GoogleDrive/GoogleDriveFileSystemAdapter.php +++ b/src/Storage/Adapters/GoogleDrive/GoogleDriveFileSystemAdapter.php @@ -235,7 +235,7 @@ public function listDirectory(string $dirname) 'fields' => '*', ]; $response = (array) $this->googleDriveApp->rpcRequest(GoogleDriveApp::FILE_METADATA_URL . '?' . http_build_query($params), [], 'GET'); - return $response['files']; + return $response['files'] ?? false; } catch (Exception) { return false; } diff --git a/tests/Unit/App/Adapters/Console/Stages/Boot/ResolveEnvironmentStageTest.php b/tests/Unit/App/Adapters/Console/Stages/Boot/ResolveEnvironmentStageTest.php index 8c7216e0e..63c04809a 100644 --- a/tests/Unit/App/Adapters/Console/Stages/Boot/ResolveEnvironmentStageTest.php +++ b/tests/Unit/App/Adapters/Console/Stages/Boot/ResolveEnvironmentStageTest.php @@ -89,7 +89,7 @@ public function testResolveEnvironmentStageThrowsTheParseExceptionWhenTheEnvFile /** * @return array */ - public function commandsThatRunWithoutTheEnvironment(): array + public static function commandsThatRunWithoutTheEnvironment(): array { return [ 'a CliCommand that does not require the environment' => ['no:env'], @@ -101,7 +101,7 @@ public function commandsThatRunWithoutTheEnvironment(): array /** * @return array}> */ - public function inputsThatNeedTheEnvironment(): array + public static function inputsThatNeedTheEnvironment(): array { return [ 'a CliCommand that requires the environment' => [['command' => 'needs:env']], diff --git a/tests/Unit/AppTestCase.php b/tests/Unit/AppTestCase.php index 7ac1fed5e..2c92b6e6a 100644 --- a/tests/Unit/AppTestCase.php +++ b/tests/Unit/AppTestCase.php @@ -55,6 +55,9 @@ public function tearDown(): void } $this->clearAppContext(); + + restore_error_handler(); + restore_exception_handler(); } protected function createContext(string $type = AppType::WEB): AppContext diff --git a/tests/Unit/Console/Commands/EnvCommandTest.php b/tests/Unit/Console/Commands/EnvCommandTest.php index 717b7b00b..c71e34a65 100644 --- a/tests/Unit/Console/Commands/EnvCommandTest.php +++ b/tests/Unit/Console/Commands/EnvCommandTest.php @@ -109,6 +109,7 @@ public function testExecShowsErrorWhenEnvExampleMissing(): void $output = $this->tester->getDisplay(); $this->assertStringContainsString('.env.example file not found', $output); + $this->assertStringNotContainsString('.env is successfully generated', $output); } finally { if ($existed && file_exists($backup)) { rename($backup, $envExample); diff --git a/tests/Unit/Console/Commands/OpenApiCommandTest.php b/tests/Unit/Console/Commands/OpenApiCommandTest.php index 6d337dd04..60f025853 100644 --- a/tests/Unit/Console/Commands/OpenApiCommandTest.php +++ b/tests/Unit/Console/Commands/OpenApiCommandTest.php @@ -216,7 +216,7 @@ public function testExecCreatesTheResourcesFoldersOfAModuleWithoutThem(): void mkdir($controllersDir, 0777, true); file_put_contents($routesDir . DS . 'routes.php', " */ - public function interfaceMethodProvider(): array + public static function interfaceMethodProvider(): array { return [ 'offsetSet' => ['offsetSet'], diff --git a/tests/Unit/Logger/LoggerTest.php b/tests/Unit/Logger/LoggerTest.php index aa40beab8..281f89325 100644 --- a/tests/Unit/Logger/LoggerTest.php +++ b/tests/Unit/Logger/LoggerTest.php @@ -70,7 +70,7 @@ public function __toString(): string /** * @return array */ - public function levelMethodProvider(): array + public static function levelMethodProvider(): array { return [ 'emergency' => ['emergency', LogLevel::EMERGENCY], diff --git a/tests/Unit/Mailer/MailerTest.php b/tests/Unit/Mailer/MailerTest.php index bc9737abf..5d2319e60 100644 --- a/tests/Unit/Mailer/MailerTest.php +++ b/tests/Unit/Mailer/MailerTest.php @@ -77,7 +77,7 @@ public function testHttpAdapterGetTransportErrorsReturnsArray(string $adapterCla /** * @return array}> */ - public function httpAdapterProvider(): array + public static function httpAdapterProvider(): array { return [ 'mailgun' => [MailgunAdapter::class, ['api_key' => 'test', 'domain' => 'test.com']], diff --git a/tests/Unit/Module/Templates/OpenApiTemplatesTest.php b/tests/Unit/Module/Templates/OpenApiTemplatesTest.php index 6cd2737c5..a7cc00bb7 100644 --- a/tests/Unit/Module/Templates/OpenApiTemplatesTest.php +++ b/tests/Unit/Module/Templates/OpenApiTemplatesTest.php @@ -81,7 +81,7 @@ public function testApiTemplatesDescribeTheApiWithAttributesOnly(string $templat /** * @return array */ - public function apiTemplateProvider(): array + public static function apiTemplateProvider(): array { return [ 'default-api' => ['DefaultApi'], diff --git a/tests/Unit/Module/Templates/RedirectTemplatesTest.php b/tests/Unit/Module/Templates/RedirectTemplatesTest.php index 62053abd3..de61a72ef 100644 --- a/tests/Unit/Module/Templates/RedirectTemplatesTest.php +++ b/tests/Unit/Module/Templates/RedirectTemplatesTest.php @@ -43,7 +43,7 @@ public function testTemplatesGoBackWithTheBackRedirect(string $template): void /** * @return array */ - public function redirectTemplateProvider(): array + public static function redirectTemplateProvider(): array { return [ 'demo-web' => ['DemoWeb'], diff --git a/tests/Unit/Session/Adapters/Database/DatabaseHandlerTest.php b/tests/Unit/Session/Adapters/Database/DatabaseHandlerTest.php index 398d7f99e..e06c56c56 100644 --- a/tests/Unit/Session/Adapters/Database/DatabaseHandlerTest.php +++ b/tests/Unit/Session/Adapters/Database/DatabaseHandlerTest.php @@ -85,7 +85,7 @@ private function insertSession(string $id, int $ttl): void /** * @return array */ - public function interfaceMethodProvider(): array + public static function interfaceMethodProvider(): array { return [ 'open' => ['open'], diff --git a/tests/Unit/Session/Adapters/Native/NativeSessionAdapterTest.php b/tests/Unit/Session/Adapters/Native/NativeSessionAdapterTest.php index b48f51c9e..c33249aec 100644 --- a/tests/Unit/Session/Adapters/Native/NativeSessionAdapterTest.php +++ b/tests/Unit/Session/Adapters/Native/NativeSessionAdapterTest.php @@ -81,6 +81,23 @@ public function testNativeSessionFlush(): void session_start(); } + public function testNativeSessionFlushWithoutAnActiveSessionDoesNotWarn(): void + { + $oldLevel = error_reporting(E_ALL); + + try { + $this->session->flush(); + + $this->session->flush(); + + $this->assertEmpty($this->session->all()); + } finally { + error_reporting($oldLevel); + + @session_start(); + } + } + public function testNativeSessionGetSessionId(): void { $this->assertEquals(session_id(), $this->session->getId()); diff --git a/tests/Unit/Storage/Adapters/Dropbox/DropboxFileSystemAdapterTest.php b/tests/Unit/Storage/Adapters/Dropbox/DropboxFileSystemAdapterTest.php index 07a80a9bd..dea5e6859 100644 --- a/tests/Unit/Storage/Adapters/Dropbox/DropboxFileSystemAdapterTest.php +++ b/tests/Unit/Storage/Adapters/Dropbox/DropboxFileSystemAdapterTest.php @@ -218,7 +218,7 @@ public function testDropboxListDirectory(): void self::$response = []; - $this->assertNull($this->fs->listDirectory('test')); + $this->assertFalse($this->fs->listDirectory('test')); } } diff --git a/tests/Unit/Storage/Adapters/GoogleDrive/GoogleDriveFileSystemAdapterTest.php b/tests/Unit/Storage/Adapters/GoogleDrive/GoogleDriveFileSystemAdapterTest.php index 90b53c116..5b685fb78 100644 --- a/tests/Unit/Storage/Adapters/GoogleDrive/GoogleDriveFileSystemAdapterTest.php +++ b/tests/Unit/Storage/Adapters/GoogleDrive/GoogleDriveFileSystemAdapterTest.php @@ -199,7 +199,7 @@ public function testGoogleDriveListDirectory(): void self::$response = []; - $this->assertNull($this->fs->listDirectory('test')); + $this->assertFalse($this->fs->listDirectory('test')); } } diff --git a/tests/Unit/Tracer/ErrorHandlerTest.php b/tests/Unit/Tracer/ErrorHandlerTest.php index 5f57f34f1..89487c2cf 100644 --- a/tests/Unit/Tracer/ErrorHandlerTest.php +++ b/tests/Unit/Tracer/ErrorHandlerTest.php @@ -278,7 +278,7 @@ public function testLogErrorFallsBackToErrorMethodForUnknownLevel(): void /** * @return array */ - public function deprecationSeverityProvider(): array + public static function deprecationSeverityProvider(): array { return [ 'deprecated' => [E_DEPRECATED], @@ -299,7 +299,7 @@ public function testGetErrorTypeMappings(\Throwable $throwable, string $expected /** * @return array */ - public function errorTypeProvider(): array + public static function errorTypeProvider(): array { return [ 'mapped-error-exception' => [new ErrorException('x', 0, E_WARNING), 'warning'], diff --git a/tests/Unit/Tracer/ExceptionSeverityResolverTest.php b/tests/Unit/Tracer/ExceptionSeverityResolverTest.php index c60981885..ae35eff71 100644 --- a/tests/Unit/Tracer/ExceptionSeverityResolverTest.php +++ b/tests/Unit/Tracer/ExceptionSeverityResolverTest.php @@ -29,7 +29,7 @@ public function testResolveMappings(\Throwable $throwable, string $expected): vo /** * @return array */ - public function mappingProvider(): array + public static function mappingProvider(): array { return [ 'mapped-error-exception' => [new ErrorException('x', 0, E_WARNING), 'warning'], diff --git a/tests/bootstrap.php b/tests/bootstrap.php index ccada9cec..5c41896ec 100644 --- a/tests/bootstrap.php +++ b/tests/bootstrap.php @@ -1,7 +1,5 @@