Symfony2 - Доступ к функциям репозитория в Entity

Скажем, что у меня есть две таблицы в моей базе: Кролики и морковь. Кролики могут иметь морскую морковь или морковь, а морковь принадлежит одному кролику. Это отношение 1, n между этими двумя таблицами.

У меня есть два сущности, кролик и морковь.

У меня есть массив кроликов, прошедших в моем шаблоне, и я хотел бы получить конкретную морковь от каждого кролика и показать их: пусть я хочу получить 10 дорогих морков (цены на морковь будут храниться в таблице для моркови) от каждого $кролика в массиве.

Что-то вроде:

{% for rabbit in rabbits %}
    {% for carrot in rabbit.getMoreExpensiveCarrots %}

        {{ carrot.price }}

    {% endfor %}
{% endfor %}

Я использую класс репозитория, но если я создам функцию getMoreExpensiveCarrots ($ rabbit) в классе репозитория кроликов, я бы не смог получить доступ к этой функции из класса сущности, как это, что я хочу:

$rabbit- > getMoreExpensiveCarrots()

Я думал, что способ сделать это - создать getMoreExpensiveCarrots() в объекте кролика:

// Entity rabbit
class Rabbit
{
    public function getMoreExpensiveCarrots()
    {
        // Access repository functions like getMoreExpensiveCarrots( $rabbit )
        // But how can I do such thing ? Isn't that bad practise ?
        return $carrots;
    }         
}

Я думал, что тоже могу это сделать:

    // Entity rabbit
    class Rabbit
    {
        public function getMoreExpensiveCarrots()
        {
            $this->getCarrots();

            // Then try here to sort the carrots by their price, using php            

            return $carrots;
        }         
    }

Вот мой контроллер:

    public function indexAction()
    {
        $em = $this->getDoctrine()->getEntityManager();

        $rabbits = $em->getRepository('AppNameBundle:Rabbit')->getSomeRabbits();

        return $this->render('AppNameBundle:Home:index.html.twig', 
                array(
                    "rabbits"=>$rabbits
        ));
    }

Какова наилучшая практика вызова функции getMoreExpensiveCarrots от каждого кролика в шаблоне?

Спасибо!

Ответы

Ответ 1

Назад к основам. Забудьте о репозитории против службы и просто сосредоточьтесь на кроликах и моркови.

class Rabbit
{
/** @ORM\OneToMany(targetEntity="Carrot", mappedBy="rabbit" */
protected $carrots;

public function getCarrots() { return $this->carrots; }

public function getMoreExpensiveCarrots()
{
    // Get all carrots
    $carrots = $this->getCarrots()->toArray();

    // Sort by price
    // http://php.net/manual/en/function.usort.php
    usort(&$carrots,array($this,'compareTwoCarrotsByPrice'));

    // Now slice off the top 10 carrots (slice - carrots, hah, kindo of funny
    $carrots = array_slice($carrots, 0, 10);

    // And return the sorted carrots
    return $carrots;
}
public function compareTwoCarrotsByPrice($carrot1,$carrot2)
{
    if ($carrot1->getPrice() > $carrot2->getPrice()) return -1;
    if ($carrot1->getPrice() < $carrot2->getPrice()) return  1;
    return 0;
}
}

Удалите все остатки моркови из вашего запроса и просто получите список кроликов.
Теперь ваш оригинальный шаблон будет работать точно так, как ожидалось:

{% for rabbit in rabbits %}
    {% for carrot in rabbit.getMoreExpensiveCarrots %}

        {{ carrot.price }}

    {% endfor %}
{% endfor %}

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

Ответ 2

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

Возможное решение здесь - использовать служебный объект (RabbitService), который содержит метод getMoreExpensiveCarrots. Службе разрешено знать диспетчер сущности и репозитории, поэтому здесь вы выполняете любые сложные операции.

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

Вы также можете пойти со своей второй опцией, считая, что морковь хранится в ArrayCollection. Вы просто выполняете любую логику сортировки, которая вам нужна в этом методе. Это будет нормально, так как вы будете работать с данными, которые были доступны сущности.

Ответ 3

Позвольте мне сделать это, чтобы объяснить это. Doctrine 2 ORM требует немного переосмысления. У вас в настоящее время настроение, что кролик должен быть в состоянии запросить его морковь всякий раз, когда он им нужен. Вам нужно изменить этот образ мышления.

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

В этом случае вы хотите 10 самых дорогих морков, поэтому ваш процесс должен это знать. Итак, теперь вы вернетесь к ответу @Arms и сделаете себе службу RabbitManager с помощью метода loadRabbitsWithExpensiveCarrots. Возвращаемое значение представляло собой массив кроликов с уже заполненной морковью.

Обновлено для просмотра комментариев.

  • Doctrine 2 Repository vs Symfony 2 Service

Репозиторий стремится сосредоточиться на одном типе сущности, то есть репозиторий Кролика лучше всего использовать, когда касается только кроликов. Когда вы начинаете сталкиваться с более сложными требованиями, требующими нескольких типов сущностей, может возникнуть трудность в определении того, какой репозиторий должен находиться в данной функции. Ваша прикладная программа (контроллер) должна будет знать, какой репозиторий должен вносить и, возможно, больше о внутренностях, чем это действительно нужно знать.

Служба Symfony 2 скрывает все детали о том, как организованы кролики и морковь. Это более общая концепция и может иметь дело с более сложными типами запросов с участием нескольких объектов. Это основной строительный блок S2, и вам действительно нужно устраивать его.

Для вашего текущего требования будет работать любой из этих подходов.

  • Вопрос о морковном параметре

Все еще не уверен, что вы имеете в виду. Возможно, вы можете отправить образец запроса? Имейте в виду, что вы можете добавить метод getExpensiveCarrots() к вашему объекту кролика. Метод начнет называть getCarrots(). Возвращенная морковь уже была загружена начальным запросом. Ваш метод будет фильтровать и сортировать.

И имейте в виду, что мы пытаемся разобраться с тем случаем, когда к кролику могут прикрепляться сотни или тысячи моркови. Поэтому мы стараемся избегать загрузки всей моркови только для рассмотрения эффективности. Возможно, было бы легче начать, не загружая морковь вообще в исходный запрос. Вместо этого, когда вы вызываете своего кролика- > getCarrots(), Doctrine 2 автоматически выдает запрос и загружает всю морковь для этого кролика. И снова ваш метод getExpensiveCarrots будет фильтровать и сортировать по мере необходимости.

Надеюсь на эту помощь. Наверное, просто смутил вас еще больше.