@popov654
Специалист в области веб-технологий

Как отключить ленивую инициализацию сущностей в Doctrine для текущего запроса?

Всем доброго времени суток.

Сегодня столкнулся со следующей очень странной проблемой.

Что имеется:

PHP на Windows 7 x64, версия 5.3.17, Apache версии 2.4 (на работе 2.2), PHP подключен как модуль. Symfony версии 2.0.16, Doctrine версии 2.1.
Были созданы для теста три таблицы - а, b, и a_b, хранящая связи "многие-ко-многим". Были созданы тестовые сущности Doctrine MyUsers и MyComments, была сделана привязка через метаданные к этим таблицам. На этом этапе всё хорошо. Вот код, который содержится в классах сущностей:

<?php

namespace TTCom\BillingBundle\Entity;

use Doctrine\ORM\Mapping as ORM;

/**
 * TTCom\BillingBundle\Entity\MyUsers
 *
 * @ORM\Table(name="a")
 * @ORM\Entity
 */
class MyUsers
{
    /**
     *  @ORM\Id
     *  @ORM\GeneratedValue
     *  @ORM\Column(name="id", type="integer", nullable=false)
     */
    private $id;
    
    /**
     * @var string $name
     *
     * @ORM\Column(name="name", type="varchar(255)", nullable=false)
     */
    private $name;

    /**
     * Bidirectional - Many users have Many favorite comments (OWNING SIDE)
     *
     * @ORM\ManyToMany(targetEntity="MyComments", inversedBy="userFavorites")
     * @ORM\JoinTable(name="a_b",
     *   joinColumns={@ORM\JoinColumn(name="a_id", referencedColumnName="id")},
     *   inverseJoinColumns={@ORM\JoinColumn(name="b_id", referencedColumnName="id")}
     * )
     */
    private $favorites;
    
    
    public function getName() {
    return $this->name;
    }
    
    public function getFavoriteComments() {
        return $this->favorites;
    }
    
    public function addFavorite(Comment $comment) {
        $this->favorites->add($comment);
        $comment->addUserFavorite($this);
    }

    public function removeFavorite(Comment $comment) {
        $this->favorites->removeElement($comment);
        $comment->removeUserFavorite($this);
    }

}

<?php

namespace TTCom\BillingBundle\Entity;

use Doctrine\ORM\Mapping as ORM;

/**
 * TTCom\BillingBundle\Entity\MyComments
 *
 * @ORM\Table(name="b")
 * @ORM\Entity
 */
class MyComments
{
    /**
     *  @ORM\Id
     *  @ORM\GeneratedValue
     *  @ORM\Column(name="id", type="integer", nullable=false)
     */
    private $id;
    
    /**
     * @var string $text
     *
     * @ORM\Column(name="text", type="varchar(255)", nullable=false)
     */
    private $text;

    /**
     * Bidirectional - Many comments are favorited by many users (INVERSE SIDE)
     *
     * @ORM\ManyToMany(targetEntity="MyUsers", mappedBy="favorites")
     */
    private $userFavorites;
    
    
    public function getCommentText() {
        return $this->text;
    }
    
    public function getUserFavorites() {
        return $this->userFavorites;
    }

}


Вот код, который я набросал для теста в одном из контроллеров:

public function testAction(Request $request) {
		$em = $this->getDoctrine()->getEntityManager();
		$user = $em->getRepository('TTComBillingBundle:MyUsers')->findOneById(1);
		$fav = $user->getFavoriteComments();
		//return new Response(print_r(\Doctrine\Common\Util\Debug::dump($fav[0]), true)); //1
		//return new Response(count($fav)); //2
		$a = array();
		//count($fav); //3
		//for ($i = 0; $i < count($fav); $i++) {
			//print_r(\Doctrine\Common\Util\Debug::dump($fav[$i])); //4
			//$a[] = $fav[$i]->getCommentText(); //5
		//}
		//array_walk($fav, function($el, $i) use ($a, $fav) { print_r(\Doctrine\Common\Util\Debug::dump($el)); $a[] = $fav[$i]->getCommentText(); }); //6
		print_r($a);
		//return new Response(print_r($f[0], true)); //7
		//return new Response(print_r(\Doctrine\Common\Util\Debug::dump($user->getFavoriteComments()), true)); //8
		return new Response($user->getName().": ".implode(", ", $a)); //9
		$objects = $em->getRepository('TTComBillingBundle:AlarmObject')->getList();
		$events = $em->getRepository('TTComBillingBundle:AlarmLogEntry')->getList();
		return $this->render('TTComBillingBundle:Default:alarm.html.twig',array('objects' => $objects, 'events' => $events));
	}


Что происходит в моём коде:

  • Массив объектов Doctrine с кучей внутренних данных получается абсолютно корректно в строке с комментарием 1
  • Его длина корректно выводится в строке с комментарием 2
  • Если теперь раскомментировать вызов arrray_walk в строке 6, то получаюarray(0) {}, а также ошибку в лог вида
    PHP Fatal error:  Call to a member function getCommentText() on a non-object

  • Но стоит раскомментировать строку 3 с вызовом count() - и всё начинает работать нормально
  • Если вместо array_walk использовать итерацию через цикл for, всё работает корректно
  • Если в callback функции использовать не элемент, а его индекс - то тоже всё хорошо


Под "нормально" подразумевается вывод
array(2) {
  [0]=>
  object(stdClass)#548 (4) {
    ["__CLASS__"]=>
    string(37) "TTCom\BillingBundle\Entity\MyComments"
    ["id"]=>
    int(1)
    ["text"]=>
    string(2) "b1"
    ["userFavorites"]=>
    string(8) "Array(2)"
  }
  [1]=>
  object(stdClass)#540 (4) {
    ["__CLASS__"]=>
    string(37) "TTCom\BillingBundle\Entity\MyComments"
    ["id"]=>
    int(2)
    ["text"]=>
    string(2) "b2"
    ["userFavorites"]=>
    string(8) "Array(1)"
  }
}

в результате вызова print_r(\Doctrine\Common\Util\Debug::dump($el)); внутри callback-функции, а также непустой массив $a, содержащий два элемента: "b1" и "b2".

Я так понимаю, проблема в ленивой инициализации объектов. Можно ли её как-нибудь отключить? Или использование array_walk и array_map с массивами объектов Doctrine - дурной тон и этого следует избегать? Потому как вызов count($fav) или пробежка через for вхолостую до первого использования array_walk/array_filter/array_map выглядит очень грязным хаком.
  • Вопрос задан
  • 178 просмотров
Пригласить эксперта
Ваш ответ на вопрос

Войдите, чтобы написать ответ

Похожие вопросы