Laravel сплющивание и выщипывание из многомерной коллекции - PullRequest
4 голосов
/ 26 мая 2020

Мне нужно получить только массив id из данной коллекции, что-то вроде [10,54,61,21, и т.д.] . Я пробовал flatten , pluck , но, похоже, ничего не работает, кроме foreach , который я хотел бы удалить на этом этапе.

// Model
class Children extends Eloquent {
    public function directChildrens(){
        return $this->hasMany('App\Children','father_id','id')->select('id','father_id');
    }

    public function childrens(){
        return $this->directChildrens()->with('childrens');
    }
}

// Controller
return $children->childrens()->get();

Как и ожидалось работает нормально. Вот результат:

[{
"id": 10,
"father_id": 2,
"childrens": [
    {
        "id": 54,
        "father_id": 10,
        "childrens": [
            {
                "id": 61,
                "father_id": 54,
                "childrens": []
            }
        ]
    },
    {
        "id": 21,
        "father_id": 10,
        "childrens": []
    }
]
}]

Как я могу выполнить pluck ('id') этой коллекции, чтобы получить [10,54,61,21] ?

Ответы [ 6 ]

2 голосов
/ 01 июня 2020
Метод
$result = [
            [
                "id" => 10,
                "father_id" => 2,
                "childrens" => [
                    [
                        "id" => 54,
                        "father_id" => 10,
                        "childrens" => [
                            [
                                "id" => 61,
                                "father_id" => 54,
                                "childrens" => []
                            ]
                        ]
                    ],
                    [
                        "id" => 21,
                        "father_id" => 10,
                        "childrens" => []
                    ]
                ]
            ]
        ];
return collect($result)
        ->map(function ($value) {
            return Arr::dot($value); // converts it to the dot notation version (added the example at the end)
        })
        ->collapse() // collapse them into a single one
        ->filter(function ($value, $key) {
            return $key === 'id' || Str::endsWith($key, '.id'); // filter first id + patterns of .id
        })
        ->values() // discard dot notation keys
        ->toArray();

Arr::dot() преобразовал коллекцию в следующий формат, который сводит все к одному уровню.

[
  {
    "id": 10,
    "father_id": 2,
    "childrens.0.id": 54,
    "childrens.0.father_id": 10,
    "childrens.0.childrens.0.id": 61,
    "childrens.0.childrens.0.father_id": 54,
    "childrens.0.childrens.0.childrens": [],
    "childrens.1.id": 21,
    "childrens.1.father_id": 10,
    "childrens.1.childrens": []
  }
]

Благодаря предупреждению @Dan об устаревшей / удаленной функции array_dot. Заменено на Arr::dot()

1 голос
/ 07 июня 2020

Вы можете вызвать array_walk_recursive только один раз, я просто обернул pipe метод , чтобы использовать функциональный подход, используемый в Eloquent и Collections

return $children->childrens()->get()->pipe(function ($collection) {
    $array = $collection->toArray();
    $ids = [];
    array_walk_recursive($array, function ($value, $key) use (&$ids) {
        if ($key === 'id') {
            $ids[] = $value;
        };
    });
    return $ids;
});

Обратите внимание, что вы должны преобразовать коллекцию в массив и сохранить ее в переменной, поскольку array_walk_recursive первый параметр передается по ссылке, тогда следующий код вызовет фатальную ошибку По ссылке следует передавать только переменные

array_walk_recursive($collection->toArray(), function (){
    // ...
}) 
1 голос
/ 07 июня 2020

вы можете использовать flatten () для получения массива [id, Father_id, id, Father_id, ...]. затем вы создаете фильтр, чтобы получить только значения с четными индексами:

$result = [
            [
                "id" => 10,
                "father_id" => 2,
                "childrens" => [
                    [
                        "id" => 54,
                        "father_id" => 10,
                        "childrens" => [
                            [
                                "id" => 61,
                                "father_id" => 54,
                                "childrens" => []
                            ]
                        ]
                    ],
                    [
                        "id" => 21,
                        "father_id" => 10,
                        "childrens" => []
                    ]
                ]
            ]
        ];


$collection = collect($result);

$flattened = $collection->flatten()->all();

return array_filter($flattened, function ($input) {return !($input & 1);}, ARRAY_FILTER_USE_KEY);

вы можете добавить проверку первого узла, если у него есть отец_id (в случае головы дерева), если нет, вы берете первый идентификатор и выполняете этот код для детей.

1 голос
/ 02 июня 2020

Не уверен, что вы хотите pluck('id') mean, но pluck не работает с рекурсивным массивом. Поэтому я использую рекурсивный метод для получения значений id.

$result->map(function ($item) {
  $ids = [];
  array_walk_recursive($item['childrens'], function ($item, $key) use(&$ids) {
     if($key === 'id') $ids[] = $item;
  });
  return array_merge([$item['id']], $ids);
});
1 голос
/ 01 июня 2020

Здесь рекурсивное решение требует объявления дополнительного метода:

function extractIds($obj)
{
    $result = [];

    if (isset($obj['id'])) {
        $result[] = $obj['id'];
    }

    if (isset($obj['children'])) {
        return array_merge($result, ...array_map('extractIds', $obj['children']));
    }

    return $result;
}

Метод будет безопасно проверять свойство id, а также свойство childrens перед рекурсивным обращением к нему. (Кстати, слово children уже является формой множественного числа, не нужно добавлять s в конце. Имейте в виду, что я удалил его в моем примере.)

Используя это, мы можем использовать коллекцию Laravel, чтобы легко связать требуемые вызовы преобразования:

$result = collect($array)
    ->map('extractIds')
    ->flatten()
    ->toArray();
0 голосов
/ 26 мая 2020

Вот рекурсивный алгоритм, который вы можете добавить в свою модель Children:

use Illuminate\Support\Collection;

public function getAllChildrenIds()
{
    return Collection::make([$this->getKey()])
        ->merge(
            $this->childrens->map->getAllChildrenIds()
        );
}
...