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

Signal Laravel Package

juststeveking/signal

Signal is a lightweight Laravel package by Steve King that provides a clean, expressive API for defining and dispatching application signals, helping you decouple components and react to events with simple, testable PHP code.

View on GitHub
Deep Wiki
Context7

Technical Evaluation

What this actually is. There is a material discrepancy between the two documented surfaces, and it must be resolved before any adoption decision. The package description sells a runtime event library ("emitting and handling application signals," "dispatch helpers," "listener registration via a service provider"). The shipped code, the bundled SKILL.md, and the engineer assessment all describe a pure PHP 8.1 attribute/annotation library with no service provider, no config, no dispatcher, and no runtime behaviour. The Attributes\ namespace and the absence of any registration surface point to the annotation library being the real product. Treat the "signals" copy as stale or aspirational until proven otherwise by reading composer.json and the source tree. This is the single highest-priority open question — it determines whether the value proposition is "replace illuminate/events with something lighter" (weak, and unlikely to survive scrutiny against a mature framework primitive) or "make architecture machine-readable" (genuinely interesting, and unserved by anything in the Laravel core).

Architecture fit. Assuming the annotation library is the real product, the fit is unusual and worth articulating precisely: this is not a library that does anything. It ships a vocabulary — one type discriminant per class (Module, Service, Repository, Action, Controller, Event, Listener, Middleware, Job, Command, Query, Aggregate, ValueObject) plus metadata (DependsOn, ListensTo, Emits, Route, Authorize, Validates, Cached, SideEffect, Throws, Deprecated, Internal). Value only materialises when we build the reflection consumer: an inventory command that emits JSON, a generated architecture doc, and CI rules that fail the build on dependency violations. That means the package is a schema, and the actual product is internal engineering work we own. A TPM should frame this honestly — we are not buying a capability, we are buying a shared vocabulary and saving perhaps a day of designing one.

Integration feasibility. Low technical friction, high organisational friction. Composer-only, additive-only, zero runtime surface, zero boot cost, no route or queue coupling. Removal is find-and-delete across annotated files — an unusually clean exit. The organisational friction is that adoption requires annotating real code and enforcing a style guide in review, which needs a named owner and a pilot module, not a mandate. The bundled SKILL.md is effectively the authoring standard and is written for a coding agent; using an agent for a first annotation draft with human review of the diff is the pragmatic path, but fabricated annotations (invented events, invented validation rules, invented abilities) are the expected failure mode.

Technical risk.

  • Bus factor and immaturity. Single maintainer, ~46 stars, zero dependents, v1.0.0 → v1.0.1 within a short window. The 1.0.1 fix addresses Composer autoloader discovery "when installed as a dependency" — i.e. the only way we would consume it was broken at 1.0.0. That is evidence the release pipeline does not exercise composer require into a real Laravel app. Every bump needs a five-minute smoke test.
  • Documentation hazard, security-shaped. #[Authorize] and #[Validates] describe checks; they do not perform them. #[Route] does not register routes. A new engineer reading an annotated controller can reasonably infer framework enforcement. This must be stated loudly in the style guide, or we create a compliance illusion around authorisation.
  • Annotation drift is unenforced. #[Emits] will not fail if the method stops dispatching. #[Throws] will not fail if the exception changed. Stale annotations are worse than none because they are trusted. #[Cached(ttl: 3600)] sitting next to cache()->remember($key, 300, ...) is a concrete divergence with no arbiter.
  • No consumer contract. One type attribute per class is required by convention only; a class with two type attributes breaks every downstream consumer. Emits/ListensTo use short names, so matching emitters to listeners requires resolving short name → FQCN ourselves, and App\Orders\Events\OrderPlaced vs App\Billing\Events\OrderPlaced will conflate.
  • Scope creep into CI. The moment CI rules block merges, the build pipeline depends on an unmaintained package's release cadence. That is the point at which we should vendor or fork.

