0

languages table languages_content table languages_content table relationship

CREATE TABLE `languages` (
  `id` int(4) UNSIGNED NOT NULL AUTO_INCREMENT,
  `name` varchar(50) NOT NULL,
  `status` INT(1) NOT NULL DEFAULT '0',
  `created_at` datetime NOT NULL,
  PRIMARY KEY (`id`)
) ENGINE=InnoDB AUTO_INCREMENT=1 DEFAULT CHARSET=utf8;

CREATE TABLE `languages_content` (
  `language_id` int(4) UNSIGNED NOT NULL,
  `language` int(4) UNSIGNED NOT NULL,  
  `label` varchar(50) NOT NULL,
  PRIMARY KEY (`language_id`,`language`),
  KEY `language` (`language`),
  CONSTRAINT `languages_content_idfk_1` FOREIGN KEY (`language_id`) REFERENCES `languages` (`id`) ON DELETE CASCADE ON UPDATE CASCADE,
  CONSTRAINT `languages_content_idfk_2` FOREIGN KEY (`language`) REFERENCES `languages` (`id`) ON DELETE CASCADE ON UPDATE CASCADE
) ENGINE=InnoDB DEFAULT CHARSET=utf8;

Is posible to do that? I already try many-to-many relationship, but this remove the 'label' field from the 'languages_content' table.

/**
 * Languages
 *
 * @ORM\Table(name="languages")
 * @ORM\Entity
 */
class Languages
{
    /**
     * @var int
     *
     * @ORM\Column(name="id", type="integer", nullable=false, options={"unsigned"=true})
     * @ORM\Id
     * @ORM\GeneratedValue(strategy="IDENTITY")
     */
    private $id;

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

    /**
     * @var int
     *
     * @ORM\Column(name="status", type="integer", nullable=false)
     */
    private $status = '0';

    /**
     * @var \DateTime
     *
     * @ORM\Column(name="created_at", type="datetime", nullable=false)
     */
    private $createdAt;

    /**
     * @var \Doctrine\Common\Collections\Collection 
     *
     * @ORM\ManyToMany(targetEntity="Languages", inversedBy="language")
     * @ORM\JoinTable(name="languages_content",
     *   joinColumns={
     *     @ORM\JoinColumn(name="language_id", referencedColumnName="id")
     *   },
     *   inverseJoinColumns={
     *     @ORM\JoinColumn(name="language", referencedColumnName="id")
     *   }
     * )
     */
    private $language = array();

I also try with many-to-one relationship, but this option add an autoincremential field (id) that doesn't exist in languages_content table, as you can see the languages_content is used to relate the translations with the languages ​​in the languages ​​table.

#[ORM\Entity(repositoryClass: LanguagesRepository::class)]
class Languages
{
    #[ORM\Id]
    #[ORM\GeneratedValue]
    #[ORM\Column]
    private ?int $id = null;

    #[ORM\Column(length: 50, nullable: false)] 
    private ?string $name = null;

    #[ORM\Column(nullable: true)] 
    private ?int $status = 0; 

    #[ORM\Column(nullable: true)]
    private ?\DateTimeImmutable $createdAt = null;

    #[ORM\OneToMany(mappedBy: 'languages', targetEntity: LanguagesContent::class)]
    private Collection $language;

Thanks in advance for your help or sugestions.

  • Could you explain us why you need to reference two times the same entity? I'm not sure Doctrine allow that. I think you have to make an intermediate entity that will make the reference. A kind of https://symfonycasts.com/screencast/collections/many-to-many-extra-fields – Skuti Nov 06 '22 at 13:37
  • If you look closely, the languages ​​table contains the supported languages ​​and the languages_content table stores the translations of each language. I use the double relation to eliminate the translations of a language if it is eliminated. – Ludwig Schmidt Nov 07 '22 at 14:08
  • For Entity translations you should use https://github.com/doctrine-extensions/DoctrineExtensions/blob/main/doc/translatable.md. But anyway, I'm not sure about your last sentence: If you make a link between languages and languages_content (unique link) you could SELECT the correct translation based on selected language, no? – Skuti Nov 08 '22 at 03:00

1 Answers1

0

Doctrine's ManyToMany does not support additional fields so you need to make the join table (i.e. languages_content) yourself

To get rid of the autoincremented ID simply remove the $id and its getter from the entity then add a #[ORM\Id] annotation to both ManyToOnes, if you make a migration this will create a composite primary key like in your SQL example.

Here is an example from the doctrine documentation https://www.doctrine-project.org/projects/doctrine-orm/en/2.13/tutorials/composite-primary-keys.html#use-case-3-join-table-with-metadata

hugo schweitzer
  • 190
  • 1
  • 7
  • Hi Hugo, thanks for your help. I was able to reproduce the SQL statement partially. The part I haven't been able to reproduce is the `ON UPDATE` constraint. What is the equivalent in doctrine? I have seen that `ON DELETE` is: `[ORM\JoinColumn(referencedColumnName: 'id', onDelete: 'CASCADE')]`. thanks in advance. – Ludwig Schmidt Nov 13 '22 at 08:45
  • @LudwigSchmidt AFAIK Doctrine cannot generate `ON UPDATE CASCADE`. If this database is only going to be used by your Symfony app then you might not need database level options because Doctrine can probably take care of it for you, see https://stackoverflow.com/a/16213195/9284519 However if you absolutely need this `ON UPDATE CASCADE` then you could always edit the migration to add it yourself and it should work – hugo schweitzer Nov 14 '22 at 11:14