Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
7 changes: 7 additions & 0 deletions .coderabbit.yaml
Original file line number Diff line number Diff line change
@@ -0,0 +1,7 @@
# CodeRabbit reviews pull requests into the default branch only, unless more branches are listed here.
# Also review the pull requests into the maintenance branches (3.1.x, 3.2.x, ...), so a change is
# reviewed in its own pull request and not only in the merge of the branch into master.
reviews:
auto_review:
base_branches:
- '^\d+\.\d+\.x$'
6 changes: 6 additions & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -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

Expand Down
2 changes: 1 addition & 1 deletion composer.json
Original file line number Diff line number Diff line change
Expand Up @@ -56,7 +56,7 @@
"twig/twig": "^3.27"
},
"require-dev": {
"phpunit/phpunit": "^9.0",
"phpunit/phpunit": "^10.5",
Comment thread
coderabbitai[bot] marked this conversation as resolved.
Resolved
"mockery/mockery": "^1.4",
"mikey179/vfsstream": "^1.6",
"rector/rector": "^2.3",
Expand Down
13 changes: 7 additions & 6 deletions phpunit.xml
Original file line number Diff line number Diff line change
Expand Up @@ -4,13 +4,14 @@
xsi:noNamespaceSchemaLocation="./vendor/phpunit/phpunit/phpunit.xsd"
bootstrap="tests/bootstrap.php"
colors="true"
verbose="true"
stderr="true"
stopOnFailure="false"
convertDeprecationsToExceptions="false"
failOnWarning="true"
failOnRisky="true"
cacheDirectory=".phpunit.cache"
>
<php>
<ini name="error_reporting" value="E_ALL &amp; ~E_DEPRECATED"/>
<ini name="error_reporting" value="-1"/>
</php>

<testsuites>
Expand All @@ -19,9 +20,9 @@
</testsuite>
</testsuites>

<coverage processUncoveredFiles="true">
<source>
<include>
<directory suffix=".php">./src</directory>
</include>
</coverage>
</phpunit>
</source>
</phpunit>
1 change: 1 addition & 0 deletions src/Console/Commands/EnvCommand.php
Original file line number Diff line number Diff line change
Expand Up @@ -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?')) {
Expand Down
2 changes: 1 addition & 1 deletion src/Debugger/Debugger.php
Original file line number Diff line number Diff line change
Expand Up @@ -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 !== []);
}

