I'm a new user of Symfony, and I want to make a relation between 2 entities.
I have an entity named 'Suggest' and the other one 'Car'
Each suggest can have one or many cars, and a car can be used to 0 or multiple suggest
I want a new field named 'Cars' ( field for the name of cars) in my 'Suggest' entity, but I don't want new field in 'Car' entity
How can I do it ?
I tried to make a relation ManyToOne on my Suggest entity but it create me an id field of cars, and I can only put one.
with a ManyToMany relation, it is possible to get only a new field on my 'suggest' entity ?
Assuming you're using the make:entity command: if you add a cars property to your Suggest entity, and make it a ManyToMany relation to Car, it does not add an extra property to the Car class (you can say no to that):
Car:
<?php
namespace App\Entity;
use App\Repository\CarRepository;
use Doctrine\ORM\Mapping as ORM;
/**
* #ORM\Entity(repositoryClass=CarRepository::class)
*/
class Car
{
/**
* #ORM\Id
* #ORM\GeneratedValue
* #ORM\Column(type="integer")
*/
private $id;
public function getId(): ?int
{
return $this->id;
}
}
And Suggest:
<?php
namespace App\Entity;
use App\Repository\SuggestRepository;
use Doctrine\Common\Collections\ArrayCollection;
use Doctrine\Common\Collections\Collection;
use Doctrine\ORM\Mapping as ORM;
/**
* #ORM\Entity(repositoryClass=SuggestRepository::class)
*/
class Suggest
{
/**
* #ORM\Id
* #ORM\GeneratedValue
* #ORM\Column(type="integer")
*/
private $id;
/**
* #ORM\ManyToMany(targetEntity=Car::class)
*/
private $cars;
public function __construct()
{
$this->cars = new ArrayCollection();
}
public function getId(): ?int
{
return $this->id;
}
/**
* #return Collection<int, Car>
*/
public function getCars(): Collection
{
return $this->cars;
}
public function addCar(Car $car): self
{
if (!$this->cars->contains($car)) {
$this->cars[] = $car;
}
return $this;
}
public function removeCar(Car $car): self
{
$this->cars->removeElement($car);
return $this;
}
}
Related
I have a Product entity with a ManyToOne relationship with Category.
use Doctrine\ORM\Mapping as ORM;
class Product
{
/**
* #ORM\ManyToOne(targetEntity="App\Entity\Category")
* #ORM\JoinColumn(nullable=true)
*/
private $category;
}
class Category
{
use BlameableTrait;
...
}
The Category entity implements a trait, with properties to record when a record is created, updated, etc.
use App\Entity\User\User;
use Gedmo\Mapping\Annotation as Gedmo;
trait BlameableTrait
{
/**
* #Gedmo\Blameable(on="create")
* #ORM\ManyToOne(targetEntity="App\Entity\User\User")
* #ORM\JoinColumn(nullable=true, onDelete="SET NULL")
* #var User
*/
private $createdBy;
/**
* #var User|null
*
* #Gedmo\Blameable(on="update")
* #ORM\ManyToOne(targetEntity="App\Entity\User\User")
* #ORM\JoinColumn(nullable=true, onDelete="SET NULL")
*/
private $updatedBy;
public function getCreatedBy(): ?User
{
return $this->createdBy;
}
public function setCreatedBy(?User $createdBy): self
{
$this->createdBy = $createdBy;
return $this;
}
public function setUpdatedBy(?User $updatedBy): self
{
$this->updatedBy = $updatedBy;
return $this;
}
public function getUpdatedBy(): ?User
{
return $this->updatedBy;
}
}
When I try to update the product entity I have an error indicating that an error has occurred while trying to update the Category entity, but I have not indicated that it should be updated.
This only happens to me in the production environment and randomly, one time it works, another time it doesn't.
Locally, debugging the symfony profiler, for the same curl, only one update is done on the Product entity, which is fine.
I don't understand where Symfony or Doctrine try to update the Category entity.
Both the production and local environments run on the same Docker image.
Symfony 5.3
Doctrine bundle ^2.4 ORM ^2.9
MariaDB 10.6.4
This has been rather difficult to diagnose especially as I was dealing with some complicated layered code. If I was sure, I would have filed a bug with Doctrine, but I want to first make sure I'm not making some glaring mistake or such, in implementation.
I have painstakingly tried to reduce the code to a simplified working example. On my test database tables, there are additional columns that are not referred to in the demo code.
// src/Entity/Record.php
declare(strict_types = 1);
namespace App\Entity;
use App\Repository\RecordRepository;
use Doctrine\ORM\Mapping as ORM;
/**
* #ORM\Entity(repositoryClass=RecordRepository::class)
* #ORM\Table(name="Records")
*/
class Record {
/**
* #ORM\Id
* #ORM\GeneratedValue
* #ORM\Column(type="integer")
*/
protected $id;
public function getId(): ?int {
return $this->id;
}
/**
* #ORM\OneToOne(targetEntity="App\Entity\RecordStatus", mappedBy="record", cascade={"persist"})
* #ORM\JoinColumn(name="Id")
*/
private ?RecordStatus $recordStatus = NULL;
public function getRecordStatus(): ?RecordStatus {
return $this->recordStatus;
}
public function setRecordStatus(RecordStatus $value): void {
$value->setRecord($this);
$this->recordStatus = $value;
}
}
// src/Entity/RecordStatus.php
declare(strict_types = 1);
namespace App\Entity;
use App\Repository\RecordStatusRepository;
use Doctrine\ORM\Mapping as ORM;
/**
* #ORM\Entity(repositoryClass=RecordStatusRepository::class)
* #ORM\Table(name="Record_Statuses")
*/
class RecordStatus {
/**
* #ORM\Id
* #ORM\GeneratedValue
* #ORM\Column(type="integer")
*/
protected $id;
public function getId(): ?int {
return $this->id;
}
/**
* #ORM\OneToOne(targetEntity="App\Entity\Record", inversedBy="record")
* #ORM\JoinColumn(name="RecordId")
*/
private Record $record;
public function getRecord(): Record {
return $this->record;
}
public function setRecord(Record $value): void {
$this->record = $value;
}
}
// src/Controller/DefaultController.php
declare(strict_types = 1);
namespace App\Controller;
use App\Entity\Record;
use App\Entity\RecordStatus;
use Symfony\Bundle\FrameworkBundle\Controller\AbstractController;
use Symfony\Component\HttpFoundation\JsonResponse;
use Symfony\Component\HttpFoundation\Response;
use Symfony\Component\Routing\Annotation\Route;
class DefaultController extends AbstractController {
/**
* #Route("/")
*/
public function home(): Response {
$doctrine = $this->getDoctrine();
$entityManager = $doctrine->getManager();
$recordRepository = $doctrine->getRepository(Record::class);
$recordStatus = new RecordStatus;
$item = $recordRepository->find(1);
$item->setRecordStatus($recordStatus);
$entityManager->flush();
return new JsonResponse(['id' => $item->getId()]);
}
}
When this route ("/") is triggered, the record is updated and the old Id is displayed. But after the update, checking the database shows that the record now has a newly Auto-Generated Id, that comes after the last previous record in the table. Note that this is on UPDATE and not INSERT.
My current partial workaround is to load the currently mapped relation if one exists and update it instead of using new RecordStatus, but it should also be possible to use a new Related instance if required (especially if there was no Relation assigned on first insert).
I have a Symfony 4.2 application. There are Entities Game and GameGenre. They have ManyToMany relation between each other. I am trying to load fixtures and receive the following error:
Could not determine access type for property "games" in class "App\Entity\GameGenre": The property "games" in class "App\Entity\GameGenre" can be defined with the methods "addGame()", "removeGame()" but the new value must be an array or an instance of \Traversable, "App\Entity\Game" given.
My code is the following.
Game.php
<?php
namespace App\Entity;
use Doctrine\Common\Collections\Collection;
use Doctrine\ORM\Mapping as ORM;
use Doctrine\Common\Collections\ArrayCollection;
/**
* #ORM\Entity(repositoryClass="App\Repository\GameRepository")
*/
class Game
{
/**
* #ORM\Id
* #ORM\GeneratedValue
* #ORM\Column(type="integer")
*/
protected $id;
...
/**
* #ORM\ManyToMany(
* targetEntity="App\Entity\GameGenre",
* inversedBy="games"
* )
*/
private $genres;
...
/**
* #return Collection|GameGenre[]
*/
public function getGenres() : Collection
{
return $this->genres;
}
public function addGenre(GameGenre $genre): self
{
if (!$this->genres->contains($genre)) {
$this->genres[] = $genre;
$genre->addGame($this);
}
return $this;
}
public function removeGenre(GameGenre $genre): self
{
if ($this->genres->contains($genre)) {
$this->genres->removeElement($genre);
$genre->removeGame($this);
}
return $this;
}
...
public function __construct()
{
$this->genres = new ArrayCollection();
}
}
GameGenre.php
<?php
namespace App\Entity;
use Doctrine\Common\Collections\Collection;
use Doctrine\ORM\Mapping as ORM;
use Doctrine\Common\Collections\ArrayCollection;
/**
* #ORM\Entity(repositoryClass="App\Repository\GameGenreRepository")
*/
class GameGenre
{
/**
* #ORM\Id
* #ORM\GeneratedValue
* #ORM\Column(type="integer")
*/
protected $id;
/**
* #ORM\ManyToMany(
* targetEntity="App\Entity\Game",
* mappedBy="genres"
* )
* #ORM\OrderBy({"name" = "ASC"})
*/
private $games;
...
/**
* #return Collection|Game[]
*/
public function getGames() : Collection
{
return $this->games;
}
public function addGame(Game $game): self
{
if (!$this->games->contains($game)) {
$this->games[] = $game;
$game->addGenre($this);
}
return $this;
}
public function removeGame(Game $game): self
{
if ($this->games->contains($game)) {
$this->games->removeElement($game);
$game->removeGenre($this);
}
return $this;
}
public function __construct()
{
$this->games = new ArrayCollection();
}
}
And it looks like there is nothing really strange in fixtures yamls:
genre.yaml
App\Entity\GameGenre:
genre_{1..9}:
...
games: '#game_*'
game.yaml has no mentioning of genre field but I tried to change relation side by calling addGenre() instead of addGame() or use them both in both fixture files but nothing help, so I think there is some other problem.
Could you please help me?
Your field is an array, but you are trying to insert a single value, it should be:
App\Entity\GameGenre:
genre_{1..9}:
...
games: ['#game_*']
or
App\Entity\GameGenre:
genre_{1..9}:
...
games:
- '#game_*'
I have this two tables (see pics below) mapped as follow:
class Brand
{
...
/**
* #var Company
*
* #ORM\ManyToOne(targetEntity="Company")
* #ORM\JoinColumn(name="companies_id", referencedColumnName="id")
*/
protected $company;
}
class Company
{
...
}
I need to add support for add a new Brand from Company but I have not idea in how to achieve this. This are handled through SonataAdminBundle but I think I need to add something else to entities in order to create brands from company but I am not sure what this would be, can I get some help? I am stucked
1st attempt
After get an answer this is how I modify Company entity:
use Doctrine\ORM\Mapping as ORM;
use Doctrine\Common\Collections\ArrayCollection;
class Company
{
...
/**
* #var Brand
* #ORM\OneToMany(targetEntity="Brand", mappedBy="company", cascade={"persist"})
**/
protected $brands;
public function __construct()
{
$this->brands = new ArrayCollection();
}
...
public function getBrands()
{
return $this->brands;
}
/**
* Add brands
*
* #param Brand $brand
* #return Brands
*/
public function addBrand( Brand $brand)
{
$this->brands[] = $brand;
return $this;
}
/**
* Remove brands
*
* #param Brand $brand
*/
public function removeBrand( Brand $brand)
{
$this->brands->removeElement($brand);
}
}
But I am getting this error:
No entity manager defined for class
Doctrine\Common\Collections\ArrayCollection
Why is that?
You could try setting up your entities like this:
class Brand
{
/**
* #var Company
*
* #ORM\ManyToOne(targetEntity="Company", inversedBy="brands")
* #ORM\JoinColumn(name="companies_id", referencedColumnName="id")
*/
protected $company;
}
class Company
{
/**
* #var ArrayCollection
*
* #OneToMany(targetEntity="Brand", mappedBy="company", cascade={"persist"})
**/
protected $brands;
}
What we're defining here is that new Brands can be created from the Company entity with cascade={"persist"}.
It's recommended you implement addBrand and removeBrand in Company for direct interaction with the ArrayCollection.
A simple example of the final functionality:
$company = $service->getCompany(1); // our company entity
$brand = new Brand();
$brand->set...
...
$company->addBrand($brand);
$entityManager->persist($company);
EDIT
This is just an example, you may choose not to add with keys or even implement a remove function, but this is a starting point:
public function addBrand(Brand $brand)
{
// key needs to be something that can uniquely identify the brand
// e.g. name
$this->getBrands()->set(*key*, $brand);
return $this;
}
public function removeBrand($key)
{
$this->getBrands()->remove($key);
return $this;
}
I'd like to store a a record from this table in the "parent" field. THe error produced is as follows
Column name 'id' referenced for relation from MODL119\Entity\Role to MODL119\Entity\Role does not exist
And the PHP Entity class that produced the error
<?php
namespace MODL119\Entity;
use Doctrine\ORM\Mapping as ORM;
/** #ORM\Entity #ORM\Table(name="role")
*/
class Role
{
/** #ORM\Id
* #ORM\Column(name="role_id", type="bigint")
* #ORM\GeneratedValue(strategy="AUTO")
*/
protected $roleId;
/** #ORM\ManyToOne(targetEntity="MODL119\Entity\Role")
* #ORM\JoinColumn(name="role_id", referencedColumnName="role_id")
*/
protected $parent;
public function setParent(Role $parent)
{
$this->parent = $parent;
}
public function getParent()
{
return $this->parent;
}
public function setRoleId($roleId)
{
$this->roleId = $roleId;
}
public function getRoleId()
{
return $this->roleId;
}
}
Any ideas?
The property Role::$parent needs to be represented in the underlying table as a foreign key reference, so you need to amend your annotation and change the JoinColumn.name as follows:
/**
* #ORM\ManyToOne(targetEntity="MODL119\Entity\Role")
* #ORM\JoinColumn(name="parent_id", referencedColumnName="role_id")
*/
protected $parent;
This establishes a one-to-one self-referencing association in your entity, and creates a parent_id column in the underlying table.
See also:
one-to-many self-referencing association
many-to-many self-referencing association
Hope this helps :)