@@ -82,9 +82,12 @@ public static function buffer( Sub_Plugin $sub_plugin ): void {
8282 * registered since the last read, a host registering from its own `plugins_loaded` callback
8383 * included.
8484 *
85- * @since 1.0.0
85+ * A read always answers with what the registrar legitimately holds. A duplicate slug is refused
86+ * and reported as it drains, never raised out of here: every caller is inside `plugins_loaded`,
87+ * and one host bootstrap mistake about one sub-plugin must not stand down a pass that had every
88+ * other sub-plugin to get on with.
8689 *
87- * @throws Config_Exception When two sub-plugins were registered under one slug.
90+ * @since 1.0.0
8891 *
8992 * @return array<string,Sub_Plugin>
9093 */
@@ -124,16 +127,25 @@ static function ( $sub_plugin ): bool {
124127 * while this object is being built, with the registrations still buffered for the read that comes
125128 * after the host has fixed its bindings.
126129 *
127- * That same emptying is why a duplicate slug is caught per entry rather than allowed to end the
128- * loop. The registrar refuses the collision, and letting the throw out of the loop would leave
129- * every sub-plugin registered *behind* the colliding one in no registrar and in no buffer — the
130- * host would get a report naming the two that collided and silently lose the rest, on both
131- * passes, for the rest of the process. Registering the whole batch and throwing afterwards costs
132- * the collision nothing: it still surfaces from the read, where both passes catch it .
130+ * A collision the registrar refuses is reported here, with the discarded registration named, and
131+ * goes no further. Throwing it on made one mistaken registration decide what a whole pass did: the
132+ * first pass to read caught it and stood down — the load pass loading nothing at all on the front
133+ * end, the conflict pass resolving nothing in wp-admin — while the registry it was standing down
134+ * over was intact and readable the entire time. A slug registered twice is one sub-plugin's
135+ * problem, and the sub-plugins around it still have to load .
133136 *
134- * @since 1.0.0
137+ * Reported as it is discovered, which is once per process and therefore once per request, since
138+ * registration runs at plugin-file scope on every one: the host sees it in the log for as long as
139+ * the duplicate exists, and the load pass does not repeat a sentence the conflict pass has
140+ * already printed a priority earlier in the same request. A registration that arrives after a
141+ * read — a host module registering from its own `plugins_loaded` callback — is checked when it
142+ * drains, so a later collision still reports.
143+ *
144+ * Every collision is reported, not just the first. They are separate mistakes naming separate
145+ * slugs, and hiding the second behind the first only means the host fixes one and gets the next
146+ * on the following request.
135147 *
136- * @throws Config_Exception When two sub-plugins were registered under one slug.
148+ * @since 1.0.0
137149 *
138150 * @return void
139151 */
@@ -146,25 +158,28 @@ public function flush(): void {
146158
147159 self ::$ pending = [];
148160
149- // The first collision, not the last, so that a buffer containing two of them reports the one
150- // the host wrote first and keeps reporting the same one until it is fixed. The exception is
151- // rethrown as the registrar raised it: it names the slug and both bundled files, which is the
152- // mistake the host has to go and correct, and what this method did with the rest of the batch
153- // is nothing they can act on.
154- $ collision = null ;
155-
156161 foreach ( $ pending as $ sub_plugin ) {
157162 try {
158163 $ this ->registrar ->register ( $ sub_plugin );
159164 } catch ( Config_Exception $ exception ) {
160- if ( $ collision === null ) {
161- $ collision = $ exception ;
162- }
165+ // The registrar's own sentence, unwrapped: it names the slug and both bundled files,
166+ // which is the whole of what the host has to go and correct. One clause is added,
167+ // because the registrar refuses a registration without saying what became of it, and
168+ // what became of it is now the consequence -- the site runs one of those two files
169+ // and silently does not run the other. Every other report in this library says what
170+ // the outcome was; this one has to as well. The clause names the loser as "the
171+ // duplicate" rather than by path alone, because the two registrations may well name
172+ // the same file, and a bare path then reads as if the surviving one went too.
173+ _doing_it_wrong (
174+ self ::class,
175+ sprintf (
176+ '%1$s The original registration was kept; the duplicate %2$s was discarded. ' ,
177+ $ exception ->getMessage (),
178+ $ sub_plugin ->get_bundled_plugin_file ()
179+ ),
180+ '1.0.0 '
181+ );
163182 }
164183 }
165-
166- if ( $ collision !== null ) {
167- throw $ collision ;
168- }
169184 }
170185}
0 commit comments