Symfony 4 / Sonata: Create a OneToMany (1N) Administration

We are going to create a 1N administration interface, with two entities. The first one, One, and the second, Many, and set up an admin panel for the One table, which can affect several elements of the Many table. To spice things up, we'll add some additional parameters, such as timestamp fields for sync dates with an SI, and primary fields that are not called ID and are not auto-incremented.

Sélection_086

In our case, we have a sync with an SI that requires a timestamp field.
Just create a datetime type field
Add the option annotation {'default': 'CURRENT_TIMESTAMP'}
The following annotation will create a Timestamps field with the default set to CURRENT_TIMESTAMP.

* @ORM\Column(type="datetime", nullable=false, options={"default": "CURRENT_TIMESTAMP"})
* @ORM\Version


To name the table correctly and create indexes, just add the following annotation at the head of the class

use Doctrine\ORM\Mapping as ORM;
use Doctrine\ORM\Mapping\Entity;
use Doctrine\ORM\Mapping\Table;
use Doctrine\ORM\Mapping\Id;
use Doctrine\ORM\Mapping\Column;
use Doctrine\ORM\Mapping\GeneratedValue;
use Doctrine\ORM\Mapping\Index;

/**
* @ORM\Entity(repositoryClass="App\Repository\WcoconRepository")
* @Table(name="table_name",indexes={@Index(name="PRIMARY", columns={"champs1", "champs2"})})
*/


To use a field other than the default 'id', just add the following annotation:

/**
* @ORM\Column(type="integer")
* @ORM\Id()
*/
private $numero_dossier;


Once the relationship is made, it is necessary to specify the id that will be used in the mapping. Otherwise, there will be an error like:
Column name id referenced for relation from App\Entity\Many towards App\Entity\One does not exist.

So in the 'Many' entity, you need to add the relationship with the id of the 'One' entity

/**
* @ORM\ManyToOne(targetEntity="App\Entity\One", inversedBy="Ones")
* @ORM\JoinColumn(nullable=false, name="many_id", referencedColumnName="one_id")
*/
private $many_id;


Last point of importance; To prevent the object from having a name like 'App\Entity\One:sdfsdfgsdgmlkpoufsdlkjfsdg' during editing, it is better to override the __toString() method, to display the object's name.
In the entity, just include a method:

public function __toString()
{
       return "Nom object ".$this->getName();
}


Once done, just proceed with the migration:

php bin/console make:migration
php bin/console doctrine:migrations:migrate


If needed, to regenerate the repository from the available entities just launch the following command:

php bin/console make:entity --regenerate

The entities will not be modified.

Sélection_092


To test the relationships between entities, you can run the following command:

php bin/console doctrine:schema:validate
Sélection_091


At this stage, all we have left to do is set up the management interfaces in the admin, and we can start developing the front-end authentication.

The first step of the admin interface is to create the class in src/Admin/TableAdmin.php

namespace App\Admin;

use Sonata\AdminBundle\Admin\AbstractAdmin;
use Sonata\AdminBundle\Datagrid\ListMapper;
use Sonata\AdminBundle\Datagrid\DatagridMapper;
use Sonata\AdminBundle\Form\FormMapper;
use Symfony\Component\Form\Extension\Core\Type\TextType;

use Symfony\Bridge\Doctrine\Form\Type\EntityType;
use Sonata\AdminBundle\Form\Type\ModelAutocompleteType;
use Sonata\AdminBundle\Form\Type\ModelType;
use Sonata\AdminBundle\Form\Type\ModelListType;
use Sonata\Form\Type\CollectionType;

class OneAdmin extends AbstractAdmin
{
	protected function configureFormFields(FormMapper $formMapper)
	{

		$formMapper->tab('General');

		$formMapper->with('Parametres', ['class' => 'col-md-4']);

		$formMapper->add('name', TextType::class, ['required' => false, 'label'=>'Site internet','attr' => ['placeholder' => '']]);

		$formMapper->end()

		$formMapper->with('Parametres', ['class' => 'col-md-8']);

		$formMapper->add('nomduchampsArrayCollection', EntityType::class, [
		'class' => 'App\Entity\Many',
		'choice_label' => 'nom_du_champs_autre_table',
		'label' => 'champs liste',
		'multiple' => true,
		//'expanded' => true,
		]);

		$formMapper->end()
		$formMapper->end('General');

	}

	protected function configureDatagridFilters(DatagridMapper $datagridMapper)
	{
		$datagridMapper->add('name');

	}

	protected function configureListFields(ListMapper $listMapper)
	{
		$listMapper->addIdentifier('name', null, ['label' => 'Nom']);

	}
	public function prePersist($object)
	{
		$this->preUpdate($object);
	}

	public function preUpdate($object)
	{

		$mapping=$object->getManys()->getMapping();
		$currentManys=$this->createQuery()->getQueryBuilder()->getQuery()->getEntityManager()->getRepository($mapping["targetEntity"])
		->findBy([$mapping["mappedBy"]=>$this->id($object)]);
		$object->setMany($object->getManys(),$currentManys);

	}
	public function setMany($collections,$currentManys)
	{
		foreach ($currentCollections as $w) 
		{
			if(!$collections->contains($w)){
			$w->setCollectionID(null);
			}

		}
		$this->collections= new ArrayCollection();

		foreach ($collections as $collection) {
			$this->addCollections($collections);
		}

	}

}

In the entity One.php, we need to add our new method to handle modifications

    public function setMany($manys,$current)
    {
        foreach ($current as $w) {
            if(!$manys->contains($w)){
                $w->setManyField(null);
            }
           
        }
        $this->manys = new ArrayCollection();
        
        foreach ($manys as $m) {
            $this->addMany($m);
        }

    }

Then register the service in config/services.yaml

    admin.MaTable:
        class: App\Admin\MaTableAdmin
        arguments: [~, App\Entity\MaTable, ~]
        tags:
            - { name: sonata.admin, manager_type: orm, label: "Table MaTable" }
        public: true