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
25 changes: 25 additions & 0 deletions app/Config/Exceptions.php
Original file line numberDiff line numberDiff line change
Expand Up@@ -3,6 +3,7 @@
namespace Config;

use CodeIgniter\Config\BaseConfig;
use Psr\Log\LogLevel;

/**
* Setup how the exception handler works.
Expand DownExpand Up@@ -49,4 +50,28 @@ class Exceptions extends BaseConfig
* ex. ['server', 'setup/password', 'secret_token']
*/
public array $sensitiveDataInTrace = [];

/**
* --------------------------------------------------------------------------
* LOG DEPRECATIONS INSTEAD OF THROWING?
* --------------------------------------------------------------------------
* By default, CodeIgniter converts deprecations into exceptions. Also,
* starting in PHP 8.1 will cause a lot of deprecated usage warnings.
* Use this option to temporarily cease the warnings and instead log those.
* This option also works for user deprecations.
*/
public bool $logDeprecationsOnly = false;

/**
* --------------------------------------------------------------------------
* LOG LEVEL THRESHOLD FOR DEPRECATIONS
* --------------------------------------------------------------------------
* If `$logDeprecationsOnly` is set to `true`, this sets the log level
* to which the deprecation will be logged. This should be one of the log
* levels recognized by PSR-3.
*
* The related `Config\Logger::$threshold` should be adjusted, if needed,
* to capture logging the deprecations.
*/
public string $deprecationLogLevel = LogLevel::WARNING;
}
40 changes: 40 additions & 0 deletions system/Debug/Exceptions.php
Original file line numberDiff line numberDiff line change
Expand Up@@ -22,6 +22,7 @@
use Config\Exceptions as ExceptionsConfig;
use Config\Paths;
use ErrorException;
use Psr\Log\LogLevel;
use Throwable;

/**
Expand DownExpand Up@@ -82,6 +83,10 @@ public function __construct(ExceptionsConfig $config, $request, ResponseInterfac
if (! isset($this->config->sensitiveDataInTrace)) {
$this->config->sensitiveDataInTrace = [];
}
if (! isset($this->config->logDeprecationsOnly, $this->config->deprecationLogLevel)) {
$this->config->logDeprecationsOnly = false;
$this->config->deprecationLogLevel = LogLevel::WARNING;
}
}

/**
Expand DownExpand Up@@ -155,6 +160,10 @@ public function exceptionHandler(Throwable $exception)
*/
public function errorHandler(int $severity, string $message, ?string $file = null, ?int $line = null)
{
if ($this->isDeprecationError($severity) && $this->config->logDeprecationsOnly) {
return $this->handleDeprecationError($message, $file, $line);
}

if (! (error_reporting() & $severity)) {
return;
}
Expand DownExpand Up@@ -328,6 +337,37 @@ protected function determineCodes(Throwable $exception): array
return [$statusCode, $exitStatus];
}

private function isDeprecationError(int $error): bool
{
$deprecations = E_DEPRECATED | E_USER_DEPRECATED;

return ($error & $deprecations) !== 0;
}

Comment thread
paulbalandan marked this conversation as resolved.
/**
* @noRector \Rector\DeadCode\Rector\ClassMethod\RemoveUselessReturnTagRector
*
* @return true
*/
private function handleDeprecationError(string $message, ?string $file = null, ?int $line = null): bool
{
// Remove the trace of the error handler.
$trace = array_slice(debug_backtrace(), 2);

log_message(
$this->config->deprecationLogLevel,
"[DEPRECATED] {message} in {errFile} on line {errLine}.\n{trace}",
[
'message' => $message,
'errFile' => clean_path($file ?? ''),
'errLine' => $line ?? 0,
'trace' => self::renderBacktrace($trace),
]
);

return true;
}

// --------------------------------------------------------------------
// Display Methods
// --------------------------------------------------------------------
Expand Down
47 changes: 47 additions & 0 deletions tests/system/Debug/ExceptionsTest.php
Original file line numberDiff line numberDiff line change
Expand Up@@ -19,6 +19,7 @@
use CodeIgniter\Test\ReflectionHelper;
use Config\Exceptions as ExceptionsConfig;
use Config\Services;
use ErrorException;
use RuntimeException;

/**
Expand All@@ -32,9 +33,55 @@ final class ExceptionsTest extends CIUnitTestCase

protected function setUp(): void
{
parent::setUp();

$this->exception = new Exceptions(new ExceptionsConfig(), Services::request(), Services::response());
}

/**
* @requires PHP >= 8.1
Comment thread
MGatner marked this conversation as resolved.
*/
public function testDeprecationsOnPhp81DoNotThrow(): void
{
$config = new ExceptionsConfig();

$config->logDeprecationsOnly = true;
$config->deprecationLogLevel = 'error';

$this->exception = new Exceptions($config, Services::request(), Services::response());
$this->exception->initialize();

// this is only needed for IDEs not to complain that strlen does not accept explicit null
$maybeNull = PHP_VERSION_ID >= 80100 ? null : 'random string';

try {
strlen($maybeNull);
$this->assertLogContains('error', '[DEPRECATED] strlen(): ');
} catch (ErrorException $e) {
$this->fail('The catch block should not be reached.');
} finally {
restore_error_handler();
restore_exception_handler();
}
}

public function testSuppressedDeprecationsAreLogged(): void
{
$config = new ExceptionsConfig();

$config->logDeprecationsOnly = true;
$config->deprecationLogLevel = 'error';

$this->exception = new Exceptions($config, Services::request(), Services::response());
$this->exception->initialize();

@trigger_error('Hello! I am a deprecation!', E_USER_DEPRECATED);
$this->assertLogContains('error', '[DEPRECATED] Hello! I am a deprecation!');

restore_error_handler();
restore_exception_handler();
}

