Ответы пользователя по тегу Symfony
  • Как не попадать на исключения при returned NULL?

    IgorPI
    @IgorPI
    $project = $projectRepository->find($project_id);
    
        if (!$project instanceof Project) {
             throw new NotFoundException("Project not found", "project_not_found");
        }
      
       // Здесь совершенно точно буддет объект.
    Ответ написан
  • Как связать элементы формы, что бы список основывался на предыдущих значениях формы?

    IgorPI
    @IgorPI Автор вопроса
    Всё получилось.

    Хотя, не понятно, сделал всё то же самое что и раньше.
    Правда, изменил HTPP method from GET to POST

    Полный код формы.
    spoiler

    <?php
    
    namespace App\Form;
    
    use App\Entity\Brand;
    use App\Entity\Model;
    use App\Entity\Modification;
    use App\Entity\Series;
    use Doctrine\ORM\EntityRepository;
    use Symfony\Bridge\Doctrine\Form\Type\EntityType;
    use Symfony\Component\Form\AbstractType;
    use Symfony\Component\Form\Extension\Core\Type\ChoiceType;
    use Symfony\Component\Form\Extension\Core\Type\NumberType;
    use Symfony\Component\Form\Extension\Core\Type\TextType;
    use Symfony\Component\Form\FormBuilderInterface;
    use Symfony\Component\Form\FormEvent;
    use Symfony\Component\Form\FormEvents;
    use Symfony\Component\OptionsResolver\OptionsResolver;
    use Symfony\Component\Validator\Constraints as Assert;
    
    class ModificationType extends AbstractType
    {
    	public function buildForm(FormBuilderInterface $builder, array $options)
    	{
    		$builder
    			->add("name", TextType::class, [
    				"attr" => ["class" => "form-control"],
    				"label" => "Наименование",
    				"label_attr" => [
    					"class" => "col-sm-2 form-control-label"
    				],
    				"constraints" => [
    					new Assert\NotBlank(),
    					new Assert\Length(["max" => 255]),
    				],
    				"required" => true,
    			])
    			->add("alt_name", TextType::class, [
    				"attr" => ["class" => "form-control"],
    				"label" => "Альтернативное наименование",
    				"label_attr" => [
    					"class" => "col-sm-2 form-control-label"
    				],
    				"constraints" => [
    					new Assert\NotBlank(),
    					new Assert\Length(["max" => 255]),
    				],
    				"required" => true,
    			])
    			->add("seo_name", TextType::class, [
    				"attr" => ["class" => "form-control"],
    				"label" => "SEO Name",
    				"label_attr" => [
    					"class" => "col-sm-2 form-control-label"
    				],
    				"constraints" => [
    					new Assert\NotBlank(),
    					new Assert\Length(["max" => 100]),
    				],
    				"required" => true,
    			])
    			->add("power_kwt", NumberType::class, [
    				"attr" => ["class" => "form-control"],
    				"label" => "Мощность кВт",
    				"label_attr" => [
    					"class" => "col-sm-2 form-control-label"
    				],
    				"constraints" => [
    					new Assert\NotBlank(),
    					new Assert\Positive(),
    				],
    				"empty_data" => 0,
    				"required" => true,
    			])
    			->add("power_horse", NumberType::class, [
    				"attr" => ["class" => "form-control"],
    				"label" => "Мощность л.с.",
    				"label_attr" => [
    					"class" => "col-sm-2 form-control-label"
    				],
    				"constraints" => [
    					new Assert\NotBlank(),
    					new Assert\Positive()
    				],
    				"empty_data" => 0,
    				"required" => true,
    			])
    			->add("year_from", ChoiceType::class, [
    				"attr" => ["class" => "form-control"],
    				"label" => "Год начала производства",
    				"label_attr" => [
    					"class" => "col-sm-2 form-control-label"
    				],
    				"choices" => $this->getYears(),
    				"constraints" => [
    					new Assert\NotBlank(),
    					new Assert\Length(["max" => 4]),
    				],
    				"required" => true,
    			])
    			->add("year_to", ChoiceType::class, [
    				"attr" => ["class" => "form-control"],
    				"label" => "Год завершения производства",
    				"label_attr" => [
    					"class" => "col-sm-2 form-control-label"
    				],
    				"choices" => $this->getYears(),
    				"empty_data" => "н.в.",
    				"constraints" => [
    					new Assert\NotBlank(),
    					new Assert\Length(["max" => 4]),
    				],
    				"required" => false,
    			])
    			->add("brand", EntityType::class, [
    				"label" => "Бренд",
    				"label_attr" => [
    					"class" => "col-sm-2 form-control-label"
    				],
    				"class" => Brand::class,
    				"choice_label" => function ($brand) {
    					return $brand->getName();
    				},
    				"attr" => [
    					"class" => "form-control select"
    				],
    				"constraints" => [
    					new Assert\NotBlank()
    				]
    			])
                ->add("model", ChoiceType::class, [
    				"label" => "Модель",
    				"label_attr" => [
    					"class" => "col-sm-2 form-control-label"
    				],
    				"choices" => [],
    				"attr" => [
    					"class" => "form-control select"
    				],
    				"constraints" => [
    					new Assert\NotBlank()
    				],
    				"disabled" => true
    			])
                ->add("series", ChoiceType::class, [
    				"label" => "Серия",
    				"label_attr" => [
    					"class" => "col-sm-2 form-control-label"
    				],
    				"choices" => [],
    				"attr" => [
    					"class" => "form-control select"
    				],
    				"constraints" => [
    					new Assert\NotBlank()
    				],
    				"disabled" => true
    			]);
    
    		$builder->get("brand")->addEventListener(
    			FormEvents::POST_SUBMIT,
    			function (FormEvent $event) {
    				$form = $event->getForm();
    
    				$brand_id = $event->getData();
    
    				$form->getParent()->add("model", EntityType::class, [
    					"label_attr" => [
    						"class" => "col-sm-2 form-control-label"
    					],
    					"attr" => [
    						"class" => "form-control select"
    					],
    					"class" => Model::class,
    					"query_builder" => function (EntityRepository $er) use ($brand_id) {
    						return $er->createQueryBuilder("m")
    							->andWhere("m.brand = :brand_id")
    							->setParameter("brand_id", $brand_id)
    							->orderBy("m.name", "ASC");
    					},
    					"choice_label" => function ($entity)
    					{
    						return $entity->getName();
    					},
    					"constraints" => [
    						new Assert\NotBlank()
    					],
    					"required" => true
    				]);
    
    				$form->getParent()->add("series", EntityType::class, [
    					"label_attr" => [
    						"class" => "col-sm-2 form-control-label"
    					],
    					"attr" => [
    						"class" => "form-control select"
    					],
    					"class" => Series::class,
    					"query_builder" => function (EntityRepository $er) use ($brand_id) {
    						return $er->createQueryBuilder("s")
    							->andWhere("s.brand = :brand_id")
    							->setParameter("brand_id", $brand_id)
    							->orderBy("s.name", "ASC");
    					},
    					"choice_label" => function ($entity)
    					{
    						return $entity->getName();
    					},
    					"constraints" => [
    						new Assert\NotBlank()
    					],
    					"required" => true
    				]);
    			}
    		);
    	}
    
    	public function configureOptions(OptionsResolver $resolver)
    	{
    		$resolver->setDefaults([
    			"data_class" => Modification::class,
    		]);
    	}
    
    	/**
    	 * @return array
    	 */
    	private function getYears(): array
    	{
    		$years = [];
    		for ($i = date("Y"); $i >= 1955; $i--) {
    			$years[$i] = $i;
    		}
    		return $years;
    	}
    }

    Ответ написан
    Комментировать
  • Регистрация и логин юзера по API в symfony. Как делать?

    IgorPI
    @IgorPI
    Вы как то всё смешали в кучу, капчу, API, Google, Формы.

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

    Авторизация, ну тут стандартные механизмы Symfony, которые можно модернизировать и юзать JWT.

    Про капчу нечего сказать.
    Ответ написан
    Комментировать
  • Как в symfony secure проверить авторизованность пользователя?

    IgorPI
    @IgorPI
    В Symfony есть так называемый фаервол.

    В моём проекте это выглядит так:
    security:
      encoders:
        App\Entity\User:
          algorithm: auto
    
      # https://symfony.com/doc/current/security.html#where-do-users-come-from-user-providers
      providers:
        app_security_user_provider:
          id: App\Security\UserProvider
    
      #    registration:
      #        pattern:  ^/secure.registration
      #        stateless: true
      #        anonymous: true
    
    
      firewalls:
        dev:
          pattern: ^/(_(profiler|wdt)|css|images|js)/
          security: false
    
        authorization:
          pattern: ^/authorization
          security: false
    
        main:
          anonymous: ~
          stateless: true
          provider: app_security_user_provider
          guard:
            authenticators:
              - App\Security\Authenticator
    
            # activate different ways to authenticate
            # https://symfony.com/doc/current/security.html#firewalls-authentication
    
            # https://symfony.com/doc/current/security/impersonating_user.html
          # switch_user: true
    
      # Easy way to control access for large sections of your site
      # Note: Only the *first* access control that matches will be used
      access_control:
      # - { path: ^/profile, roles: ROLE_USER }


    Это глобальная защита всех маршрутов.
    Так же существует так называемая локальная защита чего-то, например каких то действий в экшене.
    // throw exception
      $this->denyAccessUnlessGranted(UserVoter::READ["read"], User::class, "Вам запрещено просматривать пользователей.");
    // or
    // return boolean
    $this->isGranted(UserVoter::READ["read"], User::class)
    Ответ написан
  • Через какой стэк лучше организовать работу с symfony на windows 10?

    IgorPI
    @IgorPI
    Docker For Windows
    Linux container
    Ответ написан
    Комментировать
  • Группы и разрешения, лучшая практика RBAC в symfony?

    IgorPI
    @IgorPI Автор вопроса
    Коллеги, решил поступить следующим образом.

    Иметь некий стек разрешений для чего-либо, не важно, главное что бы он был статический.
    Что я имею в виду когда говорю "статический стек"?

    Это обычные атрибуты, которые объявлены заранее
    Пример

    class UserVoter extends Voter implements IVoter
    {
    
        public const CREATE_USERS = 'create_users';
        public const EDIT_USERS = 'edit_users';
        public const DELETE_USERS = 'delete_users';
        public const VIEW_USERS = 'view_users';
        public const VIEW_CONTACTS = 'view_contacts';
        public const SET_PRIVILEGES_USERS = 'set_privileges_users';
    ...


    Далее проверка в избирателе
    ...
    $permissions = [];
            try {
                if (is_a($user->getGroup(), UserGroup::class)) {
                    $permissions = $user->getGroup()->getPermissions();
                }
    
                foreach ($permissions as $permission) {
                    if (in_array($permission, $this->getAttributes())) {
                        return true;
                    }
                }
    
                return false;
            }catch (Exception $exception) {
                return false;
            }
    ...


    Итог, все разрешения принадлежать определённой группе, а в группу уже входит тот или иной пользователь.
    Вот и все.
    Ответ написан
    Комментировать
  • Как вызвать объект сервиса и передать в параметрах объект какого-либо класса?

    IgorPI
    @IgorPI
    $objStatus = new Status($stage, $this->getUser());

    Это уже не сервис, а хрень какая-то.

    Сервисы прописываются в конфигурации сервисов и там пишутся инструкции для их запуска.
    Вам сюда
    Ответ написан
    Комментировать
  • Как обнулить или объединить миграции и оставить одну?

    IgorPI
    @IgorPI
    Есть такое понятие как накопительная миграция.
    Если я не ошибаюсь то в доктрине она уже есть.

    нужно удалить все миграции и выполнять специальную команду для создания файла накопительной миграции.

    А далее всё начинается заново.
    Ответ написан
  • Symfony, кто знает как замэппить значение из Request?

    IgorPI
    @IgorPI Автор вопроса
    Symfony 5

    parameters:
        lang: ru
        # default configuration for services in *this* file
        _defaults:
            autowire: true      # Automatically injects dependencies in your services.
            autoconfigure: true # Automatically registers your services as commands, event subscribers, etc.
            bind:
                $default_lang: '%lang%'
    
    services:
        app.service.lang:
            class: App\Service\Lang
            arguments: ['@request_stack', '@?']


    <?php
    
    
    namespace App\Service;
    
    use Symfony\Component\HttpFoundation\RequestStack;
    
    /**
     * Class Lang
     * @package App\Service
     */
    class Lang
    {
        /** @var string  */
        private $lang;
    
        /**
         * Lang constructor.
         * @param RequestStack $requestStack
         * @param $default_lang
         */
        public function __construct(RequestStack $requestStack, $default_lang)
        {
            $this->lang = $requestStack->getCurrentRequest()->get("lang", $default_lang);
        }
    
        /**
         * @return mixed|string
         */
        public function get()
        {
            return $this->lang;
        }
    }


    Контроллер
    public function myMethod(Lang $lang) 
    {
       $value = $lang->get()
    }
    Ответ написан
  • Symfony контент в базе данных на нескольких языках?

    IgorPI
    @IgorPI Автор вопроса
    Нашёл для себя решение.
    Не уверен в том, что это адекватная практика.
    Тем не менее, как мне показалось, это то что мне нужно.

    Описываю логику.

    <?php
    
    
    namespace App\Classes\Entity;
    
    /**
     * Class AbstractEntity
     * @package App\Classes\Entity
     */
    abstract class AbstractEntity
    {
        /** @var string  */
        protected $language = "ru";
    
        /**
         * @param string $language
         */
        public function setLanguage(string $language): void
        {
            $this->language = $language;
        }
    }


    <?php
    
    
    namespace App\Entity;
    
    
    use App\Classes\Entity\AbstractEntity;
    use Doctrine\Common\Collections\ArrayCollection;
    use Doctrine\ORM\Mapping\JoinColumn;
    use Doctrine\ORM\Mapping as ORM;
    use Symfony\Component\Validator\Constraints\NotNull;
    use Symfony\Component\Validator\Mapping\ClassMetadata;
    
    /**
     * @ORM\Entity(repositoryClass="App\Repository\CategoryRepository")
     * @ORM\Table(name="categories")
     */
    class Category extends AbstractEntity
    {
    
        /**
         * @ORM\Column(type="integer")
         * @ORM\Id
         * @ORM\GeneratedValue
         */
        private $id;
    
        /**
         * @ORM\Column(name="name", length=64)
         */
        private $name_ru;
    
        /**
         * @ORM\Column(length=64, nullable=true)
         */
        private $name_cn;
    
        /**
         * @ORM\Column(length=64, nullable=true)
         */
        private $name_en;
    
        /**
         * @return mixed
         */
        public function getId()
        {
            return $this->id;
        }
    
        /**
         * @param $name
         * @param string $lang
         */
        public function setName($name, $lang)
        {
            $this->{"name_$lang"} = $name;
        }
    
        /**
         * @return string
         */
        public function getName()
        {
            return $this->{"name_$this->language"};
        }
    
    }


    Слушатель, для того что бы можно было переключать свойство language, которые мы будем наследовать от AbstractEntity, своего рода контракт.

    <?php
    
    
    namespace App\Listeners;
    
    use App\Classes\Entity\AbstractEntity;
    use Doctrine\Persistence\Event\LifecycleEventArgs;
    use Symfony\Component\HttpFoundation\Request;
    use Symfony\Component\HttpFoundation\RequestStack;
    
    
    /**
     * Class LanguageEntityListener
     * @package App\Listeners
     */
    class LanguageEntityListener
    {
    
        /** @var string  */
        private $language = "ru";
        /** @var Request */
        private $request;
    
        /**
         * LanguageEntityListener constructor.
         * @param RequestStack $requestStack
         */
        public function __construct(RequestStack $requestStack)
        {
            $this->request = $requestStack->getCurrentRequest();
            $this->language = $this->request->get("lang", "ru");
        }
    
    
        public function postLoad(LifecycleEventArgs $event)
        {
            $entity = $event->getObject();
    
            if (is_a($entity, AbstractEntity::class)) {
                $entity->setLanguage($this->language);
            }
        }
    }


    services.yaml
    services:
       
        app.listeners.language_entity_listener:
            class: App\Listeners\LanguageEntityListener
            arguments: ['@request_stack']
            tags:
                - { name: doctrine.event_listener, event: postLoad, method: postLoad }


    5e264de82c98a283903729.gif

    Понятное дело, что это решение тяжело масштабируется.
    Ответ написан
  • Как разрешить CORS запрос для API?

    IgorPI
    @IgorPI
    Используйте Nginx, выкиньте Apache
    server {
        listen                          80;
    
        client_max_body_size            208M;
        access_log                      /var/log/nginx/secure.access.log;
        error_log                       /var/log/nginx/secure.error.log error;
    
        root                            /www/public;
    
        add_header				        "Access-Control-Allow-Origin" "*";
        add_header				        "Access-Control-Allow-Headers" "Origin, X-Requested-With, Content-Type, Accept, Authorization";
        add_header				        "Access-Control-Request-Methods" "GET, POST, OPTIONS";
    
        location / {
            try_files                   $uri $uri/ /secure.php?$query_string;
        }
    
        location ~ \.php$ {
            fastcgi_split_path_info     ^(.+\.php)(/.+)$;
            fastcgi_pass                app:9000;
            fastcgi_index               secure.php;
            include                     fastcgi_params;
            fastcgi_param               SCRIPT_FILENAME $document_root$fastcgi_script_name;
            fastcgi_param               PATH_INFO $fastcgi_path_info;
        }
    }
    Ответ написан
    4 комментария
  • Nested удаление дерева, как пересчитывать lft, rgt?

    IgorPI
    @IgorPI Автор вопроса
    Решение.
    В первом приближении, пересчитываю следующим образом.

    class ServiceRepository extends NestedTreeRepository
    {
    ...
    /**
         * @param Service $node
         * @return void
         * @throws ORMException
         * @throws OptimisticLockException
         */
        public function removeTree(Service $node)
        {
            $this->createQueryBuilder("s")
                ->delete()
                ->where("s.lft >= :lft")->setParameter("lft", $node->getLft())
                ->andWhere("s.rgt <= :rgt")->setParameter("rgt", $node->getRgt())
                ->andWhere("s.root = :root")->setParameter("root", $node->getRoot())
                ->getQuery()
                ->execute();
    
            $em = $this->getEntityManager();
            $em->beginTransaction();
            $repository = $em->getRepository("App:Service");
            $refreshLeftAndRight = function($root, $left) use ($repository, &$refreshLeftAndRight) {
                $right = $left + 1;
                $children = $repository->findBy(['parent' => $root,]);
    
                foreach ($children as $entity) {
                    $right = $refreshLeftAndRight($entity, $right);
                }
                $root->setlft($left);
                $root->setRgt($right);
                return $right + 1;
            };
    
            foreach ($repository->findBy(["parent" => null]) as $rootEntry) {
                $refreshLeftAndRight($rootEntry, 1);
            }
    
            $em->flush();
            $em->commit();
        }
    
    ...


    Кто в теме тот поймет что здесь, происходит.
    Конечно не идеально.
    В будущем стоит пересчитывать только те корневые ветки, в которых произошло удаление.
    В этом случае мы пересчитываем всё.
    Ответ написан
    Комментировать
  • Не получается сделать инъекцию через метод в Symfony 3.2, как правильно?

    IgorPI
    @IgorPI
    # JsonRequest
        App\Service\JsonRequest:
            calls:
                - [setRequest, ['@request_stack']]


    <?php
    
    
    namespace App\Service;
    
    
    use Exception;
    use Symfony\Component\HttpFoundation\RequestStack;
    
    /**
     * Class JsonRequest
     * @package App\Service
     */
    class JsonRequest
    {
    
        private $json_object;
    
        public function __construct()
        {
        }
    
        /**
         * @param string $key
         * @param bool $default
         * @return mixed
         */
        public function get(string $key, $default = null)
        {
            try {
                $properties = explode(".", $key);
                $buf = $this->json_object;
                foreach ($properties as $property) {
                    if (property_exists($buf, $property)) {
                        $buf = $buf->{$property};
                    } else {
                        return $default;
                    }
                }
                return $buf;
            } catch (Exception $e) {
                return $default;
            }
        }
    
        /**
         * @param string $key
         * @return bool
         */
        public function has(string $key)
        {
            return property_exists($this->json_object, $key);
        }
    
    
        /**
         * @param RequestStack $request
         */
        public function setRequest(RequestStack $request): void
        {
            $this->json_object = json_decode($request->getCurrentRequest()->getContent());
        }
    }
    Ответ написан
    Комментировать
  • Как избежать дублирования кода для горизонтально масштабируемого веб-приложения?

    IgorPI
    @IgorPI
    Свои пять копеек.
    Конкретно из текущей ситуации в реальном проекте, на данный момент, его значимость не особо высока, но уже за это получаю деньги.

    Так сказать инвестиция в развитие.

    На протяжении года занимаюсь проектированием собственного фреймворка, есть некие достижения.
    Сразу оговорюсь, я тоже использую сторонний код.
    В частности я использую Doctrine ORM и ряд других компонентов от Symfony и других.
    Например роутинг свой, загрузка сервисов своя...

    Очень много сил потратил, а заказчик средств.
    Фреймворк уже готов.

    При разработке столкнулся с рядом проблем, как раз с дублированием кода.
    Да, эта проблема частично решена.

    Например:

    Сущности, и репозитории сущностей - это общий код для всех окружений.
    Окружений может быть сколько угодно.

    На данный момент использую два окружения
    Admin
    Api

    admin.mysite.com
    api.mysite.com
    mysite.com - например фронт вообще на NUXT

    У окружений есть общий конфиг
    Общие языковые пакеты с ленивой загрузкой

    Вот так выглядят контроллеры в своих окружениях
    5d6ae80b31c6d516616002.png

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

    Точек входа всегда ровно количеству окружений, но загрузчик приложения один
    5d6aeab1c0789688262811.png
    Код файла запуска приложения
    <?php
    
    define('ENV', basename(__DIR__));
    require '../../engine/bootstrap.php';


    В контроллере у каждого окружения могут быть дублирующие методы

    Для себя выделил неформальное определение

    Функции, модули которые решают одну и туже задачу для всех окружений, выношу в отдельную папку
    Визуально это выглядит так

    5d6aed5540883788430736.png
    Ответ написан
    1 комментарий