Doctrine 2 - Disallow null value on foreign keys of ManyToOne relationships - php

I have a ManyToOne relationship in one of my entities, like so:
class License {
// ...
/**
* Customer who owns the license
*
* #var \ISE\LicenseManagerBundle\Entity\Customer
* #ORM\ManyToOne(targetEntity="Customer", inversedBy="licenses")
* #ORM\JoinColumn(name="customer_id", referencedColumnName="id")
*/
private $customer;
// ...
}
class Customer {
// ...
/**
* Licenses that were at one point generated for the customer
*
* #var \Doctrine\Common\Collections\ArrayCollection
* #ORM\OneToMany(targetEntity="License", mappedBy="customer")
*/
private $licenses;
// ...
}
This generates a database schema where the "customer_id" field of the license table is allowed to be null, which is exactly what I do not want.
Here's some code where I create a record to prove that it indeed allows null values for the reference fields:
$em = $this->get('doctrine')->getEntityManager();
$license = new License();
// Set some fields - not the reference fields though
$license->setValidUntil(new \DateTime("2012-12-31"));
$license->setCreatedAt(new \DateTime());
// Persist the object
$em->persist($license);
$em->flush();
Basically, I don't want a License to be persisted without having a Customer assigned to it. Is there some annotation that needs to be set or should I just require a Customer object to be passed to my License's constructor?
The database engine I use is MySQL v5.1, and I am using Doctrine 2 in a Symfony2 application.

https://www.doctrine-project.org/projects/doctrine-orm/en/2.6/reference/annotations-reference.html#annref_joincolumn
Add nullable = false to the JoinColumn annotation:
#ORM\JoinColumn(..., nullable=false)

Just posting because #zim32 didn't tell where we should put the statement, so i had to make a trial and error.
Yaml:
manyToOne:
{field}:
targetEntity: {Entity}
joinColumn:
name: {field}
nullable: false
referencedColumnName: {id}
cascade: ['persist']

I couldn't find an XML example of how to do this, so I'm going to leave this snippet here in case anyone else is looking for this:
<many-to-one field="author" target-entity="User">
<join-column name="author_id" referenced-column-name="id" nullable="false" />
</many-to-one>
The name and referenced-column-name are required, see the docs: https://www.doctrine-project.org/projects/doctrine-orm/en/2.6/reference/xml-mapping.html#join-column-element

Related

DOCTRINE :: OneToOne Relation crashes on persisting

getting deeper into doctrine. But actually I get stucked with this.
Got this to persist:
$f = new Field();
$f->setValue(123);
$em = $this->getEntityManager();
$em->persist($f);
$em->flush();
In my Entity called Field I will relate to an Entity called Integer
/**
* #ORM\OneToOne(targetEntity="Integer", mappedBy="field", cascade="persist")
**/
private $integer;
In my Entity Integer I need to relate to Field like this
/**
* #ORM\OneToOne(targetEntity="Field", inversedBy="integer")
* #ORM\JoinColumn(name="fid", referencedColumnName="id")
**/
private $field;
it works, but how can I read the integer? I can see the field ID in the integer table for the new entry. But shouldn't be there a column in Fields pointing to integers?
I tried to set a column for relating to integers in fields entity like this:
* #ORM\JoinColumn(name="int", referencedColumnName="id")
But it did not work
cu n00n

Doctrine OneToOne identity through foreign entity exception on flush

