Fix #20348: ErrorHandler::convertExceptionToError() Passing E_USER_ERROR to trigger_error() is deprecated since PHP 8.4

This commit is contained in:
Wilmer Arambula
2025-06-25 08:07:46 -04:00
committed by GitHub
parent 4c5a6c5035
commit f3eefee572
7 changed files with 169 additions and 9 deletions

View File

@ -26,6 +26,7 @@ Yii Framework 2 Change Log
- Enh #20413: Add PHPStan/Psalm annotations for `Action`, `ActionEvent`, `Application`, `DynamicModel` and `InlineAction` (max-s-lab) - Enh #20413: Add PHPStan/Psalm annotations for `Action`, `ActionEvent`, `Application`, `DynamicModel` and `InlineAction` (max-s-lab)
- Enh #20416: Add `PHPStan`/`PSalm` annotation for `owner` property in `Behavior` class (terabytesoftw) - Enh #20416: Add `PHPStan`/`PSalm` annotation for `owner` property in `Behavior` class (terabytesoftw)
- Bug #20423: `strcmp()` Passing `null` to parameter `2` ($string2) of type string is deprecated (terabytesoftw) - Bug #20423: `strcmp()` Passing `null` to parameter `2` ($string2) of type string is deprecated (terabytesoftw)
- Bug #20348: `ErrorHandler::convertExceptionToError()` Passing `E_USER_ERROR` to `trigger_error()` is deprecated since PHP `8.4` (terabytesoftw)
2.0.52 February 13, 2025 2.0.52 February 13, 2025

View File

@ -51,6 +51,41 @@ if you want to upgrade from version A to version C and there is
version B between A and C, you need to follow the instructions version B between A and C, you need to follow the instructions
for both A and B. for both A and B.
Upgrade from Yii 2.0.53
-----------------------
* `ErrorHandler::convertExceptionToError()` has been deprecated and will be removed in version 22.0.
This method was deprecated due to `PHP 8.4` deprecating the use of `E_USER_ERROR` with `trigger_error()`.
The framework now handles exceptions in `__toString()` methods more appropriately based on the PHP version.
**Before (deprecated):**
```php
public function __toString() {
try {
return $this->render();
} catch (\Throwable $e) {
ErrorHandler::convertExceptionToError($e);
return '';
}
}
```
**After (recommended):**
```php
public function __toString() {
try {
return $this->render();
} catch (\Throwable $e) {
if (PHP_VERSION_ID < 70400) {
trigger_error(ErrorHandler::convertExceptionToString($e), E_USER_ERROR);
return '';
}
throw $e;
}
}
```
Upgrade from Yii 2.0.52 Upgrade from Yii 2.0.52
----------------------- -----------------------
* There was a bug when loading fixtures into PostgreSQL database, the table sequences were not reset. If you used a work-around or if you depended on this behavior, you are advised to review your code. * There was a bug when loading fixtures into PostgreSQL database, the table sequences were not reset. If you used a work-around or if you depended on this behavior, you are advised to review your code.

View File

@ -378,6 +378,11 @@ abstract class ErrorHandler extends Component
* to PHP errors because exceptions cannot be thrown inside of them. * to PHP errors because exceptions cannot be thrown inside of them.
* @param \Throwable $exception the exception to convert to a PHP error. * @param \Throwable $exception the exception to convert to a PHP error.
* @return never * @return never
*
* @deprecated since 2.0.53. Use conditional exception throwing in `__toString()` methods instead.
* For PHP < 7.4: use `trigger_error()` directly with `convertExceptionToString()` method.
* For PHP >= 7.4: throw the exception directly as `__toString()` supports exceptions.
* This method will be removed in 2.2.0.
*/ */
public static function convertExceptionToError($exception) public static function convertExceptionToError($exception)
{ {

View File

@ -59,9 +59,14 @@ abstract class BaseMessage extends BaseObject implements MessageInterface
// use trigger_error to bypass this limitation // use trigger_error to bypass this limitation
try { try {
return $this->toString(); return $this->toString();
} catch (\Exception $e) { } catch (\Throwable $e) {
ErrorHandler::convertExceptionToError($e); if (PHP_VERSION_ID < 70400) {
return ''; trigger_error(ErrorHandler::convertExceptionToString($e), E_USER_ERROR);
return '';
}
throw $e;
} }
} }
} }

