Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
31 commits
Select commit Hold shift + click to select a range
759571a
Update Handler.php
usernane Sep 15, 2025
ca5dea4
fix: Security Level Update
usernane Sep 16, 2025
53f45dc
Update Handler.php
usernane Sep 16, 2025
71519c1
Update AbstractHandler.php
usernane Sep 16, 2025
d41bd75
Update .gitattributes
usernane Sep 16, 2025
f9d8c93
doc: Added Example 1
usernane Sep 16, 2025
f23527a
Update Handler.php
usernane Sep 16, 2025
0ce686e
feat: Multiple Features
usernane Sep 16, 2025
2926b4b
Update HandlerConfig.php
usernane Sep 16, 2025
1e54a93
Update HandlerConfig.php
usernane Sep 16, 2025
ef2f704
Update DefaultHandler.php
usernane Sep 16, 2025
a9d875c
Update Handler.php
usernane Sep 16, 2025
9204579
docs: Added Example 02
usernane Sep 16, 2025
0d0db5d
docs: Added Example 3
usernane Sep 16, 2025
5b8cece
docs: Added Example 4
usernane Sep 17, 2025
cb86390
docs: Added Example 05
usernane Sep 17, 2025
062bf91
docs: Added New Example
usernane Sep 17, 2025
7f40167
docs: Added Example
usernane Sep 17, 2025
d96c795
docs: Added Example
usernane Sep 17, 2025
3ad0ade
docs: Added Example
usernane Sep 17, 2025
7505ee6
Update Handler.php
usernane Sep 17, 2025
507980e
Update DefaultHandler.php
usernane Sep 17, 2025
c26aa7e
Create README.md
usernane Sep 17, 2025
79cc95a
Update README.md
usernane Sep 17, 2025
578ef4b
fix: Check For Null Exception
usernane Sep 17, 2025
2893fd1
test: Fix Output Buffering
usernane Sep 17, 2025
8c067d8
chore: Updated Release Config
usernane Sep 17, 2025
35baf5e
test: Add Restore Handler
usernane Sep 17, 2025
4eb1b09
Update HandlerTest.php
usernane Sep 17, 2025
d5863dd
Update HandlerTest.php
usernane Sep 17, 2025
4f2e09c
Update HandlerTest.php
usernane Sep 17, 2025
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
13 changes: 5 additions & 8 deletions .gitattributes
Original file line number Diff line number Diff line change
@@ -1,8 +1,5 @@
# Files and folders here will be not included when creating package
/tests export-ignore
/.github export-ignore
/README.md export-ignore
/.gitignore export-ignore
/.travis.yml export-ignore
/phpunit.xml export-ignore
/sonar-project.properties export-ignore
# Only include WebFiori folder and composer.json in releases
* export-ignore
/WebFiori export-ignore
/WebFiori/** -export-ignore
/composer.json -export-ignore
2 changes: 2 additions & 0 deletions .gitignore
Original file line number Diff line number Diff line change
Expand Up @@ -11,3 +11,5 @@ tests/.phpunit.result.cache
*.Identifier
/tests/.phpunit.cache
clover.xml
examples/basic/02-custom-handler/example-log.log
examples/basic/02-custom-handler/example.log
2 changes: 1 addition & 1 deletion README.md
Original file line number Diff line number Diff line change
Expand Up @@ -45,7 +45,7 @@ Or add to your `composer.json`:
```json
{
"require": {
"webfiori/err": "^1.0"
"webfiori/err": "^2.0"
}
}
```
Expand Down
60 changes: 57 additions & 3 deletions WebFiori/Error/AbstractHandler.php
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,7 @@
use WebFiori\Error\Security\StackTraceFilter;
use WebFiori\Error\Security\OutputSanitizer;
use WebFiori\Error\Security\SecurityMonitor;
use WebFiori\Error\Config\HandlerConfig;

/**
* Abstract base class for implementing custom exception handlers with built-in security.
Expand Down Expand Up @@ -90,15 +91,23 @@ abstract class AbstractHandler {
private OutputSanitizer $outputSanitizer;
private SecurityMonitor $monitor;

/**
* @var HandlerConfig|null
*/
private ?HandlerConfig $config = null;