I have User and UserProfile OneToOne–related Doctrine ORM entities. They should always exist as a pair, there should be no User without UserProfile.
User should get its id from autoincrement, while UserProfile should have User's id. So they both should have the same id and there is no other column to set up the relationship (Doctrine docs: Identity through foreign Entities).
UserProfile's id is both a primary key (PK) and foreign key (FK) at the same time.
I managed to set it up, but it requires that User is saved first and only later UserProfile is created and saved in a separate step.
What I want is that UserProfile is always created with User, in the constructor, but if I do that, I get this exception:
Doctrine\ORM\ORMInvalidArgumentException: The given entity of type 'AppBundle\Entity\UserProfile' (AppBundle\Entity\UserProfile#0000000052e1b1eb00000000409c6f2c) has no identity/no id values set. It cannot be added to the identity map.
Please see code below – it works, but not the way I want. The php comments show what I want to achieve.
Test.php:
/**
* It works, both saving and loading.
* BUT, it requires that I create and save UserProfile
* in a separate step than saving User step.
*/
// create and save User
$user = new User();
$objectManager->persist($user);
$objectManager->flush();
// create and save UserProfile (this should be unnecessary)
$user->createProfile()
$objectManager->flush();
User.php:
use Doctrine\ORM\Mapping as ORM;
/**
* #ORM\Entity(repositoryClass="AppBundle\Entity\UserRepository")
* #ORM\Table(name="users")
*/
class User
{
/**
* #var int
*
* #ORM\Column(name="uid", type="integer")
* #ORM\Id
* #ORM\GeneratedValue(strategy="AUTO")
*/
private $id;
/**
* It's NULL at first, I create it later (after saving User).
*
* #var UserProfile|null
*
* #ORM\OneToOne(targetEntity="UserProfile", mappedBy="user", cascade="persist")
*/
private $profile = null;
public function __construct()
{
// I want to create UserProfile inside User's constructor,
// so that it is always present (never NULL):
//$this->profile = new UserProfile($this);
// but this would give me error:
//
// Doctrine\ORM\ORMInvalidArgumentException:
// The given entity of type 'AppBundle\Entity\UserProfile'
// (AppBundle\Entity\UserProfile#0000000058af220a0000000079dc875a)
// has no identity/no id values set. It cannot be added to the identity map.
}
public function createProfile()
{
$this->profile = new UserProfile($this);
}
}
UserProfile.php:
use Doctrine\ORM\Mapping as ORM;
/**
* #ORM\Entity
* #ORM\Table(name="profiles")
*/
class UserProfile
{
/**
* – UserProfile's "uid" column points to User's "uid" column
* – it is PK (primary key)
* - it is FK (foreign key) as well
* – "owning side"
*
* #var User
*
* #ORM\Id
* #ORM\OneToOne(targetEntity="User", inversedBy="profile")
* #ORM\JoinColumn(name="uid", referencedColumnName="uid", nullable=false)
*/
private $user;
public function __construct(User $user)
{
$this->user = $user;
}
}
Test app: https://github.com/MacDada/DoctrineOneToOneTest
Please keep in mind that the actual object needs to be saved by the EntityManager.
Just giving the class as reference to the other class does not make the entityManager aware of the fact both classes exists.
You should persist the actual userProfile to the EntityManager to be able to save the relation.
UPDATE because of negative comment:
Please read the Doctrine docs... You should persist!
The following example is an extension to the User-Comment example of this chapter. Suppose in our application a user is created whenever he writes his first comment. In this case we would use the following code:
<?php
$user = new User();
$myFirstComment = new Comment();
$user->addComment($myFirstComment);
$em->persist($user);
$em->persist($myFirstComment);
$em->flush();
Even if you persist a new User that contains our new Comment this code would fail if you removed the call to EntityManager#persist($myFirstComment). Doctrine 2 does not cascade the persist operation to all nested entities that are new as well.
Update2:
I understand what it is you wish to accomplish, but by design you should not move this logic within your entities. Entities should represent as less logic as possible, since they represent your modal.
Have that said, I believe you could accomplish what you are trying to do like this:
$user = new User();
$profile = $user->getProfile();
$objectManager->persist($user);
$objectManager->persist($profile);
$objectManager->flush();
You should however consider creating a userService containing the entitymanager and make that responsible for creating, linking and persisting the user + userProfile entity.

Doctrine : Default value on joinColumn field

New to symfony2 and Doctrine.
How can I set a default value to the field foo_id (which is a reference on Foo table) to point on the ID 1 of the Foo table (which exists in all cases) ?
Me\NavigationBundle\Entity\PublicText:
type: entity
table: public_text
id:
id:
type: integer
id: true
generator:
strategy: AUTO
fields:
title:
type: string
length: '255'
nullable: false
content:
type: string
length: '2000'
nullable: false
manyToOne:
foo:
#How to set a default value???
targetEntity: \Me\NavigationBundle\Entity\Foo
joinColumn:
name: foo_id
referencedColumnName: id
nullable: false
lifecycleCallbacks: { }
I tried a lot of things without success :
Set default value to ID 1 in the constructor of Foo
Perform a request to retrieve the Foo object of ID 1 in the Me entity (could works, but bad practice)
Look up columnDefinition at Doctrine Column Annotations Reference¶
Pros: You can set up your own column definition
Cons: doctrine orm:schema-tool used by Doctrine (if you use it), gets confused and always reports columns that have a custom columnDefinition as changed. As it it will always tell you to run the change command for your column definition, as documented here:
SchemaTool will not detect changes on the column correctly anymore if you use "columnDefinition".
Example
/**
* #ManyToOne(targetEntity="YourType")
* #JoinColumn(
* name="your_type_id",
* referencedColumnName="id",
* nullable=false,
* columnDefinition="INT NOT NULL DEFAULT 1"
* )
*/
private $yourType;
Please note that using columnDefinition alone will work for generating migrations but will break the ORM context and potentially cause FK integrity issues. You will still need to add the object association to the ORM for persisting entities. See warnings from Ocramius
Example:
(new PublicText())
->getFoo(); //null - expected object(Foo)#1 (1) { ["id"] => int(1) }
I have seen many ways to achieve this in doctrine 2.
Constructor
In general, the quickest way and what most users do is require an association in the constructor.
public function __construct(Foo $foo)
{
$this->foo = $foo;
}
Then you can use getReference to retrieve it in your controller without needing to query the database. See http://doctrine-orm.readthedocs.org/en/latest/reference/advanced-configuration.html#reference-proxies
$foo = $em->getReference('app:Foo', 1);
$text = new \Path\To\Entity\PublicText($foo);
$em->persist($text);
$em->flush();
LifeCycleEvents
My preferred way Another method to set the default value with most ManyToOne relationships is to utilize the LifeCycleEvents http://doctrine-orm.readthedocs.org/en/latest/reference/events.html
Though it does have some caveats to be aware of. So be sure to RTM before implementing into production environments. In this case it should work fine, but I don't know your entire mapping structure.
use Doctrine\ORM\Event\LifecycleEventArgs;
/**
* #Entity
* #HasLifeCycleEvents
*/
class PublicText
{
// ...
/**
* #PrePersist
* #param \Doctrine\ORM\Event\LifecycleEventArgs $event
*/
public function onPrePersist(LifecycleEventArgs $event)
{
if (false === empty($this->foo)) {
return;
}
$this->foo = $event->getEntityManager()->getReference('app:Foo', 1);
}
}
Then in your controller.
$text = new \Path\To\Entity\PublicText;
$em->persist($text); //retrieve and set foo if not set in the Entity Event.
$em->flush();
Repository Method
Another option within your Entity is to just set the property's value using a Repository.
Define Repository Class in Entity
/**
* #Entity(repositoryClass="PublicTextRepo")
*/
class PublicText
{
// ...
}
Repository
use Doctrine\ORM\EntityRepository;
class PublicTextRepo extends EntityRepository
{
public function create()
{
$text = new \Path\To\Entity\PublicText;
$foo = $this->_em->getReference('app:Foo', 1);
$text->setFoo($foo );
return $text;
}
}
Then in your controller you can then do
$text = $em->getRepository('app:PublicText')->create();
$em->persist($text);
$em->flush();
Discriminator Map
Though not always viable depending on the use case. One of the ways I go about defining a default value of an Entity is creating a DiscriminatorMap with single table inheritance. http://doctrine-orm.readthedocs.org/en/latest/reference/inheritance-mapping.html#single-table-inheritance.
This way when the object is created the default value is automatically set in the database, and locks the object as that type. The issue is that the resulting value is not an object like it is in the other methods above.
To get the object's discriminator static value, you can use constants within the objects you define.
/**
* #Entity
* #InheritanceType("SINGLE_TABLE")
* #Table(name="user")
* #DiscriminatorColumn(name="type", type="string")
* #DiscriminatorMap({Person::TYPE="Person", Employee::TYPE="Employee"})
*/
class Person
{
const TYPE = 'person';
/**
* #return string [person|employee]
*/
public function getType()
{
return $this::TYPE;
}
// ...
}
/**
* #Entity
*/
class Employee extends Person
{
const TYPE = 'employee';
// ...
}
Then all you need to do in your controller is.
$employee = new \Path\To\Entity\Employee;
$em->persist($employee); //inserts with `user.type` as `employee`
$em->flush();
echo $employee->getType(); //employee
With annotation you can use : options={"default" = YourValue} on a #ORM\Column, So in yaml I think you can add
options:
default: yourValue
I'm not sure, i gie you an idea...

symfony2 doctrine manytoone relationship

I believe it's that there must be a simple solution for my issue but I can't get that to work and I'm not sure why.
Scenario:
I've got 2 entities: Bike and Review, relationship is OneToMany:
/**
* Bike
*
* #ORM\Entity(repositoryClass="BikeRepository")
* #ORM\HasLifecycleCallbacks
* #ORM\Table(name="bike")
*/
class Bike
{
...
/**
* #var string
*
* #ORM\OneToMany(targetEntity="Review", mappedBy="bike", cascade={"persist"}, fetch="EXTRA_LAZY")
*/
private $reviews;
...
}
/**
* Review
*
* #ORM\Entity(repositoryClass="ReviewRepository")
* #ORM\Table(name="review")
* #ORM\HasLifecycleCallbacks()
*/
class Review
{
...
/**
* #var integer
*
* #ORM\ManyToOne(targetEntity="Bike", inversedBy="reviews", fetch="EXTRA_LAZY")
* #ORM\JoinColumn(name="bike_id", referencedColumnName="id")
*/
private $bike;
....
}
Getters and setters has been generated by doctrine.
On the frontend I've got a form where bike is as a hidden field because there is an autocomplete field with ajax request. So in the controller if $form->isValid() I'm getting bike id from the hidden field, search database for that bike and set bike as below:
if ($form->isValid()) {
if ($bikeId) {
$bike = $this->em->getRepository('BikeBundle:Bike')->findOneBy(array('id' => $bikeId));
$review->setBike($bike);
}
$this->em->persist($review);
$this->em->flush();
}
And this is always giving me NULL in the database for bike_id. Any idea what could be wrong? I have tried to dump $review before persist and I can get the bike details so I don't know what's going wrong. In the entity setBike() method I can get any value of that Bike object except getId() is returning NULL.
Thanks for any help.
you don't need to persist an existing entity, so you can just save your entity like
$review->setBike($bike);
$this->em->flush();
try to check if doctrine really returns any entities..I'm afraid if it doesn't return Bike entity
UPD: Also try to check the namespace of Bike Entity class. It seems like it is wrong in your case, because usually it looks like "AcmeBikeBundle:Bike"

Doctrine 2.2/Symfony 2.2: Persisting an entity with a composite foreign key

I must first establish that I'm a total newcomer to Doctrine, even though I know enough about SQL and PHP/Symfony 2.
So, I created this IssueType entity associated to a SQL table:
/**
* IssueType
*
* #ORM\Table()
* #ORM\Entity(repositoryClass="Blog\Bundle\CoreBundle\Entity\IssueTypeRepository")
*/
class IssueType
{
/**
* #var integer
*
* #ORM\Column(name="id", type="integer")
* #ORM\Id
*/
private $id;
/**
* #var string
*
* #ORM\Column(name="name", type="string", length=255)
*/
private $name;
// Getters, setters...
}
I populated it, so the content of said table is now:
id | name
1 | Bande dessinée
2 | Livre
3 | Film
4 | Disque
Now I have this other entity, Role, which uses a composite key, made up of a regular string (name) and a foreign key (id from IssueType):
/**
* Role
*
* #ORM\Table()
* #ORM\Entity(repositoryClass="Blog\Bundle\CoreBundle\Entity\RoleRepository")
*/
class Role
{
/**
* #var IssueType
*
* #ORM\ManyToOne(targetEntity="Blog\Bundle\CoreBundle\Entity\IssueType")
* #ORM\JoinColumn(nullable=false)
* #ORM\Id
*/
private $issueType;
/**
* #var string
*
* #ORM\Column(name="name", type="string", length=255)
* #ORM\JoinColumn(nullable=false)
* #ORM\Id
*/
private $name;
// Getters, setters...
}
Both tables are correctly generated by doctrine in the database. However, although it should be trivial, I can't for the life of me find a single example of a correct and successful persist operation in such a case.
What I try to do is the following:
$manager = $this->getDoctrine()->getManager();
$issueType = new IssueType();
$issueType->setId(1);
$role = new Role();
$role->setIssueType($issueType);
$role->setName('Dessinateur');
$manager->persist($role);
$manager->flush();
I thus try to persist the following:
Role: {
IssueType: {id: 1, name: ''},
name: 'Dessinateur',
}
And what I get is this nasty exception:
Entity of type Blog\Bundle\CoreBundle\Entity\Role has identity through a foreign entity Blog\Bundle\CoreBundle\Entity\IssueType, however this entity has no identity itself. You have to call EntityManager#persist() on the related entity and make sure that an identifier was generated before trying to persist 'Blog\Bundle\CoreBundle\Entity\Role'. In case of Post Insert ID Generation (such as MySQL Auto-Increment or PostgreSQL SERIAL) this means you have to call EntityManager#flush() between both persist operations.
I understand it wants me to persist first the foreign entity, but I don't want to do that, since the foreign issue type of ID#1 already exists in the database and thus don't need persisting. How can it ask me that when I did not specify any 'cascade' attribute in the annotations?
BTY I tried anyway to do as it says, and it expectedly ended up with a duplicate entry error.
So, what should I do to make Doctrine understand that the foreign issue type should not be persisted?
EDIT
artmees came up with the following solution, which works fine:
$manager = $this->getDoctrine()->getManager();
$issueType = $manager->getRepository('BlogCoreBundle:IssueType')->find(1);
$role = new Role();
$role->setIssueType($issueType);
$role->setName('Dessinateur');
$manager->persist($role);
$manager->flush();
However this implies making an additional request to the database which could have been avoided if not using Doctrine. Since I already know the foreign Id to use, is there any way to use it directly with the persist(), without going to such lengths as actually retrieving the full object from the database?
try this
$manager = $this->getDoctrine()->getManager();
$issueType = $manager->find('IssueTypeRepository', 1);
$role = new Role();
$role->setIssueType($issueType);
$role->setName('Dessinateur');
$manager->persist($role);
$manager->flush();
I know this is an old article, but...
Just to add to artmees answer, if you know the ID and you just want to insert that, you don't need to load the entity, simply use a reference.
$manager = $this->getDoctrine()->getManager();
$issueType = $manager->getReference('BlogCoreBundle:IssueType',1);
$role = new Role();
$role->setIssueType($issueType);
$role->setName('Dessinateur');
$manager->persist($role);
$manager->flush();
That will create a proxy with that ID (1), which is all you need in order to save the role entity.

Categories