• v0.8.1 c5dbfb64e5

    Ghost released this 2026-08-29 19:10:56 -04:00 | 12 commits to main since this release

    • [Capability] Do not announce an externally loaded registry as changed

    Registry suppresses its *ListChangedEvents while it is loading — dispatch()
    returns early on the loading guard, so the elements a loader registers are the
    registry's initial contents rather than a change to them.
    testListChangedEventsAreSuppressedDuringTheDeferredLoad pins that.

    The guard only covers Registry::load(), which needs the loader the constructor
    took. A registry the caller built cannot be given one that way, so
    Builder::resolve() loads it from the outside instead — by calling
    $chainLoader->load($registry) directly, which never sets the guard. Every
    element then dispatches on the way in.

    With a notification bus configured that is not quiet. PublishingEventDispatcher
    turns each event into a published notification, so every build() puts one
    list_changed per element on the bus for a registry that did not change. Under
    PHP-FPM, where the server is built per request and Psr16NotificationBus is
    shared and persistent, every request broadcasts its whole element list to every
    open subscriptions/listen stream and consumes the 256-entry backlog — after
    which a reader that fell behind silently skips real notifications.

    Split the guarded body of load() into loadFrom(LoaderInterface $loader) and
    route the custom-registry branch through it.

    loadFrom() takes only the loading guard, not the loaded bookkeeping, which
    stays with load(). The loader it runs belongs to the caller, so it cannot stand
    in for the one the registry was constructed with: marking the registry loaded
    would retire a constructor loader that never ran, and would make a second
    loadFrom() — one registry handed to two builders — a silent no-op. Keeping
    loaded out of it also keeps load()'s promise that a transient failure is
    retried on the next read, including when the failing loader reads the registry
    during its own run.

    RegistryInterface declares no load(), so this stays on the concrete
    Registry, and a third-party implementation keeps the path it has today.

    • Apply batched suggestions from code review

    Co-authored-by: Christopher Hertel mail@christopher-hertel.de

    Downloads