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 am aggregating data in json to convert it into symfony object.
A category (otology) has a type (swimming pool, restaurant). This category has a description that is translated into several languages.
I would like to be able to simply do getXXX('fr'), to have this description in French, getXXX('en') for English.
With a repository I think I can do it, but maybe I complicate it:
My class Otolongy:
class Ontology
{
#[ORM\Id]
#[ORM\GeneratedValue]
#[ORM\Column]
private ?int $id = null;
#[ORM\Column(length: 255)]
private ?string $idOntology = null;
#[ORM\OneToMany(mappedBy: 'ontology', targetEntity: Label::class, cascade: ['persist', 'remove'], fetch:'EAGER')]
private Collection $descriptions;
my class label (for fields description):
#[ORM\Entity(repositoryClass: LabelRepository::class)]
class Label
{
#[ORM\Id]
#[ORM\GeneratedValue]
#[ORM\Column]
private ?int $id = null;
#[ORM\Column(type: Types::TEXT)]
private ?string $label = null;
#[ORM\ManyToOne(inversedBy: 'descriptions')]
private ?Ontology $ontology = null;
#[ORM\ManyToOne(inversedBy: 'labels', fetch:'EAGER')]
private ?Lang $lang = null;
my class lang:
#[ORM\Entity(repositoryClass: LangRepository::class)]
class Lang
{
#[ORM\Id]
#[ORM\GeneratedValue]
#[ORM\Column]
private ?int $id = null;
#[ORM\Column(length: 10)]
private ?string $labelLang = null;
#[ORM\OneToMany(mappedBy: 'lang', targetEntity: Label::class)]
private Collection $labels;
You can see that for my tests I had to put fetch:'EAGER' I'm not convinced that this is the right way to do it... well after all put 1 eager or have to make a 'complex' request in my controller it should be equivalent...
I have already done this system on java and I had made a map key, so I used this command ontology.description.get(lang) in the template, in order to use the local as an argument and java gave me the description in the good language.
if i can avoid loading the ontology in eager i think the display of twig will go faster? Namely that I must also check that the translation in the visitor's local exists, otherwise I must display by default (English or French)
So my goal is to use the visitor's locale to know which language (language of the ontology in the database) that twig should display. Is there maybe a simpler way to do it? Maybe I'm totally wrong?!
I'm learning Symfony by working on a personal project. I'm using Symfony 6.1.
I have a page containing two forms :
the "main" form ($bookForm) deals the Book entity and persist it to the database
the second one ($isbnForm) takes a string and search the Google Books API.
When submitting the second form ($isbnForm), the page is reloaded and $bookForm is preloaded with data.
Here is the code of the BookController :
#[Route('/create', name: 'create', methods: ['GET', 'POST'])]
public function new(
Request $request,
BookshelfRepository $bookshelfRepository,
AuthorRepository $authorRepository,
PublisherRepository $publisherRepository,
BookRepository $bookRepository
): Response {
$this->denyAccessUnlessGranted('edit', $this->getUser());
$book = new Book();
// Retrieving the Bookshelf passed along the request
// and associating it to the Book
if ($request->query->get('bksid')) {
$bookshelf = $bookshelfRepository->findOneBy(['ulid' => $request->query->get('bksid')]);
$book->setBookshelf($bookshelf);
}
// Dealing with the ISBN form
$isbnForm = $this->createForm(IsbnType::class);
$isbnForm->handleRequest($request);
if ($isbnForm->isSubmitted() && $isbnForm->isValid()) {
$isbnTools = new IsbnTools();
$isbn = $isbnTools->format($isbnForm->getData()['isbn']);
$book->setIsbn($isbn);
// Getting book's details from the Google Books API using the ISBN
$gbapi = new GoogleBooksApiUtils();
$details = $gbapi->gettingVolumeInfoByIsbn($isbn);
$book->setTitle($details->getTitle());
$book->setSubtitle($details->getSubtitle());
$book->setDescription($details->getDescription());
$book->setPages($details->getPageCount());
$book->setPublicationDate(substr($details->getPublishedDate(), 0, 4));
if ($details->getPublisher()) {
$publisher = $publisherRepository->findOneBy(['name' => $details->getPublisher()]);
if (!$publisher) {
$publisher = new Publisher();
$publisher->setName($details->getPublisher());
$publisherRepository->save($publisher, true);
}
$book->setPublisher($publisher);
}
foreach ($details->getAuthors() ?? [] as $dga) {
$author = $authorRepository->findOneBy(['name' => $dga]);
if (!$author) {
$author = new Author();
$author->setName($dga);
$authorRepository->save($author, true);
}
$book->addAuthor($author);
}
}
// Dealing with the main form, dealing with the Book entity
$bookForm = $this->createForm(BookType::class, $book);
$bookForm->handleRequest($request);
if ($bookForm->isSubmitted() && $bookForm->isValid()) {
$bookRepository->save($book, true);
return $this->redirectToRoute('bks_book_view', [
'ulid' => $book->getUlid(),
], Response::HTTP_SEE_OTHER);
}
return $this->renderForm('book/create.html.twig', [
'isbn_form' => $isbnForm,
'form' => $bookForm,
'book' => $book
]);
}
It works nicely until I tried to submit the thumbnail get from the Google Books API.
I've installed the VichUploaderBundle, set up a mapping, modified my Book entity accordingly to the documentation.
vich_uploader:
db_driver: orm
metadata:
type: attribute
mappings:
books:
uri_prefix: /uploads/books
upload_destination: '%kernel.project_dir%/public/uploads/books'
namer: Vich\UploaderBundle\Naming\SmartUniqueNamer
inject_on_load: false
delete_on_update: true
delete_on_remove: true
<?php
namespace App\Entity;
use App\Repository\BookRepository;
use DateTime;
use Doctrine\Common\Collections\ArrayCollection;
use Doctrine\Common\Collections\Collection;
use Doctrine\DBAL\Types\Types;
use Doctrine\ORM\Mapping as ORM;
use Symfony\Component\HttpFoundation\File\File;
use Symfony\Component\Uid\Ulid;
use Vich\UploaderBundle\Mapping\Annotation\Uploadable;
use Vich\UploaderBundle\Mapping\Annotation\UploadableField;
#[ORM\Entity(repositoryClass: BookRepository::class)]
#[ORM\HasLifecycleCallbacks]
#[Uploadable]
class Book
{
#[ORM\Id]
#[ORM\GeneratedValue]
#[ORM\Column]
private ?int $id = null;
#[ORM\Column(length: 255)]
private ?string $title = null;
#[ORM\Column(length: 255, nullable: true)]
private ?string $subtitle = null;
#[ORM\ManyToMany(targetEntity: Author::class, inversedBy: 'books')]
private Collection $authors;
#[ORM\ManyToOne(inversedBy: 'books')]
#[ORM\JoinColumn(nullable: false)]
private ?Publisher $publisher = null;
#[ORM\Column(length: 4, nullable: true)]
private ?string $publication_date = null;
#[ORM\Column(type: Types::TEXT, nullable: true)]
private ?string $description = null;
#[ORM\Column(length: 13, nullable: true)]
private ?string $isbn = null;
#[ORM\Column(nullable: true)]
private ?int $pages = null;
#[ORM\Column(type: 'ulid')]
private ?Ulid $ulid = null;
#[ORM\ManyToOne(inversedBy: 'books')]
#[ORM\JoinColumn(nullable: false)]
private ?Bookshelf $bookshelf = null;
#[UploadableField(mapping: 'books', fileNameProperty: 'imageName', size: 'imageSize')]
private ?File $imageFile = null;
#[ORM\Column(type: 'string', nullable: true)]
private ?string $imageName = null;
#[ORM\Column(type: 'integer', nullable: true)]
private ?int $imageSize = null;
#[ORM\Column(type: 'datetime')]
private ?\DateTimeInterface $updatedAt = null;
public function __construct()
{
$this->authors = new ArrayCollection();
$this->updatedAt = new DateTime();
}
public function getId(): ?int
{
return $this->id;
}
public function getTitle(): ?string
{
return $this->title;
}
public function setTitle(string $title): self
{
$this->title = $title;
return $this;
}
public function getSubtitle(): ?string
{
return $this->subtitle;
}
public function setSubtitle(?string $subtitle): self
{
$this->subtitle = $subtitle;
return $this;
}
/**
* #return Collection<int, Author>
*/
public function getAuthors(): Collection
{
return $this->authors;
}
public function addAuthor(Author $author): self
{
if (!$this->authors->contains($author)) {
$this->authors->add($author);
}
return $this;
}
public function removeAuthor(Author $author): self
{
$this->authors->removeElement($author);
return $this;
}
public function getPublisher(): ?Publisher
{
return $this->publisher;
}
public function setPublisher(?Publisher $publisher): self
{
$this->publisher = $publisher;
return $this;
}
public function getPublicationDate(): ?string
{
return $this->publication_date;
}
public function setPublicationDate(?string $publication_date): self
{
$this->publication_date = $publication_date;
return $this;
}
public function getDescription(): ?string
{
return $this->description;
}
public function setDescription(?string $description): self
{
$this->description = $description;
return $this;
}
public function getIsbn(): ?string
{
return $this->isbn;
}
public function setIsbn(?string $isbn): self
{
$this->isbn = $isbn;
return $this;
}
public function getPages(): ?int
{
return $this->pages;
}
public function setPages(?int $pages): self
{
$this->pages = $pages;
return $this;
}
public function getUlid(): ?Ulid
{
return $this->ulid;
}
#[ORM\PrePersist]
public function setUlid(): void
{
$this->ulid = new Ulid();
}
public function getBookshelf(): ?Bookshelf
{
return $this->bookshelf;
}
public function setBookshelf(?Bookshelf $bookshelf): self
{
$this->bookshelf = $bookshelf;
return $this;
}
/**
* If manually uploading a file (i.e. not using Symfony Form) ensure an instance
* of 'UploadedFile' is injected into this setter to trigger the update. If this
* bundle's configuration parameter 'inject_on_load' is set to 'true' this setter
* must be able to accept an instance of 'File' as the bundle will inject one here
* during Doctrine hydration.
*
* #param File|\Symfony\Component\HttpFoundation\File\UploadedFile|null $imageFile
*/
public function setImageFile(?File $imageFile = null): void
{
$this->imageFile = $imageFile;
if (null !== $imageFile) {
// It is required that at least one field changes if you are using doctrine
// otherwise the event listeners won't be called and the file is lost
$this->updatedAt = new \DateTimeImmutable();
}
}
public function getImageFile(): ?File
{
return $this->imageFile;
}
public function setImageName(?string $imageName): void
{
$this->imageName = $imageName;
}
public function getImageName(): ?string
{
return $this->imageName;
}
public function setImageSize(?int $imageSize): void
{
$this->imageSize = $imageSize;
}
public function getImageSize(): ?int
{
return $this->imageSize;
}
}
Then I modify my BookController in order to add the imageFile to the Book entity :
#[Route('/create', name: 'create', methods: ['GET', 'POST'])]
public function new(
Request $request,
BookshelfRepository $bookshelfRepository,
AuthorRepository $authorRepository,
PublisherRepository $publisherRepository,
BookRepository $bookRepository
): Response {
$this->denyAccessUnlessGranted('edit', $this->getUser());
$book = new Book();
// Retrieving the Bookshelf passed along the request
// and associating it to the Book
if ($request->query->get('bksid')) {
$bookshelf = $bookshelfRepository->findOneBy(['ulid' => $request->query->get('bksid')]);
$book->setBookshelf($bookshelf);
}
// Dealing with the ISBN form
$isbnForm = $this->createForm(IsbnType::class);
$isbnForm->handleRequest($request);
if ($isbnForm->isSubmitted() && $isbnForm->isValid()) {
$isbnTools = new IsbnTools();
$isbn = $isbnTools->format($isbnForm->getData()['isbn']);
$book->setIsbn($isbn);
// Getting book's details from the Google Books API using the ISBN
$gbapi = new GoogleBooksApiUtils();
$details = $gbapi->gettingVolumeInfoByIsbn($isbn);
$book->setTitle($details->getTitle());
$book->setSubtitle($details->getSubtitle());
$book->setDescription($details->getDescription());
$book->setPages($details->getPageCount());
$book->setPublicationDate(substr($details->getPublishedDate(), 0, 4));
if ($details->getPublisher()) {
$publisher = $publisherRepository->findOneBy(['name' => $details->getPublisher()]);
if (!$publisher) {
$publisher = new Publisher();
$publisher->setName($details->getPublisher());
$publisherRepository->save($publisher, true);
}
$book->setPublisher($publisher);
}
foreach ($details->getAuthors() ?? [] as $dga) {
$author = $authorRepository->findOneBy(['name' => $dga]);
if (!$author) {
$author = new Author();
$author->setName($dga);
$authorRepository->save($author, true);
}
$book->addAuthor($author);
}
$thumb = $details->getImageLinks()->getThumbnail() ?? null;
if ($thumb) {
$path = 'uploads/books';
$filename = 'gbapi_' . $book->getIsbn() . '.jpg';
file_put_contents("$path/$filename", file_get_contents($thumb));
$file = new UploadedFile("$path/$filename", $filename, 'image/jpeg', null, false);
$book->setImageFile($file);
$book->setImageName($file->getFilename());
$book->setImageSize($file->getSize());
}
}
// Dealing with the main form, dealing with the Book entity
$bookForm = $this->createForm(BookType::class, $book);
$bookForm->handleRequest($request);
if ($bookForm->isSubmitted() && $bookForm->isValid()) {
$bookRepository->save($book, true);
return $this->redirectToRoute('bks_book_view', [
'ulid' => $book->getUlid(),
], Response::HTTP_SEE_OTHER);
}
return $this->renderForm('book/create.html.twig', [
'isbn_form' => $isbnForm,
'form' => $bookForm,
'book' => $book
]);
}
When I submit the $isbnForm, the data are well passed to the Book entity, and the image is displayed inside the $bookForm. But when I submit it, the imageFile (imageSize and imageName fields) are not persisted to the database.
I've tried to add the UploadedFile to the $request (something like that : $request->files->set('book', ['imageFile' => ['file' => $file]])) but without success...
I have been trying for two days to solve this issue and I'm not even sure I've identified clearly the issue...
Thanks in advance for any lead :-)
I'm trying somehow to attach a partner's modules to my PartnerModule entity, but I can't because it has to create as many relationships as there are associated modules.
A partner has several modules and a module belongs to several partners.
Hence this intermediate table: PartnerModule. I understand that this is definitely a problem in my entities but I can't manage to fix it. Here are my entities and my form. I specify that when I put "multiple" => false it works well.
enter image description here
My error :
Expected argument of type "?App\Entity\Module", "Doctrine\Common\Collections\ArrayCollection" given at property path "modules".
Entity Module :
#[ORM\Entity(repositoryClass: ModuleRepository::class)]
class Module
{
#[ORM\Id]
#[ORM\GeneratedValue]
#[ORM\Column]
private ?int $id = null;
#[ORM\Column(length: 250)]
private ?string $nom = null;
#[ORM\Column]
private ?bool $statut = null;
#[ORM\OneToMany(mappedBy: 'Modules', targetEntity: PartenaireModule::class)]
private Collection $partenaireModules;
public function __construct()
{
$this->partenaireModules = new ArrayCollection();
}
Entity Partenaire Module
#[ORM\Entity(repositoryClass: PartenaireModuleRepository::class)]
class PartenaireModule
{
#[ORM\Id]
#[ORM\GeneratedValue]
#[ORM\Column]
private ?int $id = null;
#[ORM\Column]
private ?bool $is_active = true;
#[ORM\ManyToOne(inversedBy: 'partenaireModules')]
private ?Partenaire $Partenaires = null;
#[ORM\ManyToOne(inversedBy: 'partenaireModules')]
private ?Module $Modules = null;
This is how my data should be mapped:
-> module_id, partenaire_id, is_active, id
Any help or clue to help me will be welcome :)
Thanks
I'm trying to apply SearchFilter to multiple resources.
Let's imagine I've got two resources: Post and Article (just for the illustration):
#[ApiRsource]
class Post
{
#[ORM\Id]
#[ORM\GeneratedValue]
#[ORM\Column]
private ?int $id = null;
#[ORM\Column(length: 255, nullable: true)]
private ?string $postDescription = null;
...
}
#[ApiRsource]
class Article
{
#[ORM\Id]
#[ORM\GeneratedValue]
#[ORM\Column]
private ?int $id = null;
#[ORM\Column(length: 255, nullable: true)]
private ?string $articleDescription = null;
...
}
I want to add a unique endpoint /search?q=key that will return Post and Article resources with a postDescription/articleDescription containing key.
Is there a way to do this without resorting to inheritance?