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;
    }

Saturday, December 28, 2013

SonataAdminBundle forms One to Many or Many to Many does not persist

This problem occurs due to the `inverse-side` object is not defined in the `owning-side`. In order to solve this, we have to manually specify it in the `owning-side` class. Suppose that we have a user class and a role class that are `inverse-side` and `owning-side` respectively. Here is the example of defining the `inverse-side` object.
//Acme/Bundle/DemeBundle/Entity\User
public function addRole(\Enstb\Bundle\VisplatBundle\Entity\Role $role)
{
    // Link each role with the user 
    // (This is important for SonataAdminBundle!!)
    $role->addUser($this);
    $this->roles->add($role);
    return $this;
}
public function removeRole(\Enstb\Bundle\VisplatBundle\Entity\Role $role)
{
    // Link each role with the user
    $role->removeUser($this);
    $this->roles->removeElement($role);
}
public function getRoles()
{
    return $this->roles;
}
The most important part is `$role->addUser($this)`. It sends the object of `Users` class to be added in the `Roles` class. This makes the `SonataAdminBundle` to realize that which object should be linked together and persisted to a database.
And here is an example of `addUser` method in the `Roles` class:
//Acme/Bundle/DemeBundle/Entity\Role
public function addUser(\Enstb\Bundle\VisplatBundle\Entity\Users $user)
{
    $this->Users->add($user);
    return $this;
} 
Note: if you are not sure what is owning-side and inverse-side, read more about What is the difference between inversedBy and mappedBy?.

SonataAdminBundle - Making fields for two entities (Many to many relation)

If you always got the "Catchable Fatal Error: Object of class Acme\Bundle\DemoBundle\Entity\Roles could not be converted to string.", this tutorial will provide you a way to deal with it.
First of all, the error is returned because the `__toString()` class is not overridden. So, let make it! 
    //Acme\Bundle\DemoBundle\Entity\Roles
    function __toString()
    {
        return $this->getName();
    }
In order to make a checkbox of the roles available, we should specify true values for  `expanded`, `compound`, and `multiple`
   //Acme\Bundle\DemoBundle\Admin\UsersAdmin
    protected function configureFormFields(FormMapper $formMapper)
    {
        $formMapper
            ->add('Roles','sonata_type_model',array(
                  'expanded' => true,
                  'compound' => true,
                  'multiple' => true))

    ;    }