/**
* Creates new instance of the class.
*
* @param HandlerConfig|null $config Optional configuration
*/
public function __construct() {
public function __construct(?HandlerConfig $config = null) {
$this->traceArr = [];
$this->name = 'New Handler';
$this->isCalled = false;
$this->isExecuting = false;
$this->priority = 0;
$this->config = $config;

$this->initializeSecurity();
}
Expand All @@ -121,6 +130,17 @@ public function createSecurityConfig(): SecurityConfig {
return new SecurityConfig();
}

/**
* Update security level after handler creation.
*/
public function updateSecurityLevel(string $level): void {
$this->security = new SecurityConfig($level);
$this->pathSanitizer = new PathSanitizer($this->security);
$this->traceFilter = new StackTraceFilter($this->security, $this->pathSanitizer);
$this->outputSanitizer = new OutputSanitizer($this->security);
$this->monitor = new SecurityMonitor($this->security);
}

/**
* Returns the priority of the handler.
*
Expand Down Expand Up @@ -171,6 +191,17 @@ public function getCode(): string {
return $this->exception !== null ? (string)$this->exception->getCode() : '0';
}

/**
* Returns a sanitized file path.
* Automatically filters sensitive path information based on environment.
*
* @return string A string that represents the file path where the exception was thrown.
*/
public function getFile(): string {
$rawFile = $this->getRawFile();
return $this->pathSanitizer->sanitizePath($rawFile);
}

/**
* Returns the exception object only if configuration allows it.
* In production, this returns null to prevent information disclosure.
Expand Down Expand Up @@ -250,12 +281,19 @@ public function secureLog(string $message, array $context = []): void {
$sanitizedMessage = $this->outputSanitizer->sanitizeMessage($message);
$sanitizedContext = $this->outputSanitizer->sanitizeContext($context);

error_log(json_encode([
$logData = json_encode([
'message' => $sanitizedMessage,
'context' => $sanitizedContext,
'timestamp' => time(),
'handler' => $this->getName()
]));
]);

$destination = $this->config?->getLogDestination();
if ($destination !== null) {
error_log($logData."\n", 3, $destination);
} else {
error_log($logData);
}
}

/**
Expand All @@ -276,6 +314,15 @@ protected function getSecurityConfig(): SecurityConfig {
return $this->security;
}

/**
* Set handler configuration.
*
* @param HandlerConfig $config The configuration
*/
public function setConfig(HandlerConfig $config): void {
$this->config = $config;
}

/**
* Handles the exception.
*
Expand Down Expand Up @@ -385,6 +432,13 @@ protected function getRawLine(): string {
return $this->exception !== null ? (string)$this->exception->getLine() : '(Unknown Line)';
}

/**
* Internal method to get raw file path.
*/
protected function getRawFile(): string {
return $this->exception !== null ? $this->exception->getFile() : '(Unknown File)';
}

/**
* Internal method to get raw message.
*/
Expand Down
29 changes: 27 additions & 2 deletions WebFiori/Error/Config/HandlerConfig.php
Original file line number Diff line number Diff line change
Expand Up @@ -58,6 +58,11 @@ class HandlerConfig {
*/
private array $originalSettings = [];

/**
* @var string|null Log destination for error_log
*/
private ?string $logDestination = null;

/**
* Initialize configuration with safe defaults.
*/
Expand Down Expand Up @@ -234,7 +239,7 @@ public function apply(): void {
error_reporting($this->errorReporting);
}

if (!$this->respectExistingSettings || ini_get('display_errors') === false) {
if (!$this->respectExistingSettings || !ini_get('display_errors')) {
ini_set('display_errors', $this->displayErrors ? '1' : '0');
}

Expand Down Expand Up @@ -334,7 +339,7 @@ public static function createDevelopmentConfig(): self {
$config->setErrorReporting(E_ALL)
->setDisplayErrors(true)
->setDisplayStartupErrors(true)
->setModifyGlobalSettings(false)
->setModifyGlobalSettings(true)
->setRespectExistingSettings(true);

return $config;
Expand All @@ -357,4 +362,24 @@ public static function createLegacyConfig(): self {

return $config;
}

/**
* Set log destination for error_log.
*
* @param string|null $destination File path or null for system log
* @return self
*/
public function setLogDestination(?string $destination): self {
$this->logDestination = $destination;
return $this;
}

/**
* Get log destination.
*
* @return string|null
*/
public function getLogDestination(): ?string {
return $this->logDestination;
}
}
18 changes: 11 additions & 7 deletions WebFiori/Error/DefaultHandler.php
Original file line number Diff line number Diff line change
Expand Up @@ -74,11 +74,15 @@ public function handle(): void {
private function outputExceptionHeader(): void {
if ($this->isCLI()) {
// CLI format - use ANSI colors and plain text
$this->secureOutput("\n" . str_repeat('=', 60) . "\n");
$this->secureOutput("\033[1;31mAPPLICATION ERROR\033[0m\n");
$this->secureOutput(str_repeat('=', 60) . "\n");
$this->secureOutput("\n" . str_repeat('-', 60) . "\n");
$this->secureOutput("\033[1;31mApplication Error\033[0m\n");
$this->secureOutput(str_repeat('-', 60) . "\n");
} else {
// HTML format
// HTML format - ensure output is sent to browser
if (ob_get_level()) {
ob_end_flush();
}

if ($this->getSecurityConfig()->allowInlineStyles()) {
$this->secureOutput('<div style="border: 1px solid #dc3545; background: #f8d7da; color: #721c24; padding: 15px; margin: 10px 0; border-radius: 4px; font-family: monospace;">');
} else {
Expand Down Expand Up @@ -114,7 +118,7 @@ private function outputCLIDetails(): void {
$this->getLine()
));
} else {
$this->secureOutput("\033[1mLocation:\033[0m Application code\n");
$this->secureOutput("\033[1mLocation:\033[0m Application Code\n");
}

// Show message (automatically sanitized)
Expand Down Expand Up @@ -272,7 +276,7 @@ private function outputCLIFooter(): void {
date('Y-m-d H:i:s')
));

$this->secureOutput(str_repeat('=', 60) . "\n");
$this->secureOutput(str_repeat('-', 60) . "\n");
}

/**
Expand Down Expand Up @@ -315,7 +319,7 @@ private function logException(): void {
}, $this->getTrace());
}

$this->secureLog('Exception handled by DefaultHandler', $context);
$this->secureLog('Exception handled by: '.$this->getName(), $context);
}

/**
Expand Down
46 changes: 43 additions & 3 deletions WebFiori/Error/Handler.php
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,7 @@
use Exception;
use Throwable;
use WebFiori\Error\Config\HandlerConfig;
use WebFiori\Error\Security\SecurityConfig;
/**
* The core class which is used to define errors and exceptions handling.
*
Expand Down Expand Up @@ -235,6 +236,9 @@ private function createExceptionsHandler(): void {
$instance->sortHandlers();

foreach ($instance->handlersPool as $handler) {
if ($ex !== null) {
$handler->setException($ex);
}
if ($handler->isActive() && !$handler->isShutdownHandler()) {
$this->executeHandler($handler, $ex);
}
Expand Down Expand Up @@ -360,12 +364,21 @@ private function initializeHandlerPool(): void {
$this->handlersPool[] = new DefaultHandler();
}

/**
* Handle an exception by invoking the exception handler.
*
* @param Throwable|null $ex The exception to handle
*/
public static function handleException(?Throwable $ex = null): void {
self::invokeExceptionsHandler($ex);
}

/**
* Invoke the exceptions handler for testing purposes.
*
* @param Throwable|null $ex The exception to handle
*/
public function invokeExceptionsHandler(?Throwable $ex = null): void {
public static function invokeExceptionsHandler(?Throwable $ex = null): void {
self::get()->sortHandlers();
self::get()->lastException = $ex;
call_user_func(self::get()->exceptionsHandler, $ex);
Expand Down Expand Up @@ -547,7 +560,7 @@ public static function unregisterHandlerByName(string $identifier): bool {
*
* @return bool True if a handler was removed, false otherwise
*/
private static function unregisterHandlerByClassName(string $className): bool {
public static function unregisterHandlerByClassName(string $className): bool {
if (!class_exists($className)) {
return false;
}
Expand Down Expand Up @@ -622,7 +635,7 @@ private function handleFailureFallback(Throwable $exception): void {
fprintf(STDERR, "Error Handler Failed: %s\n", $exception->getMessage());
} else {
// For web requests, output minimal safe HTML
echo '<p>An error occurred in the error handler. Please check the error logs.</p>';
echo 'An error occurred in the error handler. Please check the error logs.';
}
}

Expand Down Expand Up @@ -667,9 +680,18 @@ public static function setConfig(HandlerConfig $config): void {

self::$config = $config;

// Auto-update security level based on config type
if ($config->shouldDisplayErrors()) {
self::updateSecurityLevels(\WebFiori\Error\Security\SecurityConfig::LEVEL_DEV);
} else {
self::updateSecurityLevels(\WebFiori\Error\Security\SecurityConfig::LEVEL_PROD);
}

// Apply new configuration if handler is already initialized
if (self::$inst !== null) {
self::$config->apply();
// Propagate config to existing handlers
self::updateHandlerConfigs();
}
}

Expand Down Expand Up @@ -701,6 +723,24 @@ public static function resetConfig(): void {
}
}

/**
* Update security levels for all registered handlers.
*/
public static function updateSecurityLevels(string $level): void {
foreach (self::get()->handlersPool as $handler) {
$handler->updateSecurityLevel($level);
}
}

/**
* Update config for all registered handlers.
*/
private static function updateHandlerConfigs(): void {
foreach (self::get()->handlersPool as $handler) {
$handler->setConfig(self::$config);
}
}

/**
* Clean up memory by removing unused handler references and resetting counters.
* Should be called periodically in long-running processes.
Expand Down
Loading