Key questions.

  1. Which product are we adopting — the event dispatcher described in the README, or the attribute library described in SKILL.md and the source? Confirm by reading the source tree, not the description.
  2. Who owns the reflection consumer? Without a named engineer and a committed inventory command, adoption produces annotated files and zero value.
  3. Is there a concrete decision currently made from stale docs (blast radius, dependency review, onboarding) that this would improve? If not, this is a solution seeking a problem.
  4. Do we accept that annotations are documentation, not enforcement, and will we say so in the style guide?
  5. What is the CI rule we intend to enforce first, and is the team prepared for it to block merges?

Integration Approach

Stack fit. Laravel 9/10/11 on PHP 8.1+, native PHP attributes. No service provider means no boot-time cost and no config file to version, which keeps the operational footprint near zero. It also means there is no framework integration to test — the only integration is Composer autoloading plus our own reflection code.

Pin ^1.0.1, never ^1.0. v1.0.0 shipped a broken autoloader for the dependency install case. After install, verify the attributes resolve under both autoload modes before writing a single annotation:

composer dump-autoload -o
php -r "var_dump(class_exists(\JustSteveKing\Signal\Attributes\Controller::class));"

Add this to the PR checklist for any future bump, and re-run it once inside a Docker build with a cached vendor layer — that is where the original defect would have surfaced.

Migration path is additive, not a refactor. Annotating a class changes only use statements and attribute lines; method bodies are untouched. There is no BC surface for application code, no dual-write period, no data migration. Rollback is git revert on the annotation commits, independently of the Composer remove. This is the strongest argument for a timeboxed pilot: the cost of reversal is near zero.

Compatibility decisions to lock before the pilot.

  • One type attribute per class, strict. "Pick the most specific": an ambiguous Service/Action becomes an Action. A class with two type attributes silently breaks every consumer.
  • FQCN in DependsOn, and pick one namespace strategy for event names. Either store FQCNs in Emits/ListensTo and accept verbosity, or commit to a single event namespace and document that our matcher resolves short names against it. Decide now, not after the graph is half-built.
  • One source of truth per concern. If we annotate #[Cached], the TTL in the attribute must be the TTL in code. If we annotate #[Deprecated], retire the @deprecated docblock. Pick per concern in the style guide or reviewers will arbitrate ad hoc.
  • Annotate public methods only. Mixing attribute-level and docblock-level internal documentation creates an inconsistent mental model.
  • Explicit over default. Named arguments are our semver safety net, but silent defaults mean #[Job(description: '...')] with no queue: is indistinguishable from "forgot to annotate." If a CI rule needs that distinction, check raw attribute arguments, not hydrated instances.

Sequencing.

  1. Smoke test in a throwaway skeleton (half a day). Install, annotate one controller end to end, dump ReflectionClass::getAttributes(). If the dump does not look useful, stop here — the exit cost is an afternoon.
  2. Pilot one high-churn module (1–2 weeks, one engineer, 10–20 files). Controllers, actions, jobs, listeners first — they carry the most metadata per line. Apply SKILL.md verbatim so the pilot measures the real authoring cost, then human-review the agent's diff for fabricated attributes.
  3. Build the inventory command as the primary consumer. Reflection over app/, emit JSON to a build artifact or render docs/architecture.md nightly. This is the deliverable that converts annotation effort into something decision-useful.
  4. Add one CI rule over the inventory — e.g. "no Controller may DependsOn a Repository directly" or "every Listener must declare at least one ListensTo." One rule, enforced, proves the model. Fail the build on violation.
  5. Widen by module, and vendor before the rule becomes load-bearing. The moment a merge blocker depends on an unmaintained package, fork or vendor it. Budget that decision into the pilot's exit criteria rather than discovering it during an incident.

What to explicitly not do. Do not attempt a big-bang annotation pass over a monolith — it burns the goodwill the pilot requires. Do not bolt DDD vocabulary (Aggregate, ValueObject, Query) onto a CRUD codebase; a vocabulary nobody believes is worse than none. Do not try to make Signal emit OpenAPI; #[Route] is metadata, not a spec — feed it to a generator.

