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

Promise Laravel Package

torann/promise

Torann/Promise provides a lightweight promise implementation for Laravel and PHP, letting you defer work and chain success or failure callbacks through a simple, fluent API. It helps organize asynchronous-style logic while keeping application code readable and easy to manage.

View on GitHub
Deep Wiki
Context7

Getting Started

First, resolve the identity of the package before you install it. The metadata calls it "Simple Roles and Permissions for Laravel 5"; description.md documents a Promises/A+ implementation with then()/catch()/finally(), Deferred, and a facade. Those cannot both be true of one package. Spend fifteen minutes before touching composer.json:

git clone https://github.com/Torann/laravel-promise /tmp/promise
ls /tmp/promise/src && cat /tmp/promise/composer.json
grep -rl "then\|Deferred\|Role\|Permission" /tmp/promise/src

The class list settles it. Everything below assumes the Promise reading — if the source is RBAC, treat this as a design reference and pivot to a maintained authorization package.

Register the provider and alias (Laravel 5–7 era bootstrap).

// config/app.php
'providers' => [Torann\Promise\PromiseServiceProvider::class],
'aliases'   => ['Promise' => Torann\Promise\Facades\Promise::class],

On Laravel 8+ the illuminate/* constraints will likely refuse to resolve. A failing composer require is the correct first answer — fork and widen the constraint deliberately rather than reaching for --ignore-platform-reqs, which just moves the breakage to runtime.

First use case: collapse a multi-step transformation behind one error handler. This is the only thing the package buys you that try/catch does not:

use Torann\Promise\Facades\Promise;

$summary = Promise::resolve($orderId)
    ->then(fn ($id) => $this->orders->findOrFail($id))
    ->then(fn ($order) => $this->totals->calculate($order))
    ->then(fn ($totals) => $this->formatter->summary($totals))
    ->catch(function (Throwable $e) {
        Log::warning('order.summary.failed', ['exception' => $e]);
        throw $e;
    });

Write one test against this chain where the second then throws, before you adopt the pattern anywhere else. If rejection propagation does not match your mental model on the first attempt, stop — the semantics are undocumented and you are about to build on guesses.

Where to look first, in this order: the service provider (is Promise a singleton facade or a factory?), the concrete promise class's then() (are callbacks invoked eagerly at construction or deferred until settle? are return values auto-wrapped into promises?), and the rejection path (what is caught — Throwable, Exception, or only a package-specific type?). Those three answers cover nearly every question you will have later.

Implementation Patterns

Use it as in-request control flow, never as scheduling. The legitimate day-to-day shape is: three to five steps, each capable of throwing, one catch at the bottom. When you find yourself writing then() for a two-step operation that lives in one class, try/catch is shorter, dependency-free, and will still compile in five years. Reach for the chain only when the nesting genuinely hurts.

Wrap it behind an internal contract — this is the highest-leverage decision you will make.

interface DeferredWork
{
    public function then(callable $fn): static;
    public function catch(callable $fn): static;
    public function finally(callable $fn): static;
}

A thin adapter delegates to Torann\Promise; the swap to native try/catch, Guzzle promises, or PHP 8.3 concurrency later replaces one class instead of forty call sites. With zero dependents upstream, this is not ceremony — it is the only thing that makes the dependency survivable.

Centralize failure translation at the chain boundary. One catch per workflow that converts package rejection semantics into your domain's error model:

$this->deferred($payload)
    ->then(fn ($p) => $this->validate($p))
    ->then(fn ($p) => $this->normalize($p))
    ->then(fn ($p) => $this->persist($p))
    ->catch(fn (Throwable $e) => $this->report(new WorkflowFailed($e)));

Scattering catch blocks through the middle of a chain reproduces exactly the nesting you adopted the package to remove.

Use all() for fan-out shape, not for parallelism. Aggregating independent lookups into one result is the clearest real use:

Promise::all([
    'customer' => $this->crm->fetch($id),
    'invoice'  => $this->billing->fetch($id),
    'flags'    => $this->flags->for($id),
])->then(fn ($r) => $this->view->render($r));

In synchronous PHP-FPM this executes sequentially. The payoff is a single result object and one place to handle a partial failure — not latency. Say so in the PR description, because someone will later "optimize" it assuming otherwise. If you want actual concurrency on outbound HTTP, use Http::pool(); if you want work after the response, use a job.

Treat race() as a footgun unless you are wrapping real I/O promises. In a single-threaded runtime, "first to settle" is usually just "first in the array" — deterministic and semantically meaningless. Only reach for it if the underlying operations are genuine async adapters.

Testing pattern. Promises are values you construct, so tests stay simple: resolve with fakes, assert the returned value, and assert the rejection branch by supplying a seed that makes a specific step throw. Write those conformance tests — propagation order, finally execution on rejection, error type coercion — before wiring production call sites. They become the acceptance criteria for whatever replaces this package.

Prefer constructor injection of your own interface over the Promise facade. If the provider registers a singleton, facade and app() resolution may share state between chains; injected instances keep tests isolated and prevent cross-request leakage in long-running workers (Octane, queue consumers).

Gotchas and Tips

The naming mismatch is a hazard, not a documentation nit. Two engineers can read the two documents and build incompatible architectures. Pin the ambiguity in your own repo — one paragraph in docs/ or the PR body stating which capability you evaluated and why — so the next maintainer does not re-litigate it from the same contradictory inputs.

Nothing here is asynchronous. PHP-FPM request handling is synchronous, start to finish. A resolved chain gives you readable flow and one error handler within a request. It gives you no background execution, no retries, no durability, and nothing that survives the response. The most expensive failure mode this package enables is a feature that demos beautifully on seeded data, times out in production, and gets "fixed" by wrapping more of it in promises. PDF exports, third-party syncs, webhook fan-out, and report generation belong on dispatch().

Silent failures are the debugging tax. There is no unhandled-rejection reporting. A chain with no catch, or a catch that returns null instead of rethrowing, produces a blank page rather than an error. When something mysteriously does nothing: log inside each then, confirm the chain is actually awaited rather than merely constructed, and check whether callbacks fire eagerly at build time or on explicit settlement. Those are precisely the semantics the README omits.

Zero stars, zero dependents, no CI, Laravel 5-only, EOL upstream since 2019. You are adopting source code, not consuming a dependency. Before this resolves in production you need a named owner, a fork in an internal namespace, a pinned commit, and your own tests. Add it to the vendored/at-risk inventory with a quarterly review. Budget a couple of engineer-days a year — and unbounded effort the first time Laravel changes the container or bootstrap contract it leans on.

The source is the extension point. There is no issue tracker and no release cadence to ride. Need a finally that runs on rejection, guard-aware behavior, or a specific Throwable classification? You add it in your fork, write the test, and document it next to the internal contract so the next engineer knows the behavior is local to you.

Facade aliasing hides the coupling. Promise::resolve() reads like a framework primitive and survives code review unchallenged. Prefer your adapter's name (DeferredWork) so the dependency stays visible at the call site and greppable when it is time to remove it.

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.
skyraptor/chatter
edlugz/sasapay
abbasudo/artisan-route
mirvan/ippanel
ignibyte/aic-framework
glavweb/data-schema-bundle
glavweb/datagrid-bundle
tacticmedia/scim-server
uni-method/frame
neika-ai/sdk
adachsoft/php-code-style
pharaonic/laravel-notification-dependencies
beautinow/translug
dev-brainiac/bot-cloaked
octoberrain/html
octoberrain/extension
octoberrain/exception
octoberrain/contracts
adachsoft/api-version-bundle
cgv/localized-resource