Symfony - 用户即使在名称更改时也会被注销


Symfony - user gets logged out even on a name change

我面临着一个非常奇怪的情况,一旦我更改名称,具有ROLE_ADMIN的用户就会注销,即使我不更改任何内容并按保存按钮,它也会被注销。 如果我直接在数据库中更改用户的角色以ROLE_USER相同的代码工作正常,并且用户未注销。

以下是负责配置文件更新的控制器

    /**
     * @Route("/profile", name="profile")
     */
    public function profileAction(Request $request)
    {
        $em = $this->getDoctrine()->getManager();
        $userInfo = $this->getUser();
        //create the form object
        $profileForm = $this->createForm(UserType::class, $userInfo);
        $profileForm->handleRequest($request);

        //check data validity
        if($profileForm->isValid()){
            $em->persist($userInfo);
            $em->flush();
            $this->get('session')->getFlashBag()->add(
                'success',
                'Your profile information has been updated'
            );
            return $this->render('AppBundle:admin/user:admin-edit.html.twig',array(
                'edit_form' => $profileForm->createView()
            ));
        }

        // render registration form
        return $this->render('AppBundle:admin/user:admin-edit.html.twig',array(
            'edit_form' => $profileForm->createView()
        ));
    }
}

这是我的security.yml

security:
    encoders:
        # Our user class and the algorithm we'll use to encode passwords
        # http://symfony.com/doc/current/book/security.html#encoding-the-user-s-password
        AppBundle'Entity'User: bcrypt
    providers:
        # Simple example of loading users via Doctrine
        # To load users from somewhere else: http://symfony.com/doc/current/cookbook/security/custom_provider.html
        database_users:
            entity: { class: AppBundle:User, property: username }
    firewalls:
        # disables authentication for assets and the profiler, adapt it according to your needs
        dev:
            pattern: ^/(_(profiler|wdt)|css|images|js)/
            security: false
        main:
            http_basic: ~
            anonymous: ~
            logout: ~
            guard:
                authenticators:
                    - app.form_login_authenticator
                    - app.facebook_authenticator
                # by default, use the start() function from FormLoginAuthenticator
                entry_point: app.form_login_authenticator
    access_control:
        - { path: ^/login, roles: IS_AUTHENTICATED_ANONYMOUSLY }
        - { path: ^/admin/, roles: ROLE_ADMIN }
        - { path: ^/user, roles: ROLE_USER }

更新 1

这是我的UserType

namespace AppBundle'Form;
use AppBundle'Form'EventListener'AddDepartmentDegreeCourseFieldSubscriber;
use AppBundle'Form'EventListener'AddProfileFieldSubscriber;
use Doctrine'ORM'EntityManager;
use Symfony'Component'Form'AbstractType;
use Symfony'Component'Form'FormBuilderInterface;
use Symfony'Component'OptionsResolver'OptionsResolver;

class UserType extends AbstractType
{
    private $addDepartmentDegreeCourseFieldSubscriber;
    private $addProfileFieldSubscriver;
    function __construct(AddDepartmentDegreeCourseFieldSubscriber $subscriber, AddProfileFieldSubscriber $fields)
    {
        $this->addDepartmentDegreeCourseFieldSubscriber = $subscriber;
        $this->addProfileFieldSubscriver = $fields;
    }
    /**
     * @param FormBuilderInterface $builder
     * @param array $options
     */
    public function buildForm(FormBuilderInterface $builder, array $options)
    {
        $builder->addEventSubscriber($this->addProfileFieldSubscriver);
        $builder->addEventSubscriber($this->addDepartmentDegreeCourseFieldSubscriber);
    }
    /**
     * @param OptionsResolver $resolver
     */
    public function configureOptions(OptionsResolver $resolver)
    {
        $resolver->setDefaults(array(
            'data_class' => 'AppBundle'Entity'User'
        ));
    }
}

这是我的AddProfileFieldSubscriber

namespace AppBundle'Form'EventListener;

