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

Psl Laravel Package

azjezz/psl

PSL is a PHP standard library inspired by hhvm/hsl, offering consistent, well-typed, async-ready APIs for collections, networking, I/O, cryptography, terminal UI, and type-safe validation—replacing fragile PHP primitives with predictable alternatives. Requires PHP 8.4+.

View on GitHub
Deep Wiki
Context7

Getting Started

Minimal Setup

  1. Installation:

    composer require php-standard-library/php-standard-library:^6.3
    

    Requires PHP 8.4+. 6.3.0 is a minor release — no breaking changes, safe bump from 6.2.x.

  2. First Use Case: Validate and normalise untrusted input with Type combinators:

    use Psl\Type;
    
    $orderSchema = Type\shape([
        'sku'    => Type\non_empty_string(),
        'amount' => Type\numeric(),   // new in 6.3.0
        'qty'    => Type\positive_int(),
    ]);
    
    $order = $orderSchema->coerce($request->all()); // throws on failure
    
  3. Where to Look First:

    • Type safety: Psl\Type — DTO hydration, request payloads, config parsing.
    • Collections: Psl\Vec, Psl\Dict, Psl\Iter — allocation-friendly array pipelines.
    • Strings/IO: Psl\Str, Psl\Filesystem, Psl\IO — thin, predictable helpers.
    • Async: Psl\Async — genuinely useful, but only in CLI/worker contexts (see Gotchas).
    • HTTP: Psl\Http\Client — hardened significantly in 6.3.0 (see below).

Implementation Patterns

1. Type-Safe Data Handling

  • Form → DTO hydration, the most common day-to-day win:
    use Psl\Type;
    
    final readonly class CreateOrderData
    {
        public function __construct(
            public string $sku,
            public int|float|string $amount, // numeric() preserves input type
        ) {}
    
        public static function fromArray(array $input): self
        {
            $schema = Type\shape([
                'sku'    => Type\non_empty_string(),
                'amount' => Type\numeric(),
                'note'   => Type\optional(Type\string()),
            ]);
    
            $data = $schema->coerce($input);
    
            return new self($data['sku'], $data['amount']);
        }
    }
    
  • Validation vs. assertion: coerce() returns a typed value or throws; assert() verifies without coercion; matches() is the silent boolean check:
    if (!Type\numeric()->matches($request->input('amount'))) {
        // fall back / return validation error
    }
    
  • Laravel integration tip: run the schema in a FormRequest::passedValidation() or a small DataFactory service and let the thrown Type\Exception\ExceptionInterface be mapped to a 422 by an exception handler. Keep Laravel's Validator for user-facing field messages; use Psl\Type where you want typed output, not just errors.
  • Type\numeric() (6.3.0): accepts int, float, and numeric string, and preserves the input type (numeric strings stay strings). Coercion also accepts Stringable whose string form is numeric. Use it for HTML form fields/query strings where everything arrives as a string, then cast explicitly at the boundary:
    $amount = $data['amount'];               // '1.23' | 1.23 | 42
    $decimal = (float) $data['amount'];      // be explicit downstream
    
    Don't reach for it when you do want coercion to int/float — keep using Type\int(), Type\float(), or a Type\converted() wrapper so type intent stays visible in the signature.

2. Collections

  • Pure pipelines where you want a vec/dict guarantee rather than a fluent object:
    use Psl\Vec;
    use Psl\Dict;
    
    $ids    = Vec\map($rows, fn(array $r) => $r['id']);
    $active = Vec\filter($rows, fn(array $r) => $r['active'] === true);
    $indexed = Dict\from_entries(Vec\map($rows, fn($r) => [$r['id'], $r]));
    
  • Eloquent interop: convert at the edge — $models->all() or ->toArray() — then go back to collect() when you need Eloquent behaviour. Don't mix Collection side effects into a Vec pipeline.
  • Dict\diff() (6.3.0): PHPDoc value bound widened to scalar|null|resource|Stringable to match the string comparison it already performs at runtime. Runtime behaviour is unchanged — but your Psalm/PHPStan baseline may shift, so re-run static analysis after the bump.

3. HTTP Client Hardening (6.3.0) — read before upgrading

  • Psl\Http\Client now validates all Transfer-Encoding response fields, including repeated ones. Invalid or unsupported transfer codings raise ProtocolException instead of silently falling back to Content-Length. Valid chunked still wins over Content-Length.
  • Conflicting Content-Length fields are rejected in HTTP/1.1 responses and HTTP/2 headers (identical duplicates still accepted).
  • Day-to-day implication: these are anti-smuggling protections. If you have outbound integrations against quirky proxies/gateways that emit malformed framing, they will now fail loudly. That's correct — do not paper over it.
    use Psl\Http\Client;
    use Psl\Http\Exception\ProtocolException;
    
    try {
        $response = $client->get($url);
    } catch (ProtocolException $e) {
        report($e);            // terminal: malformed or hostile upstream
        throw $e;              // do NOT retry blindly
    }
    
    Keep retries/timeouts for transport errors (IO, timeouts); treat ProtocolException as a signal to alert, not to fall back to another code path.

