I have this class
#[ORM\Entity]
#[ORM\InheritanceType('JOINED')]
#[ORM\DiscriminatorColumn(name: 'type', type: 'integer')]
#[ORM\DiscriminatorMap([
1 => Successor1::class,
])]
abstract class Type
{
#[ORM\Id]
#[ORM\GeneratedValue]
#[ORM\Column(type: Types::INTEGER)]
private int $id;
public function getId(): int
{
return $this->id;
}
}
#[ORM\Entity]
class Successor1 extends Type
{
}
By default doctrine made foreign key by ID how to change it to like this
shema
Or type_id on type table
Related
I'm new to symfony, I work on an e-commerce website. I solved some bugs already trying different solutions but with this one I feel stuck. I'm trying to display my articles by categories but it's not working because it displays an error:
Warning: Trying to access array offset on value of type null
I made a consoles controller in symfony to call the view associated to display only articles with that category, expected to do the same with the games after but it doesn't seem to find the category associated with the articles ?
This is my consolescontroller:
namespace App\Controller;
use App\Repository\ArticlesRepository;
use Symfony\Component\HttpFoundation\Response;
use Symfony\Component\Routing\Annotation\Route;
use Symfony\Bundle\FrameworkBundle\Controller\AbstractController;
class ConsolesController extends AbstractController
{
#[Route('/consoles', name: 'app_consoles')]
public function index(ArticlesRepository $repositoryArticles): Response
{
$consoles = $repositoryArticles->findBy(['category' => 1]);
return $this->render('consoles/index.html.twig', [
'controller_name' => 'ConsolesController',
'consoles' => $consoles,
]);
}
}
My Entity Articles
<?php
namespace App\Entity;
use App\Repository\ArticlesRepository;
use Doctrine\Common\Collections\ArrayCollection;
use Doctrine\Common\Collections\Collection;
use Doctrine\ORM\Mapping as ORM;
#[ORM\Entity(repositoryClass: ArticlesRepository::class)]
class Articles
{
#[ORM\Id]
#[ORM\GeneratedValue]
#[ORM\Column]
private ?int $id = null;
#[ORM\Column(length: 80)]
private ?string $nomArticle = null;
#[ORM\Column(length: 255)]
private ?string $imageArticle = null;
#[ORM\Column]
private ?float $prixArticle = null;
#[ORM\Column(length: 200)]
private ?string $descriptionArticle = null;
#[ORM\ManyToMany(targetEntity: Categories::class, inversedBy: 'articles')]
#[ORM\JoinColumn(nullable: false)]
private Collection $category;
public function __construct()
{
$this->category = new ArrayCollection();
}
public function getId(): ?int
{
return $this->id;
}
public function getNomArticle(): ?string
{
return $this->nomArticle;
}
public function setNomArticle(string $nomArticle): self
{
$this->nomArticle = $nomArticle;
return $this;
}
public function getImageArticle(): ?string
{
return $this->imageArticle;
}
public function setImageArticle(string $imageArticle): self
{
$this->imageArticle = $imageArticle;
return $this;
}
public function getPrixArticle(): ?float
{
return $this->prixArticle;
}
public function setPrixArticle(float $prixArticle): self
{
$this->prixArticle = $prixArticle;
return $this;
}
public function getDescriptionArticle(): ?string
{
return $this->descriptionArticle;
}
public function setDescriptionArticle(string $descriptionArticle): self
{
$this->descriptionArticle = $descriptionArticle;
return $this;
}
/**
* #return Collection<int, Categories>
*/
public function getCategory(): Collection
{
return $this->category;
}
public function addCategory(Categories $category): self
{
if (!$this->category->contains($category)) {
$this->category->add($category);
}
return $this;
}
public function removeCategory(Categories $category): self
{
$this->category->removeElement($category);
return $this;
}
public function __toString()
{
return $this->nomArticle;
}
}
And my Entity Categories
<?php
namespace App\Entity;
use App\Repository\CategoriesRepository;
use Doctrine\Common\Collections\ArrayCollection;
use Doctrine\Common\Collections\Collection;
use Doctrine\ORM\Mapping as ORM;
#[ORM\Entity(repositoryClass: CategoriesRepository::class)]
class Categories
{
#[ORM\Id]
#[ORM\GeneratedValue]
#[ORM\Column]
private ?int $id = null;
#[ORM\Column(length: 50)]
private ?string $nomCategorie = null;
#[ORM\ManyToMany(targetEntity: Articles::class, mappedBy: 'category')]
private Collection $articles;
public function __construct()
{
$this->articles = new ArrayCollection();
}
public function getId(): ?int
{
return $this->id;
}
public function getNomCategorie(): ?string
{
return $this->nomCategorie;
}
public function setNomCategorie(string $nomCategorie): self
{
$this->nomCategorie = $nomCategorie;
return $this;
}
/**
* #return Collection<int, Articles>
*/
public function getArticles(): Collection
{
return $this->articles;
}
public function addArticle(Articles $article): self
{
if (!$this->articles->contains($article)) {
$this->articles->add($article);
$article->addCategory($this);
}
return $this;
}
public function removeArticle(Articles $article): self
{
if ($this->articles->removeElement($article)) {
$article->removeCategory($this);
}
return $this;
}
public function __toString()
{
return $this->nomCategorie;
}
}
They're both linked by a "many to many" relationship and they're both supposed to have access to the other but there still something not working.
Thank you in advance for your help.
Originally, the entity Gut had a field reaction that contained a string. The options for reaction were hard-wired in a template. By adding an entity Reaction and changing the Gut form's reaction to an EntityType I'm now plagued with the error message
SQLSTATE[42S22]: Column not found: 1054 Unknown column 't0.reaction' in 'field list'
even though I've rewritten the Gut & Reaction entities. I've probably lost sight of the forest for the trees. What's wrong with the following?
MySQL table gut: reaction column replaced by reaction_id; reaction_id correctly created; foreign key created manually.
Error occurs with this controller method:
#[Route('/', name: 'app_gut_index', methods: ['GET'])]
public function index(GutRepository $gutRepository): Response
{
$guts = $gutRepository->findBy([], ['happened' => 'DESC']); // error thrown here
return $this->render('gut/index.html.twig', [
'guts' => $guts,
]);
}
Gut entity:
#[ORM\Id]
#[ORM\GeneratedValue]
#[ORM\Column]
private ?int $id = null;
#[ORM\Column(length: 255)]
#[ORM\ManyToOne(targetEntity: Reaction::class)]
#[ORM\JoinColumn(name: 'reaction_id', referencedColumnName: 'id')]
protected $reaction;
#[ORM\Column(length: 255, nullable: true)]
private ?string $description = null;
#[ORM\Column(name: "datetime")]
private ?\DateTime $happened = null;
public function getId(): ?int
{
return $this->id;
}
public function getReaction(): ?Reaction
{
return $this->reaction;
}
public function setReaction(?Reaction $reaction): self
{
$this->reaction = $reaction;
return $this;
}
...
}
Reaction entity:
use App\Entity\Gut;
use App\Repository\ReactionRepository;
use Doctrine\ORM\Mapping as ORM;
use Doctrine\Common\Collections\ArrayCollection;
use Doctrine\Common\Collections\Collection;
#[ORM\Entity(repositoryClass: ReactionRepository::class)]
class Reaction
{
public function __construct()
{
$this->guts = new ArrayCollection();
}
#[ORM\Id]
#[ORM\GeneratedValue]
#[ORM\Column]
private ?int $id = null;
#[ORM\Column(length: 45)]
private ?string $reaction = null;
public function getId(): ?int
{
return $this->id;
}
public function getReaction(): ?string
{
return $this->reaction;
}
public function setReaction(string $reaction): self
{
$this->reaction = $reaction;
return $this;
}
#[ORM\OneToMany(targetEntity: Gut::class, mappedBy: 'reaction')]
private $guts;
/**
* #return Collection|Product[]
*/
public function getGuts(): Collection
{
return $this->guts;
}
public function addGut($gut): self
{
$this->guts[] = $gut;
return $this;
}
public function __toString()
{
return $this->getReaction();
}
}
Your $reaction property should not have both ORM\Column and ORM\JoinColumn annotations at the same time.
Because of this Doctrine thinks it's a regular column so it's looking for a database field based on the variable name: $reaction -> gut.reaction.
Remove #[ORM\Column(length: 255)] then make sure that you have gut.reaction_id in your database and now it should work.
As a little side note I don't think you need name: 'reaction_id', referencedColumnName: 'id' in ORM\JoinColumn because that's how Doctrine will name them automatically anyway
Just couldn't let go. I eventually found a path to get the Gut and Reaction entities to play nicely together. What I did:
cloned the project
manually deleted reaction property from Gut entity; created & executed a migration
in MySQL, added back in a reaction column
used make:entity Gut to add a reaction property as ManyToOne on Reaction; made a migration
used MySQL to populate the reaction_id column from the database of the cloned project.
(Probably missed a step in here somewhere, but) gut->getReaction(),etc,
now behave as expected - in a ManyToOne relationship.
I would like to create a table with an indexed columned to speed up searches.
Here is a sample:
#[ORM\Entity(repositoryClass: SettingRepository::class)]
#[ORM\Table(name: '`tr_setting`', indexes: [
new ORM\Index(columns: ['code'], name: 'idx_setting_code')
])]
class Setting
{
#[ORM\Column(type: 'string', length: 15)]
private ?string $code;
#[ORM\Column(type: 'text')]
private string $content;
#[ORM\Id]
#[ORM\GeneratedValue]
#[ORM\Column(type: 'integer')]
private ?int $id;
// Getter and setter...
}
When I use DoctrineBundle migration, the file is generated, but index is ignored...
// ....
final class Version20220719140604 extends AbstractMigration
{
public function up(Schema $schema): void
{
// this up() migration is auto-generated, please modify it to your needs
$this->addSql('CREATE SEQUENCE "tr_setting_id_seq" INCREMENT BY 1 MINVALUE 1 START 1');
$this->addSql('CREATE TABLE "tr_setting" (id INT NOT NULL, code VARCHAR(15) NOT NULL, content TEXT NOT NULL, PRIMARY KEY(id))');
}
For other projects, I already did it with annotations instead of attributes.
I carefully read this answer, but it doesn't help me.
I'm on PHP8.1.8, doctrine/orm 2.12.3 , doctrine-migrations: 3.2, postgresql: 13
My index shall be declared with this syntax:
#[
ORM\Entity(repositoryClass: SettingRepository::class),
ORM\Table(name: '`tr_setting`'),
ORM\Index(columns: ['code'], name: 'idx_setting_code'),
]
or this one:
#[ORM\Entity(repositoryClass: SettingRepository::class)]
#[ORM\Table(name: '`tr_setting`')]
#[ORM\Index(columns: ['code'], name: 'idx_setting_code')]
Here is the error message when displaying: "An exception occurred while executing a query: SQLSTATE[23000]: Integrity constraint violation: 1048 Column 'done' cannot be null"
I don't understand why this error occurs when everything seems very normal.
Here's my DefaultController.php file:
<?php
namespace App\Controller;
use App\Entity\Todo;
use App\Form\TodoType;
use Doctrine\Persistence\ManagerRegistry;
use Symfony\Component\HttpFoundation\Request;
use Symfony\Component\Routing\Annotation\Route;
use Symfony\Bundle\FrameworkBundle\Controller\AbstractController;
class DefaultController extends AbstractController {
#[Route('/', name:'index')]
public function index(Request $request, ManagerRegistry $mr) {
$todo = new Todo();
$todoForm = $this->createForm(TodoType::class, $todo);
$todoForm->handleRequest($request);
// dd($todoForm);
if($todoForm->isSubmitted() && $todoForm->isValid() ){
$todo->setCreatedAt(new \DateTime());
$entityManager = $mr->getManager();
$entityManager->persist($todo);
$entityManager->flush();
}
return $this->render('./page1.html.twig',
['form'=>$todoForm->createView()]
);
}
}
Here is the code of my entity class todo:
<?php
namespace App\Entity;
use App\Repository\TodoRepository;
use Doctrine\ORM\Mapping as ORM;
#[ORM\Entity(repositoryClass: TodoRepository::class)]
class Todo
{
#[ORM\Id]
#[ORM\GeneratedValue]
#[ORM\Column(type: 'integer')]
private $id;
#[ORM\Column(type: 'string', length: 255)]
private $content;
#[ORM\Column(type: 'boolean', options:["default" => false])]
private $done;
#[ORM\Column(type: 'datetime' , options:["default" => "CURRENT_TIMESTAMP"])]
private $createdAt;
public function getId(): ?int
{
return $this->id;
}
public function getContent(): ?string
{
return $this->content;
}
public function setContent(string $content): self
{
$this->content = $content;
return $this;
}
public function getDone(): ?bool
{
return $this->done;
}
public function setDone(bool $done): self
{
$this->done = $done;
return $this;
}
public function getCreatedAt(): ?\DateTimeInterface
{
return $this->createdAt;
}
public function setCreatedAt(\DateTimeInterface $createdAt): self
{
$this->createdAt = $createdAt;
return $this;
}
}
Here is my TodoType form:
<?php
namespace App\Form;
use App\Entity\Todo;
use Symfony\Component\Form\AbstractType;
use Symfony\Component\Form\FormBuilderInterface;
use Symfony\Component\OptionsResolver\OptionsResolver;
class TodoType extends AbstractType
{
public function buildForm(FormBuilderInterface $builder, array $options): void
{
$builder
->add('content')
;
}
public function configureOptions(OptionsResolver $resolver): void
{
$resolver->setDefaults([
'data_class' => Todo::class,
]);
}
}
I tried another method by injecting the EntityManagerInterface service in my controller, unfortunately. I get the same error message. Need help please.
I think instead of initialising your property withing the ORM you can do it underneath
#[ORM\Column(type: 'boolean')]
private $done = false;
Or you could do it as soon as you submit the form, by any means when you submit the form it sets the done value to false. However this method isn't practical.
if($todoForm->isSubmitted() && $todoForm->isValid() ){
$todo->setCreatedAt(new \DateTime());
$todo->setDone(0); // Sets the value to false as soon as the form is submitted
$entityManager = $mr->getManager();
$entityManager->persist($todo);
$entityManager->flush();
Each Product is "owned" by a given Tenant (i.e. user) and requires a color which could be either a standard Color available to all tenants or a proprietary TenantOwnedColor which was created by a given tenant and only available to that tenant.
#[ORM\Entity]
class Product implements BelongsToTenantInterface
{
#[ORM\Id]
#[ORM\GeneratedValue]
#[ORM\Column(type: 'integer')]
private int $id;
#[ORM\Column(type: 'string', length: 180)]
private string $name;
#[ORM\ManyToOne(targetEntity: Color::class)]
#[ORM\JoinColumn(nullable: false)]
private ?Color $color;
#[ORM\ManyToOne(targetEntity: Tenant::class)]
#[ORM\JoinColumn(nullable: false)]
private ?Tenant $tenant;
}
#[ORM\Entity]
#[ORM\InheritanceType(value: 'JOINED')]
#[ORM\DiscriminatorColumn(name: 'type', type: 'string')]
#[ORM\DiscriminatorMap(value: ['open' => Color::class, 'proprietary' => TenantOwnedColor::class])]
class Color
{
#[ORM\Id]
#[ORM\GeneratedValue]
#[ORM\Column(type: 'integer')]
private int $id;
#[ORM\Column(type: 'string', length: 180)]
private string $name;
#[ORM\Column(type: 'string', length: 255)]
private string $colorCode;
}
#[ORM\Entity]
class TenantOwnedColor extends Color implements BelongsToTenantInterface
{
#[ORM\ManyToOne(targetEntity: Tenant::class)]
#[ORM\JoinColumn(nullable: false)]
private ?Tenant $tenant;
}
In order to filter all entities that implement BelongsToTenantInterface and limit them to the Tenant that the logged on user belongs to, a listener adds a doctrine filter.
namespace App\EventListener;
use Doctrine\ORM\EntityManager;
use Lexik\Bundle\JWTAuthenticationBundle\Event\JWTAuthenticatedEvent;
use App\Entity\MultiTenenacy\BelongsToTenantInterface;
final class AuthenticatedTenantEntityListener
{
public function __construct(private EntityManager $entityManager)
{
}
public function onJWTAuthenticated(JWTAuthenticatedEvent $jwtAuthenticatedEvent): void
{
$user = $jwtAuthenticatedEvent->getToken()->getUser();
if (!$user instanceof BelongsToTenantInterface) {
return;
}
$this->entityManager
->getFilters()
->enable('tenant_filter')
->setParameter('tenantId', $user->getTenant()->getId());
}
}
namespace App\Doctrine;
use Doctrine\ORM\Mapping\ClassMetadata;
use Doctrine\ORM\Query\Filter\SQLFilter;
use App\Entity\MultiTenenacy\BelongsToTenantInterface;
final class TenantFilter extends SQLFilter
{
public function addFilterConstraint(ClassMetadata $classMetadata, $targetTableAlias): string
{
if ($classMetadata->getReflectionClass()->implementsInterface(BelongsToTenantInterface::class)) {
return sprintf('%s.tenant_id = %s', $targetTableAlias, $this->getParameter('tenantId'));
}
return '';
}
}
My approach works for Product but not for TenantOwnedColor. When troubleshooting, I discovered that TenantFilter::addFilterConstraint() is being passed the parent class (i.e. Color) metadata which doesn't implement BelongsToTenantInterface and thus I now know why it isn't filtering.
I also found the following in Doctrine's documentation so evidently it is by design:
In the case of joined or single table inheritance, you always get
passed the ClassMetadata of the inheritance root. This is necessary to
avoid edge cases that would break the SQL when applying the filters.
Are there other ways to implement this in order to overcome this shortcoming?
It seems that this topic has been brought up by the community some times now. There does not seem to be an official workaround, due to innestability provoked by those famous edge cases, although some people have made their changes/hacks/workarounds to the problem so it is not impossible.
Links that might help, with some workarounds mentioned in them, I hope you find them useful enough, sorry that I cannot be of more help:
https://github.com/doctrine/orm/issues/7504#issuecomment-568569307
https://github.com/doctrine/orm/issues/6329
https://github.com/doctrine/orm/issues/6329#issuecomment-538854316
https://www.doctrine-project.org/projects/doctrine-orm/en/2.11/reference/php-mapping.html#classmetadata-api