Как объединить несколько сущностей с внешним идентификатором в Symfony 4 с помощью построителя запросов? - PullRequest
3 голосов
/ 15 июня 2019

Я пытаюсь выучить Symfony.Сегодня я следил за учебным пособием по ассоциациям. Я решил сделать небольшое приложение - Дом, Кухни, Спальни и шкафы.Я (пытался ;-)) сделал небольшую диаграмму классов, используя draw.io, чтобы дать вам лучшую идею.

This is it

Так что в принципе дом можетесть несколько спален и несколько кухонь.Каждая кухня может иметь несколько шкафов.Дом имеет идентификатор и имя.Спальня и Кухня также.Шкаф имеет id, shopUrl, а также через внешний ключ (account_id) связан с родительской кухней.Я также связываю Кухню и Спальню с Домом, используя внешний ключ (house_id).Поэтому я последовал этому уроку и создал сущность House:

    <?php

    namespace App\Entity;

    use Doctrine\ORM\Mapping as ORM;
    use Doctrine\Common\Collections\ArrayCollection;
    use Doctrine\Common\Collections\Collection;

    /**
     * @ORM\Entity(repositoryClass="App\Repository\HouseRepository")
     */
    class House implements \JsonSerializable
    {

        /**
         * @ORM\OneToMany(targetEntity="App\Entity\Kitchen", mappedBy="house")
         */
        private $kitchen;

        /**
         * @ORM\OneToMany(targetEntity="App\Entity\Bedroom", mappedBy="house")
         */
        private $bedroom;    

        /**
         * House constructor
         */
        public function __construct()
        {
            $this->kitchen = new ArrayCollection();
            $this->bedroom = new ArrayCollection();
        }

        /**
         * @return Collection|Kitchen[]
         */
        public function getKitchen(): Collection
        {
            return $this->kitchen;
        }

        /**
         * @return Collection|Bedroom[]
         */
        public function getBedroom(): Collection
        {
            return $this->bedroom;
        }


        /**
         * @ORM\Id()
         * @ORM\GeneratedValue()
         * @ORM\Column(type="integer")
         */
        private $id;

        /**
         * @ORM\Column(type="string", length=255)
         */
        private $name;

        public function getId(): ?int
        {
            return $this->id;
        }

        public function getName(): ?string
        {
            return $this->name;
        }

        public function setName(string $name): self
        {
            $this->name = $name;

            return $this;
        }

        public function jsonSerialize()
        {
            return get_object_vars($this);
        }
    }

Хранилище House пусто (иначе: содержит только автоматически сгенерированный код из Symfony):

    <?php

    namespace App\Repository;

    use App\Entity\House;
    use Doctrine\Bundle\DoctrineBundle\Repository\ServiceEntityRepository;
    use Symfony\Bridge\Doctrine\RegistryInterface;

    /**
     * @method House|null find($id, $lockMode = null, $lockVersion = null)
     * @method House|null findOneBy(array $criteria, array $orderBy = null)
     * @method House[]    findAll()
     * @method House[]    findBy(array $criteria, array $orderBy = null, $limit = null, $offset = null)
     */
    class HouseRepository extends ServiceEntityRepository
    {
        public function __construct(RegistryInterface $registry)
        {
            parent::__construct($registry, House::class);
        }

    }

Bedroom сущность такова:

    <?php

    namespace App\Entity;

    use Doctrine\ORM\Mapping as ORM;

    /**
     * @ORM\Entity(repositoryClass="App\Repository\BedroomRepository")
     */
    class Bedroom implements \JsonSerializable
    {

        /**
         * @ORM\ManyToOne(targetEntity="App\Entity\House", inversedBy="bedroom")
         */
        private $house;

        public function getHouse(): House
        {
            return $this->house;
        }

        public function setHouse(House $house): self
        {
            $this->house = $house;

            return $this;
        }


        /**
         * @ORM\Id()
         * @ORM\GeneratedValue()
         * @ORM\Column(type="integer")
         */
        private $id;

        /**
         * @ORM\Column(type="string", length=255)
         */
        private $name;

        public function getId(): ?int
        {
            return $this->id;
        }

        public function getName(): ?string
        {
            return $this->name;
        }

        public function setName(string $name): self
        {
            $this->name = $name;

            return $this;
        }

        public function jsonSerialize()
        {
            return get_object_vars($this);
        }
    }

, а хранилище Спальня также пусто.

