Symfony 2.8 - ChoiceType изисква низ, но влизането изисква ролите да бъдат масив

Информация: Направих търсене преди да публикувам и изпробвах предложените решения, като всички те ме накараха да се върна към проблем a или b, както е описано по-долу. Така че, въпреки че това може да изглежда като дубликат, никой от другите отговори досега не ми помогна да разреша този проблем.

Освен това замених истинското име на пакета с MyApp за тази публикация.

Проблем: Опитвам се да накарам приложението Symfony да работи. Следвах документите на Symfony 2.8, за да създам база данни и таблица и процесът на влизане работи добре. След като добавих формуляр за създаване на нови потребители, попаднах на грешки с ChoiceType и моята страница за вход.

По-конкретно, когато задам getRoles() на return array($this->roles); в Entity/User.php, влизането работи добре, но получавам следните грешки във формуляра за създаване на потребител.

GET /admin/users с multiple=>false (Посетете страницата)

Notice: Array to string conversion
500 Internal Server Error - ContextErrorException
Stack Trace

    in vendor/symfony/symfony/src/Symfony/Component/Form/ChoiceList/ArrayChoiceList.php at line 79  -
                if (null === $value && $this->castableToString($choices)) {
                    $value = function ($choice) {
                        return false === $choice ? '0' : (string) $choice;
                    };
                }
    at ErrorHandler ->handleError ('8', 'Array to string conversion', '/home/dev/MyApp/vendor/symfony/symfony/src/Symfony/Component/Form/ChoiceList/ArrayChoiceList.php', '79', array('choice' => array(null)))
    in vendor/symfony/symfony/src/Symfony/Component/Form/ChoiceList/ArrayChoiceList.php at line 79  +
    at ArrayChoiceList ->Symfony\Component\Form\ChoiceList\{closure} (array(null))
    at call_user_func (object(Closure), array(null))
    in vendor/symfony/symfony/src/Symfony/Component/Form/ChoiceList/ArrayChoiceList.php at line 164  +
    at ArrayChoiceList ->getValuesForChoices (array(array(null)))
    in vendor/symfony/symfony/src/Symfony/Component/Form/Extension/Core/DataTransformer/ChoiceToValueTransformer.php at line 32  +
    at ChoiceToValueTransformer ->transform (array(null))
    in vendor/symfony/symfony/src/Symfony/Component/Form/Form.php at line 1156  +
    at Form ->normToView (array(null))
    in vendor/symfony/symfony/src/Symfony/Component/Form/Form.php at line 351  +
    at Form ->setData (array(null))
    in vendor/symfony/symfony/src/Symfony/Component/Form/Extension/Core/DataMapper/PropertyPathMapper.php at line 49  +
    at PropertyPathMapper ->mapDataToForms (object(User), object(RecursiveIteratorIterator))
    in vendor/symfony/symfony/src/Symfony/Component/Form/Form.php at line 384  +
    at Form ->setData (object(User))
    in vendor/symfony/symfony/src/Symfony/Component/Form/Form.php at line 488  +
    at Form ->initialize ()
    in vendor/symfony/symfony/src/Symfony/Component/Form/FormBuilder.php at line 226  +
    at FormBuilder ->getForm ()
    in vendor/symfony/symfony/src/Symfony/Component/Form/FormFactory.php at line 33  +
    at FormFactory ->create ('MyAppBundle\Form\UserType', object(User), array())
    in vendor/symfony/symfony/src/Symfony/Bundle/FrameworkBundle/Controller/Controller.php at line 282  +
    at Controller ->createForm ('MyAppBundle\Form\UserType', object(User))
    in src/MyAppBundle/Controller/AdminController.php at line 214  +
    at AdminController ->adminUsersAction (object(Request))
    in vendor/symfony/symfony/src/Symfony/Component/HttpKernel/HttpKernel.php at line 135  +
    at HttpKernel ->handleRaw (object(Request), '1')
    in vendor/symfony/symfony/src/Symfony/Component/HttpKernel/HttpKernel.php at line 57  +
    at HttpKernel ->handle (object(Request), '1', true)
    in vendor/symfony/symfony/src/Symfony/Component/HttpKernel/DependencyInjection/ContainerAwareHttpKernel.php at line 67  +
    at ContainerAwareHttpKernel ->handle (object(Request), '1', true)
    in vendor/symfony/symfony/src/Symfony/Component/HttpKernel/Kernel.php at line 183  +
    at Kernel ->handle (object(Request))
    in web/app_dev.php at line 30  +
    at require ('/home/dev/MyApp/web/app_dev.php')
    in vendor/symfony/symfony/src/Symfony/Bundle/FrameworkBundle/Resources/config/router_dev.php at line 40  +

