Ответы пользователя по тегу Doctrine ORM
  • Почему Doctrine не записывает передаваемый ей параметр?

    lexxpavlov
    @lexxpavlov
    Программист, преподаватель
    Уберите поле parentId, и всё заработает. Такое поле в БД сделает доктрина сама, при создании связи между таблицами. Можно оставить это поле в сущности, если так хочется, но устанавливать ассоциацию между записями всё равно нужно полем parent.

    Плюс, похоже, у вас ошибка в типе связи в полях parent и division. У вас тип связи OneToOne, то есть, у руководителя может быть только один сотрудник, и в подразделении может быть только один сотрудник. Поставьте тип связи ManyToOne (см. статью).
    Ответ написан
    Комментировать
  • Как правильно работать с PersistentCollection в Doctrine ORM?

    lexxpavlov
    @lexxpavlov
    Программист, преподаватель
    Скорее всего, это происходит, если вы во втором случае берёте $entityA->getEntitiesB(); у другого объекта, полученного из репозитория.
    $entityA1 = $entity_manager->getRepository(...)->find(1);
    $entityA1->addEntitiesB(new EntityB());
    
    $entityA2 = $entity_manager->getRepository(...)->find(1); // получаем новый объект (из базы)
    $entitiesB = $entityA2->getEntitiesB(); // пусто
    $entitiesB = $entityA1->getEntitiesB(); // а тут будет сохранённый объект
    Ответ написан
    Комментировать
  • Как написать нужные методы для работы с базой данных?

    lexxpavlov
    @lexxpavlov
    Программист, преподаватель
    Сам класс People не должен наследоваться от EntityRepository. Репозиторий - это отдельный класс, в который и следует создать такой метод, это хорошая практика.
    А дальше лучше сделать сервис, который будет использовать созданный репозиторий, и в контроллере остается
    class DefaultController extends Controller {
        public function showAction($login)
        {
            $human = $this->get("PeopleService")->getByLogin($login);
        }
    }
    Ответ написан
    Комментировать
  • Как лучше организовать структуру таблиц?

    lexxpavlov
    @lexxpavlov
    Программист, преподаватель
    Вот почитайте мою статью, в ней как раз рассказывается о вашей проблеме:
    Реализация системы тегов в админке с бандлом Sonat...
    Ответ написан
    Комментировать
  • Как настроить принимаемые значения некоторых полей в сущности?

    lexxpavlov
    @lexxpavlov
    Программист, преподаватель
    Можно использовать тип данных enum в записи. Enum для этого и предназначен. В базе будет использоваться число, а при получении данных из базы будет строка.
    Вот стандартное решение в доке доктрины.
    Если enum только один, то можно так и сделать. Но часто enum-ов много, и тогда я делаю вот такой способ:

    Базовый класс:
    <?php
    namespace AppBundle\DBAL;
    
    use Doctrine\DBAL\Types\Type;
    use Doctrine\DBAL\Platforms\AbstractPlatform;
    
    abstract class EnumType extends Type
    {
        protected $name;
    
        public static $VALUES = array();
    
        public static function getValues()
        {
            $values = array();
            foreach (static::$VALUES as $value) $values[$value] = $value;
            return $values;
        }
    
        public function getSqlDeclaration(array $fieldDeclaration, AbstractPlatform $platform)
        {
            $values = array();
            foreach (static::$VALUES as $value) $values[] = "'$value'";
            return "ENUM(".implode(", ", $values).") COMMENT '(DC2Type:$this->name)'";
        }
    
        public function convertToPHPValue($value, AbstractPlatform $platform)
        {
            return $value;
        }
    
        public function convertToDatabaseValue($value, AbstractPlatform $platform)
        {
            if (!in_array($value, static::$VALUES)) {
                throw new \InvalidArgumentException("Invalid value '$value' for enum '$this->name'.");
            }
            return $value;
        }
    
        public function getName()
        {
            return $this->name;
        }
    }

    И теперь легко сделать нужный enum:
    <?php
    namespace AppBundle\DBAL;
    
    class MyType extends EnumType
    {
        protected $name = 'mytype';
    
        const FIRST = 'first';
        const SECOND  = 'second';
    
        public static $VALUES = array(self::FIRST, self::SECOND);
    }


    Ну и в настройке указать тип:
    doctrine:
        dbal:
            driver:   "%database_driver%"
            host:     "%database_host%"
            port:     "%database_port%"
            dbname:   "%database_name%"
            user:     "%database_user%"
            password: "%database_password%"
            charset:  UTF8
            types:
                mytype: AppBundle\DBAL\MyType
            mapping_types:
                enum: string


    Теперь можно указать его в сущности:
    class MyEntity
    {
        /**
         * @var integer $id
         *
         * @ORM\Column(type="integer")
         * @ORM\Id
         * @ORM\GeneratedValue(strategy="AUTO")
         */
        protected $id;
        
       // ....
        
        /**
         * @var string
         *
         * @ORM\Column(type="mytype")
         */
        protected $type;


    Для чего делать отдельный класс для каждого enum-а? Для типизации значений. Например, можно было взять список значений: $types = MyType::getValues(), или проверить тип: if ($val->getType() == MyType::FIRST) {}
    Ответ написан
    Комментировать
  • Как сохранить изображение в Symfony при использовании 2ух бандлов?

    lexxpavlov
    @lexxpavlov
    Программист, преподаватель
    Я бы хранил кропленные картинки прямо бандлом ComurImageBundle, без iPHP. ComurImageBundle сам сохранит оригинальный файл (если надо, можно выключить) и обрезанный файл в указанную папку, причём сохранит сам через ajax, ещё до сохранения формы, и в сонату придёт уже готовая строка с уже готовым файлом (обрезанным и оригинальным).
    Убирайте из полей сущности атрибуты, связанные с iPHP и делайте их string вместо array. То есть, делайте прямо как описано в документации бандла ComurImageBundle. А если вам ещё нужно что-то делать ещё и с iPHP, то дополнительно делайте новые поля для iPHP, и сами вручную сохраняйте нужные данные для них в prePersist, как вам советует Денис
    Ответ написан
    Комментировать
  • Как правильно реализовать вычисляемое поле для Entity (Symfony/Doctrine)?

    lexxpavlov
    @lexxpavlov
    Программист, преподаватель
    Есть несколько вариантов решения.

    1) грязным хаком получать контейнер и извлекать из него нужные сервисы и параметры. Если нужно решить очень быстро, то можно сделать и так, но тогда потом не забыть прибрать за собой, как появится время на рефакторинг.
    2) сделать фабрику для создания сущности (как вы и говорили)
    Эти два варианта можно посмотреть вот в этом вопросе.

    3) Передать шаблон через параметр метода:
    class User
    {
        // ...
    
        public function getFullDomain($template)
        {
            return str_replace("{subdomain}", $this->subdomain, $template);
        }
    }

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

    4) Сделать расширение для Twig. Решение в зависимости от назначения этого метода - если полное имя домена выводится только в представлении, то это хороший выход. В приведённой странице документации приведён способ построения фильтра user|fullDomain, но не сложнее сделать и функцию - fullDomain(user), кому что больше нравится.

    5) Сделать сервис, который будет строить полное имя домена. Это самый универсальный способ, ведь во всех остальных случая можно вызвать этот сервис и использовать его метод для построения имени.
    Ответ написан
    1 комментарий
  • Как очистить переменную в репозитарии?

    lexxpavlov
    @lexxpavlov
    Программист, преподаватель
    Это в рамках обработки одного запроса? тогда всё понятно, никакое кэширование здесь ни при чём.
    Вы в конструкторе создаёте билдер запросов:
    $this->exist = $this->createQueryBuilder('i')
    То есть, в памяти хранится объект билдера, и в дальнейшем вы обращаетесь несколько раз к этому объекту, который уже имеет своё состояние.
    У вас два подхода:
    1) в каждом методе создавать новый билдер и указывать им нужные общие параметры. Чтобы не дублировать код, можно сделать приватный метод getBuilderExist(), который будет создавать объект билдера и задавать базовые настройки запроса.
    2) либо в методах, использующих уже существующий билдер, клонировать его и использовать копию:
    public function findByCategory($category)
        {
            $qb = clone($this->exist);
            return $qb
                ->andWhere('i.category = :category')
                ->setParameter('category', $category)
                ->getQuery()->getResult();
        }


    Я бы выбрал первый подход, чтобы не добавлять классу лишнее состояние.
    Ответ написан
    5 комментариев
  • Как убрать избыточную информацию из $entity возвращаемой Doctrine2, Symfony?

    lexxpavlov
    @lexxpavlov
    Программист, преподаватель
    Посмотрите на Partial objects.

    Или, если ваша задача позволит, используйте DBAL вместо ORM - вам вернутся только запрошенные данные в виде массива, без связанных объектов (ну, разве вы явно укажете их в SQL). В общем, DBAL - это возможность просто выполнить любой SQL-запрос, без привязки к сущностям.
    Ответ написан
  • Как написать запрос в doctrine?

    lexxpavlov
    @lexxpavlov
    Программист, преподаватель
    > Items выводятся все равно все, даже isActive = 0
    нужно ставить не 1, а true:
    ->where('i.isActive = true')
    Я не подскажу, как сделать выборку только категорий, имеющих вложенные элементы, но вы легко сможете не выводить их в шаблоне:
    {% for category in categories if category.items is not null %}
    {{category.name}}
    {% for item in category.items %}
    - {{ item.name }}
    {% endfor %}
    {% endfor %}


    Если у вас есть сложные запросы, то рассмотрите возможность использования NativeQuery:
    doctrine-orm.readthedocs.org/en/latest/reference/n... (на русском odiszapc.ru/doctrine/native-sql)
    Ответ написан
  • Стоит ли использовать типы данных ENUM & SET в БД MySQL?

    lexxpavlov
    @lexxpavlov
    Программист, преподаватель
    Set я не использовал, а вот Enum использую успешно. Используется он очень просто. Причём я создаю отдельный класс для каждого enum-а в проекте, что даёт некоторые преимущества, например, простой доступ к массиву допустимых значений.

    Я создаю в проекте папку DBAL, и там создаю такие классы:
    <?php
    
    namespace AppBundle\DBAL;
    
    use Doctrine\DBAL\Types\Type;
    use Doctrine\DBAL\Platforms\AbstractPlatform;
    
    abstract class EnumType extends Type
    {
        protected $name;
    
        public static $VALUES = array();
    
        public static function getValues()
        {
            return array_combine(static::$VALUES, static::$VALUES);
        }
    
        public function getSqlDeclaration(array $fieldDeclaration, AbstractPlatform $platform)
        {
            $values = array_map(function($val) { return "'".$val."'"; }, static::$VALUES);
    
            return "ENUM(".implode(", ", $values).") COMMENT '(DC2Type:".$this->name.")'";
        }
    
        public function convertToPHPValue($value, AbstractPlatform $platform)
        {
            return $value;
        }
    
        public function convertToDatabaseValue($value, AbstractPlatform $platform)
        {
            if (!in_array($value, static::$VALUES)) {
                throw new \InvalidArgumentException("Invalid value '$value' for enum '$this->name'.");
            }
            return $value;
        }
    
        public function getName()
        {
            return $this->name;
        }
    }

    Этот класс является базовым для кастомных классов Enum-ов, вот пример одного такого:
    <?php
    
    namespace AppBundle\DBAL;
    
    class GenderType extends EnumType
    {
        protected $name = 'gender';
        public static $VALUES = array('male', 'female');
    }

    Теперь нужно научить доктрину их использовать:
    doctrine:
        dbal:
            # ...
            types:
                gender: AppBundle\DBAL\GenderType
            mapping_types:
                enum: string
                gender: gender

    Теперь можно использовать в описаниях сущностей:
    <?php
    
    namespace AppBundle\Entity;
    
    use Doctrine\ORM\Mapping as ORM;
    use Gedmo\Mapping\Annotation as Gedmo;
    
    /**
     * @ORM\Entity
     */
    class Person
    {
        // other fields
    
        /**
         * @var string
         *
         * @ORM\Column(type="gender")
         */
        protected $gender;
    
        // setters & getters
    }

    И в любом месте кода можно получить список доступных значений:
    foreach (GenderType::$VALUES as $gender) {
        echo $gender;
    }


    Насчёт типа SET - думаю, нужно написать подобный класс, умеющий переводить из значения php в формат sql, который корректно сохранит в поле. Но по мне, для языков лучше использовать simple_array, и не мучаться с ними.
    Ответ написан
    4 комментария
  • Как создать symfony форму в случае standalone использования компонента форм?

    lexxpavlov
    @lexxpavlov
    Программист, преподаватель
    Всё о формах написано в документации: symfony.com/doc/current/book/forms.html#book-form-...

    В контроллере создавайте сразу форму:
    $form = $this->createForm(new SignInForm(), null, array(
        'action' => $this->generateUrl('login_check'),
        'method' => 'POST',
    ));


    Насколько я понял, вашей форме не нужно передавать объект $user. Если всё-таки нужно, то укажите его вместо null.
    Параметры формы (action и method) лучше указывать тоже в классе формы. Тогда получится ещё проще:
    $form = $this->createForm(new SignInForm());
    Ответ написан
  • Как многократно использовать сущность?

    lexxpavlov
    @lexxpavlov
    Программист, преподаватель
    У вас две возможности:
    1) либо разрешить, чтобы поля могли быть пустыми - nullable
    2) либо установить значение по умолчанию для поля.
    namespace YourBundle\Entity;
    
    use Doctrine\ORM\Mapping as ORM;
    
    /**
     * @ORM\Entity
     */
    class User
    {
        /**
         * @ORM\Column(type="integer")
         * @ORM\Id
         * @ORM\GeneratedValue(strategy="AUTO")
         */
        private $id;
    
        /**
         * @ORM\Column(type="string", length=100, nullable=true)
         */
        private $firstName;
    
        /**
         * @ORM\Column(type="string", length=100, nullable=true)
         */
        private $lastName;
    
        /**
         * @ORM\Column(type="string", length=100)
         */
        private $username = "";
    
        /**
         * @ORM\Column(type="string", length=100, unique=true)
         */
        private $email;
    
        /**
         * @ORM\Column(type="string", length=100)
         */
        private $password;
    
        // getters & setters
    }


    Смотрите, поля firstName и lastName отмечены как nullable=true, а поле username имеет значение по умолчанию (пустая строка).

    P.S. не забудьте добавить поле salt помимо пароля.
    P.P.S. или лучше возьмите FOSUserBundle, и не парьтесь, там уже есть отличный набор полей для юзера, плюс возможность добавить свои.
    Ответ написан
  • Как переопределить значение свойства сущности при помощи сервиса в Symfony2?

    lexxpavlov
    @lexxpavlov
    Программист, преподаватель
    прямо так - никак не сделать. Нужно достаточно сильно изменять работу с сущностью.
    1) Можно сделать прокси-класс, имеющий расширенную логику в части некоторых свойств объекта, и передающий остальные свойства без изменений.
    2) Можно обрабатывать нужные свойства отдельным вызовом перед передачей объекта в шаблон - передать объект сущности в метод сервиса, и там изменить значения свойств.
    3) Можно поставить обработчик событий, который будет перехватывать системные события загрузки сущности и в обработчике изменять объект.
    Сущности доктрины - это просто обычный класс. И в идеале не стоит передавать в неё что-то внешнее, привносить лишнюю зависимость (например, не надо передавать текущего пользователя, имеющего роли).
    Ответ написан
  • Как в Symfony2 подружить select без multiple со свойством Entity, которое ManyToMany?

    lexxpavlov
    @lexxpavlov
    Программист, преподаватель
    для ограниченного пользователя используйте поле без multiple, а потом после отправки формы, но до сохранения превращайте его в ArrayCollection - прямо в то же поле создавайте коллекцию и добавляйте в неё вашу одну категорию.
    Ответ написан
    Комментировать
  • Как пользоваться фильтрами в Symfony2?

    lexxpavlov
    @lexxpavlov
    Программист, преподаватель
    Сделайте QueryBuilder, и в нём сразу поставьте нужное условие. А в каждой странице добавляйте к билдеру дополнительные условия.
    Как вариант, создать родительский класс для всех контроллеров, и туда поставить метод, создающий этот QueryBuilder и возвращающий ссылку на него.
    Но гораздо лучше будет создать сервис, который инкапсулирует всю работу с сущностью, и сделать у сервиса несколько методов, возвращающих уже готовую сущность, возможно, из кэша.
    Ответ написан
  • Как реализовать теги для нескольких сущностей в SonataAdminBundle?

    lexxpavlov
    @lexxpavlov Автор вопроса
    Программист, преподаватель
    Решение проблемы нашлось в Saving hooks. Вот статья, где я описал решение:
    Реализация системы тегов в админке с бандлом Sonat...
    Ответ написан
    Комментировать