PHP array_merge_recursive() + realurl „fixedPostVars“

Einige Zeit hat es mich heute gekostet, um festzustellen, warum meine Erweiterung der realurl-Konfiguration via Hook zur Autoconfiguration fehlerhaft in der Konfiguration ankommt. Ursache ist ein Fehlverhalten der PHP-Funktion array_merge_recursive().

array_merge_recursive()

Zunächst, die Beschreibung der Funktion aus der Dokumentation:

array_merge_recursive() merges the elements of one or more arrays together so that the values of one are appended to the end of the previous one. It returns the resulting array.

If the input arrays have the same string keys, then the values for these keys are merged together into an array, and this is done recursively, so that if one of the values is an array itself, the function will merge it with a corresponding entry in another array too. If, however, the arrays have the same numeric key, the later value will not overwrite the original value, but will be appended.

Beachtenswert ist hier der letzte Satz, insbesondere „same numeric key“ und „same numeric key“.

Was ist hier so seltsam bzw. fehlerhaft?

Bringen wir die Funktion einmal zum Einsatz zusammen mit realurl und den fixedPostVars:

realurl + fixedPostVars

Mittels des extensionConfiguration-Hooks  der Autoconfiguration lässt sich für eigenes Extension realurl konfigurieren. Ein kleines Beispiel, mit dem wir erreichen, dass auf der Seite mit ID 123 gleich nach dem Seitentitel der Titel der Newsmeldung in der URL kommt – das in der „Standardkonfiguration“ enthaltene „article“ im Pfad entfällt.

class tx_news_realurl {
    /**
     * Generates additional RealURL configuration and merges it with provided configuration
     *
     * @param    array    $params    Default configuration
     * @param    tx_realurl_autoconfgen    $pObj    Parent object
     * @return array
     */
    function addNewsConfig($params, &$pObj) {
        return array_merge_recursive($params['config'], array(
            'fixedPostVars' => array(
                    'testPlaceHolder' => array(
                        'GETvar' => 'tx_ttnews[tt_news]',
                        'lookUpTable' => array (
                            'table' => 'tt_news',
                            'id_field' => 'uid',
                            'alias_field' => 'title',
                            'addWhereClause' => ' AND deleted=0',
                            'useUniqueCache' => '1',
                            'useUniqueCache_conf' => array (
                                'strtolower' => '1',
                                'spaceCharacter' => '-',
                            ),
                        ),
                    ),
                    '123' => 'testPlaceHolder'
                )
            )
        );
    }
}

Soweit, so gut. Nun haben wir eine weitere Extension, die ebenfalls ihre realurl-Einstellungen in fixedPostVars einbauen möchte für die Seite mit ID 98:

class tx_example_realurl {
    /**
     * Generates additional RealURL configuration and merges it with provided configuration
     *
     * @param    array    $params    Default configuration
     * @param    tx_realurl_autoconfgen    $pObj    Parent object
     * @return array
     */
    function addExampleConfig($params, &$pObj) {
        return array_merge_recursive($params['config'], array(
            'fixedPostVars' => array(
                    'examplePlaceHolder' => array(
                        'GETvar' => 'tx_example[uid]',
                        'lookUpTable' => array (
                            'table' => 'tx_example_data',
                            'id_field' => 'uid',
                            'alias_field' => 'title',
                            'addWhereClause' => ' AND deleted=0',
                            'useUniqueCache' => '1',
                            'useUniqueCache_conf' => array (
                                'strtolower' => '1',
                                'spaceCharacter' => '-',
                            ),
                        ),
                    ),
                    '98' => 'examplePlaceHolder'
                )
            )
        );
    }
}

Ergebnis

Etwas verwunderlich ist das Ergebnis. Was ich erwartet habe:

array(
    'fixedPostVars' => array(
        'testPlaceHolder' => array(
            'GETvar' => 'tx_ttnews[tt_news]',
            'lookUpTable' => array (
                'table' => 'tt_news',
                'id_field' => 'uid',
                'alias_field' => 'title',
                'addWhereClause' => ' AND deleted=0',
                'useUniqueCache' => '1',
                'useUniqueCache_conf' => array (
                    'strtolower' => '1',
                    'spaceCharacter' => '-',
                ),
            ),
        ),
        '123' => 'testPlaceHolder',
        'examplePlaceHolder' => array(
                'GETvar' => 'tx_example[uid]',
                'lookUpTable' => array (
                    'table' => 'tx_example_data',
                    'id_field' => 'uid',
                    'alias_field' => 'title',
                    'addWhereClause' => ' AND deleted=0',
                    'useUniqueCache' => '1',
                    'useUniqueCache_conf' => array (
                        'strtolower' => '1',
                        'spaceCharacter' => '-',
                    ),
            ),
        ),
        '98' => 'examplePlaceHolder'
    )
);