4. Async (CLI / Octane only)

  • Useful for fan-out I/O inside a command, Octane worker bootstrap, or a long-running worker:
    use Psl\Async;
    
    $results = Async\concurrently([
        'user'   => static fn() => $client->get("/users/{$id}"),
        'orders' => static fn() => $client->get("/users/{$id}/orders"),
    ], Async\NullCancellationToken::create());
    
  • Do not use it to replace Bus/Queue. Queues give durability, retries, backoff, and visibility; fibers give you concurrency within one process. A dropped fiber loses the job.
  • Inside FPM the request is already one-shot; async there buys you little and risks blocking-loop pitfalls. Reach for Laravel's Http::pool() first, Psl\Async second.
  • Use PSL's own primitives (Async\sleep(), Async\IO) inside tasks — a blocking sleep()/file_get_contents() stalls the scheduler for every task.

5. Laravel Wiring

// app/Providers/PslServiceProvider.php
public function register(): void
{
    $this->app->singleton(\Psl\Http\Client\ClientInterface::class, fn() => \Psl\Http\Client\create());
}

Bind the PSL client so outbound calls are swappable in tests (Psr18 adapter also exists if you'd rather keep one HTTP stack).


Gotchas and Tips

Pitfalls

  1. matches(), not validate() — TypeInterface exposes matches(mixed): bool, coerce(), and assert(). There is no validate() method and no Type\register() registry; compose new types by implementing Psl\Type\TypeInterface (or wrapping an existing one) rather than mutating a global registry.
  2. Type\numeric() preserves type — '42' stays '42', 42 stays 42. If your DTO property is int, numeric() alone won't get you there. Choose Type\int() / Type\float() for real coercion, Type\numeric() for "numeric-ish, keep as-is".
  3. Type\numeric() + Stringable — coercion accepts objects whose __toString() is numeric, and you get the string back, not the object. If you need the object downstream, keep the original alongside.
  4. coerce() throws — wrap at the boundary (a single DataFactory / exception handler mapping), not in every controller. Exception hierarchy lives under Psl\Type\Exception\*.
  5. Shapes and optional keys — shape() requires every key unless wrapped in Type\optional(). Missing keys throw InvalidShapeKeyException; unexpected keys throw UnknownShapeKeyException. Trim input first if you're passing $request->all().
  6. Immutable/by-value collections — Vec\map(), Vec\filter(), Dict\* return new arrays; nothing mutates in place. Vec\filter() reindexes, Dict\filter() preserves keys — pick deliberately.
  7. Dict\diff() PHPDoc change (6.3.0) — widened to scalar|null|resource|Stringable. Runtime unchanged, but expect new @psalm-*/PHPStan messages on mixed-type dicts. Re-baseline.
  8. HTTP ProtocolException is not transient — see above. Don't add it to a retry list.

Debugging Tips

  • Type failures: catch the specific exception and log the input shape (not values) to avoid leaking PII:
    try {
        $data = $schema->coerce($input);
    } catch (\Psl\Type\Exception\ExceptionInterface $e) {
        Log::warning('type.coerce.failed', ['path' => $e->getPath(), 'input' => array_map('gettype', $input)]);
        throw $e;
    }
    
  • Introspection: Type\TypeInterface::toString() gives a readable representation for debug logs — handy when a shape error names a nested path.
  • HTTP framing issues: after upgrading to 6.3.0, log the offending headers from the caught exception's context before rethrowing; framing bugs are almost always on the upstream side and you'll need the evidence to file a ticket.
  • Async: wrap fan-out in try/catch and remember one failing task can cancel siblings depending on the primitive used — use TaskGroup when you want per-task results, concurrently() when all-or-nothing is fine.

Extension Points

  1. Custom types: implement Psl\Type\TypeInterface (or use Type\converted() for a cheap map/validate pair):
    $priceType = Type\converted(
        Type\numeric(),
        static fn(int|float|string $v) => Money::of((string) $v, 'EUR'),
    );
    
  2. Async error handling: TaskGroup with explicit catch per awaitable; avoid global hooks.
  3. Container bindings: bind Psl\Http\Client\ClientInterface and Psl\Type\TypeInterface factories so tests can substitute fixtures.

Tooling

  • Static analysis: the Psalm plugin gives PSL-aware inference for coerce() results — worth the setup cost given how much of the value is in typed output.
    # psalm.xml
    <plugins>
        <pluginClass class="Psl\Integration\Psalm\Plugin"/>
    </plugins>
    
  • Upgrade checklist for 6.3.0: composer update php-standard-library/php-standard-library, run Psalm/PHPStan (expect Dict\diff() docblock deltas), then exercise all outbound Http\Client integrations against staging — the Transfer-Encoding / duplicate Content-Length rejection is the only behaviour change worth a regression pass.
  • Docs: the 6.3.0 docs site is now pre-rendered static HTML with section permalinks and GitHub-style anchors — link directly to sections in code review comments.
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.
roundly-consulting/kubernetes-api-for-laravel
roundly-consulting/plausible-for-laravel
gvp/gvp-laravel-template
metasyncsite/laravel-metasync-client
roundly-consulting/onboarding-for-laravel
andydefer/laravel-notification
adachsoft/cli-process-tester
jhaskell13/laraflow
sebastienheyd/boilerplate-media-manager
ccast/tagixo-primix
pretendtrue/laravel-builder
sawmainek/laravel-scout-tntsearch-driver
tomshaw/laravel-calendar-table
dibakar/laravel-ownership
steadrun/laravel-monitor
labyrinthe/connect-laravel
elmogy/layan
drewdan/sentinel-client
dskripchenko/laravel-llm
fatryst/nuonuo