PHPackages                             decodelabs/remnant - PHPackages - PHPackages  [Skip to content](#main-content)[PHPackages](/)[Directory](/)[Categories](/categories)[Trending](/trending)[Leaderboard](/leaderboard)[Changelog](/changelog)[Analyze](/analyze)[Collections](/collections)[Log in](/login)[Sign up](/register)

1. [Directory](/)
2. /
3. [Debugging &amp; Profiling](/categories/debugging)
4. /
5. decodelabs/remnant

ActiveLibrary[Debugging &amp; Profiling](/categories/debugging)

decodelabs/remnant
==================

Easier stack traces

v0.2.5(5mo ago)06.2k↓100%1MITPHPPHP ^8.4CI passing

Since Jun 4Pushed 3mo agoCompare

[ Source](https://github.com/decodelabs/remnant)[ Packagist](https://packagist.org/packages/decodelabs/remnant)[ RSS](/packages/decodelabs-remnant/feed)WikiDiscussions develop Synced today

READMEChangelog (9)Dependencies (1)Versions (11)Used By (1)

Remnant
=======

[](#remnant)

[![PHP from Packagist](https://camo.githubusercontent.com/f21171d0199a5c5afb722a53e4ee15180e025d639e8c576d676bdb5b059d08ae/68747470733a2f2f696d672e736869656c64732e696f2f7061636b61676973742f7068702d762f6465636f64656c6162732f72656d6e616e743f7374796c653d666c6174)](https://packagist.org/packages/decodelabs/remnant)[![Latest Version](https://camo.githubusercontent.com/113ccb470ab0cb1510b6e45a30836354633dd1e89d90feca3494362e12a36e77/68747470733a2f2f696d672e736869656c64732e696f2f7061636b61676973742f762f6465636f64656c6162732f72656d6e616e742e7376673f7374796c653d666c6174)](https://packagist.org/packages/decodelabs/remnant)[![Total Downloads](https://camo.githubusercontent.com/16f73056d3ec4b07cfa75354862d6b7ca7b882d5069df049e9f279fc871d94f3/68747470733a2f2f696d672e736869656c64732e696f2f7061636b61676973742f64742f6465636f64656c6162732f72656d6e616e742e7376673f7374796c653d666c6174)](https://packagist.org/packages/decodelabs/remnant)[![GitHub Workflow Status](https://camo.githubusercontent.com/8c2c842501959eb75aeb38dc550ddb72d9f8b7d0d950c2d1086b3ceade00fabe/68747470733a2f2f696d672e736869656c64732e696f2f6769746875622f616374696f6e732f776f726b666c6f772f7374617475732f6465636f64656c6162732f72656d6e616e742f696e746567726174652e796d6c3f6272616e63683d646576656c6f70)](https://github.com/decodelabs/remnant/actions/workflows/integrate.yml)[![PHPStan](https://camo.githubusercontent.com/e25c14ce011edabdd0fbd2e10415b41cc5d66ed11ef3e5b7edd074c5bdd35a2d/68747470733a2f2f696d672e736869656c64732e696f2f62616467652f5048505374616e2d656e61626c65642d3434434331312e7376673f6c6f6e6743616368653d74727565267374796c653d666c6174)](https://github.com/phpstan/phpstan)[![License](https://camo.githubusercontent.com/76ac43be89719158619c54c049459707680570d45e58bf701e6346f7ff478540/68747470733a2f2f696d672e736869656c64732e696f2f7061636b61676973742f6c2f6465636f64656c6162732f72656d6e616e743f7374796c653d666c6174)](https://packagist.org/packages/decodelabs/remnant)

### Easier stack traces

[](#easier-stack-traces)

Remnant gives you a clean, readable view for humans and a stable JSON schema for tools. It avoids leaking sensitive values by default and lets you hide the noisy bits when you want to.

- **Readable traces** with clear call-sites and a compact argument summary
- **Stable JSON** that tools can consume
- **Safe by default** – no deep value dumping, sensitive values are redacted
- **Root / package aware** – paths are prettified and simplified where possible
- **Tiny &amp; modern** – PHP 8.4+, property getters and readonly value objects

---

Installation
------------

[](#installation)

#### Requirements

[](#requirements)

- PHP **8.4+**

Install via Composer:

```
composer require decodelabs/remnant
```

Quick start
-----------

[](#quick-start)

```
use DecodeLabs\Remnant\Trace;

$trace = Trace::create();                 // from here
// or: $trace = Trace::fromException($e); // from a Throwable

echo (string)$trace;                      // pretty string output
$json = json_encode($trace, JSON_PRETTY_PRINT); // stable JSON
```

### String output

[](#string-output)

Example:

```
24: ● DecodeLabs\Remnant\Trace::create()
    @root:/src/@components/pages/index.php:28
23: ● {anonymous:@root:/src/@components/pages/index.php:25}->__construct(
        timer: object(DecodeLabs\Kairos\Timer)
    )
    @root:/src/@components/pages/index.php:25
22: ● {closure:@root:/src/@components/pages/index.php:24}()
    @root:/src/@components/pages/index.php:47 [eval:1]
21: ○ [internal] eval()
    @root:/src/@components/pages/index.php:47
20: ◐ {closure:@root:/src/@components/pages/index.php:21}(
        arg#0: object(DecodeLabs\Lucid)
    )
    @pkg:slingshot/src/Slingshot.php:500
…
02: ◒ DecodeLabs\Genesis->run()
    @root:/vendor/genesis.php:25
01: ○ [internal] require(
        arg#0: '/path/to/...'[64]
    )
    @pkg:valet/server.php:167

```

#### Legend

[](#legend)

- `●` – frame within **project root** (`@root:`)
- `◐` – frame outside of the **project root** (e.g. a **symlinked package**) (`@pkg:name:`)
- `◒` – frame under **root vendor** (`@root:/vendor/...`)
- `○` – **internal/engine** frame

#### Conventions

[](#conventions)

- Call-site (file:line) is shown on the line below the function.
- Closures and anonymous classes include their defining file:line.
- Arrays print as `array(n)`. Objects print as `object(Fully\Qualified\Class)`.
- Unknown argument names are shown as `arg#N:` to keep order explicit.
- Sensitive values are rendered as `⟪redacted⟫`.
- Long strings are truncated with an ellipsis and the original length, e.g. `'/path/…'[64]`.
- You may see fold lines like `… 3 hidden` when filters elide frames.

### JSON output

[](#json-output)

`Trace` and frame value objects implement `JsonSerializable`.

- **Schema tag**: `"schema": "remnant.trace@1"`
- **Stable keys**; some fields may be `null` (internal frames, optimised frames, etc.)
- **Absolute paths**: opt-in via a view option (off by default)

#### Example

[](#example)

```
{
  "schema": "remnant.trace@1",
  "frames": [
    {
      "function": "DecodeLabs\\Remnant\\Trace::create",
      "internal": false,
      "arguments": {},
      "callSite": {
        "file": "@root:/src/@components/pages/index.php",
        "absolute": "/Users/.../src/@components/pages/index.php",
        "line": 28
      },
      "location": {
        "file": "@pkg:remnant/src/Trace.php",
        "absolute": "/Users/.../remnant/src/Trace.php",
        "line": 90
      }
    },
    {
      "function": "eval",
      "internal": true,
      "arguments": {},
      "callSite": {
        "file": "@root:/src/@components/pages/index.php",
        "absolute": "/Users/.../src/@components/pages/index.php",
        "line": 58
      },
      "location": {
        "file": "@root:/src/@components/pages/index.php",
        "absolute": "/Users/.../src/@components/pages/index.php",
        "line": 58,
        "eval": 1
      }
    }
    // …
  ]
}
```

#### Notes

[](#notes)

- `function` is always a string.
- `internal` is `true` for engine frames (`eval`, `require`, etc.).
- `arguments` is an object keyed by arg name (or `arg#N`).
- `callSite` is where the call originated; may be `null`.
- `location` is where the frame executed; may be `null`.
- `file` paths are prettified where possible.
- `absolute` paths can be enabled or disabled.
- `eval` marks eval’d code with its eval line.

Arguments &amp; privacy
-----------------------

[](#arguments--privacy)

Remnant **does not dump deep values**. It prints a compact, single-line summary per argument:

- Scalars are inlined (with truncation where necessary).
- Arrays → `array(n)` (count only).
- Objects → `object(FQCN)`.
- Unknown names (e.g. closure params) are labelled `arg#N`.
- Redacted values use `⟪redacted⟫`.

This keeps traces useful without leaking secrets or producing log noise.

Path prettification
-------------------

[](#path-prettification)

Paths are prettified where possible using [Monarch](https://github.com/decodelabs/monarch). Ensure `Monarch` is available in your project, and prepare your path aliases in your bootstrap:

```
Monarch::getPaths()->alias('@components', '@run/src/@components');
```

Anchors
-------

[](#anchors)

Anchors let you rewind the trace to a specific point based on various different criteria. They allow you to filter out frames that are created by *generating* the trace, rather than the trace itself.

```
use DecodeLabs\Remnant\Anchor\ClassIdentifier;
use DecodeLabs\Remnant\Anchor\FunctionIdentifier;
use DecodeLabs\Remnant\Anchor\Rewind;
use DecodeLabs\Remnant\FunctionIdentifier\ObjectMethod;
use DecodeLabs\Remnant\Trace;

// Pass a rewind anchor to rewind the trace by that many frames
$trace = Trace::create(new Rewind(2));

// Pass a FunctionIdentifier anchor to rewind back to the last instance of the function
$trace = Trace::create(
    new FunctionIdentifier(
        new ObjectMethod(MyClass::class, 'myFunction')
    )
);

// Pass a ClassIdentifier anchor to rewind back to the last instance of the class
$trace = Trace::create(
    new ClassIdentifier(MyClass::class)
);
```

View options
------------

[](#view-options)

View options let you customise the output of the trace, all options have a reasonable default value.

```
use DecodeLabs\Remnant\ArgumentFormat;
use DecodeLabs\Remnant\ViewOptions;

$trace = Trace::create(options: new ViewOptions(
    rootPath: '/path/to/project',
    filters: [],
    // How arguments are rendered - `Count`, `InlineValues` or `NamedValues`
    argumentFormat: ArgumentFormat::NamedValues,
    maxStringLength: 16,
    redact: fn (string $key, mixed $value) => $key === 'password',
    collapseSingleLineArguments: false,
    absolutePaths: true
));
```

Filters
-------

[](#filters)

Filters let you filter out frames before rendering the trace. No filters are applied by default.

Available filters include:

- **Vendor** – hide frames under `{$projectRoot}/vendor/`
- **Paths** – prefix match against canonicalised (forward-slash) paths
- **Function / Class / Namespace identifiers** – match by function signature

Example:

```
use DecodeLabs\Greenleaf;
use DecodeLabs\Remnant\Filter;
use DecodeLabs\Remnant\FunctionIdentifier\ObjectMethod;
use DecodeLabs\Remnant\Trace;
use DecodeLabs\Remnant\ViewOptions;

$view = new ViewOptions(
    filters: [
        new Filter\Vendor(),
        new Filter\Paths(['/path/to/filter/']),
        new Filter\FunctionIdentifier(new ObjectMethod(Greenleaf::class, 'myFunction')),
        new Filter\ClassIdentifier(Greenleaf::class),
        new Filter\NamespaceIdentifier(Greenleaf::class),
    ]
);

echo Trace::create(options: $view);
```

Accessing frames
----------------

[](#accessing-frames)

Access frames from the trace using standard array and iterator methods - frames are indexed in order from `0` just like the array returned by `debug_backtrace()`. Negative and out of range indexes return null:

```
foreach ($trace as $frame) {
    echo $frame->location . ' - ' . $frame->function . PHP_EOL;
}

$frame = $trace[0]; // Get the first frame
echo (string)$frame; // Convert frame to string for a formatted output
```

Licensing
---------

[](#licensing)

Remnant is licensed under the MIT License. See [LICENSE](./LICENSE) for the full license text.

###  Health Score

41

—

FairBetter than 89% of packages

Maintenance75

Regular maintenance activity

Popularity20

Limited adoption so far

Community8

Small or concentrated contributor base

Maturity50

Maturing project, gaining track record

 Bus Factor1

Top contributor holds 100% of commits — single point of failure

How is this calculated?**Maintenance (25%)** — Last commit recency, latest release date, and issue-to-star ratio. Uses a 2-year decay window.

**Popularity (30%)** — Total and monthly downloads, GitHub stars, and forks. Logarithmic scaling prevents top-heavy scores.

**Community (15%)** — Contributors, dependents, forks, watchers, and maintainers. Measures real ecosystem engagement.

**Maturity (30%)** — Project age, version count, PHP version support, and release stability.

###  Release Activity

Cadence

Every ~15 days

Recently: every ~0 days

Total

9

Last Release

170d ago

### Community

Maintainers

![](https://www.gravatar.com/avatar/8a241d64d12b3b5ee94197862ec1ec30b82ed2efa34a0cd7f4c3565a021daddd?d=identicon)[betterthanclay](/maintainers/betterthanclay)

---

Top Contributors

[![betterthanclay](https://avatars.githubusercontent.com/u/1273586?v=4)](https://github.com/betterthanclay "betterthanclay (50 commits)")

### Embed Badge

![Health badge](/badges/decodelabs-remnant/health.svg)

```
[![Health](https://phpackages.com/badges/decodelabs-remnant/health.svg)](https://phpackages.com/packages/decodelabs-remnant)
```

###  Alternatives

[filp/whoops

php error handling for cool kids

13.3k394.4M1.3k](/packages/filp-whoops)[symfony/stopwatch

Provides a way to profile code

2.8k379.8M784](/packages/symfony-stopwatch)[composer/xdebug-handler

Restarts a process without Xdebug.

2.6k390.6M74](/packages/composer-xdebug-handler)[beyondcode/laravel-dump-server

Symfony Var-Dump Server for Laravel

1.6k42.1M154](/packages/beyondcode-laravel-dump-server)[fruitcake/laravel-debugbar

PHP Debugbar integration for Laravel

19.1k253.2k13](/packages/fruitcake-laravel-debugbar)[spatie/ignition

A beautiful error page for PHP applications.

510143.5M56](/packages/spatie-ignition)

PHPackages © 2026

[Directory](/)[Categories](/categories)[Trending](/trending)[Changelog](/changelog)[Analyze](/analyze)
