Doctrine 2 - как создавать вложенные объекты без рекурсии

hell0w0rd

Продвинутый новичок
У меня есть массив данных, id, parent_id, прочие данные
Как в сохранить их без рекурсии?
PHP:
    protected function updateCategories($categories, EntityManager $m)
    {
        $repository = $m->getRepository('WatchCatalogBundle:Category');

        foreach ($categories as $id => $category) {
            $obj = new Category();
            $obj->setMsId($id);

            foreach ($categories as $candidate) {
                if ($candidate['parent'] == $id) {
                    $obj->setHasChild(true);
                    break;
                }
            }

            $parent = new Category();
            $parent->setMsId($category['parent']);
            $parent = $m->merge($parent);

            $obj
                ->setMsId($id)
                ->setParent($parent)
                ->setMsName($category['name'])
                ->setMsUpdated($category['updated'])
                ->setMsDescription($category['description']);

            $obj = $m->merge($obj);
            $m->persist($obj);
        }

        $m->flush();
    }
 

keltanas

marty cats
А это вообще к чему и в каком контексте?
Изначально какая задача стоит?
 

hell0w0rd

Продвинутый новичок
А это вообще к чему и в каком контексте?
Изначально какая задача стоит?
Из API вытащить категории товаров, и добавить их в приложение. Собственно проблема в том, что у parent может быть еще parent и решить это можно через рекурсию. Но я полагаю что доктрина все объекты хранит до flush и потому можно их менять, только как? Что я делаю не верно? Или чего не понимаю?
 

keltanas

marty cats
Если ты хочешь вставить себе записи с теми primary key, с которыми они хранятся в другой программе, то так делать не стоит.
Между системами объекты лучше идентифицировать с помощью некого внешнего uuid. А внутри своей базы у тебя должен быть целочисленный id, который уникален только в твоей системе. Это необходимо потому, что у одной и той же записи в удаленной системе и в локальной id могут не совпадать. И если тебе придет запись под id, который у тебя уже существует, то получишь неприятности.

Так я бы, например, сначала создал объекты в массив, проиндексированный по uuid, а потом бы прошел по ним циклом и проставил им родовую принадлежность.
Локальные id доктрина проставит сама после flush.
 

hell0w0rd

Продвинутый новичок
keltanas Фактически по этим uuid я и определяю родителя и сам id.
То есть план действий такой:
Выкачиваю инфу по api
Связываю локальные записи и записи этой таблицы по id, не обращая внимания на uuid
 

keltanas

marty cats
Связывать локальные и внешние записи надо как раз по uuid. Для того он и придумали.

Надо создать массив вида:
PHP:
Array
(
    [c8fd1c88-f051-a609-597c-c469011418c5] => Object
        (
            [uuid] => c8fd1c88-f051-a609-597c-c469011418c5
            [parent_uuid] => null
            [name] => name1
        )

    [a55f9d4f-26da-7e54-9e50-794a528620b7] => Object
        (
            [uuid] => a55f9d4f-26da-7e54-9e50-794a528620b7
            [parent_uuid] => c8fd1c88-f051-a609-597c-c469011418c5
            [name] => name2
        )

    [74c99e96-2469-67f1-ee7f-0303c86d054d] => Object
        (
            [uuid] => 74c99e96-2469-67f1-ee7f-0303c86d054d
            [parent_uuid] => c8fd1c88-f051-a609-597c-c469011418c5
            [name] => name3
        )

    [5249eca4-40d5-addf-110a-30725b78592c] => Object
        (
            [uuid] => 5249eca4-40d5-addf-110a-30725b78592c
            [parent_uuid] => a55f9d4f-26da-7e54-9e50-794a528620b7
            [name] => name4
        )

    [87bf709f-2c37-985c-edb9-2ffe01e2d064] => Object
        (
            [uuid] => 87bf709f-2c37-985c-edb9-2ffe01e2d064
            [parent_uuid] => a55f9d4f-26da-7e54-9e50-794a528620b7
            [name] => name5
        )
)
А потом проставить в объектах этого массива связи на уровне объектов опираясь на parent_uuid, сделать всем persist и потом всем вместе flush
 

hell0w0rd

Продвинутый новичок
PHP:
 [ErrorException]                                                                                                                                                 
  Notice: Undefined index: uuid in ...lib/Doctrine/ORM/Persisters/BasicEntityPersister.php line 607
PHP:
        $catalog = $em->getRepository('WatchCatalogBundle:Category');
        $ms = $em->getRepository('WatchMoySkladBundle:Category');

        foreach ($ms->findAll() as $category) {
            $obj = $catalog->findOneByCurrent($category);
            if (!$obj) {
                $obj = new Category();
                $obj->setMs($category);
            }
            $obj->setParent($ms->findOneByUuid($category->getParentUuid()));
            $em->persist($obj);
        }
        $em->flush();
