Как прочитать результаты запроса на присоединение к доктрине, не игнорируя условия объединения? - PullRequest
0 голосов
/ 09 мая 2018

У меня есть запрос на соединение с условием соединения. Он отлично работает и getResult() дает мне доступ к результатам запроса. Результаты тоже хороши (как видно на phpmyadmin).

$em = $this->getDoctrine()->getManager();
$allRows = $em->getRepository('CampaignBundle:Kpi')->createQueryBuilder('k')
            ->leftJoin('k.countryWeights', 'w', 'WITH', 'w.country = :country')
            ->setParameter('country', $country)
            ->addSelect('w')
            ->getQuery()->getResult();

Причина объединения состоит в том, что я хочу проверить, есть ли у другой таблицы (или объекта) связанные данные, и я хочу включить их в результаты, если они существуют. Это именно то, что делает левое соединение.

Проблема возникает, когда я использую результаты запроса. Например:

foreach ($allRows as $row) {
    $rowOut = array();
    $rowOut['weight'] = ($row->getCountryWeights()) ? $row->getCountryWeights()[0]->getWeight() : 0;
}

Получатель свойства связанного объекта всегда дает мне доступ ко всем связанным данным, как если бы в моем запросе не было условия соединения. $row->getCountryWeights() всегда верно, потому что без условия соединения соединение не пустое. Как проверить, были ли присоединены какие-либо данные или нет? Как получить доступ к объединенным столбцам, например, 'weight'? Попытка получения получателем объединенной сущности по результату, похоже, не работает:

Attempted to call an undefined method named "getWeight" 

Дополнительная информация:

/**
 * Kpi
 *
 * @ORM\Table(name="kpi")
 * @ORM\Entity
 * @ORM\HasLifecycleCallbacks
 */
class Kpi 
{
    /**
     * @var int
     *
     * @ORM\Column(name="id", type="integer")
     * @ORM\Id
     * @ORM\GeneratedValue(strategy="AUTO")
     */
    private $id;

    /**
     *
     * @ORM\OneToMany(targetEntity="CampaignBundle\Entity\KpiWeight", mappedBy="kpi", cascade={"ALL"})
     */
    private $countryWeights;
}

/**
 * KPIWeight
 *
 * @ORM\Table(name="kpi_weight")
 * @ORM\Entity
 */
class KpiWeight
{
    /**
     * @ORM\Id
     * @ORM\ManyToOne(targetEntity="Kpi", inversedBy="countryWeights")
     * @ORM\JoinColumn(name="kpi", referencedColumnName="id", nullable=false)
     */
    private $kpi;

    /**
     * @ORM\Id
     * @ORM\ManyToOne(targetEntity="ZoneBundle\Entity\Country", inversedBy="countryObjectives")
     * @ORM\JoinColumn(name="country", referencedColumnName="id", nullable=false)
     */
    private $country;

    /**
     * @var float
     * @ORM\Column(name="weight", type="float")
     */
    private $weight;
}

Эти списки объектов неполны, но все соответствующие части должны быть там.

1 Ответ

0 голосов
/ 09 мая 2018

Поскольку $row->getCountryWeights() вернет объект Collection, вам необходимо проверить коллекцию.

$countryWeights = $row->getCountryWeights(); //Collection object
$rowOut['weight'] = (($firstWeight = $countryWeights->first()) ? $firstWeight->getWeight() : 0);

Использование Collection::first() вернет false или первую запись в коллекции, вызвав reset() во внутреннем массиве коллекции.

В противном случае вы можете проверить, является ли Коллекция пустой, используя $row->getCountryWeights()->isEmpty().


Explaination

Пример 1:

Скажем, у вас есть следующий набор данных;

\ CampaignBundle \ Entity \ Kpi

| id | 
| 1  | 
| 2  | 

\ CampaignBundle \ Entity \ Kpi :: $ countryWeights (OneToMany(targetEntity="CampaignBundle\Entity\CountryWeight"))

