Что такое прокси в доктрине 2?

Я только что закончил читать всю документацию Doctrine 2, я начал свою собственную песочницу, я понял большинство принципов, но есть еще вопрос, и я не мог найти никакого полного объяснения в документе.

  • Что такое Proxy классы?
  • Когда я должен использовать их над сущностями?

Насколько я понимаю, прокси-классы добавляют слой, позволяющий добавить к вашим объектам некоторые другие функции, но зачем использовать прокси вместо реализации самих методов в классе сущностей?

Ответ 1

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

@Entity
class User {
     @Column protected $id;
     @Column protected $username;
     @Column protected $firstname;
     @Column protected $lastname;

     // bunch of setters/getters here
}

DQL query:

SELECT u.id, u.username FROM Entity\User u WHERE u.id = :id

Как вы видите, этот запрос не возвращает свойства firstname и lastname, поэтому вы не можете создать объект User. Создание неполного объекта может привести к непредвиденным ошибкам.

Вот почему Doctrine создаст объект UserProxy, который поддерживает ленивую загрузку. Когда вы попытаетесь получить доступ к свойству firstname (который не загружен), он сначала загрузит это значение из базы данных.


Я хочу сказать, почему я должен использовать прокси?

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

Почему ленивая загрузка не может быть реализована в самом Entitiy?

Технически это может быть, но взгляните на некоторый класс случайных прокси-объектов. Это полный грязный код, тьфу. Приятно иметь чистый код в ваших сущностях.

Можете ли вы предоставить мне прецедент?

Вы показываете список последних 25 статей, и вы хотите отобразить детали первого. Каждый из них содержит большой объем текста, поэтому извлечение всех этих данных будет пустой тратой памяти. Вот почему вы не получаете ненужные данные.

SELECT a.title, a.createdAt
FROM Entity\Article a
ORDER BY a.createdAt DESC
LIMIT 25

$isFirst = true;
foreach ($articles as $article) {
    echo $article->getTitle();
    echo $article->getCreatedAt();

    if ($isFirst) {
        echo $article->getContent(); // Article::content is not loaded so it is transparently loaded 
                                     // for this single article.

        $isFirst = false;
    }
}

UPDATE

В разделе комментариев ниже содержится неверная информация о различиях между прокси-объектами и частичными объектами. См. Ответы @Kontrollfreak для более подробной информации: fooobar.com/questions/57664/...

Ответ 2

Доверенные

Прокси-сервер Doctrine - это просто оболочка, которая расширяет класс сущности, чтобы обеспечить для него Lazy Loading.

По умолчанию, когда вы запрашиваете Entity Manager для объекта, который связан с другим объектом, связанный объект не будет загружен из базы данных, а завернут в прокси-объект. Когда ваше приложение затем запрашивает свойство или вызывает метод этого проксированного объекта, Doctrine будет загружать объект из базы данных (кроме случаев, когда вы запрашиваете идентификатор, который всегда известен прокси).

Это происходит полностью прозрачно для вашего приложения из-за того, что прокси расширяет класс сущности.

Doctrine по умолчанию объединяет гидратные ассоциации в качестве ленивых прокси-серверов, если вы не выполняете JOIN их в своем запросе или не устанавливаете режим выборки на EAGER.


Теперь я должен добавить это, потому что у меня недостаточно репутации, чтобы комментировать везде:

К сожалению, ответ Крозина содержит дезинформацию.

Если вы выполняете DQL-запрос, например

SELECT u.id, u.username FROM Entity\User u WHERE u.id = :id

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

Имея это в виду, приходит к выводу, что пример использования не будет работать. Для доступа к $article как к объекту DQL необходимо было бы изменить что-то вроде этого:

SELECT a FROM Entity\Article a ORDER BY a.createdAt DESC LIMIT 25

И свойство, возвращаемое getContent(), должно быть ассоциацией, чтобы не загружать свойства содержимого всех 25 объектов.


Частичные объекты

Если вы хотите частично загрузить свойства объекта, которые не являются ассоциациями, вы должны явно указать эту Доктрину:

SELECT partial u.{id, username} FROM Entity\User u WHERE u.id = :id

Это дает вам частично загруженный объект объекта.

Но будьте осторожны, что частичные объекты не являются прокси! Lazy Loading не относится к ним. Поэтому использование частичных объектов обычно опасно и их следует избегать. Подробнее: Частичные объекты - документация Doctrine 2 ORM 2