use Symfony'Bridge'Doctrine'Form'Type'EntityType;
use Symfony'Component'Form'Extension'Core'Type'ChoiceType;
use Symfony'Component'Form'Extension'Core'Type'EmailType;
use Symfony'Component'Form'Extension'Core'Type'FileType;
use Symfony'Component'Form'Extension'Core'Type'PasswordType;
use Symfony'Component'Form'Extension'Core'Type'TextType;
use Symfony'Component'Form'FormEvent;
use Symfony'Component'Form'FormEvents;
use Symfony'Component'EventDispatcher'EventSubscriberInterface;
use Symfony'Component'Security'Core'Authentication'Token'Storage'TokenStorageInterface;
use Symfony'Component'Security'Core'Authorization'AuthorizationChecker;
use Symfony'Component'Validator'Constraints'NotBlank;

class AddProfileFieldSubscriber implements EventSubscriberInterface
{
    protected $authorizationChecker;

    function __construct(AuthorizationChecker $authorizationChecker)
    {
        $this->authorizationChecker = $authorizationChecker;
    }
    public static function getSubscribedEvents()
    {
        // Tells the dispatcher that you want to listen on the form.pre_set_data
        // event and that the preSetData method should be called.
        return array(FormEvents::PRE_SET_DATA => 'preSetData');
    }
    public function preSetData(FormEvent $event)
    {

        $user = $event->getData();
        $form = $event->getForm();


        if($user){
            $form->add('firstName', TextType::class);
            $form->add('lastName', TextType::class);
            $form->add('password', PasswordType::class, array(
                'mapped' => false
            ));
            $form->add('profileImage', FileType::class, array(
                'data_class' => null
            ));
            if (in_array("ROLE_USER", $user->getRoles())) {
                $form->add('contactNumber', TextType::class);
                $form->add('gender', ChoiceType::class, array(
                    'choices' => array(
                        'Male' => 'm',
                        'Female' => 'f'
                    ),
                    'placeholder' => 'provide_gender'
                ));
                $form->add('college', EntityType::class, array(
                        'placeholder' => 'provide_college',
                        'class' => 'AppBundle'Entity'College')
                );
                $form->add('interest', EntityType::class, array(
                        'class' => 'AppBundle'Entity'Interest',
                        'multiple' => true,
                        'expanded' => false,
                        'by_reference' => false,
                    )
                );
            }
            if($this->authorizationChecker->isGranted('ROLE_ADMIN')  ) {
                $form->add('isActive', ChoiceType::class, array(
                    'choices' => array(
                        'account_active' => '1',
                        'account_inactive' => '0'
                    ),
                    'placeholder' => 'provide_status'
                ));
            }
        }
        //if the selected user has role_user only then display the following fields in edit profile view
       else {
            $form->add('username', EmailType::class);
            $form->add('password', PasswordType::class, array(
                'constraints' => array(new NotBlank(array(
                        'message' => 'user.password.not_blank'
                    )
                ),),
            ));
        }
    }
}

这是AddDepartmentDegreeCourseFieldSubscriber

namespace AppBundle'Form'EventListener;

use AppBundle'Entity'Degree;
use AppBundle'Entity'Department;
use Doctrine'ORM'EntityManager;
use Symfony'Bridge'Doctrine'Form'Type'EntityType;
use Symfony'Component'Form'Extension'Core'Type'TextType;
use Symfony'Component'Form'FormEvent;
use Symfony'Component'Form'FormEvents;
use Symfony'Component'EventDispatcher'EventSubscriberInterface;
use Symfony'Component'Form'FormInterface;
class AddDepartmentDegreeCourseFieldSubscriber implements EventSubscriberInterface
{
    protected $em;

