3

I am creating an Asset Management Web App with Symfony 3.3.

I have two entities, Asset and User.

User:

namespace AppBundle\Entity;

use Doctrine\ORM\Mapping as ORM;
use FOS\UserBundle\Model\User as BaseUser;

/**
 * User
 *
 * @ORM\Table(name="user")
 * @ORM\Entity(repositoryClass="AppBundle\Repository\UserRepository")
 */
class User extends BaseUser
{
    /**
     * @ORM\ManyToMany(targetEntity="Asset", mappedBy="users")
     */
    private $assets;

    /**
     * Add asset
     *
     * @param \AppBundle\Entity\Asset $asset
     *
     * @return User
     */
    public function addAsset(\AppBundle\Entity\Asset $asset)
    {
        $this->assets[] = $asset;

        return $this;
    }

    /**
     * Remove asset
     *
     * @param \AppBundle\Entity\Asset $asset
     */
    public function removeAsset(\AppBundle\Entity\Asset $asset)
    {
        $this->assets->removeElement($asset);
    }

    /**
     * Get assets
     *
     * @return \Doctrine\Common\Collections\Collection
     */
    public function getAssets()
    {
        return $this->assets;
    }
}

Asset:

namespace AppBundle\Entity;

use Doctrine\ORM\Mapping as ORM;

/**
 * Asset
 *
 * @ORM\Table(name="asset")
 * @ORM\Entity(repositoryClass="AppBundle\Repository\AssetRepository")
 */
class Asset
{
    /**
     * @ORM\ManyToMany(targetEntity="User", inversedBy="assets")
     * @ORM\JoinTable(name="asset_user")
     */
    private $users;

    /**
     * Add user
     *
     * @param \AppBundle\Entity\User $user
     *
     * @return Asset
     */
    public function addUser(\AppBundle\Entity\User $user)
    {
        $this->users[] = $user;
        return $this;
    }

    /**
     * Remove user
     *
     * @param \AppBundle\Entity\User $user
     */
    public function removeUser(\AppBundle\Entity\User $user)
    {
        $this->users->removeElement($user);
    }

    /**
     * Get users
     *
     * @return \Doctrine\Common\Collections\Collection
     */
    public function getUsers()
    {
        return $this->users;
    }
}

They have a Many to Many relationship. If I add Users in an Asset, everything is fine, but if I add assets in a user. It won't persist.

Adding my Form types:

AssetUserAssignType:

namespace AppBundle\Form;

use Symfony\Component\Form\AbstractType;
use Symfony\Component\Form\Extension\Core\Type\CollectionType;
use Symfony\Bridge\Doctrine\Form\Type\EntityType;
use Symfony\Component\Form\FormBuilderInterface;
use Symfony\Component\OptionsResolver\OptionsResolver;


class AssetUserAssignType extends AbstractType
{
    /**
     * {@inheritdoc}
     */
    public function buildForm(FormBuilderInterface $builder, array $options)
    {
        $builder->add('users', CollectionType::class, array(
                    'label' => 'Custodians',
                    'entry_type' => EntityType::class,
                    'entry_options' => array(   'class'=>'AppBundle:User',
                                                'label'=>false,
                                                'choice_label'=>'employee_number',
                                                ),
                    'allow_delete' =>true,
                    'allow_add' => true,
                    'allow_delete' => true,
                    'prototype' => true,
                    'by_reference' => false,
                    'attr'=> array('class'=>'user_collection'),
                ));
    }

    /**
     * {@inheritdoc}
     */
    public function configureOptions(OptionsResolver $resolver)
    {
        $resolver->setDefaults(array(
            'data_class' => 'AppBundle\Entity\Asset'
        ));
    }

    /**
     * {@inheritdoc}
     */
    public function getBlockPrefix()
    {
        return 'appbundle_assetassign';
    }


}

UserAssetAssignType:

namespace AppBundle\Form;

use Symfony\Component\Form\AbstractType;
use Symfony\Component\Form\Extension\Core\Type\CollectionType;
use Symfony\Bridge\Doctrine\Form\Type\EntityType;
use Symfony\Component\Form\FormBuilderInterface;
use Symfony\Component\OptionsResolver\OptionsResolver;


class UserAssetAssignType extends AbstractType
{
    /**
     * {@inheritdoc}
     */
    public function buildForm(FormBuilderInterface $builder, array $options)
    {
        $builder->add('assets', CollectionType::class, array(
                    'label' => 'Assets',
                    'entry_type' => EntityType::class,
                    'entry_options' => array(   'class'=>'AppBundle:Asset',
                                                'label'=>false,
                                                'choice_label'=>'code',
                                                ),
                    'allow_delete' =>true,
                    'allow_add' => true,
                    'allow_delete' => true,
                    'prototype' => true,
                    'by_reference' => false,
                    'attr'=> array('class'=>'asset_collection'),
                ));
    }

    /**
     * {@inheritdoc}
     */
    public function configureOptions(OptionsResolver $resolver)
    {
        $resolver->setDefaults(array(
            'data_class' => 'AppBundle\Entity\User'
        ));
    }

    /**
     * {@inheritdoc}
     */
    public function getBlockPrefix()
    {
        return 'appbundle_assetassign';
    }


}

If you need more code, I'll paste them here also, but I think this is enough.

Antoine Galluet
  • 320
  • 4
  • 14
Akira Hora
  • 458
  • 1
  • 6
  • 18

2 Answers2

4

This is because in a Doctrine relation, the owning side (the entity which define column name and inversedBy property) always take priority on the inverse side (the other one).

As mentioned in documentation here : http://symfony.com/doc/current/doctrine/associations.html#setting-information-from-the-inverse-side a good practice is to update default setters (including add and remove functions) to make sure two entities are always synchronized.

In example :

public function addAsset(\AppBundle\Entity\Asset $asset)
{
    $this->assets[] = $asset;
    $asset->addUser($this);

    return $this;
}

public function removeAsset(\AppBundle\Entity\Asset $asset)
{
    $this->assets[] = $asset;
    $asset->removeUser($this);

    return $this;
}

As you already have set the by_reference to false in your forms (required to make sure setters are called by Form component), it will do the trick.

Hope it will help.

j-guyon
  • 1,822
  • 1
  • 19
  • 31
  • This did the trick for me. Although, I am baffled that there is only one side to this Many to Many instead of 2 sides and that I haven't even now fully understood the Many to many relationship especially in Doctrine. – Akira Hora Mar 28 '18 at 07:20
0

You have not implemented the database correctly, If the relationship is many to many, There should be an associate entity, So among User and Asset there must be an entity called UserAsset. Then user has many UserAsset and If you get one UserAsset, there is only one user for that record. One Asset can be in Many UserAsset records and If you get one UserAsset record, only one asset should associate with that record.

Viraj Amarasinghe
  • 911
  • 10
  • 20
  • 2
    Not to sound so lazy, but doesn't the Symfony Framework do that for you thru the annotation configuration you setup? – Akira Hora Mar 28 '18 at 06:06
  • This is done automatically behind the scenes by the doctrine annotations. No additional entities are needed. – GotBatteries Jan 15 '20 at 09:32