eliashaeussler/valinor-xml

View on GitHub
src/Helper/ArrayHelper.php

Summary

Maintainability
A
0 mins
Test Coverage
A
100%
<?php

declare(strict_types=1);

/*
 * This file is part of the Composer package "eliashaeussler/valinor-xml".
 *
 * Copyright (C) 2024 Elias Häußler <elias@haeussler.dev>
 *
 * This program is free software: you can redistribute it and/or modify
 * it under the terms of the GNU General Public License as published by
 * the Free Software Foundation, either version 3 of the License, or
 * (at your option) any later version.
 *
 * This program is distributed in the hope that it will be useful,
 * but WITHOUT ANY WARRANTY; without even the implied warranty of
 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
 * GNU General Public License for more details.
 *
 * You should have received a copy of the GNU General Public License
 * along with this program. If not, see <https://www.gnu.org/licenses/>.
 */

namespace EliasHaeussler\ValinorXml\Helper;

use EliasHaeussler\ValinorXml\Exception;

use function array_is_list;
use function array_key_exists;
use function array_shift;
use function array_slice;
use function gettype;
use function implode;
use function is_string;
use function str_getcsv;
use function trim;

/**
 * ArrayHelper.
 *
 * @author Elias Häußler <elias@haeussler.dev>
 * @license GPL-3.0-or-later
 */
final class ArrayHelper
{
    /**
     * @param array<string, mixed> $array
     *
     * @return array<string, mixed>
     *
     * @throws Exception\ArrayPathHasUnexpectedType
     * @throws Exception\ArrayPathIsInvalid
     */
    public static function convertToCollection(array $array, string $path): array
    {
        $reference = &$array;
        $pathSegments = str_getcsv($path, '.');
        $remainingSegments = $pathSegments;
        $currentPathSegments = [];

        foreach ($pathSegments as $pathSegment) {
            $currentPathSegments[] = array_shift($remainingSegments);

            // Validate path segment
            if (!is_string($pathSegment) || '' === trim($pathSegment)) {
                throw new Exception\ArrayPathIsInvalid(implode('.', $currentPathSegments));
            }

            // Handle placeholder for lists
            if ('*' === $pathSegment) {
                $reference = self::convertListToCollection(
                    $reference,
                    implode('.', array_slice($currentPathSegments, 0, -1)),
                    implode('.', $remainingSegments),
                );

                return $array;
            }

            // Create node value if not exists
            if (!array_key_exists($pathSegment, $reference)) {
                $reference[$pathSegment] = [];
            }

            $reference = &$reference[$pathSegment];

            // Handle non-array values
            if (!is_array($reference)) {
                throw new Exception\ArrayPathHasUnexpectedType(implode('.', $currentPathSegments), 'array', gettype($reference));
            }
        }

        // Convert array to list
        if (!array_is_list($reference)) {
            $reference = [$reference];
        }

        return $array;
    }

    /**
     * @param array<mixed> $array
     *
     * @return array<int, mixed>
     *
     * @throws Exception\ArrayPathHasUnexpectedType
     * @throws Exception\ArrayPathIsInvalid
     */
    private static function convertListToCollection(array $array, string $currentPath, string $remainingPath): array
    {
        // Handle non-lists
        if (!array_is_list($array)) {
            throw new Exception\ArrayPathHasUnexpectedType($currentPath, 'list', 'array');
        }

        foreach ($array as $key => $value) {
            $array[$key] = self::convertToCollection($value, $remainingPath);
        }

        return $array;
    }
}