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

Praspel Laravel Package

hoa/praspel

Hoa\Praspel brings Design by Contract to PHP: write behavioral specifications with preconditions, postconditions and invariants, then automatically generate test data to validate your code. A concise specification and testing toolkit for robust PHP libraries.

View on GitHub
Deep Wiki
Context7

Getting Started

Praspel is the PHP Realistic Annotation and Specification Language: a docblock dialect (@requires, @ensures, @invariant, @description, @throwable) plus a parser that turns those clauses into a traversable model. It is a design-by-contract language and model library, not a runtime enforcer — the enforcement/test-generation halves live in companion packages (Hoa\Test, Hoa\Realdom, the atoum extensions) which are equally archived.

Minimal install (dev-only — never ship this in require):

composer require --dev hoa/praspel
# on PHP 8.x you will almost certainly need:
composer require --dev hoa/praspel --ignore-platform-reqs

First steps, in order:

  1. Read the package's Test/Unit/ directory before the README — the README is a stub, the tests are the actual API documentation.
  2. Open Hoa/Praspel/Praspel.php (the entry point) and Hoa/Praspel/Model/Specification.php, Model/Clause.php, Model/Property.php (the object graph you'll actually navigate).
  3. Skim the grammar file (Grammar.pp / the Hoa\Compiler grammar) to see exactly what syntax is legal — it is not PHP.

First use case — parse a contract and inspect it:

use Hoa\Praspel\Praspel;

$praspel = new Praspel();
$praspel->interpret(
    '@requires ( x >= 0 and y >= 0 );' . "\n" .
    '@ensures  \result >= x;'
);

$spec     = $praspel->getRoot();               // Hoa\Praspel\Model\Specification
$requires = $spec->getClause('requires');      // Hoa\Praspel\Model\Clause
$property = $requires->getProperty('x');       // Hoa\Praspel\Model\Property
$body     = $property->getBody();              // Hoa\Ruler\Model\Model

The realistic Laravel use case is specification linting in CI: walk app/, pull ReflectionMethod::getDocComment(), feed the @requires/@ensures blocks to Praspel::interpret(), and fail the build on a clause that doesn't parse. That gets you a documented, machine-checked contract layer without betting production traffic on a 2017 library.

Implementation Patterns

1. A thin, swappable parsing service. Wrap Praspel behind your own interface so the archived dependency stays a single replaceable class:

namespace App\Support\Contracts;

interface SpecParser
{
    /** @return \Hoa\Praspel\Model\Specification */
    public function parse(string $declaration): object;
}

final class PraspelSpecParser implements SpecParser
{
    private array $cache = [];

    public function parse(string $declaration): object
    {
        $key = md5($declaration);
        if (! isset($this->cache[$key])) {
            $praspel = new \Hoa\Praspel\Praspel();
            $praspel->interpret($declaration);
            $this->cache[$key] = $praspel->getRoot();
        }
        return $this->cache[$key];
    }
}

Bind it in a service provider, and when the package finally breaks you swap the binding for a nikic/php-parser + webmozart/assert implementation without touching call sites.

2. Docblock extraction via reflection. Contracts live on methods, so pull them with ReflectionMethod:

$doc = (new \ReflectionMethod(TransferMoney::class, 'handle'))->getDocComment();
preg_match_all('/@(requires|ensures|invariant)\s+(.+?);/s', $doc, $m, PREG_SET_ORDER);
$declaration = implode("\n", array_map(fn ($c) => "@{$c[1]} {$c[2]};", $m));

Cache the resulting Specification per Class::method in the container or a static map — parsing goes through Hoa\Compiler, which is not something you want running on every request.

3. Spec-driven data generation for property tests. The original workflow is Praspel → Hoa\Realdom → sample values satisfying @requires, then assert @ensures holds. In Laravel terms: generate realistic inputs for actions/jobs (including boundary values implied by the predicates) and run them through an in-memory database. You'll be wiring Realdom by hand; there is no Laravel glue to inherit.

4. Contract decorator for critical domain services. If you do want runtime checking, Praspel only gives you the parsed model — you evaluate it. Build a decorator around a service that maps arguments into a Hoa\Ruler\Context, evaluates @requires, runs the method, then evaluates @ensures with \result and \old(...) bound:

$context = new \Hoa\Ruler\Context([
    'x'       => $amount,
    'result'  => $returnValue,   // \result
    'old'     => $before,        // \old(...)
]);
$ruler = new \Hoa\Ruler\Ruler();
$ruler->assert($property->getBody(), $context);

Keep this to a handful of money/state-transition services — it's expensive and throws on violations, which you do not want in a hot HTTP path.

5. Round-trip for documentation. Hoa\Praspel\Visitor\* compiles a model back into Praspel source. Use it to normalize specs (consistent formatting) or emit contract tables into generated docs — handy for proving the docblocks and the code haven't drifted.

6. CI gate, not runtime gate. The pragmatic integration: php artisan contracts:lint (a simple Artisan command) run in the pipeline over app/Domain only. Zero production risk, real value in catching stale/garbled contracts.

Gotchas and Tips

  • Archived since 2017-01-10. No fixes, no security patches, no PHP 8 support promises. Treat it as require-dev at most, and pin it exactly. Also: Dependents: 0 on Packagist is a strong signal there's no community to lean on.
  • Install pain is the first boss. hoa/* packages declare legacy PHP constraints and pull in hoa/consistency (autoloader + the hoa:// stream wrapper), hoa/ruler, hoa/compiler, hoa/ustring, hoa/protocol, hoa/zformat. On modern PHP you'll need --ignore-platform-reqs, and if hoa:// isn't initialized you'll get confusing "stream not found" errors deep inside the compiler. Verify with a one-line file_get_contents('hoa://Library/Praspel/.') smoke test.
  • The language is Hoa\Ruler, not PHP. and / or / xor / not — not &&, ||, !. No strlen(), no in_array(), no null-coalescing. Anything exotic must be registered as a custom operator/function on Hoa\Ruler\Ruler. This is the single biggest surprise for PHP devs.
  • Reserved pseudo-variables. \result, \old(...), \this, \not_this, \trigger_error are Praspel-level, not PHP-level. You must bind them yourself when evaluating — and in a double-quoted PHP string you need "\\result", while inside a docblock it's a literal \result.
  • Clauses end at ;. A missing semicolon silently swallows the next annotation or yields an empty clause. Multi-line clauses are legal but easy to break with auto-formatters (Pint/php-cs-fixer can reflow docblocks). Exclude spec-bearing files from docblock reflow, or lint after formatting in CI.
  • Parsing is not free. Hoa\Compiler builds/uses a memoized LL(k) parser; do not call interpret() inside a request loop. Cache by Class::method plus a hash of the docblock.
  • Silent-vs-loud failure. A malformed clause may throw Hoa\Compiler\Exception — catch it in your linter and report the offending Class::method with the clause text, otherwise your CI step just dies with a stack trace.
  • No Laravel integration exists. No facade, no service provider, no config. Everything above is glue you write. Don't expect @requires to be honored automatically anywhere.
  • Model objects are mutable. You can add clauses programmatically before compiling — useful for building specs from config rather than docblocks, but also a footgun if you cache and then mutate a shared Specification.
  • Where to extend: Ruler::registerFunction() / registerOperator() for domain predicates; the Hoa\Praspel\Visitor classes if you want custom output; the Hoa\Praspel\Model\* tree if you need structured queries over clauses.
  • Have an exit plan. The honest modern equivalents for the same job: webmozart/assert + phpstan/phpstan (invariants), Pest/PHPUnit datasets + fakerphp/faker (property-style input generation), and nikic/php-parser if you need real annotation parsing. Keep Praspel isolated behind an interface so replacing it is a one-file change.
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.
novasa/laravel-language-center
opencage/geocode
adolfoholzer/parameters
livenetworks/ln-starter
shitoudev/permissionmanager
johannesclimacus/laravel-tui
hocvt/seotools
vitalybaev/sypexgeo
amphibee/themosis-ignition
gito-me/fast-excel
frankyso/laravel-ipaymu
geolize-studio/laravel-chile
boomerangui/chat
jinas/dhivatar
store-doc/laravel-storage-driver
plus-it/laravel-translation-manager
diephp/perhaps
diephp/telegraph
osmaviation/spreadsheet
cgv/localized-resource