Doctrine find() и querybuilder() возвращают разные результаты в тесте PHPUnit

С Doctrine и Symfony в моем тестовом методе PHPUnit:

// Change username for user #1 (Sheriff Woody to Chuck Norris)
$form = $crawler->selectButton('Update')->form([
    'user[username]' => 'Chuck Norris',
]);
$client->submit($form);

// Find user #1
$user = $em->getRepository(User::class)->find(1);
dump($user); // Username = "Sheriff Woody"

$user = $em->createQueryBuilder()
        ->from(User::class, 'user')
        ->andWhere('user.id = :userId')
        ->setParameter('userId', 1)
        ->select('
            user
        ')
        ->getQuery()
        ->getOneOrNullResult()
    ;
dump($user); // Username = "Chuck Norris"

Почему два моих метода получения пользователя №1 возвращают разные результаты?

Мне это кажется проблемой кэширования. Можете ли вы проверить это предположение, вызвав $em->clear перед первым запросом, чтобы увидеть, приводит ли очистка кеша к возврату правильного результата? Кроме того, не могли бы вы предоставить более подробную информацию о вашей проблеме? Вы вызываете оба запроса в одном и том же тестовом примере? Не могли бы вы предоставить свои полные тестовые примеры и действия контроллера?

Arno Hilke 15.07.2019 20:53

Пробовали ли вы двигаться ->select() до ->from()?

Preciel 17.07.2019 00:50
Стоит ли изучать PHP в 2026-2027 годах?
Стоит ли изучать PHP в 2026-2027 годах?
Привет всем, сегодня я хочу высказать свои соображения по поводу вопроса, который я уже много раз получал в своем сообществе: "Стоит ли изучать PHP в...
Symfony Station Communiqué - 7 июля 2023 г
Symfony Station Communiqué - 7 июля 2023 г
Это коммюнике первоначально появилось на Symfony Station .
Symfony Station Communiqué - 17 февраля 2023 г
Symfony Station Communiqué - 17 февраля 2023 г
Это коммюнике первоначально появилось на Symfony Station , вашем источнике передовых новостей Symfony, PHP и кибербезопасности.
Управление ответами api для исключений на Symfony с помощью KernelEvents
Управление ответами api для исключений на Symfony с помощью KernelEvents
Много раз при создании api нам нужно возвращать клиентам разные ответы в зависимости от возникшего исключения.
8
2
844
1
Перейти к ответу Данный вопрос помечен как решенный

Ответы 1

Ответ принят как подходящий

диагноз / объяснение

I предполагать* вы уже создали объект пользователя, который вы редактируете с помощью сканера, ранее в этой функции и проверили, что он там. Это приводит к тому, что он является управляемым объектом.

В природе данных заложено не синхронизироваться волшебным образом с базой данных, но должен быть некий автомат или какой-то метод для их синхронизации.

Метод find() всегда будет пытаться использовать кеш (если он явно не отключен, см. также примечание). Построитель запросов не будет, если вы явно вызовете getResult() (или одну из его разновидностей), поскольку вы явно хотите, чтобы запрос был выполнен. Выполнение другого запроса может привести к тому, что кеш не попадет, что приведет к текущему результату. (хотя он должен обновить первый пользовательский объект...) [обновлено из-за комментария Арно Хильке]

((( Примечание: Синхронизация объектов — это жесткий. В основном речь идет о согласованности в базе данных, но требуется все КИСЛОТА. Любой процесс, взаимодействующий с базой данных, должен предполагать, что он работает только с состоянием в момент свой первый запрос и является единственным пользователем базы данных. Если не должны быть соблюдены дополнительные ограничения и могут возникнуть несогласованные чтения, в этом случае уровни изоляции должны быть повышены (см. Также: транзакции или, точнее: изоляция). Таким образом, автоматическая синхронизация обычно не требуется.Доктрина использует определенные предположения для повышения производительности (в основном: изоляция/блокировка оптимистичны).Однако в вашем конкретном случае все эти вещи не имеют никакого значения... так как вы на самом деле хочу a неповторяемое чтение. )))

(* в противном случае поведение, которое вы видите, было бы В самом деле неожиданным)

решение

Одним из простых решений было бы активное и явное синхронизировать данных из базы данных либо вызовом $em->refresh($user), либо — перед повторным получением пользователя — вызовом $em->clear(), что приведет к появлению объектов отделить все (очистка кеша, что может оказать заметное влияние на производительность) и позволяя вам снова вызывать find с правильными результатами.

Обратите внимание, что отсоединение сущностей означает, что любой объект, ранее возвращенный из диспетчера сущностей, должен быть отброшен и получен снова (не через обновление).

альтернативное решение 1 - все запросы

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

альтернативное решение 2 - использование только одного диспетчера сущностей

использование только одного диспетчера сущностей (то есть: совместное использование диспетчера сущностей/базы данных в модульном тесте с сервером по запросу) может быть разумным решением, но оно имеет свой собственный набор проблем. в основном пропущенные коммиты и сбросы могут избежать обнаружения.

альтернативное решение 3 - использование нескольких менеджеров сущностей

используя один менеджер сущностей для настройки теста, поскольку сервер использует новый менеджер сущностей для выполнения своей работы, вы должны теоретически - чтобы сделать это на самом деле правильно - создать еще один менеджер сущностей для проверки поведения сервера.

комментарий: альтернативные решения 1, 2 и 3 будут работать с самым высоким уровнем изоляции, исходное решение, вероятно, не будет.

Неправильно, что построитель запросов не использует кеш. Я использовал тот же построитель запросов, что и в вопросе, и получаю кешированный результат.

Arno Hilke 22.07.2019 09:59

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

Jakumi 22.07.2019 15:32

Да, я попытался воспроизвести проблему с моей собственной пользовательской сущностью и точными запросами, указанными в вопросе. В моем тестовом примере я обновляю пользователя и вызываю оба запроса. В этом тесте я получаю старый/кэшированный/неправильный результат для обоих. При вызове $em->clear() перед запросами я получаю правильный результат для обоих. Вот почему я попросил дополнительную информацию, потому что я не мог воспроизвести поведение, описанное в вопросе. Испытываете ли вы другое поведение (с построителем запросов, как описано в вопросе)? В этом случае это также может быть различием в версиях доктрины.

Arno Hilke 22.07.2019 15:42

@ArnoHilke другой причиной может быть состояние гонки ... согласно symfony.com/doc/current/components/http_client.html, http-клиент является асинхронным, поэтому технически сервер может работать медленно, а диспетчер объектов теста может возвращаться быстрее. если тогда клиент и сервер используют один и тот же диспетчер сущностей (иногда это очень удобно), первый вызов диспетчера сущностей может вернуть старый объект, а кеш обновится, а второй запрос вернет новый объект, который был помещен туда из сервер ...

Jakumi 22.07.2019 15:50

Я думаю, что стоит отметить разницу между «состояние/кэш в памяти диспетчера сущностей» и «Явное кэширование доктрины» в том, что только метод find будет извлекать данные из «состояние в памяти/кэш» с использованием «отображение личности», тогда как все варианты find, QueryBuilder, findBy будут извлекаться из явного кеша. . В результате, в случае отсутствия явного кеширования, я заметил, что варианты QueryBuilder и findBy всегда будут пытаться получить данные из базы данных.

Sivaram K 02.02.2022 00:21

Другие вопросы по теме