Что я делаю не так?:)
 

keltanas

marty cats
Ну во-первых проверь, есть ли у тебя индекс uuid там, где ты его пытаешься получить?
Во-вторых ничего общего с тем, что я писал ранее.
И да. Я описывал абстрактную систему, т.к. особенности твоей системы мне не известны. Постарайся сам переложить это на свою программу.
Жжожь ))
 
  • Like
Реакции: WMix

keltanas

marty cats
Воспользуйся отладчиком. Не могу же я весь код написать?
Для этого мне, как минимум, нужно видеть входные и выходные данные. И не плохо было бы развернуть твой проект у себя, чтобы можно было тестировать код.
Я готов подсказать советом. Но, писать все за тебя не готов ;)
 

hell0w0rd

Продвинутый новичок
keltanas
Не я не прошу все за себя писать) За наводку кстати спасибо, поковырявшись понял что у меня на uuid стоит unque, а нужен index. Отсюда и ноги у проблемы растут)
Только вот теперь вопрос, как хранить товары? Все товары связаны через uuid с категорией
В таблице с сырыми данными сделать связь на сырые категории, а в таблице приложения связь на таблицу с теми сырыми данными?
 

keltanas

marty cats
uuid тебе нужен только чтобы идентифицировать записи при обмене через api (или еще как-нибудь) с другой системой.
А внутри своей системы они должны идентифицироваться своими идентификаторами.
В случае с Doctrine все еще проще. Тебе надо всего лишь установить связь между сущностями (на уровне объектов), а доктрина сама разрулит связи ну уровне БД.

Тебе только надо в схеме прописывать зависимости по локальным id и parent_id. uuid и parent_uuid работают только для транспортировки данных. У себя в таблице ты их хранишь только для того, чтобы связать свои записи с записями из api.

Кстати, покажи, что к тебе приходит через api, чтобы понимать, что мы об одном и том же говорим ))
 

hell0w0rd

Продвинутый новичок
PHP:
    private function getCategoriesFromXml($xml)
    {
        $categories = [];
        foreach ($xml as $node) {
            $attrs = $node->attributes();

            $categories[(string)$node->uuid] = [
                'parent_id' => (string)$attrs->parentUuid,
                'name' => (string)$attrs->name,
                'description' => (string)$node->description,
                'updated' => new \DateTime($attrs->updated),
                'archived' => ((string)$attrs->archived === 'true')
            ];
        }

        return $categories;
    }
Ну вот функция обрабатывающая категории из апишки)

Да, я так и сделал, просто тупил) На счет товаров подскажешь?)

И всетаки зачем в таком случае нужен метод merge?
 

keltanas

marty cats
Ну, с товарами так же. Только ты им категории привязываешь.
Хотя, это решение все равно какой-то костыльностью попахивает, если честно.
Но, с другой стороны, как альтернативу, вижу только нативный SQL.
Про слияние тут написано. Это как раз для того, если у сущности уже есть внутренний id, а тебе надо сохранить ее в базе.
Если ты принимаешь данные из внешней системы, то у тебя такого id не должно быть.

И кстати, есть еще один нюанс. Ты через этот api забираешь готовый слепок данных, который тебе надо получить на своей системе, или разность между старым и новым состоянием?
 

hell0w0rd

Продвинутый новичок
keltanas
Слепок. Либо я идиот, либо получить разницу почти невозможно. Я смотрю на updated, и попрошу клиента не удалять, а архивировать - это единственные вещи по которым можно судить о актуальности данных
 

keltanas

marty cats
Тогда получается, что тебе необходимо дернуть все данные из базы. Потом сравнить их со списком, пришедшим из api и самому решить, что надо удалить, что обновить, а что создать?
У тебя получится 3 списка сущностей, по сути.
Ну и после этого применить к спискам необходимые операции.

Связи устанавливаются только на уровне:
PHP:
$catalogEntity->setParent($catalogEntity2)
 

hell0w0rd

Продвинутый новичок
Да, потхонечку разбираюсь и задумываюсь какой будет хитрый запрос, чтобы определить к какой категории продукт относится...
Товаров 20к - память переполняется у скрипта. Как попросить доктрину освободить память?)
 

keltanas

marty cats
Поэтому ты должен получать товары от удаленной системы уже с меткой insert, update или delete ))
Чтобы очистить память, надо уничтожить все сущности.
А вообще ORM не предназначена для такого ахтунга. Лучше используй обычный нативный SQL для импорта.
Т.е. если бы речь шла о добавлении 5-10 товаров, можно было бы на ORM, а 20k - уж никак.
 
  • Like
Реакции: WMix
Сверху