| kpi_id | country | weight |
| 1      | A       | 1      |
| 1      | B       | 1      |
| 2      | A       | 1      |

По умолчанию доктрина Lazy Load ассоциирует сущности CountryWeight при вызове метода получения.

$allRows = $em->getRepository(\CampaignBundle\Entity\Kpi::class)->findAll();
$data = [];
foreach ($allRows as $row) {
    foreach ($row->getCountryWeights() as $weight) {
        $data[$row->getId()][$weight->getCountry()] = $weight->getWeight();
    }
}
dump($data);

Результатом является Ленивая загрузка ассоциаций CountryWeight:

array:2 [▼
  1 => array:2 [▼
    "A" => 1
    "B" => 1
  ]
  2 => array:1 [▼
    "A" => 1
  ]
]

Пример 2:

Однако при использовании QueryBuilder Коллекция ограничивается в соответствии с указанными критериями Join:WITH.

$allRows = $em->getRepository(\CampaignBundle\Entity\Kpi::class)->createQueryBuilder('k')
    ->leftJoin('k.countryWeights', 'w', Join::WITH, $expr->eq('w.country', ':country'))
    ->setParameter('country', 'B')//<--- NOTE B is filtered
    ->addSelect('w')
    ->getQuery()
    ->getResult();
$data = [];
foreach ($allRows as $row) {
    $firstWeight = $row->getCountryWeights()->first();
    $data[$row->getId()][$firstWeight ? $firstWeight->getCountry() : null] = $firstWeight ? $firstWeight->getWeight() : 0;
}
dump($data);

Результатом будет:

array:2 [▼
  1 => array:1 [▼
    "B" => 1
  ]
  2 => array:1 [▼
    "" => 0
  ]
]

В том случае, если доктрина уже извлекла ассоциации CountryWeight, такие как выполнение примера 1, за которым следует пример 2. Вам нужно будет указать доктрине, чтобы принудительно очистить UnitOfWork [sic] для получить отфильтрованные записи из базы данных, в противном случае он будет использовать уже загруженные коллекции для полученных результатов.

Одним из способов достижения этого является выдача

$em->clear(\CampaignBundle\Entity\Kpi::class);

До выполнения вашего запроса. Который очистит всю единицу работы доктрины для объекта. При использовании Query::HINT_REFRESH не будет полностью обновляться исключенные значения из Kpi :: $ id => 2 со страной A.

Пример вывода из уже найденных ассоциаций.

Doctrine Dump


Рекомендации:

Doctrine позволяет вам обходить все ассоциации между всеми объектами в вашей доменной модели. Объекты, которые еще не были загружены из базы данных, заменяются экземплярами прокси с отложенной загрузкой. Незагруженные коллекции также заменяются экземплярами с отложенной загрузкой, которые извлекают все содержащиеся в них объекты при первом доступе. Однако использование механизма отложенной загрузки приводит к выполнению множества небольших запросов к базе данных, что может существенно повлиять на производительность вашего приложения. Выборочные соединения - это решение для гидратации большинства или всех сущностей, которые вам нужны, в одном запросе SELECT.

https://www.doctrine -project.org / проекты / Доктрина-ОРМ / ен / 2,6 / ссылки / DQL-доктрина-запрос-language.html # присоединяется

Если объект уже находится в памяти из предыдущего запроса любого типа, тогда используется предыдущий объект, даже если база данных может содержать более свежие данные. Данные из базы данных отбрасываются. Это даже происходит, если предыдущий объект все еще является незагруженным прокси.

https://www.doctrine -project.org / проекты / Доктрина-ОРМ / ы / 2,6 / ссылки / DQL-доктрина-запросы language.html # объекта-гидратация

Добро пожаловать на сайт PullRequest, где вы можете задавать вопросы и получать ответы от других членов сообщества.
...