---
title: "Preset exemptions — Testing for Laravel | Roundly"
description: "Exempt through the checked $ignoring parameter, not ->ignoring() — and how prefix-matched exemptions are re-checked for you."
url: https://roundly-consulting.com/open-source/docs/testing-for-laravel/arch-exemptions
language: en
---

[All packages](https://roundly-consulting.com/open-source.md)

[Testing for Laravel](https://roundly-consulting.com/open-source/docs/testing-for-laravel.md)

# Preset exemptions

Six presets accept an exemption list — strictTypes, finalByDefault, noLocalCryptoPrimitives, noDebuggingLeftovers, noVendorNamespace and modelsGoThroughTheFacade. Pass it through the $ignoring parameter: entries passed that way are rot-checked, because a hole shaped like coverage is worse than no coverage.

```php
ArchPresets::finalByDefault('RoundlyConsulting\Shops\Actions', [SomeBase::class]);
ArchPresets::noLocalCryptoPrimitives('RoundlyConsulting\Passkeys', ['RoundlyConsulting\Passkeys\Attestation']);
ArchPresets::noDebuggingLeftovers(['RoundlyConsulting\Shops\Debug\Inspector']);
```

An entry fails when:

- It names nothing that exists — no class, interface, trait or enum, and no namespace that holds PHP files: a typo, or an exemption that outlived the class it excused.
- It exists but matches nothing the preset scans — a real class from another namespace handed to strictTypes, finalByDefault or noLocalCryptoPrimitives, or a noDebuggingLeftovers or noVendorNamespace entry matching no class, trait, enum or interface declared under its $srcDir — each can be exempted by name or by namespace.
- For modelsGoThroughTheFacade, it exempts no violating class.

Not detected: an entry naming a class that already complies, such as an already-final class in a finalByDefault list. It is inert today and silently re-opens the ban the day that class stops complying — keep each list to the classes that need it.

## $alsoAllow is checked too

runtimeRequireIsWhitelisted holds its $alsoAllow to the same standard: an entry fails when the package is not in require — dropped from it, or only ever in require-dev — or when the policy already allows it (illuminate/\*, ext-\*, roundly-consulting/\*, …). The failure names each such entry with its reason, after any disallowed vendor. Without it, a dependency removed from require would leave its carve-out behind, and the vendor could come back later without anyone arguing for it again.

## The parameter, not ->ignoring()

The three presets built on Pest’s arch layer return the underlying arch expectation, so Pest’s fluent ->ignoring() still composes — but it is not checked:

```php
// Checked: a stale or misspelled entry FAILS.
ArchPresets::finalByDefault('RoundlyConsulting\Shops\Actions', ['RoundlyConsulting\Nope']);

// NOT checked: the identical bogus entry passes green, silently.
ArchPresets::finalByDefault('RoundlyConsulting\Shops\Actions')->ignoring('RoundlyConsulting\Nope');
```

The gap is stated rather than patched: ->ignoring() is Pest’s own method on an internal object whose destructor evaluates the expectation, and wedging this package between the two risks an arch case that silently stops running. Exemptions are also scoped to a class, not a function — exempting a class to permit one call relaxes the whole ban for that class, so scope it to the smallest class that needs it.

## Several lists per file

Each preset registers its exemption pin under its own description, so one arch file can carry as many lists as it has presets:

```php
ArchPresets::finalByDefault('RoundlyConsulting\Shops', [ShopException::class]);
ArchPresets::noDebuggingLeftovers([Resource::class]); // a second list — fine
```

## Exemptions match by prefix

Pest excludes a class when its fully-qualified name starts with the exemption string — a prefix test, not class identity. Exempting one class therefore silently exempts every class whose name starts with the same characters:

```php
ArchPresets::finalByDefault('RoundlyConsulting\Purchases', [Stripe::class]);
// …also silences StripeClient — without the shadow re-check, removing its final would stay green.
```

finalByDefault closes this when you pass $ignoring: it re-checks, by reflection, every class your exemptions silence without naming. A shadowed class that is already final stays green; one that isn’t goes red, naming the class and the exemption that hid it:

```
These classes are not final, and `finalByDefault` cannot see them:
  - RoundlyConsulting\Metrics\MetricsManager (hidden by the exemption RoundlyConsulting\Metrics\Metrics)
```

If a shadowed class is genuinely meant to stay open, name it in $ignoring — it becomes an explicit, reviewable decision. Namespace exemptions are left alone, since excluding a subtree is deliberate. The recovery rides on the parameter; if you must use the fluent form, bind the list once and pass it to shadowedClassesAreFinal() yourself:

```php
$ignoring = [Github::class, Batch::class];

ArchPresets::finalByDefault('RoundlyConsulting\Git')->ignoring($ignoring);
ArchPresets::shadowedClassesAreFinal('RoundlyConsulting\Git', $ignoring); // fluent form only
```

## Bespoke rules

For an arch rule of your own, call exemptionsExist() directly. $for names the rule — it is what a developer reads when the pin fires, and it keeps two pins in one file distinct. Pass the namespace your rule scans as $within, and each entry must also match something under it:

```php
ArchPresets::exemptionsExist($ignoring, 'no facades outside the facade layer');
```

## Show your open-source love

This package is free and MIT-licensed. If it saves you time, a one-off donation or a Patreon membership keeps it maintained, tested and documented.

[More ways to support, including crypto](https://roundly-consulting.com/support-us.md)

By donating, you agree to our [donation terms](https://roundly-consulting.com/donation-terms.md).

[Support our open source work (opens in a new tab)](https://donate.stripe.com/dRmeVe8FX5PF1Qd9pXcEw00) [Join us on Patreon (opens in a new tab)](https://www.patreon.com/cw/roundly)

## Want this built into your product?

We integrate our packages into custom Laravel and AI builds. Tell us what you're working on and we'll reply within 48 hours.

[Get a quote in 48 hours](https://roundly-consulting.com/contact.md) [Browse all packages](https://roundly-consulting.com/open-source/docs/testing-for-laravel.md)