/**
Expand Down
5 changes: 4 additions & 1 deletion src/Session/Traits/SessionTrait.php
Original file line number Diff line number Diff line change
Expand Up @@ -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();
}
}

/**
Expand Down
2 changes: 1 addition & 1 deletion src/Storage/Adapters/Dropbox/DropboxFileSystemAdapter.php
Original file line number Diff line number Diff line change
Expand Up @@ -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;
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -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;
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -89,7 +89,7 @@ public function testResolveEnvironmentStageThrowsTheParseExceptionWhenTheEnvFile
/**
* @return array<string, array{string}>
*/
public function commandsThatRunWithoutTheEnvironment(): array
public static function commandsThatRunWithoutTheEnvironment(): array
{
return [
'a CliCommand that does not require the environment' => ['no:env'],
Expand All @@ -101,7 +101,7 @@ public function commandsThatRunWithoutTheEnvironment(): array
/**
* @return array<string, array{array<string, string>}>
*/
public function inputsThatNeedTheEnvironment(): array
public static function inputsThatNeedTheEnvironment(): array
{
return [
'a CliCommand that requires the environment' => [['command' => 'needs:env']],
Expand Down
3 changes: 3 additions & 0 deletions tests/Unit/AppTestCase.php
Original file line number Diff line number Diff line change
Expand Up @@ -55,6 +55,9 @@ public function tearDown(): void
}

$this->clearAppContext();

restore_error_handler();
restore_exception_handler();
Comment thread
coderabbitai[bot] marked this conversation as resolved.
Resolved
}

protected function createContext(string $type = AppType::WEB): AppContext
Expand Down
1 change: 1 addition & 0 deletions tests/Unit/Console/Commands/EnvCommandTest.php
Original file line number Diff line number Diff line change
Expand Up @@ -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);
Expand Down
4 changes: 2 additions & 2 deletions tests/Unit/Console/Commands/OpenApiCommandTest.php
Original file line number Diff line number Diff line change
Expand Up @@ -216,7 +216,7 @@ public function testExecCreatesTheResourcesFoldersOfAModuleWithoutThem(): void
mkdir($controllersDir, 0777, true);

file_put_contents($routesDir . DS . 'routes.php', "<?php\n\nreturn function (\$route) {\n};\n");
file_put_contents($controllersDir . DS . 'SpecController.php', "<?php\n\nuse OpenApi\\Attributes as OA;\n\n#[OA\\Info(title: 'Spec', version: '1.0.0')]\nclass " . $className . "\n{\n}\n");
file_put_contents($controllersDir . DS . 'SpecController.php', "<?php\n\nuse OpenApi\\Attributes as OA;\n\n#[OA\\Info(title: 'Spec', version: '1.0.0')]\nclass " . $className . "\n{\n #[OA\\Get(path: '/spec', responses: [new OA\\Response(response: 200, description: 'OK')])]\n public function index(): void\n {\n }\n}\n");

require_once $controllersDir . DS . 'SpecController.php';

Expand Down Expand Up @@ -271,7 +271,7 @@ public function error(string $message): void

mkdir($annotationDir, 0777, true);
mkdir($specDir, 0777, true);
file_put_contents($annotationDir . DS . 'SpecController.php', "<?php\n\nuse OpenApi\\Attributes as OA;\n\n#[OA\\Info(title: 'Spec', version: '1.0.0')]\nclass " . $className . "\n{\n}\n");
file_put_contents($annotationDir . DS . 'SpecController.php', "<?php\n\nuse OpenApi\\Attributes as OA;\n\n#[OA\\Info(title: 'Spec', version: '1.0.0')]\nclass " . $className . "\n{\n #[OA\\Get(path: '/spec', responses: [new OA\\Response(response: 200, description: 'OK')])]\n public function index(): void\n {\n }\n}\n");

require_once $annotationDir . DS . 'SpecController.php';

Expand Down
2 changes: 1 addition & 1 deletion tests/Unit/HttpClient/ResponseHeadersTest.php
Original file line number Diff line number Diff line change
Expand Up @@ -46,7 +46,7 @@ public function testResponseHeadersDeclaresTheReturnTypeOfItsInterfaceMethods(st
/**
* @return array<string, array{0: string}>
*/
public function interfaceMethodProvider(): array
public static function interfaceMethodProvider(): array
{
return [
'offsetSet' => ['offsetSet'],
Expand Down
2 changes: 1 addition & 1 deletion tests/Unit/Logger/LoggerTest.php
Original file line number Diff line number Diff line change
Expand Up @@ -70,7 +70,7 @@ public function __toString(): string
/**
* @return array<string, array{0: string, 1: string}>
*/
public function levelMethodProvider(): array
public static function levelMethodProvider(): array
{
return [
'emergency' => ['emergency', LogLevel::EMERGENCY],
Expand Down
2 changes: 1 addition & 1 deletion tests/Unit/Mailer/MailerTest.php
Original file line number Diff line number Diff line change
Expand Up @@ -77,7 +77,7 @@ public function testHttpAdapterGetTransportErrorsReturnsArray(string $adapterCla
/**
* @return array<string, array{0: class-string, 1: array<string, mixed>}>
*/
public function httpAdapterProvider(): array
public static function httpAdapterProvider(): array
{
return [
'mailgun' => [MailgunAdapter::class, ['api_key' => 'test', 'domain' => 'test.com']],
Expand Down
2 changes: 1 addition & 1 deletion tests/Unit/Module/Templates/OpenApiTemplatesTest.php
Original file line number Diff line number Diff line change
Expand Up @@ -81,7 +81,7 @@ public function testApiTemplatesDescribeTheApiWithAttributesOnly(string $templat
/**
* @return array<string, array{0: string}>
*/
public function apiTemplateProvider(): array
public static function apiTemplateProvider(): array
{
return [
'default-api' => ['DefaultApi'],
Expand Down
2 changes: 1 addition & 1 deletion tests/Unit/Module/Templates/RedirectTemplatesTest.php
Original file line number Diff line number Diff line change
Expand Up @@ -43,7 +43,7 @@ public function testTemplatesGoBackWithTheBackRedirect(string $template): void
/**
* @return array<string, array{0: string}>
*/
public function redirectTemplateProvider(): array
public static function redirectTemplateProvider(): array
{
return [
'demo-web' => ['DemoWeb'],
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -85,7 +85,7 @@ private function insertSession(string $id, int $ttl): void
/**
* @return array<string, array{0: string}>
*/
public function interfaceMethodProvider(): array
public static function interfaceMethodProvider(): array
{
return [
'open' => ['open'],
Expand Down
17 changes: 17 additions & 0 deletions tests/Unit/Session/Adapters/Native/NativeSessionAdapterTest.php
Original file line number Diff line number Diff line change
Expand Up @@ -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());
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -218,7 +218,7 @@ public function testDropboxListDirectory(): void

self::$response = [];

$this->assertNull($this->fs->listDirectory('test'));
$this->assertFalse($this->fs->listDirectory('test'));

}
}
Original file line number Diff line number Diff line change
Expand Up @@ -199,7 +199,7 @@ public function testGoogleDriveListDirectory(): void

self::$response = [];

$this->assertNull($this->fs->listDirectory('test'));
$this->assertFalse($this->fs->listDirectory('test'));

}
}
53 changes: 28 additions & 25 deletions tests/Unit/Tracer/ErrorHandlerTest.php
Original file line number Diff line number Diff line change
Expand Up @@ -27,30 +27,28 @@ public function setUp(): void
$this->errorHandler = new ErrorHandler();
}

public function tearDown(): void
{
restore_error_handler();
restore_exception_handler();
parent::tearDown();
}

public function testSetupRegistersErrorHandler(): void
{
$logger = Mockery::mock(Logger::class);

$this->errorHandler->setup($logger);

$currentHandler = set_error_handler(function () {
});
restore_error_handler();
try {
$currentHandler = set_error_handler(function () {
});
restore_error_handler();

$this->assertNotNull($currentHandler);
$this->assertInstanceOf(Closure::class, $currentHandler);
$this->assertNotNull($currentHandler);
$this->assertInstanceOf(Closure::class, $currentHandler);

$reflection = new ReflectionFunction($currentHandler);
$reflection = new ReflectionFunction($currentHandler);

$this->assertInstanceOf(ErrorHandler::class, $reflection->getClosureThis());
$this->assertEquals('handleError', $reflection->getName());
$this->assertInstanceOf(ErrorHandler::class, $reflection->getClosureThis());
$this->assertEquals('handleError', $reflection->getName());
} finally {
restore_error_handler();
restore_exception_handler();
}
}

public function testSetupRegistersExceptionHandler(): void
Expand All @@ -59,17 +57,22 @@ public function testSetupRegistersExceptionHandler(): void

$this->errorHandler->setup($logger);

$currentHandler = set_exception_handler(function () {
});
restore_exception_handler();
try {
$currentHandler = set_exception_handler(function () {
});
restore_exception_handler();

$this->assertNotNull($currentHandler);
$this->assertInstanceOf(Closure::class, $currentHandler);
$this->assertNotNull($currentHandler);
$this->assertInstanceOf(Closure::class, $currentHandler);

$reflection = new ReflectionFunction($currentHandler);
$reflection = new ReflectionFunction($currentHandler);

$this->assertInstanceOf(ErrorHandler::class, $reflection->getClosureThis());
$this->assertEquals('handleException', $reflection->getName());
$this->assertInstanceOf(ErrorHandler::class, $reflection->getClosureThis());
$this->assertEquals('handleException', $reflection->getName());
} finally {
restore_error_handler();
restore_exception_handler();
}
}

public function testHandleErrorThrowsErrorException(): void
Expand Down Expand Up @@ -278,7 +281,7 @@ public function testLogErrorFallsBackToErrorMethodForUnknownLevel(): void
/**
* @return array<string, array{0: int}>
*/
public function deprecationSeverityProvider(): array
public static function deprecationSeverityProvider(): array
{
return [
'deprecated' => [E_DEPRECATED],
Expand All @@ -299,7 +302,7 @@ public function testGetErrorTypeMappings(\Throwable $throwable, string $expected
/**
* @return array<string, array{0: \Throwable, 1: string}>
*/
public function errorTypeProvider(): array
public static function errorTypeProvider(): array
{
return [
'mapped-error-exception' => [new ErrorException('x', 0, E_WARNING), 'warning'],
Expand Down
2 changes: 1 addition & 1 deletion tests/Unit/Tracer/ExceptionSeverityResolverTest.php
Original file line number Diff line number Diff line change
Expand Up @@ -29,7 +29,7 @@ public function testResolveMappings(\Throwable $throwable, string $expected): vo
/**
* @return array<string, array{0: \Throwable, 1: string}>
*/
public function mappingProvider(): array
public static function mappingProvider(): array
{
return [
'mapped-error-exception' => [new ErrorException('x', 0, E_WARNING), 'warning'],
Expand Down
2 changes: 0 additions & 2 deletions tests/bootstrap.php
Original file line number Diff line number Diff line change
@@ -1,7 +1,5 @@
<?php

error_reporting(E_ALL & ~E_DEPRECATED);

if (!defined('DS')) {
define('DS', DIRECTORY_SEPARATOR);
}
Expand Down
Loading