When you are using Symfony PHP Framework, the most common way to interact with Databases is using Doctrine. And if you are using a relational Database like MySql or MariaDb, you will need to handle Table Associations. One of the most common ones is the Many to Many association, that means "Many of these, can have Many of those".

You may refer to this posts:

 

Lets assume we have two entities:

  • Animals
  • Food

These entities are very simple, only contain the primary key and a column with the name. The many to many association requires a Join Table where the primary keys of each table will be associated.

With these association each Animal can have many different Food so does each Food can have many different Animals.

Steps to create this Many to Many Association.

Step 1. Open a ssh terminal and go to the root of your Symfony project.

 

Step 2.  Assuming you already have the symfony_db schema with the tables on it:

php bin/console doctrine:mapping:import "App\Entity" annotation --path=src/Entity

 

UPDATE on May-2023: I just found today that this doctrine:mapping:import that was deprecated a couple of years ago, may not longer work. Please proceed with caution and review carefully the generated Entity files.

 

That's it! the new Entities and the association are generated automatically by Symfony.

The Animals Entity will look like:

<?php

namespace App\Entity;

use Doctrine\Common\Collections\ArrayCollection;
use Doctrine\Common\Collections\Collection;
use Doctrine\ORM\Mapping as ORM;

/**
 * Animals
 *
 * @ORM\Table(name="animals")
 * @ORM\Entity
 */
class Animals
{
    /**
     * @var int
     *
     * @ORM\Column(name="id", type="integer", nullable=false)
     * @ORM\Id
     * @ORM\GeneratedValue(strategy="IDENTITY")
     */
    private $id;

    /**
     * @var string
     *
     * @ORM\Column(name="animal_name", type="string", length=45, nullable=false)
     */
    private $animalName;

    /**
     * @var Collection
     *
     * @ORM\ManyToMany(targetEntity="Food", inversedBy="animals")
     * @ORM\JoinTable(name="animals_has_food",
     *   joinColumns={
     *     @ORM\JoinColumn(name="animals_id", referencedColumnName="id")
     *   },
     *   inverseJoinColumns={
     *     @ORM\JoinColumn(name="food_id", referencedColumnName="id")
     *   }
     * )
     */
    private $food = array();

    /**
     * Constructor
     */
    public function __construct()
    {
        $this->food = new ArrayCollection();
    }

}

And the Food one:

<?php

namespace App\Entity;

use Doctrine\Common\Collections\ArrayCollection;
use Doctrine\Common\Collections\Collection;
use Doctrine\ORM\Mapping as ORM;

/**
 * Food
 *
 * @ORM\Table(name="food")
 * @ORM\Entity
 */
class Food
{
    /**
     * @var int
     *
     * @ORM\Column(name="id", type="integer", nullable=false)
     * @ORM\Id
     * @ORM\GeneratedValue(strategy="IDENTITY")
     */
    private $id;

    /**
     * @var string
     *
     * @ORM\Column(name="food_name", type="string", length=45, nullable=false)
     */
    private $foodName;

    /**
     * @var Collection
     *
     * @ORM\ManyToMany(targetEntity="Animals", mappedBy="food")
     */
    private $animals = array();

    /**
     * Constructor
     */
    public function __construct()
    {
        $this->animals = new ArrayCollection();
    }

}

 

In this case the Metadata for the joining table is kept in the Animals Entity.

From here you can go and generate your Getters and Setters:

$ php bin/console make:entity --regenerate

Now the Animals Entity will look like:

<?php

namespace App\Entity;

use Doctrine\Common\Collections\ArrayCollection;
use Doctrine\Common\Collections\Collection;
use Doctrine\ORM\Mapping as ORM;

/**
 * Animals
 *
 * @ORM\Table(name="animals")
 * @ORM\Entity
 */
class Animals
{
    /**
     * @var int
     *
     * @ORM\Column(name="id", type="integer", nullable=false)
     * @ORM\Id
     * @ORM\GeneratedValue(strategy="IDENTITY")
     */
    private $id;

    /**
     * @var string
     *
     * @ORM\Column(name="animal_name", type="string", length=45, nullable=false)
     */
    private $animalName;

    /**
     * @var Collection
     *
     * @ORM\ManyToMany(targetEntity="Food", inversedBy="animals")
     * @ORM\JoinTable(name="animals_has_food",
     *   joinColumns={
     *     @ORM\JoinColumn(name="animals_id", referencedColumnName="id")
     *   },
     *   inverseJoinColumns={
     *     @ORM\JoinColumn(name="food_id", referencedColumnName="id")
     *   }
     * )
     */
    private $food = array();

    /**
     * Constructor
     */
    public function __construct()
    {
        $this->food = new ArrayCollection();
    }
    
    /**
     * @return int|null
     */
    public function getId(): ?int
    {
        return $this->id;
    }
    
    /**
     * @return string|null
     */
    public function getAnimalName(): ?string
    {
        return $this->animalName;
    }
    