    function __construct(EntityManager $em)
    {
        $this->em = $em;
    }
    public static function getSubscribedEvents()
    {
        // Tells the dispatcher that you want to listen on the form.pre_set_data
        // event and that the preSetData method should be called.
        return array(
            FormEvents::PRE_SET_DATA => 'onPreSetData',
            FormEvents::PRE_SUBMIT => 'onPreSubmit'
        );
    }
    protected function addElements(FormInterface $form, Department $departments = null, Degree $degree = null)
    {
        // Add the department element
        $form->add('department', EntityType::class, array(
                'data' => $departments,
                'placeholder' => 'provide_department',
                'class' => 'AppBundle'Entity'Department')
        );
        // Degree are empty, unless we actually supplied a department
        $degree = array();
        if ($departments) {
            // Fetch the courses from specified degree
            $repo = $this->em->getRepository('AppBundle:Degree');
            $degree = $repo->findByDepartment($departments, array('name' => 'asc'));
        }

        // Add the province element
        $form->add('degree', EntityType::class, array(
                'placeholder' => 'provide_degree',
                'class' => 'AppBundle'Entity'Degree',
                'choices' => $degree)
        );
        // Cities are empty, unless we actually supplied a province
        $courses = array();
        if ($degree) {
            // Fetch the cities from specified province
            $repo = $this->em->getRepository('AppBundle:Course');
            $courses = $repo->findByDegree($degree, array('name' => 'asc'));
        }
        // Add the Course element
        $form->add('course', EntityType::class, array(
            'class' => 'AppBundle'Entity'Course',
            'choices' => $courses,
        ));
    }
    function onPreSubmit(FormEvent $event) {
        $form = $event->getForm();
        $data = $event->getData();
        if (isset($data['degree'])) {
            // Note that the data is not yet hydrated into the entity.
            $degree = $this->em->getRepository('AppBundle:Degree')->find($data['degree']);
            $department = $this->em->getRepository('AppBundle:Department')->find($data['department']);
            $this->addElements($form, $department, $degree);
        }
    }
    function onPreSetData(FormEvent $event) {
        //echo "before submit";die;
        $user = $event->getData();
        $form = $event->getForm();

        if($user){
            //if the selected user has role_user only then display the following fields in edit profile view
            if (in_array("ROLE_USER", $user->getRoles())) {
                $degree = ( !empty($user) && !empty($user->getCourse())) ? $user->getCourse()->getDegree() : null;
                $departments = ( !empty($user) && !empty($user->getCourse())) ? $user->getCourse()->getDegree()->getDepartment() : null;
                $this->addElements($form, $departments, $degree);
            }
        }
    }
}

在这一点上,我真的不知道可能导致这种情况的原因是什么,我将非常感谢这里的任何帮助......

您提供的内容有很多错误。 其中的元素可能会导致您看到的行为。

安全规则

在security.yml中,你有这样一行:

- { path: ^/admin/, roles: ROLE_ADMIN }

这意味着如果他们访问此模式,任何没有ROLE_ADMIN的使用都将被射回登录屏幕。

同时,在控制器中,您始终将用户引导回基于管理员的路由:

/*
 * @Route("admin/profile", name="admin_profile")
 */

这意味着,无论他们做什么,他们总是被发送到管理模式。 这意味着如果他们改变角色,他们将被防火墙踢出。

控制器

在控制器中,您将用户实体绑定到窗体类型,很好。 但是你编码的方式意味着你不明白它是如何工作的。

当你调用handleRequest时,Symfony从表单中提取数据(如果它已经提交),并将其与你传递给它的实体"合并"。 这意味着您不必在对象上调用任何 setter,因为所有操作都已经为您完成

用户实体

User实体上应该有一个PlainPassword字段和一个Password字段。 表单仅映射到PlainPassword字段。然后,在控制器中,获取PlainPassword值,对其进行编码,将其设置为实体的Password字段,并确保清除PlainPassword值(不想存储该值)。如果你已经在自定义用户实体上实现了UserInterface(你应该有),你应该有一个名为 eraseCredentials 的方法,这就是它的用途。下面是一个示例。

这样做的结果是,当您检查某些内容(例如新密码)时,您只需执行以下操作:

if($profileForm->isValid()){
        // $userInfo is populated by handleRequest
        if ($userInfo->getPlainPassword()) {
          // do your encoding/erasing credentials here
        }
        // ...
}

我还建议,你写一个合适的UserManager类来处理大部分这些事情。 它集中了事情,使调试更容易。 这是一个例子

如果您使用我在示例中编写的内容,这也意味着当您想要更新用户信息时,您只需调用$userManager->updateUser($user),它就会为您完成所有驴工作。