I realize a web application with Symfony 2 from an existing database.
I have set up an entity with a primary key consisting of two foreign keys.
example:
Entity1 with a composite primary key: property1 (PK), property2 (PK)
Entity2 primary key consists of two foreign keys: property1 (PK FK), property2 (PK FK), propriete3 (PK)
I don't how to implement this association:
In the entity2 i do :
/**
* #ORM\ManyToOne (targetEntity = "Entity1")
* #ORM\JoinColumns ({
* #ORM\JoinColumn (name = "property1" referencedColumnName = "property1")
* #ORM\JoinColumn (name = "property2" referencedColumnName = "property2")
* #ORM\Id
* #})
*/
private $entity1;
But I get an error:
It is not possible to map entity 'ExempleBundle\Entity\Entite1' with a composite primary key as part of the primary key of another entity 'ExempleBundle\Entity\Entite2#entite1'.
How to properly handle this kind of association with Doctrine
I tried to follow this example but I do not understand : http://docs.doctrine-project.org/projects/doctrine-orm/en/latest/tutorials/composite-primary-keys.html#use-case-1-dynamic-attributes
Can you give an example of two entities with a similar case and especially on how to make a joint in this case.
I found a work-around that gets around the issue, by defining a separate foreign key, using the original foreign key columns as the join columns.
/** #Id #Column(...) */
protected $property1;
/** #Id #Column(...) */
protected $property2;
/** #Id #Column(...) */
protected $property3;
/**
* #ManyToOne(targetEntity="Entity1")
* #JoinColumns({
* #JoinColumn(name="property1", referencedColumnName="property1"),
* #JoinColumn(name="property2", referencedColumnName="property2")
* })
**/
protected $foreignObject;
It is a example that works:
<?php
namespace Project\WorkflowBundle\Entity;
use Doctrine\ORM\Mapping as ORM;
/**
* #ORM\Entity
* #ORM\Table(name="opinion")
*/
class Opinion
{
/**
* #ORM\Id
* #ORM\ManyToOne(targetEntity="\Project\WorkflowBundle\Entity\Comision_Proyecto", inversedBy="opiniones")
*/
protected $comision;
/**
* #ORM\Id
* #ORM\ManyToOne(targetEntity="\Project\WorkflowBundle\Entity\Persona", inversedBy="opiniones")
*/
protected $persona;
/**
* #ORM\OneToMany(targetEntity="\Project\WorkflowBundle\Entity\Comentario", mappedBy="opinion")
*/
protected $comentarios;
}
the other class:
<?php
namespace Project\WorkflowBundle\Entity;
use Doctrine\ORM\Mapping as ORM;
/**
* #ORM\Entity
* #ORM\Table(name="comentario")
*/
class Comentario
{
/**
* #ORM\Id
* #ORM\Column(type="integer")
* #ORM\GeneratedValue
*/
protected $id;
/**
* #ORM\Column(type="boolean")
*/
protected $privado;
/**
* #ORM\ManyToOne(targetEntity="\Project\WorkflowBundle\Entity\Opinion")
* #ORM\JoinColumns({
* #ORM\JoinColumn(name="comision_id", referencedColumnName="comision_id"),
* #ORM\JoinColumn(name="persona_id", referencedColumnName="persona_id")
* })
*/
protected $opinion;
}
Doctrine2 do not manage foreign composite keys to reference an entity with its composite primary keys
The cases where Doctrine2 manage correctly composite primary keys are mainly :
OneToMany: The associated entity (ArticleAttributes) uses as primary key, the primary key of the referenced entity (Artile) and an other self field (attribute)
Article (id, title, ...),
ArticleAttributes (#article_id, #attribute, value, ...)
http://docs.doctrine-project.org/projects/doctrine-orm/en/latest/tutorials/composite-primary-keys.html#use-case-1-dynamic-attributes
ManyToMany: Join-Table with Metadata
http://docs.doctrine-project.org/projects/doctrine-orm/en/latest/tutorials/composite-primary-keys.html#use-case-3-join-table-with-metadata
In your case, you want to reference an entity which hasn't got a unqiue identifier but a composite keys, Doctrine do not manage this case. You can only have composite key for association entity type.
Generally, I avoid to use composite keys for the main models. I reserved composite keys for model of association type.
So a solution is to use a primary key for your main model Entity1
Hope this helps.
Related
There is an is_deleted column available in user table.
While a user is deleted then record not deleted just is_deleted is set to 1.
Then i create new user with same phone number. it's display unique validation error.
I want to skip is_deleted record in validation.
My UserProfiles Entity
<?php
/**
* UserProfiles
*
* #ORM\Table(name="user_profiles")
* #ORM\Entity
*/
class UserProfiles
{
/**
* #var string
*
* #ORM\Column(name="phone_number", type="string", length=255, nullable=false)
*/
private $phoneNumber;
/**
* #ORM\OneToOne(targetEntity="ApiBundle\Entity\Users", inversedBy="userProfile")
* #ORM\JoinColumn(name="user_id", referencedColumnName="id", onDelete="CASCADE")
*/
private $user;
Validation constraint yml.
ApiBundle\Entity\UserProfiles:
constraints:
- Symfony\Bridge\Doctrine\Validator\Constraints\UniqueEntity:
fields: [phoneNumber]
errorPath: phone_number
message: "Phone number is already exists."
I want to add user.is_deleted in fields:[phoneNumber, user.is_deleted]
I'm not sure UniqueEntity constraint could solve your issue here because is_deleted is on a different entity. Most probably you'll need to use Expression or Callback constraints so you can achieve more complex dynamic validation and get User fields from UserProfiles entity.
I have a mapped super class and two entities that extend that base class. I followed this docs, but when I run bin/console doctrine:schema:update both of my entities are generated only with the properties declared on the entity itself but not the ones declared in the mapped super class. What am I missing or doing wrong?
mapped super class:
<?php
namespace AppBundle\Model;
use Doctrine\ORM\Mapping as ORM;
use AppBundle\Entity\User;
/**
* #ORM\MappedSuperclass(repositoryClass="Doctrine\ORM\EntityRepository")
*/
class Comment {
/**
* #ORM\ManyToOne(targetEntity="User", inversedBy="comments")
* #ORM\JoinColumn(name="user_id", referencedColumnName="id")
*/
private $author;
/**
* #ORM\Column(name="content", type="text", length=500)
*/
private $content;
/**
* #ORM\Column(name="date", type="datetime")
*/
private $date;
// setters and getters...
}
extending entities:
<?php
namespace AppBundle\Entity;
use Doctrine\ORM\Mapping as ORM;
use AppBundle\Model\Comment;
/**
* #ORM\Entity
* #ORM\Table(name="component_comment")
*/
class ComponentComment extends Comment {
/**
* #ORM\Id
* #ORM\GeneratedValue(strategy="AUTO")
* #ORM\Column(type="integer")
*/
private $id;
/**
* #ORM\ManyToOne(targetEntity="ComponentComment", inversedBy="replies")
* #ORM\JoinColumn(name="reply_to", referencedColumnName="id")
*/
private $replyTo;
/**
* #ORM\OneToMany(targetEntity="ComponentComment", mappedBy="replyTo")
*/
private $replies;
/**
* #ORM\ManyToOne(targetEntity="Component", inversedBy="comments")
* #ORM\JoinColumn(name="component", referencedColumnName="id")
*/
private $targetComponent;
// setters and getters...
}
and
<?php
namespace AppBundle\Entity;
use Doctrine\ORM\Mapping as ORM;
use AppBundle\Model\Comment;
/**
* #ORM\Entity
* #ORM\Table(name="food_comment")
*/
class FoodComment extends Comment {
/**
* #ORM\Id
* #ORM\GeneratedValue(strategy="AUTO")
* #ORM\Column(type="integer")
*/
private $id;
/**
* #ORM\ManyToOne(targetEntity="FoodComment", inversedBy="replies")
* #ORM\JoinColumn(name="reply_to", referencedColumnName="id")
*/
private $replyTo;
/**
* #ORM\OneToMany(targetEntity="FoodComment", mappedBy="replyTo")
*/
private $replies;
/**
* #ORM\ManyToOne(targetEntity="Food", inversedBy="comments")
* #ORM\JoinColumn(name="food", referencedColumnName="id")
*/
private $targetFood;
// setters and getters...
}
And my corresponding database tables look like:
CREATE TABLE `component_comment` (
`id` int(11) NOT NULL AUTO_INCREMENT,
`reply_to` int(11) DEFAULT NULL,
`component` int(11) DEFAULT NULL,
PRIMARY KEY (`id`),
KEY `IDX_2478D345E2B0FBEB` (`reply_to`),
KEY `IDX_2478D34549FEA157` (`component`),
CONSTRAINT `FK_2478D34549FEA157` FOREIGN KEY (`component`) REFERENCES `component` (`id`),
CONSTRAINT `FK_2478D345E2B0FBEB` FOREIGN KEY (`reply_to`) REFERENCES `component_comment` (`id`)
) ENGINE=InnoDB DEFAULT CHARSET=utf8 COLLATE=utf8_unicode_ci
CREATE TABLE `food_comment` (
`id` int(11) NOT NULL AUTO_INCREMENT,
`reply_to` int(11) DEFAULT NULL,
`food` int(11) DEFAULT NULL,
PRIMARY KEY (`id`),
KEY `IDX_DBEB8E54E2B0FBEB` (`reply_to`),
KEY `IDX_DBEB8E54D43829F7` (`food`),
CONSTRAINT `FK_DBEB8E54D43829F7` FOREIGN KEY (`food`) REFERENCES `food` (`id`),
CONSTRAINT `FK_DBEB8E54E2B0FBEB` FOREIGN KEY (`reply_to`) REFERENCES `food_comment` (`id`)
) ENGINE=InnoDB DEFAULT CHARSET=utf8 COLLATE=utf8_unicode_ci
The columns from common properties inherited from the Comment parent class are missing. Why is wrong with my code?
I've fond an other solution that fits better my requirements, but I think I've figured out. I haven't tested it but very likely the problem is that the mapped super class has private properties instead of protected ones. So although they were mapped properly, the sub classes didn't inherit neither the properties nor the mappings belonging to them.
I think you should place the mapped superclass under AppBundle/Entity
I get unnecessary queries then entity has ManyToOne relationship with abstract class.
My classes structure:
/**
* #ORM\Entity
* #ORM\Table(name="tb_payment_info")
* #ORM\InheritanceType("JOINED")
* #ORM\DiscriminatorColumn(name="type", type="integer")
* #ORM\DiscriminatorMap({
* "0" = "PaymentInfoPaypal",
* "1" = "PaymentInfoSkrill",
* })
*/
abstract class AbstractPaymentInfo
{
/**
* #ORM\Column(name="payment_info_id", type="integer", nullable=false)
* #ORM\Id
* #ORM\GeneratedValue(strategy="IDENTITY")
*/
private $id;
}
/**
* #ORM\Entity
* #ORM\Table(name="tb_payment_info_paypal")
*/
class PaymentInfoPaypal extends AbstractPaymentInfo
{
}
/**
* #ORM\Entity
* #ORM\Table(name="tb_payment_info_skrill")
*/
class PaymentInfoSkrill extends AbstractPaymentInfo
{
}
My Payout class contains payment_info_id column from tb_payment_info table.
/**
* #ORM\Entity
* #ORM\Table(name="tb_payout")
*/
class Payout
{
/**
* #var AbstractPaymentInfo
*
* #ORM\ManyToOne(targetEntity="AbstractPaymentInfo")
* #ORM\JoinColumn(name="payment_info_id", referencedColumnName="payment_info_id")
*/
private $paymentInfo;
}
When I try to get any Payout entity, its paymentInfo initialize automatically. So:
$this->getEntityManager()->getRepository('TuoPayBundle:Payout')->find(255);
got 2 queries: first for Payout and second for its paymentInfo
$this->getEntityManager()->getRepository('TuoPayBundle:Payout')->findBy(['id'=>[255,256]]);
got 3 queries: first for Payout and second, third separate queries to init paymentInfo
How to achieve lazy load?
You cannot declare an abstract class in Doctrine 2 with #ORM\Entity notation. If you want to use abstract classes in your object model I suggest you check the documentation on Mapped Superclasses on how to do that correctly.
Most importantly you should declare the class with a special #ORM\MappedSuperClass annotation.
Keep in mind that Mapped superclasses come with restrictions. I quote:
A mapped superclass cannot be an entity, it is not query-able and persistent relationships defined by a mapped superclass must be unidirectional (with an owning side only). This means that One-To-Many associations are not possible on a mapped superclass at all. Furthermore Many-To-Many associations are only possible if the mapped superclass is only used in exactly one entity at the moment. For further support of inheritance, the single or joined table inheritance features have to be used.
I have a problem with Doctrine2 in Symfony2 and two relationed entities.
There is a user-entity that can (not must) have a usermeta-entity referenced which contains information like biography etc.
The usermeta is optional because user is imported by another system, while usermeta is managed in my application.
Of course I want to save both together, so that saving a user must create or update a usermeta-entity.
Both are joined by a column named aduserid (same name in both tables).
I've recognized that if usermeta is an optional reference the owning-side in this case should be usermeta, otherwise doctrine loads user and needs the usermeta entity - but it's not always there.
Please note the comments in User->setMeta..
/**
* User
*
* #ORM\Table(name="user")
* #ORM\Entity
*/
class User
{
/**
* #var Usermeta
* #ORM\OneToOne(targetEntity="Usermeta", mappedBy="user", cascade={"persist"})
*/
protected $meta;
public function getMeta()
{
return $this->meta;
}
/**
*
* #param Usermeta $metaValue
*/
public function setMeta($metaValue)
{
// I've tried setting the join-column-value here
// - but it's not getting persisted
// $metaValue->setAduserid($this->getAduserid());
// Then I've tried to set the user-object in Usermeta - but then
// it seems like Doctrine wants to update Usermeta and searches
// for ValId names aduserid (in BasicEntityPersister->_prepareUpdateData)
// but only id is given - so not luck here
// $metaValue->setUser($this);
$this->meta = $metaValue;
}
/**
* #var integer
*
* #ORM\Column(name="rowid", type="integer", nullable=false)
* #ORM\Id
* #ORM\GeneratedValue(strategy="IDENTITY")
*/
private $id;
/**
* Get rowid
*
* #return integer
*/
public function getId()
{
return $this->id;
}
/**
* #var integer
*
* #ORM\Column(name="ADuserid", type="integer", nullable=false)
*/
private $aduserid;
/**
* Set aduserid
*
* #param integer $aduserid
* #return User
*/
public function setAduserid($aduserid)
{
$this->aduserid = $aduserid;
return $this;
}
/**
* Get aduserid
*
* #return integer
*/
public function getAduserid()
{
return $this->aduserid;
}
// some mor fields....
}
And the Usermeta class:
/**
* Usermeta
*
* #ORM\Table(name="userMeta")
* #ORM\Entity
*/
class Usermeta
{
/**
* #ORM\OneToOne(targetEntity="User", inversedBy="meta")
* #ORM\JoinColumn(name="ADuserid", referencedColumnName="ADuserid")
*/
protected $user;
public function getUser()
{
return $this->$user;
}
public function setUser($userObj)
{
$this->user = $userObj;
}
/**
* #var integer
*
* #ORM\Column(name="id", type="integer", nullable=false)
* #ORM\Id
* #ORM\GeneratedValue(strategy="IDENTITY")
*/
private $id;
/**
* #var integer
*
* #ORM\Column(name="ADuserid", type="integer", nullable=false)
*/
private $aduserid;
/**
* Set aduserid
*
* #param integer $aduserid
* #return User
*/
public function setAduserid($aduserid)
{
$this->aduserid = $aduserid;
return $this;
}
/**
* Get aduserid
*
* #return integer
*/
public function getAduserid()
{
return $this->aduserid;
}
}
the controller code looks like this:
...
$userForm->bind($request);
if($userForm->isValid()) {
$em->persist($user);
$em->flush();
}
...
The Zdenek Machek comment is almost correct. As you can see from the Doctrine2 documentation, the nullable option should be in the join annotation (#JoinColumn), not in the mapping one (#OneToOne).
#JoinColumn doc:
This annotation is used in the context of relations in #ManyToOne, #OneToOne fields and in the Context of #JoinTable nested inside a #ManyToMany. This annotation is not required. If its not specified the attributes name and referencedColumnName are inferred from the table and primary key names.
Required attributes:
name: Column name that holds the foreign key identifier for this relation. In the context of #JoinTable it specifies the column name in the join table.
referencedColumnName: Name of the primary key identifier that is used for joining of this relation.
Optional attributes:
unique: Determines if this relation exclusive between the affected entities and should be enforced so on the database constraint level. Defaults to false.
nullable: Determine if the related entity is required, or if null is an allowed state for the relation. Defaults to true.
onDelete: Cascade Action (Database-level)
onUpdate: Cascade Action (Database-level)
columnDefinition: DDL SQL snippet that starts after the column name and specifies the complete (non-portable!) column definition. This attribute allows to make use of advanced RMDBS features. Using this attribute on #JoinColumn is necessary if you need slightly different column definitions for joining columns, for example regarding NULL/NOT NULL defaults. However by default a “columnDefinition” attribute on #Column also sets the related #JoinColumn’s columnDefinition. This is necessary to make foreign keys work.
http://doctrine-orm.readthedocs.org/en/latest/reference/annotations-reference.html#annref-joincolumn
#OneToOne doc:
The #OneToOne annotation works almost exactly as the #ManyToOne with one additional option that can be specified. The configuration defaults for #JoinColumn using the target entity table and primary key column names apply here too.
Required attributes:
targetEntity: FQCN of the referenced target entity. Can be the unqualified class name if both classes are in the same namespace. IMPORTANT: No leading backslash!
Optional attributes:
cascade: Cascade Option
fetch: One of LAZY or EAGER
orphanRemoval: Boolean that specifies if orphans, inverse OneToOne entities that are not connected to any owning instance, should be removed by Doctrine. Defaults to false.
inversedBy: The inversedBy attribute designates the field in the entity that is the inverse side of the relationship.
http://doctrine-orm.readthedocs.org/en/latest/reference/annotations-reference.html#onetoone
You're using the wrong type of Relation for your problem.
What you want is a unidirectional one to one from Usermeta to User.
A bidirectional one to one relationship would mean the following:
A user MUST have a Usermeta object.
A Usermeta object MUST have a User.
In your case you're only trying to require the second condition.
This does mean that you can only hydrate User from Usermeta and not the other way around.
Unfortunately doctrine does not support Zero or One to Many relationships.
I got the error message "spl_object_hash() expects parameter 1 to be object, null given in..." while trying the same thing. I tried to define a bidirectional One to One relationship while the inversed value could be null. This gave the error message. Taking away the inversed side of the relationship solved the problem.
It is a pity that Zero or One to One relationships aren't supported.
I hope I do not disturb anyone by submitting this very late answer, but here is how I solved this problem:
/**
* #var Takeabyte\GripBundle\Entity\PDF
* #ORM\OneToOne(targetEntity="Takeabyte\GripBundle\Entity\PDF", inversedBy="element", fetch="EAGER", orphanRemoval=true)
*/
protected $pdf = null;
I added = null; to the attribute declaration. I hope this is of any help for anyone who reads this.
Reading my own old question is quite fun since I see the problem at first glance now..
When it came to a solution I've thought that doctrine can only handle Ids named "id", but ... aduserid is just not marked as ID, it's missing the Id annotation and doctrine cannot use the fields for the join column..
Second thing, Zdenek Machek was right: It has to be marked as nullable.
Hi I have a problem with SonataAdminBundle.
I've created "Job" table in DB and I use in backend of my website.
When I insert data, I have an error with "not null" fields in my table Job.
For example I have "nb_comment" that is the number of comments of each job,so when I insert in backend all information about Job I don't use a NOT NULL field "nb_comment",and I have the following error :
PDOException: SQLSTATE[23000]: Integrity constraint violation: 1048 Column 'nb_comment' cannot be null
add in #ORM annotation nullable=true
use Doctrine\ORM\Mapping as ORM;
...
/**
* #var integer $nb_comment
* #ORM\Column(name="nb_comment", type="integer", nullable=true)
*/
private $nb_comment;
or add Constraint validator with #Assert declaration and initialize in construct
use Symfony\Component\Validator\Constraints as Assert;
/**
* #var integer $nb_comment
* #ORM\Column(name="nb_comment", type="integer")
* #Assert\NotNull()
*/
private $nb_comment;
public function __construct()
{
$this->nb_comment = 0;
}
I had the same problem I think and it works for me. You have to initialize the variable nb_comment in the model with null, like this:
/**
* #var integer $nb_comment
*/
private $nb_comment = null;