jooservices/exceptions

Shared exception contracts and base classes for the JOOservices ecosystem.

Maintainers

Package info

github.com/jooservices/exceptions

pkg:composer/jooservices/exceptions

Transparency log

Statistics

Installs: 383

Dependents: 2

Suggesters: 0

Stars: 0

Open Issues: 0

v0.5.0 2026-07-18 23:20 UTC

This package is auto-updated.

Last update: 2026-07-22 02:31:45 UTC


README

CI PHP Version License: MIT Packagist Version

The JOOservices Exceptions Library is a PHP 8.5+ foundational library providing shared exception contracts, base exception classes, and structured context utilities across the JOOservices package ecosystem.

Package name: jooservices/exceptions

Install

composer require jooservices/exceptions

Core Features

  • Ecosystem-Wide Catching: Provide a root interface marker JOOExceptionInterface to catch any exception thrown within the JOOservices ecosystem.
  • SPL Class Semantics Preservation: Provide separate AbstractJOORuntimeException and AbstractJOOLogicException abstract bases to preserve standard PHP SPL hierarchy.
  • Structured Diagnostic Context: Enable context-rich logging and troubleshooting using ContextAwareExceptionInterface and ExceptionContext.
  • Sensitive Data Redaction: Intercept and mask confidential context keys at the application boundary via ContextRedactorInterface.
  • Framework Decoupled: Zero external runtime dependencies. Completely agnostic and safe to use in pure PHP libraries and framework packages alike.

Basic Usage

Catching Ecosystem Exceptions

Catch any exception thrown by a JOOservices library:

use JOOservices\Exceptions\Contracts\JOOExceptionInterface;

try {
    $dto = UserDto::from($input);
} catch (JOOExceptionInterface $e) {
    // Handles any exception implementing JOOExceptionInterface
    $logger->error($e->getMessage());
}

Declaring base exceptions

Define package-level base exceptions using the appropriate base class:

use JOOservices\Exceptions\Base\AbstractJOORuntimeException;

abstract class ClientException extends AbstractJOORuntimeException {}

Context-Aware Exceptions

For exceptions that need to carry structured diagnostic data (like fields, paths, or resource keys):

use JOOservices\Exceptions\Base\AbstractContextAwareException;

final class HydrationException extends AbstractContextAwareException
{
    public static function forField(string $path, string $expectedType): self
    {
        return (new self("Hydration failed for field '{$path}'"))
            ->withContext([
                'path' => $path,
                'expectedType' => $expectedType,
            ]);
    }
}

Retrieving and Logging Context

try {
    throw HydrationException::forField('user.email', 'string');
} catch (ContextAwareExceptionInterface $e) {
    $logger->error($e->getMessage(), $e->getContext());
    // Context: ['path' => 'user.email', 'expectedType' => 'string']
}

Security & Context Redaction

To prevent sensitive keys (like passwords, keys, or credentials) from showing up in log systems, register a global redactor at application bootstrap:

use JOOservices\Exceptions\Base\AbstractContextAwareException;
use JOOservices\Exceptions\Contracts\ContextRedactorInterface;

AbstractContextAwareException::setRedactor(new class implements ContextRedactorInterface {
    private const SENSITIVE = ['password', 'secret', 'token', 'authorization'];

    public function redact(array $context): array
    {
        return array_map(
            fn ($key, $value) => in_array(strtolower($key), self::SENSITIVE, true) ? '[REDACTED]' : $value,
            array_keys($context),
            array_values($context),
        );
    }
});

Now, any exception extending AbstractContextAwareException will have its context keys masked when calling getContext().

Trait Integration

If an exception class cannot inherit from AbstractContextAwareException because it already inherits from a third-party class:

use JOOservices\Exceptions\Concerns\HasExceptionContext;
use JOOservices\Exceptions\Contracts\ContextAwareExceptionInterface;

class GuzzleWrappedException extends GuzzleException implements ContextAwareExceptionInterface
{
    use HasExceptionContext;

    public function __construct(string $message, int $code = 0, ?\Throwable $previous = null)
    {
        parent::__construct($message, $code, $previous);
        $this->initContext();
    }
}

Verification

Run test suites and style linters:

composer check