Сущность Kitchen имеет следующий код:

    <?php

    namespace App\Entity;

    use Doctrine\ORM\Mapping as ORM;

    /**
     * @ORM\Entity(repositoryClass="App\Repository\KitchenRepository")
     */
    class Kitchen implements \JsonSerializable
    {


        /**
         * @ORM\OneToMany(targetEntity="App\Entity\Cabinet", mappedBy="kitchen")
         */
        private $cabinet;

        /**
         * Kitchen constructor
         */
         public function __construct()
        {
            $this->cabinet= new ArrayCollection();
        }

        /**
         * @return Collection|Cabinet[]
         */
        public function getCabinet(): Collection
        {
            return $this->cabinet;
        }


        /**
         * @ORM\ManyToOne(targetEntity="App\Entity\House", inversedBy="kitchen")
         */
        private $house;

        public function getHouse(): House
        {
            return $this->house;
        }

        public function setHouse(House $house): self
        {
            $this->house = $house;

            return $this;
        }

        /**
         * @ORM\Id()
         * @ORM\GeneratedValue(strategy="UUID")
         * @ORM\Column(type="integer")
         */
        private $id;

        /**
         * @ORM\Column(type="string", length=255)
         */
        private $name;

        public function getId(): int
        {
            return $this->id;
        }

        public function getName(): string
        {
            return $this->name;
        }

        public function setName(string $name): self
        {
            $this->name = $name;

            return $this;
        }

        public function jsonSerialize()
        {
            return get_object_vars($this);
        }
    }

иКухонный репозиторий тоже пуст.Наконец, cabinet состоит из следующего:

    <?php

    namespace App\Entity;

    use DateTime;
    use Doctrine\ORM\Mapping as ORM;

    /**
     * @ORM\Entity(repositoryClass="App\Repository\CabinetRepository")
     */
    class Cabinet
    {
        /**
         * @ORM\Id()
         * @ORM\GeneratedValue()
         * @ORM\Column(type="integer")
         */
        private $id;

        /**
         * @ORM\Column(type="string", length=255)
         */
        private $shopUrl;

        private $account_id;

        /**
         * @ORM\ManyToOne(targetEntity="Kitchen", inversedBy="cabinet")
         */
        private $kitchen;

        /**
         * Cabinet constructor.
         * @param string $shopUrl
         * @param Kitchen $kitchen
         * @param int $id
         */
        public function __construct(string $shopUrl, Kitchen $kitchen = null, int $id = null)
        {
            $this->shopUrl = $shopUrl;
            $this->kitchen = $kitchen;
            $this->id = $id;
        }

        public function setId(int $id): self
        {
            $this->id = $id;

            return $this;
        }

        public function getId(): int
        {
            return $this->id;
        }


        public function getShopUrl(): string
        {
            return $this->shopUrl;
        }

        public function getKitchen(): Kitchen
        {
            return $this->kitchen;
        }

        public function setKitchen(Kitchen $kitchen): self
        {
            $this->kitchen = $kitchen;
            $this->account_id = $kitchen->getId();
            return $this;
        }

        public function setAccount_id(int $account_id): self
        {
            $this->account_id = $account_id;

            return $this;
        }

        public function getAccount_id(): int
        {
            return $this->account_id;
        }

    }

В отличие от других сущностей, у шкафа есть некоторая логика (вот где мне действительно нужна помощь).Поскольку спальня и кухня связаны с домом, я хотел бы дать спальню, затем найти все кухни, связанные с тем же домом, что и спальня, и вернуть все шкафы, которые есть на этих кухнях.Я знаю, что это может показаться нелогичным, но я обнаружил это слишком поздно, чтобы придумать другую концепцию.Мой текущий код не работает, потому что я не уверен, возможно ли это, и потому что он слишком сложен для понимания в данный момент.Но у меня есть это как содержимое репозитория:


    <?php

    namespace App\Repository;

    use App\Entity\Bedroom;
    use App\Entity\House;
    use App\Entity\Cabinet;
    use App\Entity\Kitchen;
    use Doctrine\Bundle\DoctrineBundle\Repository\ServiceEntityRepository;
    use Symfony\Bridge\Doctrine\RegistryInterface;

    /**
     * @method Cabinet|null find($id, $lockMode = null, $lockVersion = null)
     * @method Cabinet|null findOneBy(array $criteria, array $orderBy = null)
     * @method Cabinet[]    findAll()
     * @method Cabinet[]    findBy(array $criteria, array $orderBy = null, $limit = null, $offset = null)
     */
    class CabinetRepository extends ServiceEntityRepository
    {
        public function __construct(RegistryInterface $registry)
        {
            parent::__construct($registry, Cabinet::class);
        }

        public function findByBedroom(Bedroom $bedroom) //use joins??
        {
            return $this->createQueryBuilder('cabinet')
                ->join('cabinet.bedroom', 'bedroom')
                ->join('cabinet.kitchen', 'kitchen')
                ->addSelect('cabinet')
                ->andWhere('cabinet.bedroom = :idBedroom')
                ->setParameter('idBedroom', $bedroom->getId())
                ->orderBy('time', 'ASC')
                ->getQuery()
                ->getResult();
        }
    }

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

Я добавил данные вручную в базу данных, так что там есть данные.И с помощью Sequel Pro я вижу отношения.Данные (насколько я понимаю) в порядке.В queryBuilder есть ошибки.

Показательный пример с некоторыми данными:

Это данные спальни:

id     name              house_id  
325    bigBedroomOne     1666   
815    smallBedroomOne   555   
902    bigBedroomTwo     1666