    /**
     * @param string $animalName
     * @return $this
     */
    public function setAnimalName(string $animalName): self
    {
        $this->animalName = $animalName;

        return $this;
    }

    /**
     * @return Collection<int, Food>
     */
    public function getFood(): Collection
    {
        return $this->food;
    }
    
    /**
     * @param Food $food
     * @return $this
     */
    public function addFood(Food $food): self
    {
        if (!$this->food->contains($food)) {
            $this->food->add($food);
        }

        return $this;
    }
    
    /**
     * @param Food $food
     * @return $this
     */
    public function removeFood(Food $food): self
    {
        $this->food->removeElement($food);

        return $this;
    }

}

 

 

 

 

 

 

 

When you are using Symfony PHP Framework, the most common way to interact with Databases is using Doctrine. And if you are using a relational Database like Oracle, MSSql Server, MySql or MariaDb, you will need to handle Table Associations. One to Many means "One of these can have many of those"

You may refer to this posts:

 

In the Article How to Implement Many to Many Relation in Symfony with Doctrine in easy steps I explained how to easily make the code to handle the Many to Many relation between Animals and Food Entitites.

Now we want to place Many Animals in One Zoo. For doing that we need a One to Many relation.

Modern PHP Enterprise Systems Applications require the ability to notify the users about things like a process done, a report available, etc. through standard popular communication channels like email, text messages, slack etc. Follow this recipe if you want to send user notifications to their Slack channels:

Prerequisites:

Step 1. Install the "symfony/slack-notifier" package using composer:

composer require symfony/slack-notifier

Step 2. Create a Slack bot by following the instructions on Slack's website: https://api.slack.com/bot-users#create-a-bot

Step 3. Obtain the Slack bot token.

Step 4. Create a Slack client instance in your Symfony application. Add the following lines to your Symfony application's configuration file (e.g. config/services.yaml):

Data validation is one of the most important parts of all Software Applications. If you are using PHP Symfony Framework, you have available a very powerful yet easy to use validator* component that validates objects against constraints. There is a large set of Constraints ready to use and even you can define your own custom constraints when using this component.

If you are implementing an Application as a Rest Api I recommend you first  take a look at Using DTOs in PHP Symfony

Using DTOs and the Param Converter in Symfony allows you to do Data validation in a very easy way, and very fast to implement, even if you have a large set of Api Endpoints consuming many different payloads, because you are validating all payloads in the same function.

Make a DTO to represent the structure of the payload. This is the first thing to validate, that the payload in the Api Request, most likely a json string, has exactly the same structure as defined in the DTO. I do this using JMS Serializer

...
        $json = $request->getContent();
        $class = $configuration->getClass();

        //Try to deserialize the request dto
        try {
            $dto = $this->serializer->deserialize($json, $class, AppConstants::API_SERIALIZE_FORMAT);
        } catch (\Throwable $e) {
            $jsonMessage = $this->customValidationServiceProvider->getJsonErrorMessage(json_last_error());
            throw new JsonException($e->getMessage() . ' more info:' . $jsonMessage);
        }
...

In here the JMS Serializer will try to map the json string in the body of the request (payload) into its corresponding DTO class.  In this case

AppConstants::API_SERIALIZE_FORMAT

 is 'json' format.

If the de-serialization is successful, then we make the validator component to validate the DTO:

...
        //If there are validation errors, throw validation exception
        $errors = $this->validator->validate($dto);
        if (!empty($errors)) {
            foreach ($errors as $error) {
                $this->validationErrors[] = array('field' => $error->getPropertyPath(), ' message' => $error->getMessage());
            }
            if (!empty($this->validationErrors)) {
                throw new PreconditionFailedException($this->serializer->serialize($this->validationErrors, AppConstants::API_SERIALIZE_FORMAT));
            }
        }
...

 

 The message set for each error thrown is the one you declared in each constraint in the DTO:

...
    /**
     * @var string valid Email
     * @Assert\NotNull
     * @Assert\NotBlank(message="Email Address required")
     * @Assert\Email(message="Invalid Email Address")
     * @JMS\Type("string")
     * @JMS\SerializedName("email")
     */
    public $email;
...

 Most types of validation constraints* are already available including one that allows you to define a regular expression with your own rules:

...
    /**
     * @var string Role Name
     * @Assert\NotNull
     * @Assert\NotBlank(message="Role name cannot be blank")
     * @Assert\Regex(pattern="/^[\w]+$/", message="Role names can only contain letters, digits and underscore")
     * @JMS\Type("string")
     * @JMS\SerializedName("name")
     */
    public $name;
...

 

No matter if the DTO has a complex structure and several nested DTOs... This validation works exactly the same way as shown above saving you tons of hours on implement data validation.

 More articles on this topic:

  1. How to serialize nested Dtos in PHP Symfony 6+
  2. How to deserialize and validate nested Dtos in Symfony 6+

 

* Read more on the validator component

 

Related Videos