@@ -342,6 +342,99 @@ Registrar methods must always return an array, with keys corresponding to the pr
342342of the target config file. Existing values are merged, and Registrar properties have
343343overwrite priority.
344344
345+ By default this merge is **shallow ** (top-level only). Arrays are combined with
346+ ``array_merge() ``, so a nested array under a top-level key *replaces * the existing
347+ nested array rather than merging into it. In the following example the ``key2 ``
348+ subtree from the config is replaced entirely, dropping ``val2 `` and ``val3 ``:
349+
350+ .. literalinclude :: configuration/012.php
351+
352+ .. _registrar-merge-directives :
353+
354+ Controlling how values are merged
355+ ---------------------------------
356+
357+ .. versionadded :: 4.8.0
358+
359+ When a registrar needs finer control than the shallow default, it can return a
360+ ``CodeIgniter\Config\Merge `` directive as the value of a property. Merge
361+ directives are explicit instructions to either keep merging into a value, replace
362+ it, or add items to a list.
363+
364+ The two directives that control whole values are:
365+
366+ - ``Merge::replace($value) `` - discard the existing value and use ``$value ``.
367+ Accepts **any type ** (scalar, ``null ``, or array - e.g. ``['a', 'b'] `` becomes
368+ ``['c'] ``, or ``Merge::replace('redis') ``).
369+ - ``Merge::byKey($value) `` - deep-merge by key: **string keys recurse, integer
370+ keys append, and scalar leaves are replaced **. The name is deliberately not
371+ ``recursive `` to avoid confusion with PHP's ``array_merge_recursive() ``, which
372+ collects scalar values into arrays instead of replacing them.
373+
374+ Use ``Merge::byKey() `` when you want to navigate into nested configuration and
375+ preserve sibling keys:
376+
377+ .. literalinclude :: configuration/013.php
378+
379+ .. important :: Inside ``Merge::byKey()``, plain arrays are still merged by key.
380+ Use ``Merge::replace() `` when you want to stop merging at that key and
381+ overwrite the value. For example, ``'after' => [] `` leaves an existing
382+ ``after `` list unchanged, while ``'after' => Merge::replace([]) `` clears it.
383+
384+ The following registrar adds a filter to ``globals['before'] `` while hard-resetting
385+ ``globals['after'] ``, leaving any other ``globals `` keys untouched:
386+
387+ .. literalinclude :: configuration/014.php
388+
389+ The *list * strategies add items to a list and control where they land. They are
390+ useful where order matters, such as the filter lists in ``Config\Filters ``:
391+
392+ - ``Merge::append($value) `` - add items to the **end ** of the list
393+ (e.g. ``['a', 'b'] `` becomes ``['a', 'b', 'c'] ``).
394+ - ``Merge::prepend($value) `` - add items to the **front ** of the list
395+ (e.g. ``['a', 'b'] `` becomes ``['c', 'a', 'b'] ``).
396+ - ``Merge::before($anchor, $value) `` - insert items immediately **before ** the
397+ first element equal to ``$anchor ``.
398+ - ``Merge::after($anchor, $value) `` - insert items immediately **after ** the
399+ first element equal to ``$anchor ``.
400+
401+ All four de-duplicate, so the directives never *introduce * a duplicate value: a
402+ value already in the list is not added again, and duplicate payload values are
403+ collapsed (e.g. ``Merge::append(['x', 'x']) `` adds ``x `` once). Duplicates that
404+ already exist in the current list are left as-is. They differ only in how they
405+ treat a value that is *already present *:
406+
407+ - ``append() `` / ``prepend() `` leave an already-present value **where it is **
408+ (they only add values that are missing).
409+ - ``before() `` / ``after() `` **move ** an already-present value to the anchor
410+ position - but only when the anchor is in the list. If the anchor is missing
411+ they fall back to ``append() `` / ``prepend() `` respectively, and do **not **
412+ relocate a value that is already present.
413+
414+ The anchor is matched strictly against the **direct elements ** of the list; the
415+ list strategies act on a single list level and never recurse. To reach a list
416+ that is nested under other keys (such as ``globals['before'] ``), navigate to it
417+ with ``Merge::byKey() `` and place the list directive at that key:
418+
419+ .. literalinclude :: configuration/015.php
420+
421+ .. important :: Merge directives are interpreted only when used as the **value of
422+ a config property** returned by a registrar, and recursively **inside **
423+ ``Merge::byKey() ``. The payloads of the ``replace() ``, ``append() ``,
424+ ``prepend() ``, ``before() ``, and ``after() `` strategies are taken literally and
425+ are **not ** scanned for nested directives - for nested control, wrap the
426+ property in ``Merge::byKey() `` and place the directives at its keys.
427+
428+ .. note :: Merge directives sharpen a *single* registrar's intent; they do not add
429+ an explicit priority mechanism between registrars. Registrars are still applied
430+ in discovery order, and an item's final position follows from that order *plus *
431+ the strategy: with ``append() `` an earlier registrar's items sit ahead of a
432+ later one's, while with ``prepend() ``, ``before() ``, and ``after() `` a later
433+ registrar's items land **nearer the front or the anchor ** than an earlier one's.
434+ For example, ``after('csrf', ['a']) `` followed by ``after('csrf', ['b']) `` from a
435+ second registrar yields ``['csrf', 'b', 'a'] ``. As always, **.env ** values take
436+ priority over registrars.
437+
345438Explicit Registrars
346439===================
347440
0 commit comments