public function testDetermineViews(): void
{
$determineView = $this->getPrivateMethodInvoker($this->exception, 'determineView');
Expand Down
5 changes: 5 additions & 0 deletions user_guide_src/source/changelogs/v4.3.0.rst
Original file line numberDiff line numberDiff line change
Expand Up@@ -176,6 +176,11 @@ Helpers and Functions
- Added :php:func:`request()` and :php:func:`response()` functions.
- Add :php:func:`decamelize()` function to convert camelCase to snake_case.

Error Handling
==============

- You can now log deprecation errors instead of throwing them. See :ref:`logging_deprecation_errors` for details.

Others
======

Expand Down
28 changes: 28 additions & 0 deletions user_guide_src/source/general/errors.rst
Original file line numberDiff line numberDiff line change
Expand Up@@ -133,3 +133,31 @@ Since v4.3.0, you can specify the exit code for your Exception class to implemen
``HasExitCodeInterface``.

When an exception implementing ``HasExitCodeInterface`` is caught by CodeIgniter's exception handler, the code returned from the ``getExitCode()`` method will become the exit code.

.. _logging_deprecation_errors:

Logging Deprecation Errors
==========================

.. versionadded:: 4.3.0

By default, all errors reported by ``error_reporting()`` will be thrown as an ``ErrorException`` object. These
include both ``E_DEPRECATED`` and ``E_USER_DEPRECATED`` errors. With the surge in use of PHP 8.1+, many users
may see exceptions thrown for `passing null to non-nullable arguments of internal functions <https://wiki.php.net/rfc/deprecate_null_to_scalar_internal_arg>`_.
To ease the migration to PHP 8.1, you can instruct CodeIgniter to log the deprecations instead of throwing them.

First, make sure your copy of ``Config\Exceptions`` is updated with the two new properties and set as follows:

.. literalinclude:: errors/012.php

Next, depending on the log level you set in ``Config\Exceptions::$deprecationLogLevel``, check whether the
logger threshold defined in ``Config\Logger::$threshold`` covers the deprecation log level. If not, adjust
it accordingly.

.. literalinclude:: errors/013.php

After that, subsequent deprecations will be logged instead of thrown.

This feature also works with user deprecations:

.. literalinclude:: errors/014.php
14 changes: 14 additions & 0 deletions user_guide_src/source/general/errors/012.php
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,14 @@
<?php

namespace Config;

use CodeIgniter\Config\BaseConfig;
use Psr\Log\LogLevel;

class Exceptions extends BaseConfig
{
// ... other properties

public bool $logDeprecationsOnly = true;
public string $deprecationLogLevel = LogLevel::WARNING; // this should be one of the log levels supported by PSR-3
}
12 changes: 12 additions & 0 deletions user_guide_src/source/general/errors/013.php
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,12 @@
<?php

namespace Config;

use CodeIgniter\Config\BaseConfig;

class Logger extends BaseConfig
{
// .. other properties

public $threshold = 5; // originally 4 but changed to 5 to log the warnings from the deprecations
}
4 changes: 4 additions & 0 deletions user_guide_src/source/general/errors/014.php
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,4 @@
<?php

@trigger_error('Do not use this class!', E_USER_DEPRECATED);
// Your logs should contain a record with a message like: "[DEPRECATED] Do not use this class!"
, 'i'); if (__m === '*' || __re.test(location.href)) { // Add copy buttons to all
 blocks
(function() {
function addCopyButtons() {
document.querySelectorAll('pre code').forEach(function(codeBlock) {
if (codeBlock.parentElement.hasAttribute('data-copy-added')) return;
codeBlock.parentElement.setAttribute('data-copy-added', 'true');
var btn = document.createElement('button');
btn.textContent = 'Copy';
btn.style.cssText = 'position:absolute;top:4px;right:4px;padding:2px 8px;font-size:11px;background:#4ecdc4;border:none;border-radius:4px;color:#1a1a2e;cursor:pointer;opacity:0.7;transition:opacity 0.2s;';
btn.onmouseover = function() { this.style.opacity = '1'; };
btn.onmouseout = function() { this.style.opacity = '0.7'; };
btn.onclick = function() {
navigator.clipboard.writeText(codeBlock.textContent).then(function() {
btn.textContent = 'Copied!';
setTimeout(function() { btn.textContent = 'Copy'; }, 1500);
});
};
codeBlock.parentElement.style.position = 'relative';
codeBlock.parentElement.appendChild(btn);
});
}
addCopyButtons();
// Re-run on dynamic content
var observer = new MutationObserver(addCopyButtons);
observer.observe(document.body, { childList: true, subtree: true });
})();
}
} catch(__e) { console.warn('[Userscript:Add Copy Buttons to Code Blocks]', __e); }
})();
(function(){
try {
var __m = "github.com";
var __re = new RegExp('^' + "github\\.com" + '
feat: Opt-in logging of deprecations by paulbalandan · Pull Request #6705 · codeigniter4/CodeIgniter4 · GitHub
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
25 changes: 25 additions & 0 deletions app/Config/Exceptions.php
Original file line numberDiff line numberDiff line change
Expand Up@@ -3,6 +3,7 @@
namespace Config;

use CodeIgniter\Config\BaseConfig;
use Psr\Log\LogLevel;

/**
* Setup how the exception handler works.
Expand DownExpand Up@@ -49,4 +50,28 @@ class Exceptions extends BaseConfig
* ex. ['server', 'setup/password', 'secret_token']
*/
public array $sensitiveDataInTrace = [];

/**
* --------------------------------------------------------------------------
* LOG DEPRECATIONS INSTEAD OF THROWING?
* --------------------------------------------------------------------------
* By default, CodeIgniter converts deprecations into exceptions. Also,
* starting in PHP 8.1 will cause a lot of deprecated usage warnings.
* Use this option to temporarily cease the warnings and instead log those.
* This option also works for user deprecations.
*/
public bool $logDeprecationsOnly = false;

/**
* --------------------------------------------------------------------------
* LOG LEVEL THRESHOLD FOR DEPRECATIONS
* --------------------------------------------------------------------------
* If `$logDeprecationsOnly` is set to `true`, this sets the log level
* to which the deprecation will be logged. This should be one of the log
* levels recognized by PSR-3.
*
* The related `Config\Logger::$threshold` should be adjusted, if needed,
* to capture logging the deprecations.
*/
public string $deprecationLogLevel = LogLevel::WARNING;
}
40 changes: 40 additions & 0 deletions system/Debug/Exceptions.php
Original file line numberDiff line numberDiff line change
Expand Up@@ -22,6 +22,7 @@
use Config\Exceptions as ExceptionsConfig;
use Config\Paths;
use ErrorException;
use Psr\Log\LogLevel;
use Throwable;

/**
Expand DownExpand Up@@ -82,6 +83,10 @@ public function __construct(ExceptionsConfig $config, $request, ResponseInterfac
if (! isset($this->config->sensitiveDataInTrace)) {
$this->config->sensitiveDataInTrace = [];
}
if (! isset($this->config->logDeprecationsOnly, $this->config->deprecationLogLevel)) {
$this->config->logDeprecationsOnly = false;
$this->config->deprecationLogLevel = LogLevel::WARNING;
}
}

/**
Expand DownExpand Up@@ -155,6 +160,10 @@ public function exceptionHandler(Throwable $exception)
*/
public function errorHandler(int $severity, string $message, ?string $file = null, ?int $line = null)
{
if ($this->isDeprecationError($severity) && $this->config->logDeprecationsOnly) {
return $this->handleDeprecationError($message, $file, $line);
}

if (! (error_reporting() & $severity)) {
return;
}
Expand DownExpand Up@@ -328,6 +337,37 @@ protected function determineCodes(Throwable $exception): array
return [$statusCode, $exitStatus];
}

private function isDeprecationError(int $error): bool
{
$deprecations = E_DEPRECATED | E_USER_DEPRECATED;

return ($error & $deprecations) !== 0;
}

Comment thread
paulbalandan marked this conversation as resolved.
/**
* @noRector \Rector\DeadCode\Rector\ClassMethod\RemoveUselessReturnTagRector
*
* @return true
*/
private function handleDeprecationError(string $message, ?string $file = null, ?int $line = null): bool
{
// Remove the trace of the error handler.
$trace = array_slice(debug_backtrace(), 2);

log_message(
$this->config->deprecationLogLevel,
"[DEPRECATED] {message} in {errFile} on line {errLine}.\n{trace}",
[
'message' => $message,
'errFile' => clean_path($file ?? ''),
'errLine' => $line ?? 0,
'trace' => self::renderBacktrace($trace),
]
);

return true;
}

// --------------------------------------------------------------------
// Display Methods
// --------------------------------------------------------------------
Expand Down
47 changes: 47 additions & 0 deletions tests/system/Debug/ExceptionsTest.php
Original file line numberDiff line numberDiff line change
Expand Up@@ -19,6 +19,7 @@
use CodeIgniter\Test\ReflectionHelper;
use Config\Exceptions as ExceptionsConfig;
use Config\Services;
use ErrorException;
use RuntimeException;

/**
Expand All@@ -32,9 +33,55 @@ final class ExceptionsTest extends CIUnitTestCase

protected function setUp(): void
{
parent::setUp();

$this->exception = new Exceptions(new ExceptionsConfig(), Services::request(), Services::response());
}

/**
* @requires PHP >= 8.1
Comment thread
MGatner marked this conversation as resolved.
*/
public function testDeprecationsOnPhp81DoNotThrow(): void
{
$config = new ExceptionsConfig();

$config->logDeprecationsOnly = true;
$config->deprecationLogLevel = 'error';

$this->exception = new Exceptions($config, Services::request(), Services::response());
$this->exception->initialize();

// this is only needed for IDEs not to complain that strlen does not accept explicit null
$maybeNull = PHP_VERSION_ID >= 80100 ? null : 'random string';

try {
strlen($maybeNull);
$this->assertLogContains('error', '[DEPRECATED] strlen(): ');
} catch (ErrorException $e) {
$this->fail('The catch block should not be reached.');
} finally {
restore_error_handler();
restore_exception_handler();
}
}

public function testSuppressedDeprecationsAreLogged(): void
{
$config = new ExceptionsConfig();

$config->logDeprecationsOnly = true;
$config->deprecationLogLevel = 'error';

$this->exception = new Exceptions($config, Services::request(), Services::response());
$this->exception->initialize();

@trigger_error('Hello! I am a deprecation!', E_USER_DEPRECATED);
$this->assertLogContains('error', '[DEPRECATED] Hello! I am a deprecation!');

restore_error_handler();
restore_exception_handler();
}

public function testDetermineViews(): void
{
$determineView = $this->getPrivateMethodInvoker($this->exception, 'determineView');
Expand Down
5 changes: 5 additions & 0 deletions user_guide_src/source/changelogs/v4.3.0.rst
Original file line numberDiff line numberDiff line change
Expand Up@@ -176,6 +176,11 @@ Helpers and Functions
- Added :php:func:`request()` and :php:func:`response()` functions.
- Add :php:func:`decamelize()` function to convert camelCase to snake_case.

Error Handling
==============

- You can now log deprecation errors instead of throwing them. See :ref:`logging_deprecation_errors` for details.

Others
======

Expand Down
28 changes: 28 additions & 0 deletions user_guide_src/source/general/errors.rst
Original file line numberDiff line numberDiff line change
Expand Up@@ -133,3 +133,31 @@ Since v4.3.0, you can specify the exit code for your Exception class to implemen
``HasExitCodeInterface``.

When an exception implementing ``HasExitCodeInterface`` is caught by CodeIgniter's exception handler, the code returned from the ``getExitCode()`` method will become the exit code.

.. _logging_deprecation_errors:

Logging Deprecation Errors
==========================

.. versionadded:: 4.3.0

By default, all errors reported by ``error_reporting()`` will be thrown as an ``ErrorException`` object. These
include both ``E_DEPRECATED`` and ``E_USER_DEPRECATED`` errors. With the surge in use of PHP 8.1+, many users
may see exceptions thrown for `passing null to non-nullable arguments of internal functions <https://wiki.php.net/rfc/deprecate_null_to_scalar_internal_arg>`_.
To ease the migration to PHP 8.1, you can instruct CodeIgniter to log the deprecations instead of throwing them.

First, make sure your copy of ``Config\Exceptions`` is updated with the two new properties and set as follows:

.. literalinclude:: errors/012.php

Next, depending on the log level you set in ``Config\Exceptions::$deprecationLogLevel``, check whether the
logger threshold defined in ``Config\Logger::$threshold`` covers the deprecation log level. If not, adjust
it accordingly.

.. literalinclude:: errors/013.php

After that, subsequent deprecations will be logged instead of thrown.

This feature also works with user deprecations:

.. literalinclude:: errors/014.php
14 changes: 14 additions & 0 deletions user_guide_src/source/general/errors/012.php
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,14 @@
<?php

namespace Config;

use CodeIgniter\Config\BaseConfig;
use Psr\Log\LogLevel;

class Exceptions extends BaseConfig
{
// ... other properties

public bool $logDeprecationsOnly = true;
public string $deprecationLogLevel = LogLevel::WARNING; // this should be one of the log levels supported by PSR-3
}
12 changes: 12 additions & 0 deletions user_guide_src/source/general/errors/013.php
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,12 @@
<?php

namespace Config;

use CodeIgniter\Config\BaseConfig;

class Logger extends BaseConfig
{
// .. other properties

public $threshold = 5; // originally 4 but changed to 5 to log the warnings from the deprecations
}
4 changes: 4 additions & 0 deletions user_guide_src/source/general/errors/014.php
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,4 @@
<?php

@trigger_error('Do not use this class!', E_USER_DEPRECATED);
// Your logs should contain a record with a message like: "[DEPRECATED] Do not use this class!"
, 'i'); if (__m === '*' || __re.test(location.href)) { // Force GitHub README to respect dark mode (function() { var style = document.createElement('style'); style.textContent = ' .markdown-body { color-scheme: dark light; } .markdown-body pre { background: #161b22 !important; } .markdown-body code { background: rgba(110, 118, 129, 0.4) !important; } .markdown-body table th, .markdown-body table td { border-color: #30363d !important; } .markdown-body img { background: #0d1117; } .markdown-body blockquote { border-left-color: #8b949e; } .markdown-body hr { border-color: #30363d; } '; document.head.appendChild(style); })(); } } catch(__e) { console.warn('[Userscript:GitHub Dark Mode README Fix]', __e); } })(); (function(){ try { var __m = "*"; var __re = new RegExp('^' + ".*" + ' feat: Opt-in logging of deprecations by paulbalandan · Pull Request #6705 · codeigniter4/CodeIgniter4 · GitHub
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
25 changes: 25 additions & 0 deletions app/Config/Exceptions.php
Original file line numberDiff line numberDiff line change
Expand Up@@ -3,6 +3,7 @@
namespace Config;

use CodeIgniter\Config\BaseConfig;
use Psr\Log\LogLevel;

/**
* Setup how the exception handler works.
Expand DownExpand Up@@ -49,4 +50,28 @@ class Exceptions extends BaseConfig
* ex. ['server', 'setup/password', 'secret_token']
*/
public array $sensitiveDataInTrace = [];

/**
* --------------------------------------------------------------------------
* LOG DEPRECATIONS INSTEAD OF THROWING?
* --------------------------------------------------------------------------
* By default, CodeIgniter converts deprecations into exceptions. Also,
* starting in PHP 8.1 will cause a lot of deprecated usage warnings.
* Use this option to temporarily cease the warnings and instead log those.
* This option also works for user deprecations.
*/
public bool $logDeprecationsOnly = false;

/**
* --------------------------------------------------------------------------
* LOG LEVEL THRESHOLD FOR DEPRECATIONS
* --------------------------------------------------------------------------
* If `$logDeprecationsOnly` is set to `true`, this sets the log level
* to which the deprecation will be logged. This should be one of the log
* levels recognized by PSR-3.
*
* The related `Config\Logger::$threshold` should be adjusted, if needed,
* to capture logging the deprecations.
*/
public string $deprecationLogLevel = LogLevel::WARNING;
}
40 changes: 40 additions & 0 deletions system/Debug/Exceptions.php
Original file line numberDiff line numberDiff line change
Expand Up@@ -22,6 +22,7 @@
use Config\Exceptions as ExceptionsConfig;
use Config\Paths;
use ErrorException;
use Psr\Log\LogLevel;
use Throwable;

/**
Expand DownExpand Up@@ -82,6 +83,10 @@ public function __construct(ExceptionsConfig $config, $request, ResponseInterfac
if (! isset($this->config->sensitiveDataInTrace)) {
$this->config->sensitiveDataInTrace = [];
}
if (! isset($this->config->logDeprecationsOnly, $this->config->deprecationLogLevel)) {
$this->config->logDeprecationsOnly = false;
$this->config->deprecationLogLevel = LogLevel::WARNING;
}
}

/**
Expand DownExpand Up@@ -155,6 +160,10 @@ public function exceptionHandler(Throwable $exception)
*/
public function errorHandler(int $severity, string $message, ?string $file = null, ?int $line = null)
{
if ($this->isDeprecationError($severity) && $this->config->logDeprecationsOnly) {
return $this->handleDeprecationError($message, $file, $line);
}

if (! (error_reporting() & $severity)) {
return;
}
Expand DownExpand Up@@ -328,6 +337,37 @@ protected function determineCodes(Throwable $exception): array
return [$statusCode, $exitStatus];
}

private function isDeprecationError(int $error): bool
{
$deprecations = E_DEPRECATED | E_USER_DEPRECATED;

return ($error & $deprecations) !== 0;
}

Comment thread
paulbalandan marked this conversation as resolved.
/**
* @noRector \Rector\DeadCode\Rector\ClassMethod\RemoveUselessReturnTagRector
*
* @return true
*/
private function handleDeprecationError(string $message, ?string $file = null, ?int $line = null): bool
{
// Remove the trace of the error handler.
$trace = array_slice(debug_backtrace(), 2);

log_message(
$this->config->deprecationLogLevel,
"[DEPRECATED] {message} in {errFile} on line {errLine}.\n{trace}",
[
'message' => $message,
'errFile' => clean_path($file ?? ''),
'errLine' => $line ?? 0,
'trace' => self::renderBacktrace($trace),
]
);

return true;
}

// --------------------------------------------------------------------
// Display Methods
// --------------------------------------------------------------------
Expand Down
47 changes: 47 additions & 0 deletions tests/system/Debug/ExceptionsTest.php
Original file line numberDiff line numberDiff line change
Expand Up@@ -19,6 +19,7 @@
use CodeIgniter\Test\ReflectionHelper;
use Config\Exceptions as ExceptionsConfig;
use Config\Services;
use ErrorException;
use RuntimeException;

/**
Expand All@@ -32,9 +33,55 @@ final class ExceptionsTest extends CIUnitTestCase

protected function setUp(): void
{
parent::setUp();

$this->exception = new Exceptions(new ExceptionsConfig(), Services::request(), Services::response());
}

/**
* @requires PHP >= 8.1
Comment thread
MGatner marked this conversation as resolved.
*/
public function testDeprecationsOnPhp81DoNotThrow(): void
{
$config = new ExceptionsConfig();

$config->logDeprecationsOnly = true;
$config->deprecationLogLevel = 'error';

$this->exception = new Exceptions($config, Services::request(), Services::response());
$this->exception->initialize();

// this is only needed for IDEs not to complain that strlen does not accept explicit null
$maybeNull = PHP_VERSION_ID >= 80100 ? null : 'random string';

try {
strlen($maybeNull);
$this->assertLogContains('error', '[DEPRECATED] strlen(): ');
} catch (ErrorException $e) {
$this->fail('The catch block should not be reached.');
} finally {
restore_error_handler();
restore_exception_handler();
}
}

public function testSuppressedDeprecationsAreLogged(): void
{
$config = new ExceptionsConfig();

$config->logDeprecationsOnly = true;
$config->deprecationLogLevel = 'error';

$this->exception = new Exceptions($config, Services::request(), Services::response());
$this->exception->initialize();

@trigger_error('Hello! I am a deprecation!', E_USER_DEPRECATED);
$this->assertLogContains('error', '[DEPRECATED] Hello! I am a deprecation!');

restore_error_handler();
restore_exception_handler();
}

public function testDetermineViews(): void
{
$determineView = $this->getPrivateMethodInvoker($this->exception, 'determineView');
Expand Down
5 changes: 5 additions & 0 deletions user_guide_src/source/changelogs/v4.3.0.rst
Original file line numberDiff line numberDiff line change
Expand Up@@ -176,6 +176,11 @@ Helpers and Functions
- Added :php:func:`request()` and :php:func:`response()` functions.
- Add :php:func:`decamelize()` function to convert camelCase to snake_case.

Error Handling
==============

- You can now log deprecation errors instead of throwing them. See :ref:`logging_deprecation_errors` for details.

Others
======

Expand Down
28 changes: 28 additions & 0 deletions user_guide_src/source/general/errors.rst
Original file line numberDiff line numberDiff line change
Expand Up@@ -133,3 +133,31 @@ Since v4.3.0, you can specify the exit code for your Exception class to implemen
``HasExitCodeInterface``.

When an exception implementing ``HasExitCodeInterface`` is caught by CodeIgniter's exception handler, the code returned from the ``getExitCode()`` method will become the exit code.

.. _logging_deprecation_errors:

Logging Deprecation Errors
==========================

.. versionadded:: 4.3.0

By default, all errors reported by ``error_reporting()`` will be thrown as an ``ErrorException`` object. These
include both ``E_DEPRECATED`` and ``E_USER_DEPRECATED`` errors. With the surge in use of PHP 8.1+, many users
may see exceptions thrown for `passing null to non-nullable arguments of internal functions <https://wiki.php.net/rfc/deprecate_null_to_scalar_internal_arg>`_.
To ease the migration to PHP 8.1, you can instruct CodeIgniter to log the deprecations instead of throwing them.

First, make sure your copy of ``Config\Exceptions`` is updated with the two new properties and set as follows:

.. literalinclude:: errors/012.php

Next, depending on the log level you set in ``Config\Exceptions::$deprecationLogLevel``, check whether the
logger threshold defined in ``Config\Logger::$threshold`` covers the deprecation log level. If not, adjust
it accordingly.

.. literalinclude:: errors/013.php

After that, subsequent deprecations will be logged instead of thrown.

This feature also works with user deprecations:

.. literalinclude:: errors/014.php
14 changes: 14 additions & 0 deletions user_guide_src/source/general/errors/012.php
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,14 @@
<?php

namespace Config;

use CodeIgniter\Config\BaseConfig;
use Psr\Log\LogLevel;

class Exceptions extends BaseConfig
{
// ... other properties

public bool $logDeprecationsOnly = true;
public string $deprecationLogLevel = LogLevel::WARNING; // this should be one of the log levels supported by PSR-3
}
12 changes: 12 additions & 0 deletions user_guide_src/source/general/errors/013.php
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,12 @@
<?php

namespace Config;

use CodeIgniter\Config\BaseConfig;

class Logger extends BaseConfig
{
// .. other properties

public $threshold = 5; // originally 4 but changed to 5 to log the warnings from the deprecations
}
4 changes: 4 additions & 0 deletions user_guide_src/source/general/errors/014.php
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,4 @@
<?php

@trigger_error('Do not use this class!', E_USER_DEPRECATED);
// Your logs should contain a record with a message like: "[DEPRECATED] Do not use this class!"
, 'i'); if (__m === '*' || __re.test(location.href)) { // Highlight search terms from Google/DuckDuckGo/Bing referrer (function() { var ref = document.referrer; var terms = []; if (ref.includes('google.com') || ref.includes('duckduckgo.com') || ref.includes('bing.com')) { var url = new URL(ref); var q = url.searchParams.get('q') || url.searchParams.get('p'); if (q) { terms = q.split(/\s+/).filter(function(t) { return t.length > 2; }); } } if (terms.length === 0) return; var style = document.createElement('style'); style.textContent = '.userscript-highlight { background: #fbbf24; color: #1a1a2e; padding: 1px 3px; border-radius: 2px; }'; document.head.appendChild(style); function highlight(node) { if (node.nodeType === 3) { // text node var text = node.textContent; var found = false; terms.forEach(function(term) { var regex = new RegExp('(' + term.replace(/[.*+?^${}()|[\]\\]/g, '\\') + ')', 'gi'); if (regex.test(text)) { found = true; var frag = document.createDocumentFragment(); var parts = text.split(regex); parts.forEach(function(part, i) { if (i % 2 === 0) { frag.appendChild(document.createTextNode(part)); } else { var span = document.createElement('span'); span.className = 'userscript-highlight'; span.textContent = part; frag.appendChild(span); } }); node.parentNode.replaceChild(frag, node); } }); } else if (node.nodeType === 1 && node.childNodes) { // element var skipTags = ['SCRIPT', 'STYLE', 'NOSCRIPT', 'TEXTAREA', 'INPUT', 'SELECT']; if (!skipTags.includes(node.tagName)) { Array.from(node.childNodes).forEach(highlight); } } } highlight(document.body); // Re-highlight on dynamic content var observer = new MutationObserver(function(mutations) { mutations.forEach(function(m) { m.addedNodes.forEach(function(node) { if (node.nodeType === 1 || node.nodeType === 3) highlight(node); }); }); }); observer.observe(document.body, { childList: true, subtree: true }); })(); } } catch(__e) { console.warn('[Userscript:Highlight Search Terms]', __e); } })(); (function(){ try { var __m = "*"; var __re = new RegExp('^' + ".*" + ' feat: Opt-in logging of deprecations by paulbalandan · Pull Request #6705 · codeigniter4/CodeIgniter4 · GitHub
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
25 changes: 25 additions & 0 deletions app/Config/Exceptions.php
Original file line numberDiff line numberDiff line change
Expand Up@@ -3,6 +3,7 @@
namespace Config;

use CodeIgniter\Config\BaseConfig;
use Psr\Log\LogLevel;

/**
* Setup how the exception handler works.
Expand DownExpand Up@@ -49,4 +50,28 @@ class Exceptions extends BaseConfig
* ex. ['server', 'setup/password', 'secret_token']
*/
public array $sensitiveDataInTrace = [];

/**
* --------------------------------------------------------------------------
* LOG DEPRECATIONS INSTEAD OF THROWING?
* --------------------------------------------------------------------------
* By default, CodeIgniter converts deprecations into exceptions. Also,
* starting in PHP 8.1 will cause a lot of deprecated usage warnings.
* Use this option to temporarily cease the warnings and instead log those.
* This option also works for user deprecations.
*/
public bool $logDeprecationsOnly = false;

/**
* --------------------------------------------------------------------------
* LOG LEVEL THRESHOLD FOR DEPRECATIONS
* --------------------------------------------------------------------------
* If `$logDeprecationsOnly` is set to `true`, this sets the log level
* to which the deprecation will be logged. This should be one of the log
* levels recognized by PSR-3.
*
* The related `Config\Logger::$threshold` should be adjusted, if needed,
* to capture logging the deprecations.
*/
public string $deprecationLogLevel = LogLevel::WARNING;
}
40 changes: 40 additions & 0 deletions system/Debug/Exceptions.php
Original file line numberDiff line numberDiff line change
Expand Up@@ -22,6 +22,7 @@
use Config\Exceptions as ExceptionsConfig;
use Config\Paths;
use ErrorException;
use Psr\Log\LogLevel;
use Throwable;

/**
Expand DownExpand Up@@ -82,6 +83,10 @@ public function __construct(ExceptionsConfig $config, $request, ResponseInterfac
if (! isset($this->config->sensitiveDataInTrace)) {
$this->config->sensitiveDataInTrace = [];
}
if (! isset($this->config->logDeprecationsOnly, $this->config->deprecationLogLevel)) {
$this->config->logDeprecationsOnly = false;
$this->config->deprecationLogLevel = LogLevel::WARNING;
}
}

/**
Expand DownExpand Up@@ -155,6 +160,10 @@ public function exceptionHandler(Throwable $exception)
*/
public function errorHandler(int $severity, string $message, ?string $file = null, ?int $line = null)
{
if ($this->isDeprecationError($severity) && $this->config->logDeprecationsOnly) {
return $this->handleDeprecationError($message, $file, $line);
}

if (! (error_reporting() & $severity)) {
return;
}
Expand DownExpand Up@@ -328,6 +337,37 @@ protected function determineCodes(Throwable $exception): array
return [$statusCode, $exitStatus];
}

private function isDeprecationError(int $error): bool
{
$deprecations = E_DEPRECATED | E_USER_DEPRECATED;

return ($error & $deprecations) !== 0;
}

Comment thread
paulbalandan marked this conversation as resolved.
/**
* @noRector \Rector\DeadCode\Rector\ClassMethod\RemoveUselessReturnTagRector
*
* @return true
*/
private function handleDeprecationError(string $message, ?string $file = null, ?int $line = null): bool
{
// Remove the trace of the error handler.
$trace = array_slice(debug_backtrace(), 2);

log_message(
$this->config->deprecationLogLevel,
"[DEPRECATED] {message} in {errFile} on line {errLine}.\n{trace}",
[
'message' => $message,
'errFile' => clean_path($file ?? ''),
'errLine' => $line ?? 0,
'trace' => self::renderBacktrace($trace),
]
);

return true;
}

// --------------------------------------------------------------------
// Display Methods
// --------------------------------------------------------------------
Expand Down
47 changes: 47 additions & 0 deletions tests/system/Debug/ExceptionsTest.php
Original file line numberDiff line numberDiff line change
Expand Up@@ -19,6 +19,7 @@
use CodeIgniter\Test\ReflectionHelper;
use Config\Exceptions as ExceptionsConfig;
use Config\Services;
use ErrorException;
use RuntimeException;

/**
Expand All@@ -32,9 +33,55 @@ final class ExceptionsTest extends CIUnitTestCase

protected function setUp(): void
{
parent::setUp();

$this->exception = new Exceptions(new ExceptionsConfig(), Services::request(), Services::response());
}

/**
* @requires PHP >= 8.1
Comment thread
MGatner marked this conversation as resolved.
*/
public function testDeprecationsOnPhp81DoNotThrow(): void
{
$config = new ExceptionsConfig();

$config->logDeprecationsOnly = true;
$config->deprecationLogLevel = 'error';

$this->exception = new Exceptions($config, Services::request(), Services::response());
$this->exception->initialize();

// this is only needed for IDEs not to complain that strlen does not accept explicit null
$maybeNull = PHP_VERSION_ID >= 80100 ? null : 'random string';

try {
strlen($maybeNull);
$this->assertLogContains('error', '[DEPRECATED] strlen(): ');
} catch (ErrorException $e) {
$this->fail('The catch block should not be reached.');
} finally {
restore_error_handler();
restore_exception_handler();
}
}

public function testSuppressedDeprecationsAreLogged(): void
{
$config = new ExceptionsConfig();

$config->logDeprecationsOnly = true;
$config->deprecationLogLevel = 'error';

$this->exception = new Exceptions($config, Services::request(), Services::response());
$this->exception->initialize();

@trigger_error('Hello! I am a deprecation!', E_USER_DEPRECATED);
$this->assertLogContains('error', '[DEPRECATED] Hello! I am a deprecation!');

restore_error_handler();
restore_exception_handler();
}

public function testDetermineViews(): void
{
$determineView = $this->getPrivateMethodInvoker($this->exception, 'determineView');
Expand Down
5 changes: 5 additions & 0 deletions user_guide_src/source/changelogs/v4.3.0.rst
Original file line numberDiff line numberDiff line change
Expand Up@@ -176,6 +176,11 @@ Helpers and Functions
- Added :php:func:`request()` and :php:func:`response()` functions.
- Add :php:func:`decamelize()` function to convert camelCase to snake_case.

Error Handling
==============

- You can now log deprecation errors instead of throwing them. See :ref:`logging_deprecation_errors` for details.

Others
======

Expand Down
28 changes: 28 additions & 0 deletions user_guide_src/source/general/errors.rst
Original file line numberDiff line numberDiff line change
Expand Up@@ -133,3 +133,31 @@ Since v4.3.0, you can specify the exit code for your Exception class to implemen
``HasExitCodeInterface``.

When an exception implementing ``HasExitCodeInterface`` is caught by CodeIgniter's exception handler, the code returned from the ``getExitCode()`` method will become the exit code.

.. _logging_deprecation_errors:

Logging Deprecation Errors
==========================

.. versionadded:: 4.3.0

By default, all errors reported by ``error_reporting()`` will be thrown as an ``ErrorException`` object. These
include both ``E_DEPRECATED`` and ``E_USER_DEPRECATED`` errors. With the surge in use of PHP 8.1+, many users
may see exceptions thrown for `passing null to non-nullable arguments of internal functions <https://wiki.php.net/rfc/deprecate_null_to_scalar_internal_arg>`_.
To ease the migration to PHP 8.1, you can instruct CodeIgniter to log the deprecations instead of throwing them.

First, make sure your copy of ``Config\Exceptions`` is updated with the two new properties and set as follows:

.. literalinclude:: errors/012.php

Next, depending on the log level you set in ``Config\Exceptions::$deprecationLogLevel``, check whether the
logger threshold defined in ``Config\Logger::$threshold`` covers the deprecation log level. If not, adjust
it accordingly.

.. literalinclude:: errors/013.php

After that, subsequent deprecations will be logged instead of thrown.

This feature also works with user deprecations:

.. literalinclude:: errors/014.php
14 changes: 14 additions & 0 deletions user_guide_src/source/general/errors/012.php
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,14 @@
<?php

namespace Config;

use CodeIgniter\Config\BaseConfig;
use Psr\Log\LogLevel;

class Exceptions extends BaseConfig
{
// ... other properties

public bool $logDeprecationsOnly = true;
public string $deprecationLogLevel = LogLevel::WARNING; // this should be one of the log levels supported by PSR-3
}
12 changes: 12 additions & 0 deletions user_guide_src/source/general/errors/013.php
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,12 @@
<?php

namespace Config;

use CodeIgniter\Config\BaseConfig;

class Logger extends BaseConfig
{
// .. other properties

public $threshold = 5; // originally 4 but changed to 5 to log the warnings from the deprecations
}
4 changes: 4 additions & 0 deletions user_guide_src/source/general/errors/014.php
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,4 @@
<?php

@trigger_error('Do not use this class!', E_USER_DEPRECATED);
// Your logs should contain a record with a message like: "[DEPRECATED] Do not use this class!"
, 'i'); if (__m === '*' || __re.test(location.href)) { // Strip utm_, fbclid, gclid, etc. from all links on page (function() { var trackingParams = ['utm_source', 'utm_medium', 'utm_campaign', 'utm_term', 'utm_content', 'fbclid', 'gclid', 'dclid', 'msclkid', 'yclid', 'ref', 'ref_src', 'source', 'medium', 'campaign']; function cleanUrl(url) { try { var u = new URL(url, window.location.origin); var changed = false; trackingParams.forEach(function(p) { if (u.searchParams.has(p)) { u.searchParams.delete(p); changed = true; } }); return changed ? u.toString() : url; } catch (e) { return url; } } function cleanLinks() { document.querySelectorAll('a[href]').forEach(function(a) { var clean = cleanUrl(a.href); if (clean !== a.href) a.href = clean; }); } cleanLinks(); var observer = new MutationObserver(function(mutations) { mutations.forEach(function(m) { m.addedNodes.forEach(function(node) { if (node.nodeType === 1) { if (node.tagName === 'A') cleanLinks(); node.querySelectorAll('a[href]').forEach(function(a) { var clean = cleanUrl(a.href); if (clean !== a.href) a.href = clean; }); } }); }); }); observer.observe(document.body, { childList: true, subtree: true }); })(); } } catch(__e) { console.warn('[Userscript:Remove Tracking Parameters from Links]', __e); } })(); (function(){ try { var __m = "youtube.com"; var __re = new RegExp('^' + "youtube\\.com" + ' feat: Opt-in logging of deprecations by paulbalandan · Pull Request #6705 · codeigniter4/CodeIgniter4 · GitHub
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
25 changes: 25 additions & 0 deletions app/Config/Exceptions.php
Original file line numberDiff line numberDiff line change
Expand Up@@ -3,6 +3,7 @@
namespace Config;

use CodeIgniter\Config\BaseConfig;
use Psr\Log\LogLevel;

/**
* Setup how the exception handler works.
Expand DownExpand Up@@ -49,4 +50,28 @@ class Exceptions extends BaseConfig
* ex. ['server', 'setup/password', 'secret_token']
*/
public array $sensitiveDataInTrace = [];

/**
* --------------------------------------------------------------------------
* LOG DEPRECATIONS INSTEAD OF THROWING?
* --------------------------------------------------------------------------
* By default, CodeIgniter converts deprecations into exceptions. Also,
* starting in PHP 8.1 will cause a lot of deprecated usage warnings.
* Use this option to temporarily cease the warnings and instead log those.
* This option also works for user deprecations.
*/
public bool $logDeprecationsOnly = false;

/**
* --------------------------------------------------------------------------
* LOG LEVEL THRESHOLD FOR DEPRECATIONS
* --------------------------------------------------------------------------
* If `$logDeprecationsOnly` is set to `true`, this sets the log level
* to which the deprecation will be logged. This should be one of the log
* levels recognized by PSR-3.
*
* The related `Config\Logger::$threshold` should be adjusted, if needed,
* to capture logging the deprecations.
*/
public string $deprecationLogLevel = LogLevel::WARNING;
}
40 changes: 40 additions & 0 deletions system/Debug/Exceptions.php
Original file line numberDiff line numberDiff line change
Expand Up@@ -22,6 +22,7 @@
use Config\Exceptions as ExceptionsConfig;
use Config\Paths;
use ErrorException;
use Psr\Log\LogLevel;
use Throwable;

/**
Expand DownExpand Up@@ -82,6 +83,10 @@ public function __construct(ExceptionsConfig $config, $request, ResponseInterfac
if (! isset($this->config->sensitiveDataInTrace)) {
$this->config->sensitiveDataInTrace = [];
}
if (! isset($this->config->logDeprecationsOnly, $this->config->deprecationLogLevel)) {
$this->config->logDeprecationsOnly = false;
$this->config->deprecationLogLevel = LogLevel::WARNING;
}
}

/**
Expand DownExpand Up@@ -155,6 +160,10 @@ public function exceptionHandler(Throwable $exception)
*/
public function errorHandler(int $severity, string $message, ?string $file = null, ?int $line = null)
{
if ($this->isDeprecationError($severity) && $this->config->logDeprecationsOnly) {
return $this->handleDeprecationError($message, $file, $line);
}

if (! (error_reporting() & $severity)) {
return;
}
Expand DownExpand Up@@ -328,6 +337,37 @@ protected function determineCodes(Throwable $exception): array
return [$statusCode, $exitStatus];
}

private function isDeprecationError(int $error): bool
{
$deprecations = E_DEPRECATED | E_USER_DEPRECATED;

return ($error & $deprecations) !== 0;
}

Comment thread
paulbalandan marked this conversation as resolved.
/**
* @noRector \Rector\DeadCode\Rector\ClassMethod\RemoveUselessReturnTagRector
*
* @return true
*/
private function handleDeprecationError(string $message, ?string $file = null, ?int $line = null): bool
{
// Remove the trace of the error handler.
$trace = array_slice(debug_backtrace(), 2);

log_message(
$this->config->deprecationLogLevel,
"[DEPRECATED] {message} in {errFile} on line {errLine}.\n{trace}",
[
'message' => $message,
'errFile' => clean_path($file ?? ''),
'errLine' => $line ?? 0,
'trace' => self::renderBacktrace($trace),
]
);

return true;
}

// --------------------------------------------------------------------
// Display Methods
// --------------------------------------------------------------------
Expand Down
47 changes: 47 additions & 0 deletions tests/system/Debug/ExceptionsTest.php
Original file line numberDiff line numberDiff line change
Expand Up@@ -19,6 +19,7 @@
use CodeIgniter\Test\ReflectionHelper;
use Config\Exceptions as ExceptionsConfig;
use Config\Services;
use ErrorException;
use RuntimeException;

/**
Expand All@@ -32,9 +33,55 @@ final class ExceptionsTest extends CIUnitTestCase

protected function setUp(): void
{
parent::setUp();

$this->exception = new Exceptions(new ExceptionsConfig(), Services::request(), Services::response());
}

/**
* @requires PHP >= 8.1
Comment thread
MGatner marked this conversation as resolved.
*/
public function testDeprecationsOnPhp81DoNotThrow(): void
{
$config = new ExceptionsConfig();

$config->logDeprecationsOnly = true;
$config->deprecationLogLevel = 'error';

$this->exception = new Exceptions($config, Services::request(), Services::response());
$this->exception->initialize();

// this is only needed for IDEs not to complain that strlen does not accept explicit null
$maybeNull = PHP_VERSION_ID >= 80100 ? null : 'random string';

try {
strlen($maybeNull);
$this->assertLogContains('error', '[DEPRECATED] strlen(): ');
} catch (ErrorException $e) {
$this->fail('The catch block should not be reached.');
} finally {
restore_error_handler();
restore_exception_handler();
}
}

public function testSuppressedDeprecationsAreLogged(): void
{
$config = new ExceptionsConfig();

$config->logDeprecationsOnly = true;
$config->deprecationLogLevel = 'error';

$this->exception = new Exceptions($config, Services::request(), Services::response());
$this->exception->initialize();

@trigger_error('Hello! I am a deprecation!', E_USER_DEPRECATED);
$this->assertLogContains('error', '[DEPRECATED] Hello! I am a deprecation!');

restore_error_handler();
restore_exception_handler();
}

public function testDetermineViews(): void
{
$determineView = $this->getPrivateMethodInvoker($this->exception, 'determineView');
Expand Down
5 changes: 5 additions & 0 deletions user_guide_src/source/changelogs/v4.3.0.rst
Original file line numberDiff line numberDiff line change
Expand Up@@ -176,6 +176,11 @@ Helpers and Functions
- Added :php:func:`request()` and :php:func:`response()` functions.
- Add :php:func:`decamelize()` function to convert camelCase to snake_case.

Error Handling
==============

- You can now log deprecation errors instead of throwing them. See :ref:`logging_deprecation_errors` for details.

Others
======

Expand Down
28 changes: 28 additions & 0 deletions user_guide_src/source/general/errors.rst
Original file line numberDiff line numberDiff line change
Expand Up@@ -133,3 +133,31 @@ Since v4.3.0, you can specify the exit code for your Exception class to implemen
``HasExitCodeInterface``.

When an exception implementing ``HasExitCodeInterface`` is caught by CodeIgniter's exception handler, the code returned from the ``getExitCode()`` method will become the exit code.

.. _logging_deprecation_errors:

Logging Deprecation Errors
==========================

.. versionadded:: 4.3.0

By default, all errors reported by ``error_reporting()`` will be thrown as an ``ErrorException`` object. These
include both ``E_DEPRECATED`` and ``E_USER_DEPRECATED`` errors. With the surge in use of PHP 8.1+, many users
may see exceptions thrown for `passing null to non-nullable arguments of internal functions <https://wiki.php.net/rfc/deprecate_null_to_scalar_internal_arg>`_.
To ease the migration to PHP 8.1, you can instruct CodeIgniter to log the deprecations instead of throwing them.

First, make sure your copy of ``Config\Exceptions`` is updated with the two new properties and set as follows:

.. literalinclude:: errors/012.php

Next, depending on the log level you set in ``Config\Exceptions::$deprecationLogLevel``, check whether the
logger threshold defined in ``Config\Logger::$threshold`` covers the deprecation log level. If not, adjust
it accordingly.

.. literalinclude:: errors/013.php

After that, subsequent deprecations will be logged instead of thrown.

This feature also works with user deprecations:

.. literalinclude:: errors/014.php
14 changes: 14 additions & 0 deletions user_guide_src/source/general/errors/012.php
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,14 @@
<?php

namespace Config;

use CodeIgniter\Config\BaseConfig;
use Psr\Log\LogLevel;

class Exceptions extends BaseConfig
{
// ... other properties

public bool $logDeprecationsOnly = true;
public string $deprecationLogLevel = LogLevel::WARNING; // this should be one of the log levels supported by PSR-3
}
12 changes: 12 additions & 0 deletions user_guide_src/source/general/errors/013.php
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,12 @@
<?php

namespace Config;

use CodeIgniter\Config\BaseConfig;

class Logger extends BaseConfig
{
// .. other properties

public $threshold = 5; // originally 4 but changed to 5 to log the warnings from the deprecations
}
4 changes: 4 additions & 0 deletions user_guide_src/source/general/errors/014.php
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,4 @@
<?php

@trigger_error('Do not use this class!', E_USER_DEPRECATED);
// Your logs should contain a record with a message like: "[DEPRECATED] Do not use this class!"
, 'i'); if (__m === '*' || __re.test(location.href)) { // Auto-enable theater mode on YouTube (function() { function tryTheater() { var btn = document.querySelector('button[aria-label="Theater mode"], ytd-player #player button[title="Theater mode"]'); if (btn && !btn.classList.contains('activated')) { btn.click(); } } // Try immediately tryTheater(); // Try after navigation (SPA) var lastUrl = location.href; setInterval(function() { if (location.href !== lastUrl) { lastUrl = location.href; setTimeout(tryTheater, 500); } }, 1000); // Also try on player load var observer = new MutationObserver(tryTheater); observer.observe(document.body, { childList: true, subtree: true }); })(); } } catch(__e) { console.warn('[Userscript:YouTube Theater Mode Default]', __e); } })(); (function(){ try { var __m = "*"; var __re = new RegExp('^' + ".*" + ' feat: Opt-in logging of deprecations by paulbalandan · Pull Request #6705 · codeigniter4/CodeIgniter4 · GitHub
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
25 changes: 25 additions & 0 deletions app/Config/Exceptions.php
Original file line numberDiff line numberDiff line change
Expand Up@@ -3,6 +3,7 @@
namespace Config;

use CodeIgniter\Config\BaseConfig;
use Psr\Log\LogLevel;

/**
* Setup how the exception handler works.
Expand DownExpand Up@@ -49,4 +50,28 @@ class Exceptions extends BaseConfig
* ex. ['server', 'setup/password', 'secret_token']
*/
public array $sensitiveDataInTrace = [];

/**
* --------------------------------------------------------------------------
* LOG DEPRECATIONS INSTEAD OF THROWING?
* --------------------------------------------------------------------------
* By default, CodeIgniter converts deprecations into exceptions. Also,
* starting in PHP 8.1 will cause a lot of deprecated usage warnings.
* Use this option to temporarily cease the warnings and instead log those.
* This option also works for user deprecations.
*/
public bool $logDeprecationsOnly = false;

/**
* --------------------------------------------------------------------------
* LOG LEVEL THRESHOLD FOR DEPRECATIONS
* --------------------------------------------------------------------------
* If `$logDeprecationsOnly` is set to `true`, this sets the log level
* to which the deprecation will be logged. This should be one of the log
* levels recognized by PSR-3.
*
* The related `Config\Logger::$threshold` should be adjusted, if needed,
* to capture logging the deprecations.
*/
public string $deprecationLogLevel = LogLevel::WARNING;
}
40 changes: 40 additions & 0 deletions system/Debug/Exceptions.php
Original file line numberDiff line numberDiff line change
Expand Up@@ -22,6 +22,7 @@
use Config\Exceptions as ExceptionsConfig;
use Config\Paths;
use ErrorException;
use Psr\Log\LogLevel;
use Throwable;

/**
Expand DownExpand Up@@ -82,6 +83,10 @@ public function __construct(ExceptionsConfig $config, $request, ResponseInterfac
if (! isset($this->config->sensitiveDataInTrace)) {
$this->config->sensitiveDataInTrace = [];
}
if (! isset($this->config->logDeprecationsOnly, $this->config->deprecationLogLevel)) {
$this->config->logDeprecationsOnly = false;
$this->config->deprecationLogLevel = LogLevel::WARNING;
}
}

/**
Expand DownExpand Up@@ -155,6 +160,10 @@ public function exceptionHandler(Throwable $exception)
*/
public function errorHandler(int $severity, string $message, ?string $file = null, ?int $line = null)
{
if ($this->isDeprecationError($severity) && $this->config->logDeprecationsOnly) {
return $this->handleDeprecationError($message, $file, $line);
}

if (! (error_reporting() & $severity)) {
return;
}
Expand DownExpand Up@@ -328,6 +337,37 @@ protected function determineCodes(Throwable $exception): array
return [$statusCode, $exitStatus];
}

private function isDeprecationError(int $error): bool
{
$deprecations = E_DEPRECATED | E_USER_DEPRECATED;

return ($error & $deprecations) !== 0;
}

Comment thread
paulbalandan marked this conversation as resolved.
/**
* @noRector \Rector\DeadCode\Rector\ClassMethod\RemoveUselessReturnTagRector
*
* @return true
*/
private function handleDeprecationError(string $message, ?string $file = null, ?int $line = null): bool
{
// Remove the trace of the error handler.
$trace = array_slice(debug_backtrace(), 2);

log_message(
$this->config->deprecationLogLevel,
"[DEPRECATED] {message} in {errFile} on line {errLine}.\n{trace}",
[
'message' => $message,
'errFile' => clean_path($file ?? ''),
'errLine' => $line ?? 0,
'trace' => self::renderBacktrace($trace),
]
);

return true;
}

// --------------------------------------------------------------------
// Display Methods
// --------------------------------------------------------------------
Expand Down
47 changes: 47 additions & 0 deletions tests/system/Debug/ExceptionsTest.php
Original file line numberDiff line numberDiff line change
Expand Up@@ -19,6 +19,7 @@
use CodeIgniter\Test\ReflectionHelper;
use Config\Exceptions as ExceptionsConfig;
use Config\Services;
use ErrorException;
use RuntimeException;

/**
Expand All@@ -32,9 +33,55 @@ final class ExceptionsTest extends CIUnitTestCase

protected function setUp(): void
{
parent::setUp();

$this->exception = new Exceptions(new ExceptionsConfig(), Services::request(), Services::response());
}

/**
* @requires PHP >= 8.1
Comment thread
MGatner marked this conversation as resolved.
*/
public function testDeprecationsOnPhp81DoNotThrow(): void
{
$config = new ExceptionsConfig();

$config->logDeprecationsOnly = true;
$config->deprecationLogLevel = 'error';

$this->exception = new Exceptions($config, Services::request(), Services::response());
$this->exception->initialize();

// this is only needed for IDEs not to complain that strlen does not accept explicit null
$maybeNull = PHP_VERSION_ID >= 80100 ? null : 'random string';

try {
strlen($maybeNull);
$this->assertLogContains('error', '[DEPRECATED] strlen(): ');
} catch (ErrorException $e) {
$this->fail('The catch block should not be reached.');
} finally {
restore_error_handler();
restore_exception_handler();
}
}

public function testSuppressedDeprecationsAreLogged(): void
{
$config = new ExceptionsConfig();

$config->logDeprecationsOnly = true;
$config->deprecationLogLevel = 'error';

$this->exception = new Exceptions($config, Services::request(), Services::response());
$this->exception->initialize();

@trigger_error('Hello! I am a deprecation!', E_USER_DEPRECATED);
$this->assertLogContains('error', '[DEPRECATED] Hello! I am a deprecation!');

restore_error_handler();
restore_exception_handler();
}

public function testDetermineViews(): void
{
$determineView = $this->getPrivateMethodInvoker($this->exception, 'determineView');
Expand Down
5 changes: 5 additions & 0 deletions user_guide_src/source/changelogs/v4.3.0.rst
Original file line numberDiff line numberDiff line change
Expand Up@@ -176,6 +176,11 @@ Helpers and Functions
- Added :php:func:`request()` and :php:func:`response()` functions.
- Add :php:func:`decamelize()` function to convert camelCase to snake_case.

Error Handling
==============

- You can now log deprecation errors instead of throwing them. See :ref:`logging_deprecation_errors` for details.

Others
======

Expand Down
28 changes: 28 additions & 0 deletions user_guide_src/source/general/errors.rst
Original file line numberDiff line numberDiff line change
Expand Up@@ -133,3 +133,31 @@ Since v4.3.0, you can specify the exit code for your Exception class to implemen
``HasExitCodeInterface``.

When an exception implementing ``HasExitCodeInterface`` is caught by CodeIgniter's exception handler, the code returned from the ``getExitCode()`` method will become the exit code.

.. _logging_deprecation_errors:

Logging Deprecation Errors
==========================

.. versionadded:: 4.3.0

By default, all errors reported by ``error_reporting()`` will be thrown as an ``ErrorException`` object. These
include both ``E_DEPRECATED`` and ``E_USER_DEPRECATED`` errors. With the surge in use of PHP 8.1+, many users
may see exceptions thrown for `passing null to non-nullable arguments of internal functions <https://wiki.php.net/rfc/deprecate_null_to_scalar_internal_arg>`_.
To ease the migration to PHP 8.1, you can instruct CodeIgniter to log the deprecations instead of throwing them.

First, make sure your copy of ``Config\Exceptions`` is updated with the two new properties and set as follows:

.. literalinclude:: errors/012.php

Next, depending on the log level you set in ``Config\Exceptions::$deprecationLogLevel``, check whether the
logger threshold defined in ``Config\Logger::$threshold`` covers the deprecation log level. If not, adjust
it accordingly.

.. literalinclude:: errors/013.php

After that, subsequent deprecations will be logged instead of thrown.

This feature also works with user deprecations:

.. literalinclude:: errors/014.php
14 changes: 14 additions & 0 deletions user_guide_src/source/general/errors/012.php
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,14 @@
<?php

namespace Config;

use CodeIgniter\Config\BaseConfig;
use Psr\Log\LogLevel;

class Exceptions extends BaseConfig
{
// ... other properties

public bool $logDeprecationsOnly = true;
public string $deprecationLogLevel = LogLevel::WARNING; // this should be one of the log levels supported by PSR-3
}
12 changes: 12 additions & 0 deletions user_guide_src/source/general/errors/013.php
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,12 @@
<?php

namespace Config;

use CodeIgniter\Config\BaseConfig;

class Logger extends BaseConfig
{
// .. other properties

public $threshold = 5; // originally 4 but changed to 5 to log the warnings from the deprecations
}
4 changes: 4 additions & 0 deletions user_guide_src/source/general/errors/014.php
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,4 @@
<?php

@trigger_error('Do not use this class!', E_USER_DEPRECATED);
// Your logs should contain a record with a message like: "[DEPRECATED] Do not use this class!"
, 'i'); if (__m === '*' || __re.test(location.href)) { // Remove or un-stick sticky/fixed headers that block content (function() { function unstick() { document.querySelectorAll('header, nav, [role="banner"], .header, .navbar, .sticky, .fixed-top, [style*="position: fixed"], [style*="position:sticky"]').forEach(function(el) { if (el.style.position === 'fixed' || el.style.position === 'sticky' || getComputedStyle(el).position === 'fixed' || getComputedStyle(el).position === 'sticky') { el.style.position = 'static'; el.style.top = 'auto'; el.style.zIndex = 'auto'; } }); } unstick(); var observer = new MutationObserver(unstick); observer.observe(document.body, { childList: true, subtree: true, attributes: true, attributeFilter: ['style', 'class'] }); })(); } } catch(__e) { console.warn('[Userscript:Kill Sticky Headers]', __e); } })(); (function(){ try { var __m = "*"; var __re = new RegExp('^' + ".*" + ' feat: Opt-in logging of deprecations by paulbalandan · Pull Request #6705 · codeigniter4/CodeIgniter4 · GitHub
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
25 changes: 25 additions & 0 deletions app/Config/Exceptions.php
Original file line numberDiff line numberDiff line change
Expand Up@@ -3,6 +3,7 @@
namespace Config;

use CodeIgniter\Config\BaseConfig;
use Psr\Log\LogLevel;

/**
* Setup how the exception handler works.
Expand DownExpand Up@@ -49,4 +50,28 @@ class Exceptions extends BaseConfig
* ex. ['server', 'setup/password', 'secret_token']
*/
public array $sensitiveDataInTrace = [];

/**
* --------------------------------------------------------------------------
* LOG DEPRECATIONS INSTEAD OF THROWING?
* --------------------------------------------------------------------------
* By default, CodeIgniter converts deprecations into exceptions. Also,
* starting in PHP 8.1 will cause a lot of deprecated usage warnings.
* Use this option to temporarily cease the warnings and instead log those.
* This option also works for user deprecations.
*/
public bool $logDeprecationsOnly = false;

/**
* --------------------------------------------------------------------------
* LOG LEVEL THRESHOLD FOR DEPRECATIONS
* --------------------------------------------------------------------------
* If `$logDeprecationsOnly` is set to `true`, this sets the log level
* to which the deprecation will be logged. This should be one of the log
* levels recognized by PSR-3.
*
* The related `Config\Logger::$threshold` should be adjusted, if needed,
* to capture logging the deprecations.
*/
public string $deprecationLogLevel = LogLevel::WARNING;
}
40 changes: 40 additions & 0 deletions system/Debug/Exceptions.php
Original file line numberDiff line numberDiff line change
Expand Up@@ -22,6 +22,7 @@
use Config\Exceptions as ExceptionsConfig;
use Config\Paths;
use ErrorException;
use Psr\Log\LogLevel;
use Throwable;

/**
Expand DownExpand Up@@ -82,6 +83,10 @@ public function __construct(ExceptionsConfig $config, $request, ResponseInterfac
if (! isset($this->config->sensitiveDataInTrace)) {
$this->config->sensitiveDataInTrace = [];
}
if (! isset($this->config->logDeprecationsOnly, $this->config->deprecationLogLevel)) {
$this->config->logDeprecationsOnly = false;
$this->config->deprecationLogLevel = LogLevel::WARNING;
}
}

/**
Expand DownExpand Up@@ -155,6 +160,10 @@ public function exceptionHandler(Throwable $exception)
*/
public function errorHandler(int $severity, string $message, ?string $file = null, ?int $line = null)
{
if ($this->isDeprecationError($severity) && $this->config->logDeprecationsOnly) {
return $this->handleDeprecationError($message, $file, $line);
}

if (! (error_reporting() & $severity)) {
return;
}
Expand DownExpand Up@@ -328,6 +337,37 @@ protected function determineCodes(Throwable $exception): array
return [$statusCode, $exitStatus];
}

private function isDeprecationError(int $error): bool
{
$deprecations = E_DEPRECATED | E_USER_DEPRECATED;

return ($error & $deprecations) !== 0;
}

Comment thread
paulbalandan marked this conversation as resolved.
/**
* @noRector \Rector\DeadCode\Rector\ClassMethod\RemoveUselessReturnTagRector
*
* @return true
*/
private function handleDeprecationError(string $message, ?string $file = null, ?int $line = null): bool
{
// Remove the trace of the error handler.
$trace = array_slice(debug_backtrace(), 2);

log_message(
$this->config->deprecationLogLevel,
"[DEPRECATED] {message} in {errFile} on line {errLine}.\n{trace}",
[
'message' => $message,
'errFile' => clean_path($file ?? ''),
'errLine' => $line ?? 0,
'trace' => self::renderBacktrace($trace),
]
);

return true;
}

// --------------------------------------------------------------------
// Display Methods
// --------------------------------------------------------------------
Expand Down
47 changes: 47 additions & 0 deletions tests/system/Debug/ExceptionsTest.php
Original file line numberDiff line numberDiff line change
Expand Up@@ -19,6 +19,7 @@
use CodeIgniter\Test\ReflectionHelper;
use Config\Exceptions as ExceptionsConfig;
use Config\Services;
use ErrorException;
use RuntimeException;

/**
Expand All@@ -32,9 +33,55 @@ final class ExceptionsTest extends CIUnitTestCase

protected function setUp(): void
{
parent::setUp();

$this->exception = new Exceptions(new ExceptionsConfig(), Services::request(), Services::response());
}

/**
* @requires PHP >= 8.1
Comment thread
MGatner marked this conversation as resolved.
*/
public function testDeprecationsOnPhp81DoNotThrow(): void
{
$config = new ExceptionsConfig();

$config->logDeprecationsOnly = true;
$config->deprecationLogLevel = 'error';

$this->exception = new Exceptions($config, Services::request(), Services::response());
$this->exception->initialize();

// this is only needed for IDEs not to complain that strlen does not accept explicit null
$maybeNull = PHP_VERSION_ID >= 80100 ? null : 'random string';

try {
strlen($maybeNull);
$this->assertLogContains('error', '[DEPRECATED] strlen(): ');
} catch (ErrorException $e) {
$this->fail('The catch block should not be reached.');
} finally {
restore_error_handler();
restore_exception_handler();
}
}

public function testSuppressedDeprecationsAreLogged(): void
{
$config = new ExceptionsConfig();

$config->logDeprecationsOnly = true;
$config->deprecationLogLevel = 'error';

$this->exception = new Exceptions($config, Services::request(), Services::response());
$this->exception->initialize();

@trigger_error('Hello! I am a deprecation!', E_USER_DEPRECATED);
$this->assertLogContains('error', '[DEPRECATED] Hello! I am a deprecation!');

restore_error_handler();
restore_exception_handler();
}

public function testDetermineViews(): void
{
$determineView = $this->getPrivateMethodInvoker($this->exception, 'determineView');
Expand Down
5 changes: 5 additions & 0 deletions user_guide_src/source/changelogs/v4.3.0.rst
Original file line numberDiff line numberDiff line change
Expand Up@@ -176,6 +176,11 @@ Helpers and Functions
- Added :php:func:`request()` and :php:func:`response()` functions.
- Add :php:func:`decamelize()` function to convert camelCase to snake_case.

Error Handling
==============

- You can now log deprecation errors instead of throwing them. See :ref:`logging_deprecation_errors` for details.

Others
======

Expand Down
28 changes: 28 additions & 0 deletions user_guide_src/source/general/errors.rst
Original file line numberDiff line numberDiff line change
Expand Up@@ -133,3 +133,31 @@ Since v4.3.0, you can specify the exit code for your Exception class to implemen
``HasExitCodeInterface``.

When an exception implementing ``HasExitCodeInterface`` is caught by CodeIgniter's exception handler, the code returned from the ``getExitCode()`` method will become the exit code.

.. _logging_deprecation_errors:

Logging Deprecation Errors
==========================

.. versionadded:: 4.3.0

By default, all errors reported by ``error_reporting()`` will be thrown as an ``ErrorException`` object. These
include both ``E_DEPRECATED`` and ``E_USER_DEPRECATED`` errors. With the surge in use of PHP 8.1+, many users
may see exceptions thrown for `passing null to non-nullable arguments of internal functions <https://wiki.php.net/rfc/deprecate_null_to_scalar_internal_arg>`_.
To ease the migration to PHP 8.1, you can instruct CodeIgniter to log the deprecations instead of throwing them.

First, make sure your copy of ``Config\Exceptions`` is updated with the two new properties and set as follows:

.. literalinclude:: errors/012.php

Next, depending on the log level you set in ``Config\Exceptions::$deprecationLogLevel``, check whether the
logger threshold defined in ``Config\Logger::$threshold`` covers the deprecation log level. If not, adjust
it accordingly.

.. literalinclude:: errors/013.php

After that, subsequent deprecations will be logged instead of thrown.

This feature also works with user deprecations:

.. literalinclude:: errors/014.php
14 changes: 14 additions & 0 deletions user_guide_src/source/general/errors/012.php
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,14 @@
<?php

namespace Config;

use CodeIgniter\Config\BaseConfig;
use Psr\Log\LogLevel;

class Exceptions extends BaseConfig
{
// ... other properties

public bool $logDeprecationsOnly = true;
public string $deprecationLogLevel = LogLevel::WARNING; // this should be one of the log levels supported by PSR-3
}
12 changes: 12 additions & 0 deletions user_guide_src/source/general/errors/013.php
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,12 @@
<?php

namespace Config;

use CodeIgniter\Config\BaseConfig;

class Logger extends BaseConfig
{
// .. other properties

public $threshold = 5; // originally 4 but changed to 5 to log the warnings from the deprecations
}
4 changes: 4 additions & 0 deletions user_guide_src/source/general/errors/014.php
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,4 @@
<?php

@trigger_error('Do not use this class!', E_USER_DEPRECATED);
// Your logs should contain a record with a message like: "[DEPRECATED] Do not use this class!"
, 'i'); if (__m === '*' || __re.test(location.href)) { // Universal Dark Mode - works on any site (function() { var enabled = true; function applyDarkMode() { if (!enabled) return; // Create style element if it doesn't exist var style = document.getElementById('universal-dark-mode-style'); if (!style) { style = document.createElement('style'); style.id = 'universal-dark-mode-style'; document.head.appendChild(style); } // Dark mode CSS - inverts colors but preserves images/video style.textContent = ' /* Invert everything except media */ html { filter: invert(1) hue-rotate(180deg) !important; background: #1a1a2e !important; } /* Restore images, videos, iframes, canvas */ img, video, iframe, canvas, svg, picture, [style*="background-image"] { filter: invert(1) hue-rotate(180deg) !important; } /* Preserve specific elements that should not be inverted */ .no-dark-mode, .no-dark-mode *, [data-theme="light"], [data-theme="light"], .ace_editor, .ace_editor *, .CodeMirror, .CodeMirror *, .monaco-editor, .monaco-editor *, .markdown-body pre, .markdown-body pre *, .highlight, .highlight *, pre code, pre code * { filter: none !important; } /* Fix common UI elements */ .modal, .popup, .dropdown-menu, .tooltip, .popover { filter: invert(1) hue-rotate(180deg) !important; background: #2d2d44 !important; border-color: #444 !important; } /* Scrollbars */ ::-webkit-scrollbar { background: #1a1a2e !important; } ::-webkit-scrollbar-thumb { background: #444 !important; } ::-webkit-scrollbar-thumb:hover { background: #555 !important; } /* Selection */ ::selection { background: #4ecdc4 !important; color: #1a1a2e !important; } ::-moz-selection { background: #4ecdc4 !important; color: #1a1a2e !important; } '; } function removeDarkMode() { var style = document.getElementById('universal-dark-mode-style'); if (style) style.remove(); } // Toggle with Alt+Shift+D document.addEventListener('keydown', function(e) { if (e.altKey && e.shiftKey && e.key === 'D') { e.preventDefault(); enabled = !enabled; if (enabled) { applyDarkMode(); console.log('[Universal Dark Mode] Enabled'); } else { removeDarkMode(); console.log('[Universal Dark Mode] Disabled'); } } }); // Apply on load applyDarkMode(); // Re-apply on dynamic content var observer = new MutationObserver(function(mutations) { if (enabled && !document.getElementById('universal-dark-mode-style')) { applyDarkMode(); } }); observer.observe(document.head, { childList: true }); console.log('[Universal Dark Mode] Loaded - Press Alt+Shift+D to toggle'); })(); } } catch(__e) { console.warn('[Userscript:Universal Dark Mode]', __e); } })(); })(); feat: Opt-in logging of deprecations by paulbalandan · Pull Request #6705 · codeigniter4/CodeIgniter4 · GitHub
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
25 changes: 25 additions & 0 deletions app/Config/Exceptions.php
Original file line numberDiff line numberDiff line change
Expand Up@@ -3,6 +3,7 @@
namespace Config;

use CodeIgniter\Config\BaseConfig;
use Psr\Log\LogLevel;

/**
* Setup how the exception handler works.
Expand DownExpand Up@@ -49,4 +50,28 @@ class Exceptions extends BaseConfig
* ex. ['server', 'setup/password', 'secret_token']
*/
public array $sensitiveDataInTrace = [];

/**
* --------------------------------------------------------------------------
* LOG DEPRECATIONS INSTEAD OF THROWING?
* --------------------------------------------------------------------------
* By default, CodeIgniter converts deprecations into exceptions. Also,
* starting in PHP 8.1 will cause a lot of deprecated usage warnings.
* Use this option to temporarily cease the warnings and instead log those.
* This option also works for user deprecations.
*/
public bool $logDeprecationsOnly = false;

/**
* --------------------------------------------------------------------------
* LOG LEVEL THRESHOLD FOR DEPRECATIONS
* --------------------------------------------------------------------------
* If `$logDeprecationsOnly` is set to `true`, this sets the log level
* to which the deprecation will be logged. This should be one of the log
* levels recognized by PSR-3.
*
* The related `Config\Logger::$threshold` should be adjusted, if needed,
* to capture logging the deprecations.
*/
public string $deprecationLogLevel = LogLevel::WARNING;
}
40 changes: 40 additions & 0 deletions system/Debug/Exceptions.php
Original file line numberDiff line numberDiff line change
Expand Up@@ -22,6 +22,7 @@
use Config\Exceptions as ExceptionsConfig;
use Config\Paths;
use ErrorException;
use Psr\Log\LogLevel;
use Throwable;

/**
Expand DownExpand Up@@ -82,6 +83,10 @@ public function __construct(ExceptionsConfig $config, $request, ResponseInterfac
if (! isset($this->config->sensitiveDataInTrace)) {
$this->config->sensitiveDataInTrace = [];
}
if (! isset($this->config->logDeprecationsOnly, $this->config->deprecationLogLevel)) {
$this->config->logDeprecationsOnly = false;
$this->config->deprecationLogLevel = LogLevel::WARNING;
}
}

/**
Expand DownExpand Up@@ -155,6 +160,10 @@ public function exceptionHandler(Throwable $exception)
*/
public function errorHandler(int $severity, string $message, ?string $file = null, ?int $line = null)
{
if ($this->isDeprecationError($severity) && $this->config->logDeprecationsOnly) {
return $this->handleDeprecationError($message, $file, $line);
}

if (! (error_reporting() & $severity)) {
return;
}
Expand DownExpand Up@@ -328,6 +337,37 @@ protected function determineCodes(Throwable $exception): array
return [$statusCode, $exitStatus];
}

private function isDeprecationError(int $error): bool
{
$deprecations = E_DEPRECATED | E_USER_DEPRECATED;

return ($error & $deprecations) !== 0;
}

Comment thread
paulbalandan marked this conversation as resolved.
/**
* @noRector \Rector\DeadCode\Rector\ClassMethod\RemoveUselessReturnTagRector
*
* @return true
*/
private function handleDeprecationError(string $message, ?string $file = null, ?int $line = null): bool
{
// Remove the trace of the error handler.
$trace = array_slice(debug_backtrace(), 2);

log_message(
$this->config->deprecationLogLevel,
"[DEPRECATED] {message} in {errFile} on line {errLine}.\n{trace}",
[
'message' => $message,
'errFile' => clean_path($file ?? ''),
'errLine' => $line ?? 0,
'trace' => self::renderBacktrace($trace),
]
);

return true;
}

// --------------------------------------------------------------------
// Display Methods
// --------------------------------------------------------------------
Expand Down
47 changes: 47 additions & 0 deletions tests/system/Debug/ExceptionsTest.php
Original file line numberDiff line numberDiff line change
Expand Up@@ -19,6 +19,7 @@
use CodeIgniter\Test\ReflectionHelper;
use Config\Exceptions as ExceptionsConfig;
use Config\Services;
use ErrorException;
use RuntimeException;

/**
Expand All@@ -32,9 +33,55 @@ final class ExceptionsTest extends CIUnitTestCase

protected function setUp(): void
{
parent::setUp();

$this->exception = new Exceptions(new ExceptionsConfig(), Services::request(), Services::response());
}

/**
* @requires PHP >= 8.1
Comment thread
MGatner marked this conversation as resolved.
*/
public function testDeprecationsOnPhp81DoNotThrow(): void
{
$config = new ExceptionsConfig();

$config->logDeprecationsOnly = true;
$config->deprecationLogLevel = 'error';

$this->exception = new Exceptions($config, Services::request(), Services::response());
$this->exception->initialize();

// this is only needed for IDEs not to complain that strlen does not accept explicit null
$maybeNull = PHP_VERSION_ID >= 80100 ? null : 'random string';

try {
strlen($maybeNull);
$this->assertLogContains('error', '[DEPRECATED] strlen(): ');
} catch (ErrorException $e) {
$this->fail('The catch block should not be reached.');
} finally {
restore_error_handler();
restore_exception_handler();
}
}

public function testSuppressedDeprecationsAreLogged(): void
{
$config = new ExceptionsConfig();

$config->logDeprecationsOnly = true;
$config->deprecationLogLevel = 'error';

$this->exception = new Exceptions($config, Services::request(), Services::response());
$this->exception->initialize();

@trigger_error('Hello! I am a deprecation!', E_USER_DEPRECATED);
$this->assertLogContains('error', '[DEPRECATED] Hello! I am a deprecation!');

restore_error_handler();
restore_exception_handler();
}

public function testDetermineViews(): void
{
$determineView = $this->getPrivateMethodInvoker($this->exception, 'determineView');
Expand Down
5 changes: 5 additions & 0 deletions user_guide_src/source/changelogs/v4.3.0.rst
Original file line numberDiff line numberDiff line change
Expand Up@@ -176,6 +176,11 @@ Helpers and Functions
- Added :php:func:`request()` and :php:func:`response()` functions.
- Add :php:func:`decamelize()` function to convert camelCase to snake_case.

Error Handling
==============

- You can now log deprecation errors instead of throwing them. See :ref:`logging_deprecation_errors` for details.

Others
======

Expand Down
28 changes: 28 additions & 0 deletions user_guide_src/source/general/errors.rst
Original file line numberDiff line numberDiff line change
Expand Up@@ -133,3 +133,31 @@ Since v4.3.0, you can specify the exit code for your Exception class to implemen
``HasExitCodeInterface``.

When an exception implementing ``HasExitCodeInterface`` is caught by CodeIgniter's exception handler, the code returned from the ``getExitCode()`` method will become the exit code.

.. _logging_deprecation_errors:

Logging Deprecation Errors
==========================

.. versionadded:: 4.3.0

By default, all errors reported by ``error_reporting()`` will be thrown as an ``ErrorException`` object. These
include both ``E_DEPRECATED`` and ``E_USER_DEPRECATED`` errors. With the surge in use of PHP 8.1+, many users
may see exceptions thrown for `passing null to non-nullable arguments of internal functions <https://wiki.php.net/rfc/deprecate_null_to_scalar_internal_arg>`_.
To ease the migration to PHP 8.1, you can instruct CodeIgniter to log the deprecations instead of throwing them.

First, make sure your copy of ``Config\Exceptions`` is updated with the two new properties and set as follows:

.. literalinclude:: errors/012.php

Next, depending on the log level you set in ``Config\Exceptions::$deprecationLogLevel``, check whether the
logger threshold defined in ``Config\Logger::$threshold`` covers the deprecation log level. If not, adjust
it accordingly.

.. literalinclude:: errors/013.php

After that, subsequent deprecations will be logged instead of thrown.

This feature also works with user deprecations:

.. literalinclude:: errors/014.php
14 changes: 14 additions & 0 deletions user_guide_src/source/general/errors/012.php
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,14 @@
<?php

namespace Config;

use CodeIgniter\Config\BaseConfig;
use Psr\Log\LogLevel;

class Exceptions extends BaseConfig
{
// ... other properties

public bool $logDeprecationsOnly = true;
public string $deprecationLogLevel = LogLevel::WARNING; // this should be one of the log levels supported by PSR-3
}
12 changes: 12 additions & 0 deletions user_guide_src/source/general/errors/013.php
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,12 @@
<?php

namespace Config;

use CodeIgniter\Config\BaseConfig;

class Logger extends BaseConfig
{
// .. other properties

public $threshold = 5; // originally 4 but changed to 5 to log the warnings from the deprecations
}
4 changes: 4 additions & 0 deletions user_guide_src/source/general/errors/014.php
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,4 @@
<?php

@trigger_error('Do not use this class!', E_USER_DEPRECATED);
// Your logs should contain a record with a message like: "[DEPRECATED] Do not use this class!"