PHPackages                             visifo/smack-clause - 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. visifo/smack-clause

ActiveLibrary

visifo/smack-clause
===================

SmackClause is a punchy, no-nonsense guard layer: it hits bad input fast, exits early, and leaves a clean trace of what failed, where, and why. The vibe is direct and a little aggressive, but the behavior is precise: stack small, composable rules, get strong default messages, and keep the happy path readable.

00PHPCI failing

Since Mar 28Pushed 1mo agoCompare

[ Source](https://github.com/visifo/smack-clause)[ Packagist](https://packagist.org/packages/visifo/smack-clause)[ RSS](/packages/visifo-smack-clause/feed)WikiDiscussions main Synced 1mo ago

READMEChangelogDependenciesVersions (1)Used By (0)

SmackClause 🥊
=============

[](#smackclause-)

[![Latest Version on Packagist](https://camo.githubusercontent.com/da7116cfc7efca61cfaf222e422948148cb827bfe77408585b60f3418072cfb3/68747470733a2f2f696d672e736869656c64732e696f2f7061636b61676973742f762f76697369666f2f736d61636b636c617573652e7376673f7374796c653d666c61742d737175617265)](https://packagist.org/packages/visifo/smackclause)[![Tests](https://camo.githubusercontent.com/db1d9a8dd9add96eb563bb0972b86f382e470b2ac4f5ec0a1c4fa4653c921841/68747470733a2f2f696d672e736869656c64732e696f2f6769746875622f616374696f6e732f776f726b666c6f772f7374617475732f76697369666f2f736d61636b636c617573652f72756e2d74657374732e796d6c3f6272616e63683d6d61696e266c6162656c3d7465737473267374796c653d666c61742d737175617265)](https://github.com/visifo/smackclause/actions/workflows/run-tests.yml)[![Total Downloads](https://camo.githubusercontent.com/366f516ea70b04115185dcc05b1ebebe4cbfc0a8a1ae7d4f1a729cd36508d1eb/68747470733a2f2f696d672e736869656c64732e696f2f7061636b61676973742f64742f76697369666f2f736d61636b636c617573652e7376673f7374796c653d666c61742d737175617265)](https://packagist.org/packages/visifo/smackclause)

**Fail fast. Speak clear. Stop bad input early.**

SmackClause is a no-nonsense guard layer for PHP 8.5+. It hits bad input before it reaches your business logic, leaving a clean, structured trace of exactly what went wrong.

Why Smack?
----------

[](#why-smack)

- **Direct Assertions**: No "should be" or "must be" fluff. Just `isInt()`, `isEmail()`, `min()`.
- **Auto-Discovery**: It knows your variable names. No more passing string labels manually.
- **PHP 8.5 Native**: Built for the future with property hooks, asymmetric visibility, and high-performance tokenization.
- **Smart Nullability**: Choose between a hard hit (`that()`) or a quiet pass (`maybe()`).

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

[](#installation)

```
composer require visifo/smack-clause
```

Usage
-----

[](#usage)

### The Hard Hit (Mandatory)

[](#the-hard-hit-mandatory)

`Smack::that()` expects a value. If it's null or fails a rule, it smacks back.

```
Smack::that($email)->isString()->isEmail();
Smack::that($age)->isInt()->min(18);
```

### The Quiet Pass (Optional)

[](#the-quiet-pass-optional)

`Smack::maybe()` allows nulls. If the value is there, it better be right.

```
Smack::maybe($bio)->isString()->max(200);
```

### Hitting Collections

[](#hitting-collections)

```
Smack::that($userIDs)->each()->isInt()->isPositive();
```

Custom Smacks
-------------

[](#custom-smacks)

Extend the library with your own logic while keeping `Smack::that(...)->...` syntax.

```
use App\Smack\PlayerSmack;
use Visifo\SmackClause\Smack;

Smack::register(PlayerSmack::class);

Smack::that($player)
    ->isPlayer()
    ->isNotUn()
    ->isInPlayState();
```

You can register as many project-specific root methods as you need, one class at a time:

```
Smack::register(PlayerSmack::class);
Smack::register(VatSmack::class);
```

Use `#[SmackMethod('...')]` on the class and implement `screenInto(...)`. Dynamic smack root methods do not accept arguments.

### Option 1: Implement `Smackable` directly

[](#option-1-implement-smackable-directly)

```
use Visifo\SmackClause\Exceptions\SmackException;
use Visifo\SmackClause\Exceptions\Trace;
use Visifo\SmackClause\Extensions\SmackMethod;
use Visifo\SmackClause\Smackable;

#[SmackMethod('isVat')]
final readonly class VatSmack implements Smackable
{
    private function __construct(
        private VatId $vat,
        private Trace $trace,
    ) {}

    public static function screenInto(
        mixed $value,
        Trace $trace,
    ): self {
        if ($value instanceof VatId) {
            return new self($value, $trace);
        }

        throw SmackException::forExpectedType(VatId::class, $value, $trace);
    }

    public function isEu(): self
    {
        if ($this->vat->isEu()) {
            return $this;
        }

        throw SmackException::forConstraint('EU VAT ID', $this->vat, $this->trace);
    }
}
```

### Option 2: Extend a typed smack (for method reuse)

[](#option-2-extend-a-typed-smack-for-method-reuse)

If you extend `ObjectSmack`, always override `screenInto(...)` and call `parent::__construct(...)` to keep inherited object methods safe.

```
use Visifo\SmackClause\Exceptions\SmackException;
use Visifo\SmackClause\Exceptions\Trace;
use Visifo\SmackClause\Extensions\SmackMethod;
use Visifo\SmackClause\Types\ObjectSmack;

#[SmackMethod('isPlayer')]
final readonly class PlayerSmack extends ObjectSmack
{
    public function __construct(
        private GamePlayer $player,
        private Trace $trace,
    ) {
        parent::__construct($player, $trace);
    }

    public static function screenInto(mixed $value, Trace $trace): self
    {
        if ($value instanceof GamePlayer) {
            return new self($value, $trace);
        }

        throw SmackException::forExpectedType(GamePlayer::class, $value, $trace);
    }

    public function isNotUn(): self
    {
        if (! $this->player->isUn()) {
            return $this;
        }

        throw SmackException::forConstraint('not UN', $this->player, $this->trace);
    }
}
```

See [tests/Fixtures/Smacks/PlayerSmack.php](tests/Fixtures/Smacks/PlayerSmack.php) for a complete example.

IDE Helper
----------

[](#ide-helper)

For dynamic custom methods (`isPlayer()`, `isVat()`, ...), generate a typed helper class for IDE/static tooling:

```
vendor/bin/smack-ide-helper
```

Available parameters:

- `--root=`
    - Default: current working directory (`getcwd()`).
    - Purpose: project root used for `composer.json`, autoload, and output file location.
- `--scan=`
    - Default: all `autoload.psr-4` directories from `/composer.json`.
    - Purpose: limit scanning to specific directory.
    - Notes: can be passed only once; when provided, it must point to a directory inside one of the root package's `autoload.psr-4` directories.

This command generates `_smack_ide_helper.php` in your project root with a `Visifo\\SmackClause\\IdeHelperSmack` class that contains `@method` annotations for dynamic smack methods. `Smack` is linked to it via `@mixin`.

After generating, static tooling can understand calls like:

```
use Visifo\SmackClause\Smack;

Smack::that($player)
    ->isPlayer()
    ->isNotUn();
```

The generator is strict and fails if any registered smack class is invalid (missing `#[SmackMethod('...')]`, duplicate method name, missing `screenInto(...)`, inherited `screenInto(...)` without override, invalid class, etc.).

The Violation Report
--------------------

[](#the-violation-report)

When a check fails, `SmackViolation` *(extends `InvalidArgumentException`)* gives you the forensics:

- **Path**: The variable or nested key name.
- **Value**: What was actually received.
- **Rule**: Which assertion failed.

Changelog
---------

[](#changelog)

Please see [CHANGELOG](CHANGELOG.md) for more information on what has changed recently.

Contributing
------------

[](#contributing)

Please see [CONTRIBUTING](https://github.com/spatie/.github/blob/main/CONTRIBUTING.md) for details.

### Branding &amp; Tone Guide

[](#branding--tone-guide)

[TONE.md](docs/TONE.md) file ensures that anyone contributing to the project maintains the "Smack" vibe.

Security Vulnerabilities
------------------------

[](#security-vulnerabilities)

Please review [our security policy](../../security/policy) on how to report security vulnerabilities.

Credits
-------

[](#credits)

- [Sergej Tihonov](https://github.com/Sergej-Tihonov)
- [All Contributors](../../contributors)

License
-------

[](#license)

The MIT License (MIT). Please see [License File](LICENSE.md) for more information.

###  Health Score

19

—

LowBetter than 10% of packages

Maintenance60

Regular maintenance activity

Popularity0

Limited adoption so far

Community6

Small or concentrated contributor base

Maturity11

Early-stage or recently created project

 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.

### Community

Maintainers

![](https://www.gravatar.com/avatar/3756665fca21e9394353f53f77c529b1b5f8feec5bc76e41fd0c718a88d39a55?d=identicon)[visifo](/maintainers/visifo)

---

Top Contributors

[![Sergej-Tihonov](https://avatars.githubusercontent.com/u/15265981?v=4)](https://github.com/Sergej-Tihonov "Sergej-Tihonov (13 commits)")

### Embed Badge

![Health badge](/badges/visifo-smack-clause/health.svg)

```
[![Health](https://phpackages.com/badges/visifo-smack-clause/health.svg)](https://phpackages.com/packages/visifo-smack-clause)
```

PHPackages © 2026

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