Опитах се да задам multiple=>true, както е предложено в някои отговори (въпреки че не искам множествени избори). Това разрешава грешката при рендиране, но същото се появява при изпращане на формуляр.

Публикувайте в /admin/users с multiple=>true (Изпратете потребителски формуляр)

Notice: Array to string conversion
500 Internal Server Error - ContextErrorException
Stack Trace

    in vendor/doctrine/dbal/lib/Doctrine/DBAL/Driver/PDOStatement.php at line 67  -
            public function bindValue($param, $value, $type = \PDO::PARAM_STR)
            {
                try {
                    return parent::bindValue($param, $value, $type);
                } catch (\PDOException $exception) {
                    throw new PDOException($exception);
                }
    at ErrorHandler ->handleError ('8', 'Array to string conversion', '/home/dev/MyApp/vendor/doctrine/dbal/lib/Doctrine/DBAL/Driver/PDOStatement.php', '67', array('param' => '5', 'value' => array('ROLE_USER'), 'type' => '2'))
    at PDOStatement ->bindValue ('5', array('ROLE_USER'), '2')
    in vendor/doctrine/dbal/lib/Doctrine/DBAL/Driver/PDOStatement.php at line 67  +
    at PDOStatement ->bindValue ('5', array('ROLE_USER'), '2')
    in vendor/doctrine/dbal/lib/Doctrine/DBAL/Statement.php at line 120  +
    at Statement ->bindValue ('5', array('ROLE_USER'), object(StringType))
    in vendor/doctrine/orm/lib/Doctrine/ORM/Persisters/Entity/BasicEntityPersister.php at line 277  +
    at BasicEntityPersister ->executeInserts ()
    in vendor/doctrine/orm/lib/Doctrine/ORM/UnitOfWork.php at line 1014  +
    at UnitOfWork ->executeInserts (object(ClassMetadata))
    in vendor/doctrine/orm/lib/Doctrine/ORM/UnitOfWork.php at line 378  +
    at UnitOfWork ->commit (null)
    in vendor/doctrine/orm/lib/Doctrine/ORM/EntityManager.php at line 356  +
    at EntityManager ->flush ()
    in src/MyAppBundle/Controller/AdminController.php at line 228  +
    at AdminController ->adminUsersAction (object(Request))
    in vendor/symfony/symfony/src/Symfony/Component/HttpKernel/HttpKernel.php at line 135  +
    at HttpKernel ->handleRaw (object(Request), '1')
    in vendor/symfony/symfony/src/Symfony/Component/HttpKernel/HttpKernel.php at line 57  +
    at HttpKernel ->handle (object(Request), '1', true)
    in vendor/symfony/symfony/src/Symfony/Component/HttpKernel/DependencyInjection/ContainerAwareHttpKernel.php at line 67  +
    at ContainerAwareHttpKernel ->handle (object(Request), '1', true)
    in vendor/symfony/symfony/src/Symfony/Component/HttpKernel/Kernel.php at line 183  +
    at Kernel ->handle (object(Request))
    in web/app_dev.php at line 30  +
    at require ('/home/dev/MyApp/web/app_dev.php')
    in vendor/symfony/symfony/src/Symfony/Bundle/FrameworkBundle/Resources/config/router_dev.php at line 40  +

Промяната на getRoles() на return $this->roles; решава както проблема с изобразяването, така и проблема с публикацията, но причинява обратния проблем във функцията за влизане.

Заявка за влизане до /login

Type error: Argument 4 passed to Symfony\Component\Security\Core\Authentication\Token\UsernamePasswordToken::__construct() must be of the type array, string given, called in /home/dev/MyApp/vendor/symfony/security/Core/Authentication/Provider/UserAuthenticationProvider.php on line 96
500 Internal Server Error - FatalThrowableError
Stack Trace

    in vendor/symfony/security/Core/Authentication/Token/UsernamePasswordToken.php at line 36  -
            *
            * @throws \InvalidArgumentException
            */
            public function __construct($user, $credentials, $providerKey, array $roles = array())
            {
                parent::__construct($roles);
    at UsernamePasswordToken ->__construct (object(User), 'test', 'main', 'ROLE_USER')
    in vendor/symfony/security/Core/Authentication/Provider/UserAuthenticationProvider.php at line 96  +
    at UserAuthenticationProvider ->authenticate (object(UsernamePasswordToken))
    in app/cache/dev/classes.php at line 2749  +
    at AuthenticationProviderManager ->authenticate (object(UsernamePasswordToken))
    in vendor/symfony/security/Http/Firewall/UsernamePasswordFormAuthenticationListener.php at line 93  +
    at UsernamePasswordFormAuthenticationListener ->attemptAuthentication (object(Request))
    in vendor/symfony/security/Http/Firewall/AbstractAuthenticationListener.php at line 146  +
    at AbstractAuthenticationListener ->handle (object(GetResponseEvent))
    in app/cache/dev/classes.php at line 2664  +
    at Firewall ->onKernelRequest (object(GetResponseEvent), 'kernel.request', object(TraceableEventDispatcher))
    at call_user_func (array(object(Firewall), 'onKernelRequest'), object(GetResponseEvent), 'kernel.request', object(TraceableEventDispatcher))
    in vendor/symfony/symfony/src/Symfony/Component/EventDispatcher/Debug/WrappedListener.php at line 61  +
    at WrappedListener ->__invoke (object(GetResponseEvent), 'kernel.request', object(ContainerAwareEventDispatcher))
    at call_user_func (object(WrappedListener), object(GetResponseEvent), 'kernel.request', object(ContainerAwareEventDispatcher))
    in app/cache/dev/classes.php at line 1888  +
    at EventDispatcher ->doDispatch (array(object(WrappedListener), object(WrappedListener), object(WrappedListener), object(WrappedListener), object(WrappedListener), object(WrappedListener), object(WrappedListener), object(WrappedListener), object(WrappedListener), object(WrappedListener)), 'kernel.request', object(GetResponseEvent))
    in app/cache/dev/classes.php at line 1803  +
    at EventDispatcher ->dispatch ('kernel.request', object(GetResponseEvent))
    in vendor/symfony/symfony/src/Symfony/Component/EventDispatcher/Debug/TraceableEventDispatcher.php at line 133  +
    at TraceableEventDispatcher ->dispatch ('kernel.request', object(GetResponseEvent))
    in vendor/symfony/symfony/src/Symfony/Component/HttpKernel/HttpKernel.php at line 116  +
    at HttpKernel ->handleRaw (object(Request), '1')
    in vendor/symfony/symfony/src/Symfony/Component/HttpKernel/HttpKernel.php at line 57  +
    at HttpKernel ->handle (object(Request), '1', true)
    in vendor/symfony/symfony/src/Symfony/Component/HttpKernel/DependencyInjection/ContainerAwareHttpKernel.php at line 67  +
    at ContainerAwareHttpKernel ->handle (object(Request), '1', true)
    in vendor/symfony/symfony/src/Symfony/Component/HttpKernel/Kernel.php at line 183  +
    at Kernel ->handle (object(Request))
    in web/app_dev.php at line 30  +
    at require ('/home/dev/MyApp/web/app_dev.php')
    in vendor/symfony/symfony/src/Symfony/Bundle/FrameworkBundle/Resources/config/router_dev.php at line 40  +

Как да се справя с избор на роли във формуляра на потребителя и в данните за влизане, така че и двете да са доволни от формата на $roles?

Изходен код за справка.

Form/UserType.php

[...]
public function buildForm(FormBuilderInterface $builder, array $options)
{
    $builder
        ->add('email', EmailType::class)
        ->add('username', TextType::class)
        ->add('roles', ChoiceType::class, array(
            'multiple' => true,
            'expanded' => false,
            'choices'  => array(
                'User' => 'ROLE_USER',
                'Admin' => 'ROLE_ADMIN'),
            'choices_as_values' => true,
        ))
        ->add('plainPassword', RepeatedType::class, array(
            'type' => PasswordType::class,
            'first_options'  => array('label' => 'Password'),
            'second_options' => array('label' => 'Repeat Password'),
        )
    );
}

public function configureOptions(OptionsResolver $resolver)
{
    $resolver->setDefaults(array(
        'data_class' => 'MyAppBundle\Entity\User',
    ));
}
[...]

Entity/User.php

[...]
/**
* @ORM\Table(name="app_users")
* @ORM\Entity(repositoryClass="MyAppBundle\Entity\UserRepository")
*/
class User implements UserInterface, \Serializable
{
    /**
    * @ORM\Column(type="integer")
    * @ORM\Id
    * @ORM\GeneratedValue(strategy="AUTO")
    */
    private $id;

    /**
    * @ORM\Column(type="string", length=25, unique=true)
    */
    private $username;

    /**
    * @ORM\Column(type="string", length=64)
    */
    private $password;

    /**
    * @ORM\Column(type="string", length=60, unique=true)
    */
    private $email;

    /**
    * @ORM\Column(name="is_active", type="boolean")
    */
    private $isActive;

    /**
    * @ORM\Column(name="roles", type="string")
    */
    private $roles;

    /**
    * @Assert\NotBlank()
    * @Assert\Length(max=4096)
    */
    private $plainPassword;


    public function __construct()
    {
        // default for new users
        $this->isActive = true;
        //$this->roles = "ROLE_USER";
    }
    [...]
    public function getRoles()
    {
        //return $this->roles;
        return array($this->roles);
    }

    /** @see \Serializable::serialize() */
    public function serialize()
    {
        return serialize(array(
            $this->id,
            $this->username,
            $this->password,
        ));
    }

    /** @see \Serializable::unserialize() */
    public function unserialize($serialized)
    {
        list (
            $this->id,
            $this->username,
            $this->password,
        ) = unserialize($serialized);
    }
    [...]
    /**
    * Set Roles
    *
    * @param string $roles
    *
    * @return User
    */
    public function setRoles($roles)
    {
        $this->roles = $roles;
        return $this;
    }
    [...]

Контролер/AdminController.php

/**
* @Route("/admin/users")
*/
public function adminUsersAction(Request $request)
{
    // 
    $repository = $this->getDoctrine()
        ->getRepository('MyAppBundle:User');

    $users = $repository->findAll();

    // USER FORM
    // 1) build the form
    $user = new User();
    $form = $this->createForm(UserType::class, $user);

    // 2) handle the submit (will only happen on POST)
    $form->handleRequest($request);
    if ($form->isSubmitted() && $form->isValid()) {

        // 3) Encode the password (you could also do this via Doctrine listener)
        $password = $this->get('security.password_encoder')
            ->encodePassword($user, $user->getPlainPassword());
        $user->setPassword($password);

        // 4) save the User!
        $em = $this->getDoctrine()->getManager();
        $em->persist($user);
        $em->flush();

        return $this->redirectToRoute('dashboard');
    }

    return $this->render(
        'MyAppBundle:Admin:users.html.twig',
        array(
            "form"=>$form->createView(),
            "users"=>$users
            )
    );
}

Resources/views/Admin/users.html.twig

<h2>Add Users</h2>
{{ form_start(form) }}
    {{ form_row(form.username) }}
    {{ form_row(form.email) }}
    {{ form_row(form.roles) }}
    {{ form_row(form.plainPassword.first) }}
    {{ form_row(form.plainPassword.second) }}
    <button type="submit">Add User</button>
{{ form_end(form) }}

person mohrphium    schedule 30.04.2018    source източник


Отговори (1)


Изглежда, че вашите роли се записват като низ:

/**
* @ORM\Column(name="roles", type="string")
*/
private $roles;

Това ще съхранява литералния низ ROLE_USER вместо сериализиран масив a:1:{i:0;s:9:"ROLE_USER";}. Опитвам:

/**
* @ORM\Column(name="roles", type="array")
*/
private $roles;

Установителят трябва да вземе масив от ChoiceType с поддръжка на множество избори:

/**
* @param array $roles
* @return User
*/
public function setRoles($roles)
{
    $this->roles = $roles;
    return $this;
}

Getter трябва да върне свойството като масив, без да се налага да го превръща в масив ръчно:

public function getRoles()
{
    return $this->roles;
}
person OK sure    schedule 30.04.2018
comment
Благодаря, направих го, но все още получавам грешката в страницата за вход, която argument 4 [...] must be of type array. Полето roles в базата данни вече съдържа s:9:"ROLE_USER"; вместо ROLE_USER. - person mohrphium; 30.04.2018
comment
хм добре, трябва да го превръща обратно в масив, но може и да греша. Ще актуализирам отговора с поправка за това. - person OK sure; 30.04.2018
comment
unserialize($this->roles) води до Notice: unserialize(): Error at offset 0 of 9 bytes при влизане. - person mohrphium; 30.04.2018
comment
Това изглежда по-скоро като тип колона/проблем с кодиране - какъв е типът колона, в който съхранявате роли? Ще обясни защо гетерът връща низ, ако не може да се десериализира. Вероятно трябва да се съхранява като тип BLOB - person OK sure; 30.04.2018
comment
Но също така трябва да съхранява масив, а не само сериализирания низ: a:1:{i:0;s:9:"ROLE_USER";}, s:9:"ROLE_USER"; - person OK sure; 30.04.2018
comment
setRoles също трябва да приема масив, а не низ - person OK sure; 30.04.2018
comment
Това последното ми го оправи. DB ролите съдържат a:1:{i:0;s:9:"ROLE_USER";}, setRoles сега е $this->roles = array($roles); return $this; и getRoles се връща към return $this->roles;. Благодаря много! - person mohrphium; 30.04.2018