Имеется ли возможность получить реальный объект, а не Proxy класс Doctrine
Предисловие:
- Пишется сложный логгер на основе рефлексии, Doctrine UnitOfWork и метаданных.
- Возможности отрефакторить сущности и добавить аннотацию
fetch="EAGER"нет (получим не гибкую реализацию). - Установлена дефолтная конфигурация для EntityManager, которая автоматически генерирует Proxy-классы для связей (отказать от этой возможности тоже не можем).
- Вариант с
$this-em->detach($entity)- это не вариант, т.к. уходим из под Doctrine, в данном случае, в рамках логгера, недостижимая роскошь (так же, принесет больше проблем, чем пользы).
Вопрос: Из предисловия уже наверно понятно, что необходимо получить реальный объект, а не Proxy-класс. Имеется ли такая возможность (если нет, хотелось бы услышать почему, т.к. в каких-то частных случаях, это крайне необходимо)?
Дополнение: смотрел переписку разработчиков Doctrine, много гуглил, по всей видимости, складывается ощущение, что такой возможности нет, однако, имеется догма с утверждением, что это все таки реализуемо.
Код и результат попыток:
$userRepo = $this-em->getRepository(User::class);
$userRoleRepo = $this->em->getRepository(UserRole::class);
$user = $userRepo->find(1);
$userRole = $userRoleRepo->find(1); // принадлежит $user
var_dump(get_class($user->getUserRole())); // соответственно string(47) "Proxies\__CG__\App\Entity\UserRole"
var_dump(get_class($userRole)); // аналогично предыдущему дампу, это понятно, т.к. ссылаемся на тот же, уже полученный Proxy-класс (так работает Doctrine, только один инстанс одного и того же наблюдаемого объекта).
// попытки
$userRole->getId(); // это бы помогло, если бы была указана аннотация
var_dump(get_class($userRole)); // string(47) "Proxies\__CG__\App\Entity\UserRole"
$userRole->setName('changedName'); // удивительно, но мы работаем все с тем же Proxy
var_dump(get_class($userRole)); // string(47) "Proxies\__CG__\App\Entity\UserRole"
$userRole->__load(); // вариант из сети
var_dump(get_class($userRole)); // string(47) "Proxies\__CG__\App\Entity\UserRole"
// этот вариант фигурирует везде (в сети), как рабочий, однако - нет
$proxyClassName = get_class($userRole);
$realClassName = $this-em->getClassMetadata($proxyClassName)->rootEntityName;
$isOriginUserRole = $this->em->find($realClassName, $userRole->getId());
var_dump(get_class($userRole)); // string(47) "Proxies\__CG__\App\Entity\UserRole"
$uR = $this->em->createQueryBuilder()
->select('ur')
->from($realClassName, 'ur')
->where('ur.id = :id')
->setParameter('id', 1)
->getQuery()
->getResult(AbstractQuery::HYDRATE_OBJECT);
var_dump(get_class($uR)); // string(47) "Proxies\__CG__\App\Entity\UserRole"
Желаемый результат:
$userRepo = $this-em->getRepository(User::class);
$userRoleRepo = $this->em->getRepository(UserRole::class);
$user = $userRepo->find(1);
$userRole = $userRoleRepo->find(1);
// некий код, который позволит получить реальный инстанс UserRole
var_dump(get_class($userRole)); // "string(19) App\Entity\UserRole"
var_dump(get_class($user->getUserRole())); // "string(19) App\Entity\UserRole"
Предметная область проблемы (извиняюсь за отход от темы вопроса):
$realObjectNotProxy = $anyRepo->find(1);
$originalDataOfNotProxy = $this->em->getUnitOfWork()->getOriginalEntityData($realObjectNotProxy);
var_dump(array_keys($originalDataOfNotProxy)); // получаем непустой массив свойств объекта $realObjectNotProxy
// тут должно быть получение сущности, которая ссылается на $proxyObject, чтобы как раз он был вгружен как Proxy
$proxyObject = $anyRepoProxy->find(1);
$originalDataOfProxy = $this-em->>getUnitOfWork()->getOriginalEntityData($proxyObject)
var_dump(array_keys($originalDataOfProxy)); // получаем пустой массив :(
P.S. Код переписан с реального, сущности User и UserRole взяты из головы, чтобы не нагружать вопрос бизнесом.
Ответы (1 шт):
Прокси-объекты используются для механизма lazy при запросе данных. по умолчанию lazy, запрос в БД пойдет как только вы вызовете связанную сущность
Варианта 2:
вы можете явно в маппинге указать, например
@ManyToOne(targetEntity="UserRole", cascade={"all"}, fetch="EAGER") public ArrayCollection $roles;
Теперь все запросы getUserRoles() будут доставать нормальный объект, предварительно в запросе из репозитория их получая
- Вы можете написать метод в репозитории, где приджойните связанные сущности и получите также объекты с связанными данными без допзапроса
Обойти иначе не получится, тк для составления запроса данных ORM руководствуется настройками жадности (eager, lazy, extra_lazy) или квери-билдером, который направляет какие данные нужно выбирать — джойн явно указывает, что надо в запросе эти данные получить, а тк получит их — значит загидрирует уже реальный объект, а не его проксюху
Просто представьте, что вы отключили проксирование... lazy по умолчанию в маппинге, то есть ORM не добавит получение их в запрос, на основе каких данных реальный объект гидрировать? :)