Sunday, January 26, 2014

SonataAdminBundle: FatalErrorException: Error: Call to a member function add() on a non-object...

The problem usually occurs during updating a data record in the `admin/dashboard` of `SonataAdminBundle`.
The cause of the problem is that the add() method, defined in "vendor/sonata-project/doctrine-orm-admin-bundle/Sonata/DoctrineORMAdminBundle/Model/ModelManager.php line 560" received a two dimensional array object instead of a simple array.
Let's take a look at the `User.php`:
//src/Enstb/Bundle/VisplatBundle/Entity/User.php
public function getRoles()
    {
        return $this->roles->toArray();
    }
The `getRoles` method is automatically called when updating a user. As you can see, it wraps the roles object with an array, this is required for authentication system of `Symfony`.
Well, what we can do? One of the possible solutions is making a new name for roles, in our case is `rolesCollection`, to let us configure a different return type.
//src/Enstb/Bundle/VisplatBundle/Administrator/UserAdmin.php
protected function configureFormFields(FormMapper $formMapper)
    {
        $formMapper
            //...
            ->add('rolesCollection','entity',array(
                'class' => 'EnstbVisplatBundle:Role',
                'property'=>'name',
                'expanded' => true,
                'compound' => true,
                'multiple' => true,
                'by_reference' => false
            ));
    }
Then, the add,remove,get, and set are required:
//src/Enstb/Bundle/VisplatBundle/Entity/User.php
 public function addRolesCollection($role){
        $this->addRole($role);
    }
public function removeRolesCollection($role){
        $this->removeRole($role);
    }
public function getRolesCollection()
    {
        return $this->roles;
    }
public function setRolesCollection($roles)
    {
        if (count($roles) > 0) {
            foreach ($roles as $role) {
                $this->addRole($role);
            }
        }
        return $this;
    }
Finally, DONT forget to verify an existing roles before adding it in order to prevent duplicating roles added into a database.
//src/Enstb/Bundle/VisplatBundle/Entity/User.php
public function addRole(\Enstb\Bundle\VisplatBundle\Entity\Role $role)
    {
        if(!in_array($role,$this->getRoles())){
            // Link each role with the user
            $role->addUser($this);
            $this->roles->add($role);
        }

        return $this;
    }