View File

@ -174,12 +174,14 @@ class ActiveField extends Component
// use trigger_error to bypass this limitation // use trigger_error to bypass this limitation
try { try {
return $this->render(); return $this->render();
} catch (\Exception $e) {
ErrorHandler::convertExceptionToError($e);
return '';
} catch (\Throwable $e) { } catch (\Throwable $e) {
ErrorHandler::convertExceptionToError($e); if (PHP_VERSION_ID < 70400) {
return ''; trigger_error(ErrorHandler::convertExceptionToString($e), E_USER_ERROR);
return '';
}
throw $e;
} }
} }

View File

@ -60,6 +60,54 @@ class BaseMessageTest extends TestCase
$message = $mailer->compose(); $message = $mailer->compose();
$this->assertEquals($message->toString(), '' . $message); $this->assertEquals($message->toString(), '' . $message);
} }
public function testExceptionToString()
{
if (PHP_VERSION_ID < 70400) {
$this->markTestSkipped('This test is for PHP 7.4+ only');
}
$message = new TestMessageWithException();
$this->expectException(\Exception::class);
$this->expectExceptionMessage('Test exception in toString.');
(string) $message;
}
public function testExceptionToStringLegacy()
{
if (PHP_VERSION_ID >= 70400) {
$this->markTestSkipped('This test is for PHP < 7.4 only');
}
$message = new TestMessageWithException();
$errorTriggered = false;
$errorMessage = '';
set_error_handler(
function ($severity, $message, $file, $line) use (&$errorTriggered, &$errorMessage) {
if ($severity === E_USER_ERROR) {
$errorTriggered = true;
$errorMessage = $message;
return true;
}
return false;
},
E_USER_ERROR,
);
$result = (string) $message;
restore_error_handler();
$this->assertTrue($errorTriggered, 'E_USER_ERROR should have been triggered');
$this->assertStringContainsString('Test exception in toString.', $errorMessage);
$this->assertSame('', $result, 'Result should be an empty string');
}
} }
/** /**
@ -178,3 +226,11 @@ class TestMessage extends BaseMessage
return get_class($this); return get_class($this);
} }
} }
class TestMessageWithException extends TestMessage
{
public function toString()
{
throw new \Exception('Test exception in toString.');
}
}

View File

@ -25,7 +25,7 @@ use yii\widgets\MaskedInput;
class ActiveFieldTest extends \yiiunit\TestCase class ActiveFieldTest extends \yiiunit\TestCase
{ {
use ArraySubsetAsserts; use ArraySubsetAsserts;
/** /**
* @var ActiveFieldExtend * @var ActiveFieldExtend
*/ */
@ -700,6 +700,54 @@ HTML;
$this->assertStringContainsString('placeholder="pholder_both_direct"', (string) $widget); $this->assertStringContainsString('placeholder="pholder_both_direct"', (string) $widget);
} }
public function testExceptionToString()
{
if (PHP_VERSION_ID < 70400) {
$this->markTestSkipped('This test is for PHP 7.4+ only');
}
$field = new TestActiveFieldWithException();
$this->expectException(\Exception::class);
$this->expectExceptionMessage('Test exception in toString.');
(string) $field;
}
public function testExceptionToStringLegacy()
{
if (PHP_VERSION_ID >= 70400) {
$this->markTestSkipped('This test is for PHP < 7.4 only');
}
$field = new TestActiveFieldWithException();
$errorTriggered = false;
$errorMessage = '';
set_error_handler(
function ($severity, $message, $file, $line) use (&$errorTriggered, &$errorMessage) {
if ($severity === E_USER_ERROR) {
$errorTriggered = true;
$errorMessage = $message;
return true;
}
return false;
},
E_USER_ERROR,
);
$result = (string) $field;
restore_error_handler();
$this->assertTrue($errorTriggered, 'E_USER_ERROR should have been triggered');
$this->assertStringContainsString('Test exception in toString.', $errorMessage);
$this->assertSame('', $result, 'Result should be an empty string');
}
/** /**
* Helper methods. * Helper methods.
*/ */
@ -811,3 +859,11 @@ class TestMaskedInput extends MaskedInput
} }
} }
class TestActiveFieldWithException extends ActiveField
{
public function render($content = null)
{
throw new \Exception('Test exception in toString.');
}
}