Weave Code
Weaver
Helps Laravel developers discover, compare, and choose open-source packages. See popularity, security, maintainers, and scores at a glance to make better decisions.
Feedback
Share your thoughts, report bugs, or suggest improvements.
Subject
Message

Contextable Exception Laravel Package

pioniro/contextable-exception

Contextable exceptions for Laravel and PHP: throw exceptions that carry structured context data, so you can attach the values, inputs, or state behind a failure and read them back later when handling or logging it. Lightweight and simple to adopt.

View on GitHub
Deep Wiki
Context7

Getting Started

Install the package and confirm the trait/interface names it ships with before writing any application code — the repository is small and its README is the source of truth:

composer require pioniro/contextable-exception

There is no service provider to register and no config to publish in a package of this scope; it is a trait + interface pair you compose into your own exception classes. The first place to look is vendor/pioniro/contextable-exception/src — you need to know (a) the interface name, (b) the trait name, and (c) the exact accessor for context (getContext(), context(), or similar). Confirm the method signature once, then treat the package as frozen: last release was April 2020, so assume it will not change and that you own any compatibility fixes.

Your first use case should be a single domain exception in a service class, not a wholesale refactor:

// app/Exceptions/PaymentFailedException.php
namespace App\Exceptions;

use Exception;
use Pioniro\ContextableException\ContextableExceptionInterface;
use Pioniro\ContextableException\ContextableExceptionTrait;

class PaymentFailedException extends Exception implements ContextableExceptionInterface
{
    use ContextableExceptionTrait;
}
throw (new PaymentFailedException('Gateway declined the charge'))
    ->withContext([
        'order_id'   => $order->id,
        'gateway'    => $gateway->name(),
        'status'     => $response->status(),
    ]);

That single throw gives you structured, greppable context in storage/logs/laravel.log instead of string-concatenated messages. Once that works end to end, expand to the two or three exception classes that actually get thrown in production.

Implementation Patterns

Base exception class per domain. Rather than sprinkling the trait, create a shared abstract base and inherit:

abstract class DomainException extends Exception implements ContextableExceptionInterface
{
    use ContextableExceptionTrait;
}

Every concrete exception then gets context support for free, and you can add render()/report() overrides once instead of per class.

Handler integration. The highest-leverage hook is app/Exceptions/Handler.php. Flatten the context into the log record so it is queryable:

public function report(Throwable $e): void
{
    if ($e instanceof ContextableExceptionInterface) {
        Log::withContext(['exception_context' => $e->getContext()]);
    }
    parent::report($e);
}

Log::withContext() is the right primitive in modern Laravel because it attaches to every subsequent log line in the request, including chained handlers.

Context enrichment as the exception bubbles. Because the context is mutable data on the object, you can layer it: the repository adds the query, the service adds the aggregate ID, the job adds the attempt number. Prefer one throw with a rich context over wrapping the same exception three times — wrapping loses the original message in most log formatters.

Carrying context through queued jobs. Queued exceptions are serialized into failed_jobs. Anything you put in context must be JSON-serializable — models will be hydrated if you pass them, so pass IDs and scalars instead. A practical pattern is a JobFailed listener that reads $event->exception->getContext() and dispatches an alert with a stable schema.

API boundary. If you expose error payloads, do not dump raw context to the client. Keep a whitelist:

public function render($request, Throwable $e)
{
    if ($e instanceof PaymentFailedException) {
        return response()->json([
            'message' => 'Payment could not be processed.',
            'code'    => $e->getContext()['status'] ?? null,
        ], 422);
    }
    return parent::render($request, $e);
}

Testing. Context makes assertions much cheaper than message matching:

$this->assertSame($order->id, $caught->getContext()['order_id']);

Build a small helper trait in tests/ to catch-and-return exceptions so you do not repeat try/catch blocks.

Gotchas and Tips

  • Verify the API before committing. With 0 stars, 0 dependents, and no release since April 2020, there is no community contract guaranteeing method names or behavior. Read the source once; if withContext() is actually setContext() or the trait is named differently, adapt immediately rather than guessing from the README.
  • Serialization will bite you. Exceptions are serialized by Laravel when a queue job fails. A closure, a resource handle, or a full Eloquent model with loaded relations inside the context array can produce bloated or unserializable failed_jobs payloads. Store IDs and primitives.
  • Never put secrets in context. Tokens, passwords, full request bodies, and PII end up in laravel.log and in whatever log aggregation you ship to. Treat the context array as public-to-operators data and scrub at the source, or add a redaction pass in Handler::report().
  • Log-line size limits. Context is printed inline; a large array (a 200-row collection) can be truncated by your log driver or exceed CloudWatch/Loki per-line limits. Cap collections with ->take(20) and a 'truncated' => true flag.
  • Message vs. context drift. Teams often start stuffing everything into the message string. Once you adopt this package, make the message a stable, human-readable constant and move all variable data to context — otherwise you defeat the purpose and break log-based alerting.
  • No framework coupling means no magic. The package does not know about Laravel's Handler, Log, or Http\Exceptions. Any rendering, reporting, or redaction behavior is yours to write. That is fine, but budget for it — the package is roughly one trait and one interface, so expect to write the integration yourself.
  • Extension point is the trait, not the package. If you need getContext(): array on a class that already extends a vendor exception, you cannot add the trait to a parent — use composition or a ContextualException wrapper instead of an inheritance chain.
  • Debugging. When context is not appearing in logs, check in this order: does the class actually implements the interface (the instanceof check in Handler::report silently fails otherwise), is report() overridden in the concrete exception or a parent (which skips your handler), and is the exception being caught and swallowed somewhere upstream. dd($e instanceof ContextableExceptionInterface) at the throw site resolves it in seconds.
  • Maintenance posture. Pin the version in composer.json and consider forking it into app/Support/ if you rely on it heavily — it is ~100 lines, so vendoring removes an unmaintained dependency risk entirely at almost no cost.
Weaver

How can I help you explore Laravel packages today?

Conversation history is not saved when not logged in.
Prompt
Add packages to context
No packages found.
frankyso/ipaymu
3mdikram/worldmoneywords
snowsoft/config
itmosoft/laravel-subscriptions
larva/laravel-ai-qwen
diephp/sequences
users-au/socialite-provider
twidpay/twid-logger
mrnewport/laravel-plaid
mrnewport/laravel-repo
mrnewport/laravel-docsign
mrnewport/laravel-stow
mrnewport/laravel-flow
mrnewport/laravel-priceable
mrnewport/laravel-nestedterms
fredmeng/laravel-apidoc-generator
itstructure/laravel-multilingual-tools
pdustdar/elasticsearch
markupai/php-sdk
rlacerda83/lumem-microservice-shipment