Это данные дома:

id     name          
1666   bigHouse      
555    smallHouse      

Это данные Кухни:

id   name              house_id
1    bigKitchen        1666
2    smallKitchen      555
55   mediumKitchen     555

И, наконец, это данные кабинета:

id  shopUrl    account_id
1   ur.l       55
88  co.m       2
33  ne.t       1

Так что в этом примере я хотел бы подключитьидентификатор спальни 815, связанный с идентификатором house_id 555.Затем оттуда должна быть выбрана вся Кухня, связанная с этим house_id, поэтому 2 и 55.Наконец, должны быть возвращены шкафы с идентификаторами 1 и 88.

Редактировать: При запуске bin/console doc:sch:val я получаю это обратно:

`Mapping

[OK] Файлы сопоставления правильные.

База данных

[ОК] Схема базы данных синхронизирована с файлами сопоставления.

Ответы [ 3 ]

2 голосов
/ 16 июня 2019

В вашей панели отладки в Symfony вы, вероятно, должны увидеть некоторые ошибки в доктрине.Они не будут отображаться в PHPStorm.Вам нужно переименовать $kitchen и $bedroom в их множественные формы $kitchens и $bedrooms (и изменить ваши получатели / установщики так, чтобы они соответствовали), поскольку именно так вы определяете вещи в собственной стороне ваших отношений доктрины.

Более простой подход, чем ваш метод репозитория, состоит в том, чтобы делать то, что вы хотите, в вашем контроллере, чтобы доктрина выполняла вашу тяжелую работу:

$cabinets = [];

$house = $bedroom->getHouse();
$kitchens = $house->getKitchens();
foreach ($kitchens as $kitchen) {
    $kitchenCabinets = $kitchen->getCabinets();
    $cabinets = array_merge($cabinets, $kitchenCabinets);
}
2 голосов
/ 18 июня 2019

В вашем коде есть несколько мелких проблем, подробнее об этом позже.

Здесь находится \ App \ Repository \ CabinetRepository :: findByBedroom:

    public function findByBedroom(Bedroom $bedroom) //use joins??
    {
        return $this->createQueryBuilder('cabinet')
            ->join('cabinet.kitchen', 'kitchen')
            ->join('kitchen.house', 'house')
            ->join('house.bedroom', 'bedroom')
            ->addSelect('cabinet')
            ->andWhere('bedroom = :bedroom')
            ->setParameter('bedroom', $bedroom)
            ->getQuery()
            ->getResult();
    }

Для объекта спальни с идентификатором 815вышеприведенный код возвращает следующее (отформатированный как symfony / var-dumper сделает это):

array:2 [▼
  0 => Cabinet {#387 ▼
    -id: 88
    -shopUrl: "co.m"
    -account_id: null
    -kitchen: Kitchen {#354 ▼
      +__isInitialized__: false
      -cabinet: null
      -house: null
      -id: 2
      -name: null
       …2
    }
  }
  1 => Cabinet {#364 ▼
    -id: 1
    -shopUrl: "ur.l "
    -account_id: null
    -kitchen: Kitchen {#370 ▼
      +__isInitialized__: false
      -cabinet: null
      -house: null
      -id: 55
      -name: null
       …2
    }
  }
]

Примечание: домашние ссылки равны нулю из-за отложенной загрузки.

Итак, небольшие проблемы вВаш код:

  1. Ваш запрос в CabinerRepository делал неправильные объединения.Для правильных объединений см. Код выше.

  2. Этот запрос ссылается на неизвестное поле time.Я удалил эту ссылку.

  3. И также использовал идентификатор спальни вместо объекта спальни.

  4. Ваш Kitchen.php неполный, он ссылаетсяКлассы Collection и ArrayCollection, но соответствующие директивы use отсутствуют.Просто добавьте это после namespace до class:

use Doctrine\Common\Collections\ArrayCollection;
use Doctrine\Common\Collections\Collection;

Обновление: вот как получить ссылку на репозиторий:

public function myControllerAction(/* other parameters */, CabinetRepository $cabRepo)
{
    $cabinet = $cabRepo->find($id);
    // OR, if you don't want to add parameter for some reason:
    $cabRepo = $this->getDoctrine()->getRepository(Cabinet::class);
    $cabinet = $cabRepo->find($id);
}
0 голосов
/ 16 июня 2019

Сначала я думаю, потому что вы объединяетесь с обеими сущностями одновременно

...
            ->join('cabinet.bedroom', 'bedroom')
            ->join('cabinet.kitchen', 'kitchen')
...

и так как это будет с ВНУТРЕННИМ СОЕДИНЕНИЕМ, потребуется, чтобы в каюте потребовались и спальня, и кухонный шкаф.

Для этого есть несколько решений:

  • Правильнее было бы перепроектировать вас. Я думаю, что может быть нетрудно использовать Доктрина наследования
  • вы можете поменять соединения на левую, поэтому связь не обязательна (будет работать, но в целом это не очень хорошее решение из-за неправильного дизайна)
...