Ответы пользователя по тегу Symfony
  • Как правильно реализовать вычисляемое поле для 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 комментарий
  • Как следить за локализациями в Symfony проектах?

    lexxpavlov
    @lexxpavlov
    Программист, преподаватель
    Для управления переводами есть довольно много решений, как и десктопных (poedit), так и онлайновых (crowdin.net, webtranslateit.com). Подобные программы обычно выдают .mo/.po-файлы, которые можно сконвертировать в xliff, который понимает Symony (кстати, в документации рекомендуется именно xliff).

    А можно поставить LswGettextTranslationBundle, который умеет использовать po-файлы напрямую (в этом вопросе есть кое-какая инфа на этот счёт).
    Ответ написан
    Комментировать
  • Как правильно организовать переходы по ссылкам в Symfony?

    lexxpavlov
    @lexxpavlov
    Программист, преподаватель
    Существует несколько путей:
    1. стандартные ссылки (get-параметры или роуты):

      1.1) через get-параметры: /page?id=123&name=user

      1.2) через роуты с параметрами: /page/user/123 (хуже так: /page/name/user/id/123 или что-то наподобие)
    2. сделать скрытую POST-форму с hidden-данными, и ссылки будут отправлять эту форму
    3. делать js-роутинг. На самом деле, то же самое, что и вариант 2, но больше гибкости
    4. делать ajax-запрос новых страниц, то есть делать одностраничное приложение. Тогда symfony становится rest-бэкендом, а в браузер ставится что-то типа angular
    5. иметь длинные ссылки, но при щелчке на неё сохранять данные в сессию, и делать редирект на более красивую ссылку (что-то подобное делается у phpMyAdmin)
    6. отправлять данные через куки - скриптом ставить данные в куки и переходить на новую страницу, и браузер сам передаст только что записанные данные на сервер.


    Опишите, какие ссылки вы хотите "украсить", может, что-то получится ещё придумать.

    И помните, что все варианты, кроме первого, не позволят нормально поделиться ссылкой - передать ссылку через скайп, или поставить ссылку на другом сайте (реклама), или даже в закладки не поставить. Поэтому, используйте первый способ. К тому же, роуты - это правильный способ делать ссылки в symfony.
    Ответ написан
    2 комментария
  • Как очистить переменную в репозитарии?

    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-запрос, без привязки к сущностям.
    Ответ написан
  • Symfony2. Как сделать элементарный фильтр для формы?

    lexxpavlov
    @lexxpavlov
    Программист, преподаватель
    Я использую jQuery-плагин Datatables. Умеет очень многое, в том числе поможет и в этой задаче.
    Лично я вставлял непосредственно через шаблоны, но вот сейчас увидел, что есть два бандла: stwe/DatatablesBundle и brown298/data-tables-bundle.
    Ответ написан
  • Symfony2: как в форме сделать выбор через модальное окно?

    lexxpavlov
    @lexxpavlov
    Программист, преподаватель
    используйте тип sonata_type_model_list
    https://sonata-project.org/bundles/doctrine-orm-ad...

    Просто поставьте тип поля sonata_type_model_list и всё:
    ->add('user', 'sonata_type_model_list')
    Ответ написан
  • Symfony2. Как указать правильный путь до бандла?

    lexxpavlov
    @lexxpavlov
    Программист, преподаватель
    а как вы передаёте файлы проекта на прод? Через git? Добавлен ли vendor в репозитории?

    Возможно, композер должен обновить autoload. Сделайте команду в консоли
    php composer.phar dump-autoload -o

    UPD
    composer не обязательно устанавливать. Это просто php-файл, только заархивированный - в формате phar. Для установки локально нужно выполнить вот такую команду в консоли:
    php -r "readfile('https://getcomposer.org/installer');" | php

    Эта команда скачает композер и положит его в файл composer.phar в текущую папку. (подробнее - см. документацию composer-а - https://getcomposer.org/doc/00-intro.md)
    В дальнейшем, для запуска используйте команду php composer.phar <команда>

    Лучше всего управлять вендорами с помощью composer-а и на dev, и на prod.
    На своей локальной машине вы используете команду composer update для установки новых пакетов и обновления уже существующих. При обновлении все установленные пакеты сохраняются в файл composer.lock, который хранит информацию о установленных версиях каждого пакета.
    В репозиторий вы добавляете файлы composer.json и composer.lock, а папку vendor добавлять в репозиторий не нужно. На prod-сервере для установки вендоров нужно запустить команду php composer.phar install, которая возьмёт нужные версии пакетов из composer.lock и установит их в папку vendor, а также обновит файл autoload-а.
    Ответ написан
    4 комментария
  • Как написать запрос в 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)
    Ответ написан
  • Как добавить изображения в Sonata Admin?

    lexxpavlov
    @lexxpavlov
    Программист, преподаватель
    Я использую VichUploaderBundle для сохранения картинок в сущность. Получается достаточно удобно (мне не нравится только то, что нельзя в twig нельзя напрямую выводить item.picture, а приходится выводить через vich_uploader_asset(item, 'image')).

    А в админке я вывожу миниатюру картинки через хелпы
    $helper = $this->getConfigurationPool()->getContainer()->get('vich_uploader.templating.helper.uploader_helper');
    $src = $helper->asset($this->getSubject(), 'image');
    $formMapper->setHelps([
        'picture' => "<img src=\"$src\" width=\"100\">",
    ]);


    Но это я использую в случае, если картинку нужно добавлять посетителю, а если я сам добавляю в админке, то я использую с помощью FMElfinderBundle.
    Ответ написан
    Комментировать
  • Стоит ли использовать типы данных 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 комментария
  • Каковы тонкости реализации пользовательских шаблонов Symfony2 + Twig?

    lexxpavlov
    @lexxpavlov
    Программист, преподаватель
    Попробуйте дать пользователю возможность писать свой HTML, но ограничьте возможности с помощью HTMLPurifier-а (о нём на хабре, есть бандл). Он видится достаточно надёжным для того, чтобы пользователь не смог ввести ничего опасного. Дать ему возможность ввести основные html-теги и классы, а внешний вид - в отдельном css.
    Ответ написан
    Комментировать
  • Как создать 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());
    Ответ написан
  • Хорошая архитектура symfony app?

    lexxpavlov
    @lexxpavlov
    Программист, преподаватель
    Хорошая архитектура - это очень понятная архитектура, когда для каждого типа задачи созданы логичные и понятные средства. Когда приложение построено таким образом, то его получится и тестировать, и изменять/расширять. Если нужно добавить новый класс, то в хорошей архитектуре понятно, куда его нужно добавить.

    Всего существует не так много типов задач:
    1) хранение данных приложения - модель. Модель ничего не должна знать о базе данных.
    2) слой доступа к БД - репозиторий. Вся работа с БД - здесь, и всё, что связано с одной сущностью - в репозитории этой сущности. Если нужно взаимодействие нескольких сущностей, то, скорее всего, репозиторий одной из сущностей не подойдёт, эта задача пойдёт в сервис.
    3) бизнес-логика - сервисы. Сервисы умеют получать данные (модели), обращаясь к репозиториям или лучше к другим сервисам (служебным).
    4) служебные задачи - сервисы. Например, кэширование данных реализуется в специальном сервисе.
    5) отображение данных - шаблоны. Весь html - только в шаблоне, а также логика отображения тоже в нём (вывод списков, некоторые фильтры)
    6) подготовка данных к отображению - контроллер. Запрос пользователя приходит в контроллер, контроллер же обращается к сервису (или в простом случае к репозиторию) за данными. Возможно, для обработки запроса нужно получить данные из нескольких сервисов/репозиториев, а скорее, правильный сервис сам подготовит все данные для запроса.

    Получается, контроллер вызывает сервис, который предоставит готовые данные, и будет очень простым - "тонким". Основной код, отвечающий за работу задач приложения - в сервисах. Один и тот же сервис часто будет вызываться из разных контроллеров. Работа с БД - не в сервисе и уж точно не в контроллере, для этого нужен репозиторий.
    Если структура БД изменится, то в хорошей архитектуре поменять придётся только репозиторий, но не сервисы/контроллеры. С другой стороны, изменение схемы БД чаще всего связаны с новыми фичами, поэтому придётся добавить новый сервис или изменить существующий.
    Модель же умеет не так много - выводить свои данные, возможно, в разных форматах (данные, возвращаемые моделью не обязаны совпадать с полями сущности - см. пример в комментарии keltanas в ответе Владимир Балин).

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

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

    P.S. Я вот написал, что модель не должна ничего не знать о базе данных, получается, что неправильно использовать аннотации для маппинга полей сущности на поля таблиц. После ответа на этот вопрос я, скорее всего, пересмотрю свои взгляды на @ОRМ-аннотации в моделях.
    Ответ написан
    23 комментария
  • Массивы деревья на php почему исчезает дерево?

    lexxpavlov
    @lexxpavlov
    Программист, преподаватель
    Вывод подобных массивов - задача для Twig. Это делается простейшим способом:
    {% extends '::layout.html.twig'%}
    
    {% block content %}
    <ul>
    {% for items in tree %}
    {% for item in items %}
        <li data-node="{{ item.id }}" style="position:relative;" class="list-group-item">
             <a href="/wikiedit/{{ item.id }}">{{ item.name }}</a>
             <i class="glyphicon glyphicon-remove pull-right" style="cursor:pointer;" onclick="R.wiki.delete({{ item.id }});"></i>
        </li>
    {% endfor %}
    {% endfor %}
    </ul>
    {% endblock %}

    Это если нет вложенных элементов. Если есть вложенные, то нужно сделать отдельный шаблон (без extends), и тут его вызывать через include.

    А у вас есть ещё ошибка в выводе - метод $str .= $this->viewtree($arr, $r['id']); вызывается сначала для массивов первой вложенности, а у них нет ключа id. Тут нужно сделать два разных метода - один будет обрабатывать первый уровень вложенности, а второй метод - сами элементы с данными.
    И если у вас нет вложенных друг в друга элементов с данными, то рекурсию использовать не стоит. У вас же явно видно, что элементы разных уровней вложенности имеют разный формат данных, значит, обрабатывать их одним методом некорректно.
    Ответ написан
    Комментировать
  • Как многократно использовать сущность?

    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 и возвращающий ссылку на него.
    Но гораздо лучше будет создать сервис, который инкапсулирует всю работу с сущностью, и сделать у сервиса несколько методов, возвращающих уже готовую сущность, возможно, из кэша.
    Ответ написан
  • Как включить перевод в symfony для форм?

    lexxpavlov
    @lexxpavlov
    Программист, преподаватель
    не делайте в messages.ru.yml раздел "forms:", поставьте перевод прямо так, как написано на странице. У вас в форме поля maker и category, а на странице они выводятся как Maker и Category. Так?
    Напишите в переводе именно так:
    Maker: Создатель
    Category: Категория

    Ну а если хотите добавлять префикс form, то добавьте в описание формы атрибут label:
    $builder
            ->add(
                'maker',
                'entity',
                array(
                    'label' => 'form.maker',
                    'class' => 'MyBundle:ItemMaker',
                    'property' => 'name',
                    'empty_value' => '-- Все --'
                )
            )
    
            ->add(
                'category',
                'entity',
                array(
                    'label' => 'form.category',
                    'class' => 'MyBundleCategory',
                    'property' => 'name',
                    'empty_value' => '-- Все --'
                )
    );

    то есть, укажите label в том же виде, как фреймворк его видит в файле перевода. Но тогда не забудьте добавить и английский перевод! (если он вам нужен)
    Ответ написан
    4 комментария