I am a beginner in API platform. I am working with two entities Author and Book with a OneToMany relationship:
a user can have several books
a book can belong to only one Author.
I've tried POST, GET and DELETE methods, they work. However, the PUT method does not work and returns this error:
"hydra:title": "An error occurred",
"hydra:description": **"An exception occurred while executing 'UPDATE Book SET author_id = ? WHERE id = ?' with params [null, 1]:\n\nSQLSTATE[23000]:
Integrity constraint violation: 1048 Column 'author_id' cannot be null"
Here's my code:
/**
* #ORM\Entity(repositoryClass="App\Repository\AuthorRepository")
* #ApiResource(
* normalizationContext={"groups"={"book:output"}},
* denormalizationContext={"groups"={"book:input"}}
* )
*/
class Author
{
/**
* #ORM\Id()
* #ORM\GeneratedValue()
* #ORM\Column(type="integer")
* #Groups({"book:input"})
*/
private $id;
/**
* #ORM\Column(type="string", length=255)
* #Groups({"book:output", "book:input"})
*/
private $firstname;
/**
* #ORM\Column(type="string", length=255)
* #Groups({"book:output", "book:input"})
*/
private $lastname;
/**
* #ORM\OneToMany(targetEntity="App\Entity\Book", mappedBy="author", cascade={"all"})
* #Groups({"book:output", "book:input"})
*/
private $books;
/**
* #ORM\Entity(repositoryClass="App\Repository\BookRepository")
* #ApiResource()
*/
class Book
{
/**
* #ORM\Id()
* #ORM\GeneratedValue()
* #ORM\Column(type="integer")
* #Groups({"book:output", "book:input"})
*/
private $id;
/**
* #ORM\Column(type="string", length=255)
* #Groups({"book:output", "book:input"})
*/
private $name;
/**
* #ORM\ManyToOne(targetEntity="App\Entity\Author", inversedBy="books")
* #ORM\JoinColumn(nullable=false)
*/
private $author;
You seem like passing a null id for an updated entity which is essential for the ORM to know.
Related
I am developing symfony 4.2 application. I have entity Meal and OrderItem. OrderItem should have all Meal entity properties + few of it's own. The problem is with ManyToOne relationship column. It is not added to order_item table.
https://www.doctrine-project.org/projects/doctrine-orm/en/2.6/reference/inheritance-mapping.html
I tried following "Class Table Inheritance" and "Mapped Superclasses". Example from "Mapped Superclasses" does not add $mappedRelated1 to EntitySubClass . And example from "Class Table Inheritance" removes every other extended field from Meal class and adds some kind of "dtype" column to Meal table.
/**
* #ORM\Entity(repositoryClass="App\Repository\OrderItemRepository")
*/
class OrderItem extends Meal
{
/**
* #ORM\ManyToOne(targetEntity="App\Entity\Order", inversedBy="orderItems")
*/
private $order;
/**
* #ORM\Column(type="integer")
* #Assert\NotBlank
* #Assert\Type("string")
*/
private $amount;
}
/**
* #ORM\Entity(repositoryClass="App\Repository\MealRepository")
*/
class Meal
{
/**
* #ORM\Id()
* #ORM\GeneratedValue()
* #ORM\Column(type="integer")
*/
protected $id;
/**
* #ORM\Column(type="string", length=255)
* #Assert\NotBlank
* #Assert\Type("string")
*/
protected $name;
/**
* #ORM\Column(type="float")
* #Assert\NotBlank
* #Assert\Type("float")
*/
protected $price;
/**
* #ORM\Column(type="string", length=255)
* #Assert\Image(
* mimeTypes={"image/jpeg", "image/png"}
* )
*/
protected $image;
/**
* #ORM\ManyToOne(targetEntity="App\Entity\Menu", inversedBy="meals")
*/
protected $menu;
}
I expect to have "menu_id" column in "order_item" table, which would have relation to Menu entity.
I know that I could copy all the properties from Meal to OrderItem, but that does not sound right.
EDIT:
Both Meal and OrderItem should be able to have it's instance.
menu_id column is already exists in meal
/**
* #ORM\ManyToOne(targetEntity="App\Entity\Menu", inversedBy="meals")
*/
protected $menu;
OrderItem class doesnt need to have just because it is already subclass of meal.
Also you dont need to copy all propertites from Meal to OrderItem.
u can check php extends here
I had the same issue. I used Trait instead of inheritance and the problem was solved.
Your class will be like this
trait MealInfoTrait
{
/**
* #ORM\Column(type="string", length=255)
* #Assert\NotBlank
* #Assert\Type("string")
*/
protected $name;
/**
* #ORM\Column(type="float")
* #Assert\NotBlank
* #Assert\Type("float")
*/
protected $price;
/**
* #ORM\Column(type="string", length=255)
* #Assert\Image(
* mimeTypes={"image/jpeg", "image/png"}
* )
*/
protected $image;
/**
* #ORM\ManyToOne(targetEntity="App\Entity\Menu", inversedBy="meals")
*/
protected $menu;
}
Then, change you Meal class
/**
* #ORM\Entity(repositoryClass="App\Repository\MealRepository")
*/
class Meal
{
use MealInfoTrait;
/**
* #ORM\Id()
* #ORM\GeneratedValue()
* #ORM\Column(type="integer")
*/
protected $id;
}
And do the same with your OrderItem class. Don't forget to add the id for the database.
/**
* #ORM\Entity(repositoryClass="App\Repository\OrderItemRepository")
*/
class OrderItem
{
use MealInfoTrait;
/**
* #ORM\Id()
* #ORM\GeneratedValue()
* #ORM\Column(type="integer")
*/
protected $id;
/**
* #ORM\ManyToOne(targetEntity="App\Entity\Order", inversedBy="orderItems")
*/
private $order;
/**
* #ORM\Column(type="integer")
* #Assert\NotBlank
* #Assert\Type("string")
*/
private $amount;
}
I have a series of classes with a slightly complicated set of references between the properties of those classes. I am trying to remove an entity and have that remove be cascaded to its children, but I'm running into foreign key constraint errors. Here is an example of my class structure:
<?php
/**
* #ORM\Entity
* #ORM\Table(name="student_tests")
*/
class StudentTest implements IEntityAccess {
/**
*
* #var int
* #ORM\Column(type="integer")
* #ORM\Id
* #ORM\GeneratedValue(strategy="AUTO")
*/
protected $id;
/**
* #var StudentTestItem[]
* #ORM\OneToMany(targetEntity="StudentTestItem", mappedBy="studentTest", cascade{"remove","persist"})
*/
protected $studentTestItems;
/**
* #var Test
* #ORM\ManyToOne(targetEntity="Test", inversedBy="studentTests")
*/
protected $test;
/**
* #var \DateTime
* #ORM\Column(type="datetime", nullable=true)
*/
protected $created;
/**
* #var User
* #ORM\ManyToOne(targetEntity="User", inversedBy="studentTests")
*/
protected $student;
}
//...
<?php
/**
* #ORM\Entity
* #ORM\Table(name="student_test_items")
*/
class StudentTestItem {
/**
*
* #var int
* #ORM\Column(type="integer")
* #ORM\Id
* #ORM\GeneratedValue(strategy="AUTO")
*/
protected $id;
/**
* #var StudentTest
* #ORM\ManyToOne(targetEntity="StudentTest", inversedBy="studentTestItems")
*/
protected $studentTest;
/**
* #var User
* #ORM\ManyToOne(targetEntity="User", inversedBy="studentTestItems", cascade={"persist"})
*/
protected $student;
/**
* #var TestItem
* #ORM\ManyToOne(targetEntity="TestItem", inversedBy="studentTestItems", cascade{"persist"})
*/
protected $testItem;
}
//...
/**
*
* #ORM\Table(name="tests")
* #ORM\Entity
*
* #ORM\HasLifecycleCallbacks
*/
class Test implements IEntityAccess {
/**
*
* #var int
* #ORM\Column(type="integer")
* #ORM\Id
* #ORM\GeneratedValue(strategy="AUTO")
*/
protected $id;
/**
* #var \DateTime
* #ORM\Column(type="datetime", nullable=true)
*/
protected $startDate;
/**
* #var StudentTest[]
* #ORM\OneToMany(targetEntity="StudentTest", mappedBy="test" )
*/
protected $studentTests;
/**
* #var TestItem[]
* #ORM\OneToMany(targetEntity="TestItem", mappedBy="test", cascade={"all"})
*/
protected $items;
}
//...
/**
*
* #ORM\Table(name="test_items")
* #ORM\Entity
*/
abstract class TestItem {
/**
*
* #var int
* #ORM\Column(type="integer")
* #ORM\Id
* #ORM\GeneratedValue(strategy="AUTO")
*/
protected $id;
/**
* #var Test
* #ORM\ManyToOne(targetEntity="Test", inversedBy="items")
*/
/**
* #var StudentTestItem[]
* #ORM\OneToMany(targetEntity="StudentTestItem", mappedBy="testItem")
*/
protected $studentTestItems;
}
/**
* This is the primary user object. Used for login and all the other
* good stuff.
*
* #ORM\Table(name="users")
* #ORM\HasLifecycleCallbacks
class User implements AdvancedUserInterface, \Serializable, IEntityAccess
{
/**
* #ORM\Column(type="integer")
* #ORM\Id
* #ORM\GeneratedValue(strategy="AUTO")
* #var int
*/
private $id;
/**
* #var StudentTest[]
* #ORM\OneToMany(targetEntity="StudentTest", mappedBy="student", cascade={"persist", "remove"})
*/
protected $studentTests;
/**
* #var StudentTestItem[]
* #ORM\OneToMany(targetEntity="StudentTestItem", mappedBy="student", cascade={"persist", "remove"})
*/
protected $studentTestItems;
}
Let's say I want to delete a student test, and have that delete cascaded to its StudentTestItem children. To do so, I run the following code inside of a controller.
//... blah blah class definition
/**
* Delete a student test
*
* #return \Symfony\Component\HttpFoundation\Response
* #Route("/studenttest/delete", name="student_test_delete")
*/
public function DeleteStudentTestAction(Request $request)
{
$em = $this->getDoctrine()->getManager();
$studentTest = $em->getRepository("MyAcmeBundle:StudentTest")->findOneBy(array("id" => 3));
$em->remove($studentTest);
$em->flush();
return $this->redirect($this->generateUrl('student_delete_success'));
}
When I try to run that code, I get the following error message:
An exception occurred while executing 'DELETE FROM student_tests WHERE id = ?' with params [3]:
SQLSTATE[23000]: Integrity constraint violation: 1451 Cannot delete or update a parent row: a foreign key constraint fails (`my_acme_bundle/student_test_items`, CONSTRAINT `FK_71FA2A7F36BB1A1` FOREIGN KEY (`student_test_id`) REFERENCES `student_tests` (`id`))
500 Internal Server Error - DBALException
NOW, if I remove all references to studentTestItems from the classes, i.e. I comment out $studentTestItems from the TestItem and User classes, it deletes fine without that issue. Why is this happening? Does Doctrine keep track of the parent references through associations or something?
Looks like you forgot to add ON DELETE CASCADE to the foreign key constraint. Try changing the following association in class StudentTestItem:
/**
* #var StudentTest
* #ORM\ManyToOne(targetEntity="StudentTest", inversedBy="studentTestItems")
*/
protected $studentTest;
To this:
/**
* #var StudentTest
* #ORM\ManyToOne(targetEntity="StudentTest", inversedBy="studentTestItems")
* #ORM\JoinColumn(name="student_test_id", referencedColumnName="id", onDelete="CASCADE")
*/
protected $studentTest;
Very very weird. I have used this method from doctrine hundreds of times. I have a simple controller that takes an id as parameter. The query that Doctrine generates is wrong and crash.
/**
* #Security("has_role('ROLE_ADMIN')")
* #return Response
*/
public function editSellerAction($id)
{
$em = $this->getDoctrine()->getManager();
$seller = $em->getRepository('SiteUserBundle:Seller')->find($id);
// ...
$form = $this->createForm(new SellerType(), $seller, array(
'method' => 'POST'
));
// ...
}
The query generated is the following
[2/2] DBALException: An exception occurred while executing 'SELECT t1.id AS id2, t1.username AS username3, t1.password AS password4, t1.firstname AS firstname5, t1.lastname AS lastname6 FROM seller t1 WHERE t0.id = ? LIMIT 1' with params ["2"]:
SQLSTATE[42S22]: Column not found: 1054 Unknown column 't0.id' in 'where clause' +
The error thrown makes sense because it's looking at "WHERE t0.id" when it should be looking at "WHERE t1.id". I tried the query with t1 using phpmyadmin and it works.
Any idea what might cause this issue?
/**
* Seller have access to their customer and are able to RW access to the customers
*
* #ORM\Table("seller")
* #ORM\Entity
* #author Michael Villeneuve
*/
class Seller extends User
{
/**
* #var array
*
* #ORM\OneToMany(targetEntity="Customer", mappedBy="seller", cascade={"persist", "remove"})
* #ORM\JoinColumn(name="seller_id", referencedColumnName="id")
**/
protected $customers;
/**
* #var string
*
* #ORM\Column(name="firstname", type="string", length=255, nullable=false)
*/
protected $firstname;
/**
* #var string
*
* #ORM\Column(name="lastname", type="string", length=255, nullable=false)
*/
protected $lastname;
// Other attributes and only getters/setter
/**
*
* #ORM\Entity
*/
class User implements UserInterface
{
/**
* #var integer
*
* #ORM\Column(name="id", type="integer")
* #ORM\Id
* #ORM\GeneratedValue(strategy="AUTO")
*/
private $id;
/**
* #ORM\Column(type="string", length=255, unique=true)
*/
private $username;
/**
* #ORM\Column(type="string", length=64)
*/
private $password;
I have 3 entities that extends the User (customer, admin and seller).
Updated link: https://www.doctrine-project.org/projects/doctrine-orm/en/2.7/reference/inheritance-mapping.html
Read up a bit on mapped super classes: http://docs.doctrine-project.org/en/latest/reference/inheritance-mapping.html. Basically, your abstract base user class cannot itself be an entity.
So take the #ORM\Entity line out of your User class. That is where the table 0 (t0) is coming from.
You have 2 options:
The first one is to create an abstract User entity and inherit all values from it. This is useful if you have many entities with the same behaviour. I e.g. like to create a BaseEntity with a ID field and some basic methods. All entities can extend this one and automatically have an ID. Cerad explained in his answer how this is done.
The second option are so called discriminator fields. Basically they allow you to have one User table and sub-tables for every extended entity. You can read about them in the official docs.
Which one you end up using is probably case dependent.
Try to add id field to the Seller entity instead of User
/**
* Seller have access to their customer and are able to RW access to the customers
*
* #ORM\Table("seller")
* #ORM\Entity
*/
class Seller extends User
{
/**
* #var integer
*
* #ORM\Column(name="id", type="integer")
* #ORM\Id
* #ORM\GeneratedValue(strategy="AUTO")
*/
private $id;
/**
* #var array
*
* #ORM\OneToMany(targetEntity="Customer", mappedBy="seller", cascade={"persist", "remove"})
* #ORM\JoinColumn(name="seller_id", referencedColumnName="id")
**/
protected $customers;
/**
* #var string
*
* #ORM\Column(name="firstname", type="string", length=255, nullable=false)
*/
protected $firstname;
/**
* #var string
*
* #ORM\Column(name="lastname", type="string", length=255, nullable=false)
*/
protected $lastname;
// Other attributes and only getters/setter
/**
*
* #ORM\Entity
* #author Michael Villeneuve<michael#panierdachat.com>
*/
class User implements UserInterface
{
/**
* #ORM\Column(type="string", length=255, unique=true)
*/
private $username;
/**
* #ORM\Column(type="string", length=64)
*/
private $password;
I need a hand with the following code.
I have this two clases:
Class Expert{
/**
* #var integer
*
* #ORM\Id
* #ORM\Column(name="id", type="integer")
* #ORM\GeneratedValue(strategy="AUTO")
*/
protected $id;
/**
* #ORM\Column(name="username", type="string", length=255)
* #Assert\NotBlank()
*/
protected $username;
/**
* #ORM\Column(name="email", type="string", length=255, unique=true)
* #Assert\NotBlank()
*/
protected $email;
/**
* #ORM\Column(name="password", type="string", length=40)
* #Assert\NotBlank()
*/
protected $password;
}
Class Job{
/**
* #var integer
*
* #ORM\Column(name="id", type="integer")
* #ORM\Id
* #ORM\GeneratedValue(strategy="AUTO")
*/
protected $id;
/**
* #ORM\Column(name="titulo", type="string", length=255)
* #Assert\NotBlank()
*/
protected $title;
/**
* #ORM\Column(name="description", type="text")
* #Assert\NotBlank()
*/
protected $description;
/**
* #ORM\ManyToOne(targetEntity="Expert")
* #ORM\JoinColumn(name="expert_id", referencedColumnName="id")
*/
protected $assigned_expert;
}
And this custom Repository:
class JobRepository extends EntityRepository
{
public function getTechnicianFinishedJobs($id)
{
$Q = $this->getQueryBuilder('j')
->where('j.expert = :expert_id')
->setParameter('expert_id', $id)
->getQuery()
try{
return $q->getResult();
}catch(NoResultException $e){
return false;
}
}
}
When I run this I get the following error:
[Semantical Error] line 0, col 68 near 'expert = :e': Error: Class Job has no field or association named expert
The idea is that one expert can be assigned to many jobs and one job can be assigned to one expert . The job needs to know who's the designated expert but not the other way around, so that's why I use a ManyToOne unidirectional association.
I tried changing the repository to ->where('j.expert_id = :expert_id') and other combinations with no avail.
Can somebody tell me what I'm doing wrong?
Thanks in advance.
If 'j' is your job table, you can't use j.expert, because this is (as far as I can tell) no attribute of your table/entity. You named the field 'expert_id'.
I guess it should be:
$Q = $this->getQueryBuilder('j')
->where('j.assigned_expert = :expert_id')
->setParameter('expert_id', $id)
->getQuery()
#alvk4: He explained why he didn't use bidirectional association. What you suggested, forgive me if I'm wrong, is bidirectional association.
You miss something on your annotation see an working example:
/**
* Fluency\Bundle\DesktopBundle\Entity\Application
*
* #ORM\Table(
* name="desktop.applications",
* uniqueConstraints={
* #ORM\UniqueConstraint(name="applications_jsid_key", columns={"jsid"}),
* #ORM\UniqueConstraint(name="applications_type_key", columns={"type"}),
* #ORM\UniqueConstraint(name="applications_classname_key", columns={"classname"})
* }
* )
* #ORM\Entity(repositoryClass="Fluency\Bundle\DesktopBundle\Entity\Repository\ApplicationRepository")
*/
class Application
{
...
/**
* #var ArrayCollection
*
* #ORM\OneToMany(targetEntity="Fluency\Bundle\DesktopBundle\Entity\ApplicationFile", mappedBy="application", cascade={"persist"})
* #ORM\JoinTable(name="desktop.application_files",
* joinColumns={
* #ORM\JoinColumn(name="idapplication", referencedColumnName="idapplication")
* }
* )
*/
private $files;
...
}
/**
* Fluency\Bundle\DesktopBundle\Entity\ApplicationFile
*
* #ORM\Table(name="desktop.application_files")
* #ORM\Entity
*/
class ApplicationFile
{
...
/**
* #var \Fluency\Bundle\DesktopBundle\Entity\Application
*
* #ORM\ManyToOne(targetEntity="Application", inversedBy="files")
* #ORM\JoinColumns({
* #ORM\JoinColumn(name="idapplication", referencedColumnName="idapplication", onDelete="CASCADE")
* })
*/
private $application;
...
}
See a working example of DQL on my repository class:
...
public function getApplicationFilesByJsid($jsid)
{
if(empty($jsid) OR !$jsid OR !is_string($jsid))
{
throw new \Psr\Log\InvalidArgumentException();
}
$query = $this->getEntityManager()->createQueryBuilder()
->select('a, af, m, ft')
->from($this->getEntityName(), 'a')
->innerJoin('a.files', 'af')
->innerJoin('a.module', 'm')
->innerJoin('af.filetype', 'ft')
->where('a.active = 1 AND a.jsid = :jsid')
->setParameter('jsid', $jsid)
->orderBy('af.id', 'ASC')
->getQuery();
$applicationFiles = $query->getSingleResult(\Doctrine\ORM\Query::HYDRATE_ARRAY);
return $applicationFiles;
}
...
#enigma: Yes is bidirectional, but your DQL its'n right, would be j.assigned_expert, but anyway the Expert is owning side of relationship, also he needs set mappedBy=assigned_expert on annotation.
I'm working on a solution for adding tags to two differente entities.
in order to get data easily in the frontend i created a joinTable named Tag_Mapping like this :
class TagMapping
{
/**
* #var integer
*
* #ORM\Column(name="id", type="integer")
* #ORM\Id
* #ORM\GeneratedValue(strategy="AUTO")
*/
private $id;
/**
* #ORM\ManyToOne(targetEntity="Tag", inversedBy="tags")
*/
private $tag;
/**
* #ORM\ManyToOne(targetEntity="Feed", inversedBy="tags")
*/
private $feed;
/**
* #ORM\ManyToOne(targetEntity="Question", inversedBy="tags")
*/
private $question;
...
}
The Tag Entity :
class Tag
{
/**
* #var integer
*
* #ORM\Column(name="id", type="integer")
* #ORM\Id
* #ORM\GeneratedValue(strategy="AUTO")
*/
private $id;
/**
* #var string
*
* #ORM\Column(name="title", type="string", length=255, unique=true)
*/
private $name;
/**
* #Gedmo\Slug(fields={"name"})
* #ORM\Column(unique=true)
*/
private $slug;
/**
* #ORM\OneToMany(targetEntity="TagMapping", mappedBy="tag", cascade="remove")
*/
private $tags;
...
}
and in both other entities (Feed and Question) I made reference to TagMapping entity like this
...
/**
* #ORM\ManyToMany(targetEntity="Tag")
* #JoinTable(name="tag_mapping",
* joinColumns={#JoinColumn(name="feed_id", referencedColumnName="id")},
* inverseJoinColumns={#JoinColumn(name="tag_id", referencedColumnName="id")}
* )
*/
private $tags;
...
the problem I'm facing is that it's not a valid way to do it, as it shows an error when i'm trying to execute :
php app/console doctrine:schema:update --force
saying that tag_mapping table already exists.
do you have any idea how can i get it done using only one joinTable instead of one for each relation ?
Thanks.