Doctrine2 oneToOne composite primary key not nullable - php

I've a problem with a composite primary key on a OneToOne relation in Doctrine. When one object has no relation (which is normally possible) I got the following error message:
Doctrine\Common\Proxy\Exception\OutOfBoundsException: Missing value for
primary key idEngine on Farkas\CoreBundle\Entity\Engine
I've found the problem directly in Doctrine:
vendor/doctrine/orm/lib/Doctrine/ORM/UnitOfWork.php Line 2116
// TODO: Is this even computed right in all cases of composite keys?
foreach ($assoc['targetToSourceKeyColumns'] as $targetColumn => $srcColumn) {
$joinColumnValue = isset($data[$srcColumn]) ? $data[$srcColumn] : null;
if ($joinColumnValue !== null) {
if ($targetClass->containsForeignIdentifier) {
$associatedId[$targetClass->getFieldForColumn($targetColumn)] = $joinColumnValue;
} else {
$associatedId[$targetClass->fieldNames[$targetColumn]] = $joinColumnValue;
}
}
}
$joinColumnValue is null when the foreign key is empty because the array $data which includes the row data doesn't have the array key for the relation field. The todo comment tells me that something is wrong with composite keys?
Here my two Entities (just a small test, don't think about the logic of my entities^^):
1st
/**
* Car
*
* #ORM\Table()
* #ORM\Entity(repositoryClass="Farkas\CoreBundle\Entity\CarRepository")
*/
class Car
{
/**
* #var integer
*
* #ORM\Column(name="id_car", type="integer")
* #ORM\Id
* #ORM\GeneratedValue(strategy="NONE")
*/
private $idCar;
/**
* #var string
*
* #ORM\Column(name="brand", type="string", length=255)
*/
private $brand;
/**
* #var string
*
*
* #ORM\OneToOne(targetEntity="Engine", mappedBy="car")
* #ORM\JoinColumns(
* #ORM\JoinColumn(name="engine", referencedColumnName="id_engine"),
* #ORM\JoinColumn(name="country", referencedColumnName="country")
* )
*/
private $engine;
/**
* #var integer
* #ORM\Id
* #ORM\Column(name="country", type="integer")
*/
private $country;
}
2nd
/**
* Engine
*
* #ORM\Table()
* #ORM\Entity(repositoryClass="Farkas\CoreBundle\Entity\EngineRepository")
*/
class Engine
{
/**
* #var integer
*
* #ORM\Column(name="id_engine", type="integer")
* #ORM\Id
* #ORM\GeneratedValue(strategy="NONE")
*/
private $idEngine;
/**
* #var string
*
* #ORM\Column(name="engineName", type="string", length=255)
*/
private $engineName;
/**
* #var string
*
* #ORM\Column(name="description", type="string", length=255)
*/
private $description;
/**
* #var integer
* #ORM\Id
* #ORM\Column(name="country", type="integer")
*/
private $country;
/**
* #var integer
*
* #ORM\OneToOne(targetEntity="Car", inversedBy="engine")
* #ORM\JoinColumns(
* #ORM\JoinColumn(name="car_id", referencedColumnName="id_car"),
* #ORM\JoinColumn(name="country", referencedColumnName="country")
* )
*/
private $car;
}
When I try to execute findAll() I got the error message above. When I execute the generated SQL directly in MySQL, everything looks fine:
SELECT t0.id_car AS id_car1, t0.brand AS brand2, t0.country AS country3,
t0.engine AS engine4, t0.country AS country5 FROM Car t0
It means, the hydration can't work with an empty oneToOne relation.

(Posted on behalf of the OP.)
As expected it was just an error from my side. This is the solution:
Car Entity
/**
* #var string
*
*
* #ORM\OneToOne(targetEntity="Engine", mappedBy="car")
* #ORM\JoinColumns(
* #ORM\JoinColumn(name="fk_engine", referencedColumnName="id_engine"),
* #ORM\JoinColumn(name="fk_engine_country", referencedColumnName="country")
* )
*/
private $engine;
Engine Entity
/**
* #var integer
*
* #ORM\OneToOne(targetEntity="Car", inversedBy="engine")
* #ORM\JoinColumns(
* #ORM\JoinColumn(name="fk_car", referencedColumnName="id_car"),
* #ORM\JoinColumn(name="fk_car_country", referencedColumnName="country")
* )
*/
private $car;
The name of the join column is an attribute which will be created as relation fields inside the db. I already had an attribute with the name country inside the table. So finally the name is always the foreign key to identify the relation.

Related

Doctrine - Need ordering or filtering result with custom fields

I try to order/filter a query with Doctrine but using custom field (ie Field which is not in table).
I have this class "Entry":
class SosfEntry
{
/**
* #var int
* #Groups("id")
*
* #ORM\Column(name="id", type="integer")
* #ORM\Id
* #ORM\GeneratedValue(strategy="AUTO")
*/
protected $id;
/**
* #var DateTime
* #Groups("date")
*
* #ORM\Column(name="`date`", type="datetime", nullable=true)
*/
protected $date;
/**
* #var string
* #Groups("ref")
*
* #ORM\Column(name="ref", type="string", length=255, nullable=true)
*/
protected $ref;
/**
* CUSTOM -------------------------------
*/
/**
* #var integer
* #Groups("achievment_percentage")
*/
protected $achievment_percentage;
/**
* #return int
*/
public function getAchievmentPercentage(): ? int
{
// value between 0 and 100 (here 100 for the example)
return 100;
}
I'm doing a Doctrine query Builder like this :
SELECT a FROM Entry a WHERE a.achievment_percentage = :achievment_percentage ORDER BY a.date DESC
But I got this error :
Error: Class Entry has no field or association named achievment_percentage.
Is there a way to do this directly from the query builder ? Or need I to do some filters manually after the query ?

How to constrain saving id number as a value of field in ManyToOne Doctrine relation?

I have ManyToOne relation defined below.
Persisting owning side object, with inversed object setted in there, resulting in saving row in db of owning side where column that I expect to have value of id foreign key (integer) instead it save name (string) field of inversed table side.
In other words:
column iptra_documents.document_category_id contains value from iptra_document_category.name. I'm expecting to have iptra_document_category.id value in iptra_documents.document_category_id
Owning side:
/**
* #ORM\Table(name="iptra_documents")
*/
class IptraDocuments
{
/**
* #var int
*
* #ORM\Column(name="document_id", type="integer", unique=true)
* #ORM\Id
* #ORM\GeneratedValue(strategy="AUTO")
*/
private $documentId;
/**
* #var IptraDocumentCategory
* #ORM\Column(name="document_category_id")
* #ORM\ManyToOne(targetEntity="AppBundle\Entity\IptraDocumentCategory", inversedBy="id")})
*/
private $documentCategory;
//.....
public function setCategory(IptraDocumentCategory $documentCategory)
{
$this->documentCategory = $documentCategory;
}
Inverse side:
/**
* #ORM\Entity
* #ORM\Table(name="iptra_document_category")
*/
class IptraDocumentCategory
{
/**
* #ORM\Id
* #ORM\GeneratedValue(strategy="AUTO")
* #ORM\Column(name="id", type="integer")
*/
private $id;
/**
* #ORM\Column(name="parent_id", type="integer")
*/
private $parentId;
/**
* #ORM\Column(name="name", type="string")
*/
private $name;
/**
* #ORM\OneToMany(targetEntity="AppBundle\Entity\IptraDocuments", mappedBy="documentCategory")
*/
private $iptraDocuments;
According to the docs, you don't need #ORM\Column but #ORM\JoinColumn to define "Many" side
/**
* #var IptraDocumentCategory
*
* #ORM\JoinColumn(name="category_id", referencedColumnName="id")
* #ORM\ManyToOne(targetEntity="AppBundle\Entity\IptraDocumentCategory", inversedBy="id")})
*/
private $documentCategory;

Doctrine Entity Relationship

Am having a bit of a challenge creating entity relationship between a product its Category and associated color(s) with the following Entities (I omitted the getters and setters though):
#Product
/**
* Product
*
* #ORM\Table(name="product")
* #ORM\Entity(repositoryClass="AppBundle\Repository\ProductRepository")
*/
class Product
{
/**
* #var int
*
* #ORM\Column(name="id", type="integer")
* #ORM\Id
* #ORM\GeneratedValue(strategy="AUTO")
*/
private $id;
/**
* #var string
*
* #ORM\Column(name="name", type="string", length=255)
*/
private $name;
/**
* #var float
*
* #ORM\Column(name="price", type="float")
*/
private $price;
/**
* #var int
*
* #ORM\Column(name="category", type="integer")
*
* Many Products have one category
*
* #ORM\ManyToOne(targetEntity="Category", inversedBy="products")
* #ORM\JoinColumn(name="category_id", referencedColumnName="id", nullable=false)
*/
protected $category;
/**
* #var int
*
* Many Products have one color
*
* #ORM\ManyToOne(targetEntity="Color", inversedBy="products")
* #ORM\JoinColumn(name="color_id", referencedColumnName="id")
*
* #ORM\Column(name="color", type="integer")
*/
private $color;
}
#Category
/**
* Category
*
* #ORM\Table(name="category")
* #ORM\Entity(repositoryClass="AppBundle\Repository\CategoryRepository")
*/
class Category
{
/**
* #var int
*
* #ORM\Column(name="id", type="integer")
* #ORM\Id
* #ORM\GeneratedValue(strategy="AUTO")
*/
private $id;
/**
* #var string
*
* #ORM\Column(name="name", type="string", length=170, unique=true)
*/
private $name;
/**
* #var string
*
* #ORM\Column(name="desc", type="string", length=170, nullable=true)
*/
private $description;
/**
* One Category has many products assigned to it
*
* #ORM\OneToMany(targetEntity="Product", mappedBy="category", cascade={"persist"})
*/
private $products;
/**
* Class Constructor
*
* #param None
* #return void
**/
public function __construct()
{
$this->products = new ArrayCollection();
}
}
#Color
class Color{
/**
* #var int
*
* #ORM\Column(name="id", type="integer")
* #ORM\Id
* #ORM\GeneratedValue(strategy="AUTO")
*/
private $id;
/**
* #var string
*
* #ORM\Column(name="color", type="string", length=191, unique=true)
*/
private $color;
/**
* #var string
*
* #ORM\Column(name="code", type="string", length=191, unique=true)
*/
private $hexcode;
/**
* One Color has many products assigned to it
*
* #ORM\OneToMany(targetEntity="Product", mappedBy="color", cascade={"persist"})
*/
private $products;
/**
* Class Constructor
*
* #param none
* #return void
**/
public function _construct(){
$this->products = new ArrayCollection();
}
}
When I run:
* php bin/console doctrine:schema:validate:
I get the error messages:
* The association AppBundle\Entity\Category#products refers to the owning
side field AppBundle\Entity\Product#category which is not defined as
association, but as field.
* The association AppBundle\Entity\Category#products refers to the owning
side field AppBundle\Entity\Product#category which does not exist.
and:
* The association AppBundle\Entity\Color#products refers to the owning side
field AppBundle\Entity\Product#color which is not defined as association,
but as field.
* The association AppBundle\Entity\Color#products refers to the owning side
field AppBundle\Entity\Product#color which does not exist.
I noticed that whenever I comment out the lines:
** #ORM\Column(name="category", type="integer")
** #ORM\Column(name="color", type="integer")
The above errors vanish but I get a new message and error saying:
** [Mapping] OK - The mapping files are correct.
** [Database] FAIL - The database schema is not in sync with the current
mapping file.
What could i be doing wrong, am new to the doctrine concept, and i have followed the documentations. Any help would be appreciated...
For the Doctrine ORM the relations are not Integers but Entity objects.
Remove The #ORM\Column annotations from all fields with relations (in each entity).
Then update your database schema in development environment with:
php bin/console doctrine:schema:update --force
Then generate a doctrine migration file to execute on your production server
php bin/console doctrine:migrations:generate
Update the generetad migration file to your needs
And execute him by this way
php bin/console doctrine:migrations:execute timestampOfTheMigrateFile

Symfony relational mapping many to one

In my application I have a cigar humidor entity that has 8 slots. I would like each slot to hold an instance of any given of the hundreds of cigars there are to pick from.I can add a cigar to the cigar slot once for one humidor but I can not swap to a different humidor and add the same cigar to the slot1. I was thinking that with being in different humidors that it would surely not be an issue but I am now getting the exception "An exception occurred while executing 'UPDATE humidor SET slot_1 = ? WHERE id = ?' with params [2, 8]:SQLSTATE[23000]: Integrity constraint violation: 1062 Duplicate entry '2' for key 'UNIQ_4AE64E7F3CF622F8'"' I'm not exactly an expert with doctrine and am not totally sure on how I should go about modeling this. Any advice would be fantastic.
Here is the humidor with the slots
/**
* Humidor
*
* #ORM\Table(name="humidor")
* #ORM\Entity(repositoryClass="AppBundle\Repository\HumidorRepository")
*/
class Humidor
{
/**
* #var int
*
* #ORM\Column(name="id", type="integer")
* #ORM\Id
* #ORM\GeneratedValue(strategy="AUTO")
*/
private $id;
/**
* #var string
*
* #ORM\Column(name="name", type="string", length=255, nullable=true)
*/
private $name;
/**
* #ORM\ManyToOne(targetEntity="UserBundle\Entity\User", inversedBy="humidors")
* #ORM\JoinColumn(name="user_id", referencedColumnName="id")
*/
private $user;
/**
* #ORM\OneToOne(targetEntity="Cigar")
* #ORM\JoinColumn(name="slot_1", referencedColumnName="id")
*/
private $slot1;
/**
* #ORM\OneToOne(targetEntity="Cigar")
* #ORM\JoinColumn(name="slot_2", referencedColumnName="id")
*/
private $slot2;
/**
* #ORM\OneToOne(targetEntity="Cigar")
* #ORM\JoinColumn(name="slot_3", referencedColumnName="id")
*/
private $slot3;
/**
* #ORM\OneToOne(targetEntity="Cigar")
* #ORM\JoinColumn(name="slot_4", referencedColumnName="id")
*/
private $slot4;
/**
* #ORM\OneToOne(targetEntity="Cigar")
* #ORM\JoinColumn(name="slot_5", referencedColumnName="id")
*/
private $slot5;
/**
* #ORM\OneToOne(targetEntity="Cigar")
* #ORM\JoinColumn(name="slot_6", referencedColumnName="id")
*/
private $slot6;
/**
* #ORM\OneToOne(targetEntity="Cigar")
* #ORM\JoinColumn(name="slot_7", referencedColumnName="id")
*/
private $slot7;
/**
* #ORM\OneToOne(targetEntity="Cigar")
* #ORM\JoinColumn(name="slot_8", referencedColumnName="id")
*/
private $slot8;
and then my cigar entity
/**
* Cigar
*
* #ORM\Table(name="cigar")
* #ORM\Entity(repositoryClass="AppBundle\Repository\CigarRepository")
* #ORM\HasLifecycleCallbacks()
*/
class Cigar
{
/**
* #ORM\PrePersist()
*/
public function onPrePersist(){
$this->setName($this->getManufacturer()->getName() . " " . $this->getVariant());
}
/**
* #var int
*
* #ORM\Column(name="id", type="integer")
* #ORM\Id
* #ORM\GeneratedValue(strategy="AUTO")
*/
private $id;
/**
* #var int
*
* #ORM\Column(name="gauge", type="integer")
*/
private $gauge;
/**
* #var string
*
* #ORM\Column(name="body", type="string", length=255)
*/
private $body;
/**
* #var string
*
* #ORM\Column(name="wrapper_country", type="string", length=255)
*/
private $wrapperCountry;
/**
* #var string
*
* #ORM\Column(name="variant", type="string", length=255)
*/
private $variant;
/**
* #var string
*
* #ORM\Column(name="description", type="text")
*/
private $description;
/**
* #var string
*
* #ORM\Column(name="filler_country", type="string", length=255)
*/
private $fillerCountry;
/**
* #ORM\ManyToOne(targetEntity="Manufacturer", inversedBy="cigars")
* #JoinColumn(name="manufacturer_id", referencedColumnName="id")
*/
private $manufacturer;
/**
* #ORM\ManyToOne(targetEntity="Wrapper", inversedBy="cigars")
* #JoinColumn(name="wrapper_id", referencedColumnName="id")
*/
private $wrapper;
/**
* #ORM\ManyToOne(targetEntity="Shape", inversedBy="cigars")
* #JoinColumn(name="shape_id", referencedColumnName="id")
*/
private $shape;
/**
* #ORM\Column(type="string")
*
*/
private $image;
/**
* #var string
* #ORM\Column(type="string")
*/
private $name;
As you have one-to-one mapping, doctrine creates unique index for those columns. Let's consider this example (I've left only single slot as it's enough for this example):
id slot_1
1 11
2 12
This means that you have 2 cigars with IDs 11 and 12 assigned to humidor 1 and 2 respectively. There is unique index on slot_1 column here - this guarantees that any single cigar does not belong to two different humidors.
If you try to switch them, following SQL statements are generated:
UPDATE humidors SET slot_1 = 12 WHERE id = 1;
UPDATE humidors SET slot_1 = 11 WHERE id = 2;
Unfortunately, first statement cannot be executed, as database does not allow cigar 12 to be in both humidor 1 and 2 at once.
Simplest solution would be to change one-to-one relations to many-to-one (in your $slotX fields) - this would remove unique constraints, otherwise it would work the same in your example as there is no reverse relation. Furthermore, as there are 8 slots and cigars cannot belong to several of these (if I correctly understand), rules are already not strictly controlled by the database itself.
Another way would be to switch those with temporary null values etc. but it's even harder with doctrine, as you would need two separate flush statements and, optionally, manually wrapping these in a transaction to avoid inconsistent state in the database.

Doctrine cascade removing fails with OneToMany and OneToOne

I've been struggling for the past few days on a simple case of cascade removing using Doctrine.
Doctrine and Symfony are up to date.
I have two entities Serie and Asset that are linked to each other by two relationships OneToOne and OneToMany.
The schema is exactly like this :
A Serie has many Assets. (content).
A Serie can have an Asset. (a preview, this field is nullable).
However, no matter how I try to write and rewrite the annotations, I ALWAYS end up with this error:
An exception occurred while executing 'DELETE FROM serie WHERE id = ?' with params [1]:
SQLSTATE[23000]: Integrity constraint violation: 1451 Cannot delete or
update a parent row: a foreign key constraint fails
(galanthis.asset, CONSTRAINT FK_2AF5A5CAA3A9334 FOREIGN KEY
(serie) REFERENCES serie (id))
Of course, the problem disappear if I delete the "preview" field and its annotations in the following code:
/**
* Serie
*
* #ORM\Table(name="serie")
* #ORM\Entity(repositoryClass="Gedmo\Sortable\Entity\Repository\SortableRepository")
* #ORM\HasLifecycleCallbacks
*/
class Serie
{
/**
* #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=96)
*/
private $title;
/**
* #var integer
*
* #Gedmo\SortablePosition
* #ORM\Column(name="position", type="integer", nullable=true)
*/
private $position;
/**
* #var \Portfolio
*
* #ORM\ManyToOne(targetEntity="Portfolio", inversedBy="series")
* #ORM\JoinColumns({
* #ORM\JoinColumn(name="portfolio", referencedColumnName="id")
* })
*/
private $portfolio;
/**
* #var \Asset
*
* #ORM\OneToOne(targetEntity="Asset")
* #ORM\JoinColumns({
* #ORM\JoinColumn(name="preview", referencedColumnName="id", nullable=true, onDelete="SET NULL")
* })
*/
private $preview;
/**
* #var \Doctrine\Common\Collections\Collection
*
* #ORM\OneToMany(targetEntity="Asset", mappedBy="serie", cascade={"remove"})
**/
private $assets;
Here's the code for the Asset entity:
/**
* Asset
*
* #ORM\Table(name="asset")
* #ORM\Entity(repositoryClass="Gedmo\Sortable\Entity\Repository\SortableRepository")
* #ORM\HasLifecycleCallbacks
* #ORM\InheritanceType("SINGLE_TABLE")
* #ORM\DiscriminatorColumn(name="asset", type="string")
* #ORM\DiscriminatorMap({"asset" = "Asset", "video" = "Video","image" = "Image"})
*
*/
class Asset
{
/**
* #var integer
*
* #ORM\Column(name="id", type="integer")
* #ORM\Id
* #ORM\GeneratedValue(strategy="AUTO")
*/
protected $id;
/**
* #var string
*
* #ORM\Column(name="path", type="string", length=128)
*/
protected $path;
/**
* #var string
*
* #ORM\Column(name="filename", type="string", length=64)
*/
protected $filename;
/**
* #var integer
*
* #ORM\Column(name="position", type="integer", nullable=true)
* #Gedmo\SortablePosition
*/
protected $position;
/**
* #var string
*
* #ORM\Column(name="description", type="string", length=255, nullable=true)
*/
protected $description;
/**
* #var string
*
* #ORM\Column(name="mime", type="string", length=16, nullable=true)
*/
protected $mime;
/**
* #var \Serie
*
* #ORM\ManyToOne(targetEntity="Serie", inversedBy="assets")
* #ORM\JoinColumns({
* #ORM\JoinColumn(name="serie", referencedColumnName="id")
* })
*/
protected $serie;
/**
* #var UploadedFile
*/
protected $file;
/**
* #var string
*/
protected $extension;
It's driving me crazy, it's just some simple relationships... Is there a mistake I'm not seeing anymore, or do i need to use a workaround?
My guess is to set the cascade={"remove"} on the ManyToOne relationship in the Asset entity and not the other way around. That way, it tells Doctrine what to do when you delete a serie that is linked to many assets.

Categories