I'm working on a project in Symfony2 with a legacy database, that uses a string to have a 6 digit 'franchisee_number' with floating 0's on the front - eg. 000123. When I try to flush the entity, I get an error that shows the SQL statement it tried to do, and it doesn't even try to insert to insert my primary key field. Up until the point of flushing the entity, the 'franchisee_id' is showing up in the entity, so I'm assuming there's a problem with Doctrine not wanting to set the Primary Key, but have it generated.
My problem is very similar to this question: when flush() primary key does not get inserted, but I've tried the answers listed, and they don't work.
Here is the code from my Entity for the 'franchisee_number' field:
/**
* #var string
* #ORM\Id
* #ORM\GeneratedValue(strategy="NONE")
* #ORM\Column(name="franchisee_number", type="string", length=255, nullable=false)
*
*/
private $franchiseeNumber;
I assumed that #ORM\GeneratedValue(strategy="NONE") would tell doctrine I'm inserting my own value into the field, and not trying to generate it.
And my controller code
public function createAction(Request $request)
{
$this->denyAccessUnlessGranted('ROLE_ADMIN');
$entity = new Accounts();
$form = $this->createCreateForm($entity);
$form->handleRequest($request);
if ($form->isValid()) {
$em = $this->getDoctrine()->getManager('inertia');
$em->persist($entity);
$em->flush();
return $this->redirect($this->generateUrl('accounts_show', array('id' => $entity->getId())));
}
return $this->render('InertiaBundle:Accounts:new.html.twig', array(
'entity' => $entity,
'form' => $form->createView(),
));
}
I also made sure there were no xml config files in the Bundle/Resources/config/doctrine directory.
Thanks in advance for the help!
I wasted a lot of time trying to figure this out, and couldn't really find a great answer. I just decided to do it the hard way and wrote a custom query with Doctrine DBAL, inspired by this question/answer: https://stackoverflow.com/a/15650290/1647183
It works like a charm for me!
If you use strategy="NONE", you must create a identifier in __construct or another systems before persist/flush entity.
Example:
class MyEntity
{
/**
* #var string
*
* #ORM\Id
* #ORM\Column(type="string", name="id")
* #ORM\GeneratedValue(strategy="NONE")
*/
private $id;
public function __construct()
{
$this->id = 123; // Inject you custom ID here!
}
}
Related
I have 3 Entities (Group, GroupCategory and GroupLanguage)
Group Table
id (pk)
id_group_category (fk)
GroupCategory Table
id (pk)
GroupCategoryLanguage Table
id (pk)
id_language (fk)
id_group_category (fk)
I have created a GroupType which takes in GroupCategory as a subform.
$builder->add('id_group_category', 'entity', array(
'class' => 'BazaarBundle:GroupCategory',
'property' => 'id',
'query_builder' => function(EntityRepository $a) {
return $a->createQueryBuilder('a')
->innerJoin('BazaarBundle:GroupCategoryLanguage', 'b')
->where('b.id_group_category = a.id')
->orderBy('a.id', 'ASC');
}
)
)
->add('Add', 'submit');
I'm trying to innerJoin the language table so that the dropdownlist would be populated with text and not the ids of the category.
I'm quite new to Symfony2 and have already looked up to their documentation and sorry to say it was quite puzzling for me. Am I doing it right because i'm having some errors with the code.
The error message:
[Semantical Error] line 0, col 111 near 'id_group_category': Error: Class Karl\BazaarBundle\Entity\GroupCategoryLanguage has no field or association named id_group_category
GroupCategory.php
class GroupCategory
{
public function __construct()
{
$this->groupCategoryLanguage = new ArrayCollection();
}
public function __toString(){
return $this->groupCategoryLanguage->getName();
}
/**
* #ORM\OneToMany(targetEntity="GroupCategoryLanguage", mappedBy="idGroupCategory")
* #ORM\JoinColumn(nullable=false,referencedColumnName="id_group_category")
*/
protected $groupCategoryLanguage;
}
GroupCategoryLanguage.php
class GroupCategoryLanguage
{
/**
* #var integer
*
* #ORM\Column(name="id", type="integer")
* #ORM\Id
* #ORM\GeneratedValue(strategy="AUTO")
*/
private $id;
/**
* #var integer
*
* #ORM\Column(name="id_language", type="integer")
*/
private $idLanguage;
/**
* #var integer
*
* #ORM\JoinColumn(name="id_group_category", nullable=false)
* #ORM\ManyToOne(targetEntity="Karl\BazaarBundle\Entity\GroupCategory", inversedBy="groupCategoryLanguage")
*/
private $idGroupCategory;
/**
* #var string
*
* #ORM\Column(name="name", type="string", length=32)
*/
private $name;
}
i think you need to add GroupCategoryLanguage data into your query by adding:
->addSelect('b')
to your query builer object.
Exemple below.
Please note i have deleted the where condition because it seems to be a join condition, adn this is not needed bacause Doctrine is suposed to know all about relations. If i'm wrong, don't delete it...
$builder->add('id_group_category', 'entity', array(
'class' => 'BazaarBundle:GroupCategory',
'property' => 'id',
'query_builder' => function(EntityRepository $a) {
return $a->createQueryBuilder('a')
->innerJoin('a.languages', 'b')
->addSelect('b')
->orderBy('a.id', 'ASC');
}
))
->add('Add', 'submit');
EDIT:
Regarding our discussions, i update my answer:.
Let's start from the beginning:
You have a relation between GroupCategory and GroupCategoryLanguage and the GroupCategoryLanguage is the owner of this relation (it have to FK).
Here you want to get languages from the GroupCategory so it's $owner->getSlave() and you need a bidirectionnal relation.
For that you need to add a field into the slave entity:
So in GroupCategory entity:
/**
* #ORM\OneToMany(targetEntity="Karl\BazaarBundle\Entity\GroupCategoryLanguage",referencedColumnName="id_group_category", mappedBy="category")
* #ORM\JoinColumn(nullable=false)
*/
private $languages;
And i assume that in GroupCategoryLanguages you have:
/**
* #ORM\ManyToOne(targetEntity="Karl\BazaarBundle\Entity\GroupCategory", inversedBy="languages")
* #ORM\JoinColumn(name="id_group_category", nullable=false)
*/
private $category;
I think one of your problems is that you think in terms of tables, am i wrong ?
You really need to think in term of objects (entities) and let Doctrine manage the boring things :)
Display language in place of id
You can totally delete the 'property' option and add a __toString method into your GroupCategory entity, which one will be called and the returned value will appear in your form.
I think we are good :)
Cheers
I have a Person entity and an Address entity, set up with a bi-directional one-to-one relationship, with the FK on Address:
...
class Person
{
...
/**
* #ORM\OneToOne(targetEntity="Address", mappedBy="person")
*/
protected $address;
...
}
...
class Address
{
...
/**
* #ORM\Id
* #ORM\OneToOne(targetEntity="Person", inversedBy="address")
* #ORM\JoinColumn(name="personID", referencedColumnName="id")
*/
protected $person;
}
The Address Entity does NOT have a dedicated primary key, it instead derives its identity through the foreign key relationship with Person, as explained here
The form I have for creating a new Person also embeds the form for Address. When the form is submitted, this controller action is executed:
public function createAction(Request $request)
{
$person = new Person();
$form = $this->createCreateForm($person);
$form->handleRequest($request);
if ($form->isValid()) {
$em = $this->getDoctrine()->getManager();
$em->persist($person);
$em->flush();
return $this->redirect($this->generateUrl('people'));
}
return array(
'person' => $person,
'form' => $form->createView(),
);
}
I've inspected the data, and $person has its $address property filled out with the proper form details as expected. However, once the $person object is persisted, I get the error:
A new entity was found through the relationship
'Acme\AcmeBundle\Entity\Person#address' that was not configured to
cascade persist operations for entity...
I've tried a couple of things and none seem to work:
Setting cascade={"persist"} on the OneToOne relationship definition on the Person object. Doing so results in error:
Entity of type Acme\AcmeBundle\Entity\Address is missing an assigned
ID for field 'person'...
On the Person#setAddress method, I've taken the $address parameter and manually called $address->setPerson($this) on it. Doesn't work either.
It seems like my problem is that Doctrine is trying to save the Address object before saving the Person object, and it can't because it needs to know the ID of the associated Person first.
For instance, If I alter the the persist code to something like this, it works:
...
// Pull out the address data and remove it from the Person object
$address = $person->getAddress();
$person->setAddress(null);
// Save the person object and flush so we get an ID
$em->persist($person);
$em->flush();
// Now set the person object on the address and save the address
$address->setPerson($person);
$em->persist($address);
$em->flush();
...
How can I do this properly? I want to retain the ability to embed forms with this type of one-to-one relationship, but things are starting to get complicated. How do I get Doctrine to flush the $person object before flushing the $address object, without manually doing it myself like above?
Keep the cascade=persist.
Then modify Person::setAddress
class Person
{
public function setAddress($address)
{
$this->address = $address;
$address->setPerson($this); //*** This is what you are missing ***
This is a very common question but it's hard to search for.
Your mapping is incorrect. You are using #ORM\Id incorrectly on $person.
If you haven't yet, add a real $id to Address and add again `cascade={"persist"}.
class Person
{
...
/**
* #ORM\OneToOne(targetEntity="Address", mappedBy="person", cascade={"persist"})
*/
protected $address;
...
}
...
class Address
{
...
/**
* #ORM\Column(name="id", type="integer")
* #ORM\Id
* #ORM\GeneratedValue(strategy="AUTO")
*/
protected $id;
/**
* #ORM\OneToOne(targetEntity="Person", inversedBy="address")
* #ORM\JoinColumn(name="personID", referencedColumnName="id")
*/
protected $person;
}
If Person were the owning side, Address should also be automatically persisted by Doctrine, don't know your model but maybe you should consider changing it.
Doctrine's many-to-many logic is confusing me a bit. I have a pretty simple many-to-many relationship of recipes to categories. My base entity classes are equally simple.
The Recipe entity class...
class Recipe
{
/**
* #ORM\ManyToMany(targetEntity="Category", inversedBy="categories")
* #ORM\JoinTable(name="recipe_category")
**/
private $categories;
/**
* #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)
*/
private $title;
public function __construct() {
$this->categories = new \Doctrine\Common\Collections\ArrayCollection();
}
}
And the Category entity class...
class Category
{
/**
* #ORM\ManyToMany(targetEntity="Recipe")
**/
private $recipes;
/**
* #var integer
*
* #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;
public function __construct() {
$this->recipes = new \Doctrine\Common\Collections\ArrayCollection();
}
}
Seems pretty strait forward and matches Doctrine (and Symfony2's) documentation examples. The strange behavior comes when I try and generate the getters and setters for these classes via the Symfony console app.
The relationship setters/getters are incorrect. Take, for instance, the Category setter in the Recipe class that's generated...
/**
* Add categories
*
* #param \Namespace\CookbookBundle\Entity\Category $categories
* #return Recipe
*/
public function addCategorie(\Namespace\CookbookBundle\Entity\Category $categories)
{
$this->categories[] = $categories;
return $this;
}
It looks like the auto-generation of the method name is off. It should be "addCategory" and should be passed a "category."
While I can just correct this manually, if I re-run the entity generator, it will just add them again.
Am I doing this incorrectly or is this just a quirk of the entity generator? Can I specify an over-ride via annotation?
You're not doing anything wrong as that's how symfony generates them. I usually don't use the app/console to generate them as currently they're not doing a good job. One example is as you've mentioned the pluralization of words as you've mentioned. Another obvious one is the fact that it's using the [] notation which is pretty much treating an ArrayCollection object as a PHP array. You should never treat ArrayCollections as arrays.
This is how I have implemented it myself:
public function addCategory(Category $category)
{
if (!$this->categories->contains($category)
$this->categories->add($category);
return $this;
}
Which doesn't add duplicates to the Array collection if it's already added. Same thing goes with remove:
public function removeCategory(Category $category)
{
if ($this->categories->contains($category)
$this->categories->remove($category);
}
What I've run into many times is let's say you have 4 categories and you add and remove them
$r = new Recipe();
$c1 = new Category();
$c2 = new Category();
$r->addCategory($c1);
$r->addCategory($c2);
// at this point $r->getCategories()->toArray()[0] contains $c1
// and $r->getCategories()->toArray()[1] contains $c2
$r->removeCategory($c1);
// now $r->getCategories()->toArray()[0] is empty and
// $r->getCategories()->toArray()[1] contains $c2 still
// so in order to get the first category you need to:
$r->getCategories()->first();
You are not doing anything wrong. It is just that Doctrine automatically tries to singularize the names of method stubs whenever there is a plural name for a collection property. This is the function that Doctrine calls when you run the command doctrine:generate:entities:
$methodName = Inflector::singularize($methodName);
In your case, Doctrine tries to 'singularize' the word categories but fails to recognize the singular form correctly, so it just removes an 's' from the end returning categorie.
Also, as you see, Doctrine does not singularize the parameter passed to the method stubs, leaving $categories instead of being consistent and modifying it to $categorie.
If you want to avoid this, either you do not use plural words for collections, or use plural words and change the methods afterwards. As #keyboardSmasher comments to your post, doctrine won't overwrite methods you already have when using the command doctrine:generate:entities, and wrong methods won't hurt much if left there alone.
A final note: using ArrayCollections as arrays is perfectly fine, so this code is correct:
$this->categories[] = $category;
ArrayCollection object implements Collection, which in turn implements ArrayAccess. It is done this way precisely to be able to use ArrayCollections as Arrays.
I had a big time trying to figure out how to setup a ManyToOne -> OneToMany relationship with Doctrine 2 and it still not working...
Here is the application behaviour:
A site has Pages
A User can write Comment on a Page
Here are my Entities (simplified):
Comment Entity:
**
* #ORM\Entity
* #ORM\Table(name="comment")
*/
class Comment {
/**
* #ORM\Id
* #ORM\Column(type="integer")
* #ORM\GeneratedValue(strategy="AUTO")
*/
protected $id;
/**
* Many Comments have One User
*
* #ORM\ManyToOne(targetEntity="\Acme\UserBundle\Entity\User", inversedBy="comments")
*/
protected $user;
/**
* Many Comments have One Page
*
* #ORM\ManyToOne(targetEntity="\Acme\PageBundle\Entity\Page", inversedBy="comments")
*/
protected $page;
...
/**
* Set user
*
* #param \Acme\UserBundle\Entity\User $user
* #return Comment
*/
public function setUser(\Acme\UserBundle\Entity\User $user)
{
$this->user = $user;
return $this;
}
/**
* Set page
*
* #param \Acme\PageBundle\Entity\Page $page
* #return Comment
*/
public function setPage(\Acme\PageBundle\Entity\Page $page)
{
$this->page = $page;
return $this;
}
User Entity:
/**
* #ORM\Entity
* #ORM\Table(name="fos_user")
*/
class User extends BaseUser
{
/**
* #ORM\Id
* #ORM\Column(type="integer")
* #ORM\GeneratedValue(strategy="AUTO")
*/
protected $id;
/**
* The User create the Comment so he's supposed to be the owner of this relationship
* However, Doctrine doc says: "The many side of OneToMany/ManyToOne bidirectional relationships must be the owning
* side", so Comment is the owner
*
* One User can write Many Comments
*
* #ORM\OneToMany(targetEntity="Acme\CommentBundle\Entity\Comment", mappedBy="user")
*/
protected $comments;
...
/**
* Get Comments
*
* #return \Doctrine\Common\Collections\Collection
*/
public function getComments() {
return $this->comments ?: $this->comments = new ArrayCollection();
}
Page Entity:
/**
* #ORM\Entity
* #ORM\Table(name="page")
*/
class Page
{
/**
* #ORM\Id
* #ORM\Column(type="integer")
* #ORM\GeneratedValue(strategy="AUTO")
*/
protected $id;
/**
* One Page can have Many Comments
* Owner is Comment
*
* #ORM\OneToMany(targetEntity="\Acme\CommentBundle\Entity\Comment", mappedBy="page")
*/
protected $comments;
...
/**
* #return \Doctrine\Common\Collections\Collection
*/
public function getComments(){
return $this->comments ?: $this->comments = new ArrayCollection();
}
I want a bidirectional relationship to be able to get the collection of Comments from the Page or from the User (using getComments()).
My problem is that when I try to save a new Comment, I get an error saying that doctrine is not able to create a Page entity. I guess this is happening because it's not finding the Page (but it should) so it's trying to create a new Page entity to later link it to the Comment entity that I'm trying to create.
Here is the method from my controller to create a Comment:
public function createAction()
{
$user = $this->getUser();
$page = $this->getPage();
$comment = new EntityComment();
$form = $this->createForm(new CommentType(), $comment);
if ($this->getRequest()->getMethod() === 'POST') {
$form->bind($this->getRequest());
if ($form->isValid()) {
$em = $this->getDoctrine()->getManager();
$comment->setPage($page);
$comment->setUser($user);
$em->persist($comment);
$em->flush();
return $this->redirect($this->generateUrl('acme_comment_listing'));
}
}
return $this->render('AcmeCommentBundle:Default:create.html.twig', array(
'form' => $form->createView()
));
}
I don't understand why this is happening. I've checked my Page object in this controller (returned by $this->getPage() - which return the object stored in session) and it's a valid Page entity that exists (I've checked in the DB too).
I don't know what to do now and I can't find anyone having the same problem :(
This is the exact error message I have:
A new entity was found through the relationship
'Acme\CommentBundle\Entity\Comment#page' that was not configured to
cascade persist operations for entity:
Acme\PageBundle\Entity\Page#000000005d8a1f2000000000753399d4. To solve
this issue: Either explicitly call EntityManager#persist() on this
unknown entity or configure cascade persist this association in the
mapping for example #ManyToOne(..,cascade={"persist"}). If you cannot
find out which entity causes the problem implement
'Acme\PageBundle\Entity\Page#__toString()' to get a clue.
But I don't want to add cascade={"persist"} because I don't want to create the page on cascade, but just link the existing one.
UPDATE1:
If I fetch the page before to set it, it's working. But I still don't know why I should.
public function createAction()
{
$user = $this->getUser();
$page = $this->getPage();
// Fetch the page from the repository
$page = $this->getDoctrine()->getRepository('AcmePageBundle:page')->findOneBy(array(
'id' => $page->getId()
));
$comment = new EntityComment();
// Set the relation ManyToOne
$comment->setPage($page);
$comment->setUser($user);
$form = $this->createForm(new CommentType(), $comment);
if ($this->getRequest()->getMethod() === 'POST') {
$form->bind($this->getRequest());
if ($form->isValid()) {
$em = $this->getDoctrine()->getManager();
$em->persist($comment);
$em->flush();
return $this->redirect($this->generateUrl('acme_comment_listing'));
}
}
return $this->render('AcmeCommentBundle:Default:create.html.twig', array(
'form' => $form->createView()
));
}
UPDATE2:
I've ended up storing the page_id in the session (instead of the full object) which I think is a better idea considering the fact that I won't have a use session to store but just the id. I'm also expecting Doctrine to cache the query when retrieving the Page Entity.
But can someone explain why I could not use the Page entity from the session? This is how I was setting the session:
$pages = $site->getPages(); // return doctrine collection
if (!$pages->isEmpty()) {
// Set the first page of the collection in session
$session = $request->getSession();
$session->set('page', $pages->first());
}
Actually, your Page object is not known by the entity manager, the object come from the session. (The correct term is "detached" from the entity manager.)
That's why it tries to create a new one.
When you get an object from different source, you have to use merge function. (from the session, from an unserialize function, etc...)
Instead of
// Fetch the page from the repository
$page = $this->getDoctrine()->getRepository('AcmePageBundle:page')->findOneBy(array(
'id' => $page->getId()
));
You can simply use :
$page = $em->merge($page);
It will help you if you want to work with object in your session.
More information on merging entities here
I am trying to persist an user entity with a profile entity from a single form submit. Following the instructions at the Doctrine2 documentation and after adding additional attributes this seemed to be sufficient to achieve the goal.
Entities
Setting up the entites in accordance is pretty straight forward and resulted in this (I left out the generated getter/setter):
// ...
/**
* #ORM\Entity
*/
class User
{
/**
* #ORM\Id
* #ORM\Column(type="integer")
* #ORM\GeneratedValue
*/
private $id;
/**
* #ORM\Column(type="string", length=64)
*/
private $data;
/**
* #ORM\OneToOne(targetEntity="Profile", mappedBy="user", cascade={"persist", "remove"})
*/
private $Profile;
// ...
}
// ...
/**
* #ORM\Entity
*/
class Profile
{
/**
* #ORM\Id
* #ORM\OneToOne(targetEntity="User")
*/
private $user;
/**
* #ORM\Column(type="string", length=64)
*/
private $data;
// ...
}
Forms
Now modifiying the forms is not too difficult as well:
// ...
class ProfileType extends AbstractType
{
public function buildForm(FormBuilder $builder, array $options)
{
$builder
->add('data')
;
}
public function getName()
{
return 'profile';
}
public function getDefaultOptions(array $options)
{
return array('data_class' => 'Acme\TestBundle\Entity\Profile');
}
}
// ...
class TestUserType extends AbstractType
{
public function buildForm(FormBuilder $builder, array $options)
{
$builder
->add('data')
->add('Profile', new ProfileType())
;
}
public function getName()
{
return 'user';
}
}
Controller
class UserController extends Controller
{
// ...
public function newAction()
{
$entity = new User();
$form = $this->createForm(new UserType(), $entity);
return array(
'entity' => $entity,
'form' => $form->createView()
);
}
public function createAction()
{
$entity = new User();
$request = $this->getRequest();
$form = $this->createForm(new UserType(), $entity);
$form->bindRequest($request);
if ($form->isValid()) {
$em = $this->getDoctrine()->getEntityManager();
$em->persist($entity);
$em->flush();
return $this->redirect($this->generateUrl('user_show',
array('id' => $entity->getId())));
}
return array(
'entity' => $entity,
'form' => $form->createView()
);
}
// ...
}
But now comes the part where testing takes place. I start to create a new user-object, the embedded form shows up as expected, but hitting submit returns this:
Exception
Entity of type Acme\TestBundle\Entity\Profile is missing an
assigned ID. The identifier generation strategy for this entity
requires the ID field to be populated before EntityManager#persist()
is called. If you want automatically generated identifiers instead
you need to adjust the metadata mapping accordingly.
A possible solution I am already aware of is to add an additional column for a stand-alone primary key on the Profile entity.
However I wonder if there is a way to keep the mapping roughly the same but deal with persisting the embedded form instead?
After debating for quite a while with a couple of people via IRC I modified the mapping and came up with this:
Entities
// ...
/**
* #ORM\Entity
*/
class User
{
/**
* #ORM\Id
* #ORM\Column(type="integer")
* #ORM\GeneratedValue(strategy="AUTO")
*/
private $id;
/**
* #ORM\Column(type="string", length=64)
*/
private $data;
/**
* #ORM\OneToOne(targetEntity="Profile", cascade={"persist", "remove"})
*/
private $Profile;
// ...
}
// ...
/**
* #ORM\Entity
*/
class Profile
{
/**
* #ORM\Id
* #ORM\GeneratedValue(strategy="AUTO")
* #ORM\Column(type="integer")
*/
private $id;
/**
* #ORM\Column(type="string", length=64)
*/
private $data;
// ...
}
So what does this change? First of all I removed the mappedBy and inversedBy options for the relation. In addition the OneToOne annotation on the Profile-entity was not needed.
The relation between User and Profile can be bi-directional however a uni-directional relation with User being the owning side is sufficient to have control over the data. Due to the cascade option you can be sure there are no left-over Profiles without Users and Users can maintain a Profile but do not have to.
If you want to use a bi-directional relation I recommand taking a look at Github: Doctrine2 - Tests - DDC117 and especially pay attention to Article and ArticleDetails' OneToOne relation. However you need to be aware that saving this bi-directional relation is a bit more tricky as can be seen from the test file (link provided in comment): you need to persist the Article first and setup the constructor in ArticleDetails::__construct accordingly to cover the bi-directional nature of the relationship.
The problem from what I can see is that you're only creating / saving a User object.
As the User / Profile is a One to One relation (with User being the owning side) would it be safe to assume that a User will always have a Profile relation, and so could be initialised in the Users construction
class User
{
public function __construct()
{
$this->profile = new Profile();
}
}
After all you've set User up to cascade persistence of the related Profile object. This will then have your entity manager create both Entities and establish the relation.