Tatsächlich heraus kam jedoch:

array(
    'fixedPostVars' => array(
        'testPlaceHolder' => array(
            'GETvar' => 'tx_ttnews[tt_news]',
            'lookUpTable' => array (
                'table' => 'tt_news',
                'id_field' => 'uid',
                'alias_field' => 'title',
                'addWhereClause' => ' AND deleted=0',
                'useUniqueCache' => '1',
                'useUniqueCache_conf' => array (
                    'strtolower' => '1',
                    'spaceCharacter' => '-',
                ),
            ),
        ),
        123 => 'testPlaceHolder',
        'examplePlaceHolder' => array(
                'GETvar' => 'tx_example[uid]',
                'lookUpTable' => array (
                    'table' => 'tx_example_data',
                    'id_field' => 'uid',
                    'alias_field' => 'title',
                    'addWhereClause' => ' AND deleted=0',
                    'useUniqueCache' => '1',
                    'useUniqueCache_conf' => array (
                        'strtolower' => '1',
                        'spaceCharacter' => '-',
                    ),
            ),
        ),
        124 => 'examplePlaceHolder'
    )
);

Wo ist der Unterschied? Was ist die Ursache?

Bei genauerem Hinsehen fallen zwei Unterschiede auf:

  1. Aus den Seiten-IDs, die als Keys in Form eines nummerischer Strings in den Ausgangs-Arrays waren, wurden nun nummerische Indexes.
  2. Der Key „98“ aus dem zweiten Array ist verschwunden, dafür aber ein neues Key „124“ aufgetaucht.

Kommen wir wieder zurück, zum letzten Satz der Doku zu array_merge_recursive: „same numeric key“.

  • Bei Seiten-IDs handelt es sich zweifelsfrei um nummerische Werte. Da wir sie als in Quotes gepackt haben, sollten diese allerdings als Strings interpretiert werden.
  • Durch die Umwandlung in numerische Indexes scheint die Funktion durcheinander zu kommen, und einen Key „98“ nicht mehr setzten zu können/wollen, da zuvor der Index ja bereits (durch tt_new-Array) auf „123“ hochgesetzt wurde. Der Key wird hier verworfen udn einfach der nächste frei Index (123+1) verwendet.

Während Ersteres noch verkraftbar ist (da sich PHP ja wenigstens konsequent nicht so exakt an Datentypen hält) und die Verarbeitung dennoch klappt, wirkt sich der letzte Punkt fatal aus: der Parameter „tx_example[uid]“ wird nun nicht mehr auf der Seite 98 umgeschrieben, sonder nur auf der Seite 124 (ob dort ein passendes Plugin ist, oder nicht).

Nach einigen weiteren Testszenarien und Lesen der Kommentare in der PHP-Doku, lässt sich das Problem auf array_merge_recursive() zurückführen…
(Nebenbei: das Verhalten der Keys unterscheidet sich übrigens auch, zwischen erster und zweiter Ebene des Arrays – warum auch immer)

Workaround

Ein Ausweg ist, die Werte mit nummerischen Strings nicht reinmischen zu lassen, sondern händisch (und mit der Keule ;-)) reinzubauen:

function addConfig($params, &$pObj) {
    $realurlAddConf = array(
        'fixedPostVars' => array(
            'examplePlaceHolder' => array(
                'GETvar' => 'tx_example[uid]',
                'lookUpTable' => array (
                    'table' => 'tx_example_data',
                    'id_field' => 'uid',
                    'alias_field' => 'title',
                    'addWhereClause' => ' AND deleted=0',
                    'useUniqueCache' => '1',
                    'useUniqueCache_conf' => array (
                        'strtolower' => '1',
                        'spaceCharacter' => '-',
                    )
                )
            )
        )
    );
    $params['config'] = array_merge_recursive($params['config'], $realurlAddConf);
    // array_merge_recursive() akzeptiert keine nummerischen Strings als Keys - warum auch immer :-(
    $params['config']['fixedPostVars']['98'] = 'examplePlaceHolder';
    return $params['config'];
}

Schreibe einen Kommentar