Operational Impact

Maintenance. The dependency itself is close to zero-maintenance: one Composer entry, no runtime, MIT, no boot hook. The maintenance burden sits entirely with us — the inventory command is ours, the CI rules are ours, and the style guide is ours. That is the honest resourcing picture: the package is a rounding error, the programme is a small ongoing engineering commitment. A single maintainer with ~46 stars and no dependents offers no release-cadence guarantee, so treat upgrades as discretionary and re-smoke-test each one. If the pilot succeeds and CI starts blocking merges, fork.

Support. Effectively none. No dependents means no battlefield archaeology on Stack Overflow, no second opinion on edge cases, no ecosystem of integration patterns to crib from. The effective support corpus is SKILL.md plus the source, which is small enough to read in an afternoon — a mitigating factor. Internal support therefore means one named engineer who has read it and can answer questions, and a style guide committed to the repo. Ramp-up for an additional senior engineer is 1–2 days to fluency, most of which is reading SKILL.md and the attribute constructors rather than learning a runtime.

Scaling. No request-path risk whatsoever — the package does nothing at runtime, so there is no queue, no middleware, no boot cost, no per-request reflection. The only scaling concern is the reflection scan: getAttributes() on one class is negligible; iterating every class in app/ and calling newInstance() on every attribute is not, and gets worse linearly as the codebase grows. Run it in CI or a nightly job, cache the output to a JSON artifact, memoise in the dev command. Never put a full-codebase scan on a hot path. If a future need pushes this into runtime, that is the moment to reassess the architecture, not to optimise the loop.

Failure modes to instrument.

Failure Symptom Mitigation
Autoload break on install/bump Attributes silently resolve to nothing; inventory returns empty Smoke test per bump, both autoload modes; assert non-empty attribute count in CI
Stale annotation trusted #[Emits] claims an event never dispatched; #[Throws] claims a retired exception PR checklist ("behaviour changed → attributes changed?"); flag commits touching dispatch(/event( without attributes; scheduled sweep ticket
Compliance illusion Reader assumes #[Authorize]/#[Validates] are enforced State it in the style guide; keep enforcement in policies/form requests, visibly
Duplicate type attribute Consumer breaks on single-discriminant assumption CI rule asserting exactly one type attribute per class
Short-name event collision Emitter/listener graph conflates two OrderPlaced classes One event namespace, documented, or FQCNs throughout
Silent defaults #[Job] with no queue: indistinguishable from intentional default Check raw arguments in the rule; annotate explicitly
Maintainer abandonment No releases; CI pinned to a package nobody owns Fork/vendor before CI rules become merge blockers

Ramp-up and decision framing. Approve a timeboxed pilot, not a platform commitment: one engineer, one module, 2–3 weeks, exit criteria defined as "the inventory command produced something a real decision was made from" and "one CI rule caught a real violation." The cost of trying is low and the cost of reversing is lower, which is exactly what justifies the spend against a 46-star, zero-dependent, single-maintainer package. What should not be approved is a roadmap line item called "architecture observability" justified by this dependency — the capability is ours to build; Signal is only the vocabulary, and the vocabulary is replaceable in a day if it turns out to be wrong.

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.
adachsoft/changelog-linter
adachsoft/collection
payerurl/telegrambot-crypto-payment-instant-payout
dskripchenko/laravel-hybrid-search
riorizkyrainey/lara-ced
mehedimi/static-vel
zharp/zharp
nonsapiens/bigquery-model-sync
lgu-bayambang-ict-office/lgu-laravel-development-standards
andydefer/laravel-support
se7enxweb/flysystem
lampwick/laravel
jeslage/statamic-emojis
micahdshackelford/rmap-laravel
draftsmaninc/draftsman
square-bit/laravel-volition
himito/laravel-mailman
htinlynn/genius-helper
maco-studios/hephaestus
elmogy/larafast