Doctrine OneToMany not return relationships Collection - symfony

I have a OneToMany relation that doesn't return items if the method stays as the return with Collection type
Account::class
.
.
#[ORM\OneToMany(mappedBy: 'account', targetEntity: Folder::class, orphanRemoval: true)]
public Collection $folders;
.
.
/**
* #return Collection<int, Folder>
*/
public function getFolders(): Collection
{
return $this->folders;
}
Folder::class
#[ORM\ManyToOne(inversedBy: 'folders')]
#[ORM\JoinColumn(name: 'account_id', referencedColumnName: 'id', nullable: false)]
public ?Account $account = null;
`
If I change the return type from getFolders to array and call $this->folders->toArray() the data is returned.
Is this related to EAGER and LAZY? I can't understand what doctrine is doing here.
I thought that since it is a code generalized by symfony itself, the return with the Collection type should have the same result. Where am I getting lost?

Related

Bi-directionnal relationship inversedBy/mappedBy troubleshooting

I'm trying to do a follower/following system on Symfony, but get these errors:
The field App\Entity\User#following is on the owning side of a bi-directional relationship, but the specified mappedBy association on the target-entity App\Entity\User# does not contain the required 'inversedBy' attribute.
The field App\Entity\User#follower is on the owning side of a bi-directional relationship, but the specified mappedBy association on the target-entity App\Entity\User# does not contain the required 'inversedBy' attribute.
I made it this way:
#[ORM\ManyToMany(targetEntity: self::class, inversedBy: 'following')]
#[JoinTable(name: "user_following")]
private $following;
#[ORM\ManyToMany(targetEntity: self::class, inversedBy: 'follower')]
#[JoinTable(name: "user_follower")]
private $follower;
and (same thing for follower):
/**
* #return Collection<int, self>
*/
public function getFollowing(): Collection
{
return $this->following;
}
public function addFollowing(self $following): self
{
if (!$this->following->contains($following)) {
$this->following[] = $following;
}
return $this;
}
public function removeFollowing(self $following): self
{
$this->following->removeElement($following);
return $this;
}
It's working as it should, but the error is bothering me, and after checking the documentation/google for hours I can't fix it.
Thanks for your help

Doctrine flush() error: Expected value of type "Doctrine\Common\Collections\Collection|array"

I have a strange problem using a many-to-many relation in Symfony (with Doctrine), I've never had before in symfony projects with many-to-many relations and I can't find any difference to the other projects.
I have the two entitys Product and Tag and a many-to-many relation to each other. Unfortunately, if I try to add a product to a tag or vice versa, the error
Expected value of type "Doctrine\Common\Collections\Collection|array" for association field "TestBundle\Entity\Product#$tags", got "TestBundle\Entity\Tag" instead.
appears.
The code used to add a tag to a product:
$tag1 = $em->getRepository('TestBundle:Tag')->findOneBy(array(
'tag' => "Bla"
));
$tag1->addProduct($product);
$em->persist($tag1);
$em->persist($product);
$em->flush();
Of course, the variable $tag1 and $product both contain a valid entity.
The YAML file for the many-to-many relations (I cut away irrelevant parts):
TestBundle\Entity\Tag:
type: entity
table: tags
repositoryClass: TestBundle\Repository\TagRepository
id:
id:
type: integer
id: true
generator:
strategy: AUTO
fields:
tag:
type: string
length: 255
unique: true
manyToMany:
products:
targetEntity: Product
mappedBy: tags
lifecycleCallbacks: { }
Product:
TestBundle\Entity\Product:
type: entity
table: products
repositoryClass: TestBundle\Repository\ProductRepository
id:
id:
type: integer
id: true
generator:
strategy: AUTO
fields:
name:
type: string
length: 255
unique: true
manyToOne:
manufacturer:
targetEntity: Manufacturer
inversedBy: products
joinColumn:
name: manufacturer_id
referencedColumnName: id
onDelete: CASCADE
manyToMany:
tags:
targetEntity: Product
inversedBy: products
joinTable:
name: tags2products
joinColumns:
tag_id:
referencedColumnName: id
inverseJoinColumns:
product_id:
referencedColumnName: id
lifecycleCallbacks: { }
The setter and getter functions also don't contain any special tricks:
The Tag.php entity file contains:
/**
* Constructor
*/
public function __construct()
{
$this->product = new \Doctrine\Common\Collections\ArrayCollection();
}
/**
* Add product
*
* #param \TestBundle\Entity\Product $product
*
* #return Tag
*/
public function addProduct(\TestBundle\Entity\Product $product)
{
$product->addTag($this);
$this->product[] = $product;
return $this;
}
public function removeProduct(\TestBundle\Entity\Product $product)
{
$this->product->removeElement($product);
}
/**
* Get products
*
* #return \Doctrine\Common\Collections\Collection
*/
public function getProducts()
{
return $this->products;
}
While the Product.php contains:
/**
* Add tag
*
* #param \TestBundle\Entity\Tag $tag
*
* #return Product
*/
public function addTag(Tag $tag)
{
$this->tags->add($tag);
//$this->tags[] = $tag;
return $this;
}
/**
* Remove tag
*
* #param \TestBundle\Entity\Tag $webpage
*/
public function removeTag(Tag $tag)
{
$this->tags->removeElement($tag) ;
}
/**
* Get webpages
*
* #return \Doctrine\Common\Collections\Collection
*/
public function getTags()
{
return $this->tags;
}
I also tried to add a $this->tags = new ArrayCollection(); to the constructor of the product, but it didnt change anything.
Also, there is no problem adding, reading and persisting tags to products. The error is thrown as soon as I call $em->flush().
Does anybody know why my Product entity expects a array collection? I never told him to expect one! Thank you very much in advance!
The error is telling you that the property "#tags" of the entity TestBundle\Entity\Product that you are trying to flush, contains an object of type TestBundle\Entity\Tag instead of a collection of this object. Doctrine expects this collection/array because the metadata for that property states that TestBundle\Entity\Product is in a many-yo-many with TestBundle\Entity\Tag and the relation is done via the property "#tags". This should happen if:
//You did this
$this->tags = $tag;
//instead of what you actually did which is correct
$this->tags->add($tag);
//Or
$this->tags[] = $tag;
But the code that you posted here should not produce that exception.
Are you sure there is no other place where an accessor method is called that changes the tags property of TestBundle\Entity\Product? Something like and event listener?
I finally found out what the strange problem was. Thank you Alexandr Cosoi for confirming the way I tried to add my entity.
The Problem was a configuration error I didn't notice.
manyToMany:
tags:
targetEntity: Product
inversedBy: products
joinTable:
name: tags2products
joinColumns:
tag_id:
referencedColumnName: id
inverseJoinColumns:
product_id:
referencedColumnName: id
targetEntity was set to Product but it had to be set to "Tag". Changing it just solved my problem as expected. :)

Get child entities returns null instead of arrayCollection object

I have two entities with a oneToMany relationship:
Post entity:
...
oneToMany:
images:
mappedBy: post
targetEntity: Shop\Bundle\ManagementBundle\Entity\Image
Image entity:
...
manyToOne:
post:
targetEntity: Shop\Bundle\ManagementBundle\Entity\Post
inversedBy: images
joinColumn:
onDelete: cascade
With $entity instance of Post, when I was doing $entity->getImages(), I was receiving something like:
object(Doctrine\ORM\PersistentCollection)[65]
private 'snapshot' =>
array (size=0)
empty
private 'owner' =>
object(Acme\Bundle\ImageUpBundle\Entity\Post)[54]
private 'id' => int 41
private 'title' => string 'kbd' (length=3)
private 'images' =>
&object(Doctrine\ORM\PersistentCollection)[65]
private 'association' =>
array (size=15)
'fieldName' => string 'images' (length=6)
'targetEntity' => string 'Shop\Bundle\ManagementBundle\Entity\Image' (length=38)
'mappedBy' => string 'post' (length=4)
'type' => int 4
'inversedBy' => null
'isOwningSide' => boolean false
'sourceEntity' => string 'Shop\Bundle\ManagementBundle\Entity\Post' (length=37)
'fetch' => int 2
'cascade' =>
array (size=0)
empty
'isCascadeRemove' => boolean false
'isCascadePersist' => boolean false
'isCascadeRefresh' => boolean false
'isCascadeMerge' => boolean false
'isCascadeDetach' => boolean false
'orphanRemoval' => boolean false
private 'em' =>
....
But now i unfortunately get null.
I really did all my best to figure out what might cause such issue. Your help is much appreciated.
Edit:
given an in integer $id, I fetch a Post entity using:
$em = $this->getDoctrine()->getManager();
$entity = $em->getRepository('ShopManagementBundle:Post')->find($id);
I successfully get all the attributes of Post entity except from images.
Well here are the things that solved my issue:
1- I gathered all One To Many associations under one oneToMany parameter in config file. In other words, instead of having:
oneToMany:
images:
targetEntity....
....
oneToMany:
messages:
targerEntity...
....
I would have:
oneToMany:
images:
targerEntity...
....
messages:
targerEntity...
....
I generated entities again using app/console doc:gen:entities making the only one constructor constructs the two ArrayCollections.
/**
* Constructor
*/
public function __construct()
{
$this->messages = new \Doctrine\Common\Collections\ArrayCollection();
$this->images = new \Doctrine\Common\Collections\ArrayCollection();
}
Now when I call $em->getRepository('ShopManagementBundle:Post)->find($id) I have the child entities (Images) attached to my parent entity (Post) when concrete records exist in database, and not Null. When I insantiate a new entity using new Post(), I have empty ArrayCollection and not Null.
I know this answer is lacking of programming logic and seems arbitrary effort, but I write it in the sake of sharing in the case someone encounters suddenly this problem (As I did). I hope it helps.
$entity = $em->getRepository('ShopManagementBundle:Post')->find($id);
By Default, you will get only the proxy object of the child entities, this lazy way of fetching the associated entities is called Lazy loading, which will fetch values from images entity only a call to its getter method is invoked such as
$entity->getImage();
To load all the associated entities at one shot, you should instruct doctrine to do eager loading. This can be done using DQL, unfortunately its not possible to instruct using find method
$query = $em->createQuery("SELECT p FROM Shop\Bundle\ManagementBundle\Entity\Post p");
$query->setFetchMode("Shop\Bundle\ManagementBundle\Entity\Post", "images", \Doctrine\ORM\Mapping\ClassMetadata::FETCH_EAGER);
$query->execute();
Make sure your column name, member name and method naming all syntactically align. For example 'searchCampaignType':
ORM Column: searchCampaignTypeId
Member: $searchCampaignType
Getter: getSearchCampaignTypes
Setter: setSearchCampaignTypes
Other steps to take:
Regenerate your Doctrine Proxies
If you are using memcached then ensure you restart it or
rebuild the data
The question was oneToMany. The example below is ManyToMany but the principle is the same.
/**
* #ORM\ManyToMany(targetEntity="CampaignType")
* #ORM\JoinTable(name="Roles__SearchCampaignTypes",
* joinColumns={#ORM\JoinColumn(name="roleId", referencedColumnName="id")},
* inverseJoinColumns={#ORM\JoinColumn(name="searchCampaignTypeId", referencedColumnName="id")}
* )
**/
private $searchCampaignTypes;
/**
* #return ArrayCollection
*/
public function getSearchCampaignTypes()
{
return $this->searchCampaignTypes;
}
/**
* #param $searchCampaignTypes
*/
public function setSearchCampaignTypes($searchCampaignTypes)
{
$this->searchCampaignTypes = $searchCampaignTypes;
}
Thanks to the hint of #satdev86 due to proxies, I could solve the problem in my case by only regenerating the proxies with orm:generate-proxies.

query entities contained in a composite entity in doctrine2

I have a Friendship class that contains a $user and a $friend. I'd like to get a list of a all friends for a user. I'm unsure on how to create a query builder to do this.
Here's my yml.
Acme\Project\Domain\User\Entity\Friendship:
type: entity
table: friendships
id:
user:
associationKey: true
friend:
associationKey: true
fields:
createdAt:
type: datetimetz
column: created_at
manyToOne:
user:
targetEntity: Acme\Project\Domain\User\Entity\User
joinColumn:
name: user_id
referencedColumnName: id
onDelete: CASCADE
friend:
targetEntity: Acme\Project\Domain\User\Entity\User
joinColumn:
name: friend_id
referencedColumnName: id
onDelete: CASCADE
I've tried this
$qb->select('f.friend')
->from(Friendship::CLASS, 'f')
->where('IDENTITY(f.user) = :user_id')
->setParameter('user_id', $user->getId());
But get the following error.
[Semantical Error] line 0, col 9 near 'friend FROM Acme\\Project\\Domain\\User\\Entity\\Friendship': Error: Invalid PathExpression. Must be a StateFieldPathExpression.
I'm almost certain it's because the select portion contains a ".".
OK you have an error "must be a statefield expression" because as you say there is a dot in your select, you select a member of your root variable 'f'.
But in your case I think your mapping is wrond and that's why you encounter difficulties to write your query.
I think you shouldn't have a Friendship class, and some ManyToOne associations.
But only a ManyToMany association in User class with self-referencing
Here from the official doc :
many-to-many-self-referencing
I quote :
You can even have a self-referencing many-to-many association. A
common scenario is where a User has friends and the target entity of
that relationship is a User so it is self referencing. In this
example it is bidirectional so User has a field named $friendsWithMe
and $myFriends.
You have to translate the following mapping with annotations, to mapping with yml in your case :
<?php
/** #Entity **/
class User
{
// ...
/**
* #ManyToMany(targetEntity="User", mappedBy="myFriends")
**/
private $friendsWithMe;
/**
* #ManyToMany(targetEntity="User", inversedBy="friendsWithMe")
* #JoinTable(name="friends",
* joinColumns={#JoinColumn(name="user_id", referencedColumnName="id")},
* inverseJoinColumns={#JoinColumn(name="friend_user_id", referencedColumnName="id")}
* )
**/
private $myFriends;
public function __construct() {
$this->friendsWithMe = new \Doctrine\Common\Collections\ArrayCollection();
$this->myFriends = new \Doctrine\Common\Collections\ArrayCollection();
}
// ...
}
And the query :
$qb->select('u')
->from(User::CLASS, 'u')
->join('u.friendsWithMe', 'friendWithMe')
->where('IDENTITY(friendWithMe) = :user_id')
->setParameter('user_id', $user->getId());

Symfony ManyToMany Bidirectionnal Relationship Only persist on one direction

After a long time searching on the web, i decide myself to write my first post.
I hope I do it the right way.
Here is my problem.
I use symfony 2.1 on my project.
And I have to deal with a bi-directionnal Many-to-many relationship Between 2 objects.
I've created the 2 entities, done the mapping, done the controllers, the templates and the formBuilder.
I manage to persist both entities.
For one entity, i could persist the entity and the relation with the other.
But For the other entity, i could only persist the entity.
The relation with the other entity do not persist.
Here are extracts of the 2 entities :
class EntrainementCategorie{
{...}
/** #ORM\ManyToMany(targetEntity="EntrainementType", mappedBy="categories", cascade="persist") */
protected $types;
}
Here is the second entity :
class EntrainementType{
{...}
/**
* #ORM\ManyToMany(targetEntity="EntrainementCategorie",inversedBy="types", cascade="persist")
* #ORM\JoinTable(name="mottet_entrainement_types_categories",
* joinColumns={#ORM\JoinColumn(name="idType", referencedColumnName="id")},
* inverseJoinColumns={#ORM\JoinColumn(name="idCategorie", referencedColumnName="id")})
*/
protected $categories;
}
So you can see, there is a bidirectionnal Many-to-Many relationship between category and type.
Here are the controllers :
class EntrainementCategorieController extends GenericController{
{...}
public function creerAction(Request $request){
return $this->creerActionGeneric($request,new Categorie(),new CategorieType());
}
}
The second one :
class EntrainementTypeController extends GenericController{
{...}
public function creerAction(Request $request){
return $this->creerActionGeneric($request,new Type(),new TypeType());
}
}
And here is the GenericController :
class GenericController extends Controller{
{...}
protected function creerActionGeneric(Request $request,$object,$objectType){
$form = $this->createForm($objectType,$object);
$isThereProblem = false;
if ($request->isMethod('POST')) {
$isThereProblem = true;
$form->bind($request);
if ($form->isValid()) {
$this->getEntityManager()->persist($object);
$this->getEntityManager()->flush();
$this->get('session')->getFlashBag()->add('information', $this->FORM_SUCCESS_MESSAGE);
$isThereProblem = false;
}
}
if ($isThereProblem){
$this->get('session')->getFlashBag()->add('error', $this->FORM_ERROR_MESSAGE);
}
return $this->render($this->BUNDLE.':'.$this->ENTITY.':'.$this->CREATE_TEMPLATE, array('form' => $form->createView()));
}
}
Here are the formBuilder :
class EntrainementCategorieType extends AbstractType{
{...}
public function buildForm(FormBuilderInterface $builder, array $options){
$builder->add('label','text')
->add('types','entity',array(
'class' => 'KarateEntrainementBundle:EntrainementType',
'property' => 'label',
'multiple' => true,
'expanded' => true));
}
}
And the second one :
class EntrainementTypeType extends AbstractType{
{...}
public function buildForm(FormBuilderInterface $builder, array $options){
$builder->add('label','text')
->add('categories','entity',array(
'class' => 'KarateEntrainementBundle:EntrainementCategorie',
'property' => 'label',
'multiple' => true,
'expanded' => true));
}
}
So when i fill the EntrainementType form, both the type and its relations with category are persisted.
But when i fille the EntrainementCategory form, only the category is persisted, not its relations with type.
Does anyone knows what am i doing the wrong way ?
Hope i've been clear enought.
Thank you for you help !
I finally manage to do it.
I can't use the creerActionGeneric on that one.
I have to set explicitly the association between category and each type :
$form->bind($request);
if ($form->isValid()) {
$this->getEntityManager()->persist($categorie);
foreach($categorie->getTypes() as $type){
$type->addCategorie($categorie);
$this->getEntityManager()->persist($type);
}
$this->getEntityManager()->flush();
}
And that is working just fine.
But I don't know why on the other direction when i persist from the Type, I do not have to do like that ??? oO
There is a better way to go about doing via doctrine configuration of relationship. Doctrine will allow you to specify the join table and colums which reference the relationships.
I actually stumbled upon this answer before deciding to read more into it as this didn't seem appropriate... This is the right way to go about making a relationship like this (and in yml because that's what I prefer), and with my entities obviously (feeds can have/belong-to many lists and lists can have/belong-to many feeds)
Configuration for Feed:
manyToMany:
providerLists:
targetEntity: ProviderList
joinTable:
name: providerlist_feed
joinColumns:
feed_id:
referencedColumnName: id
inverseJoinColumns:
providerlist_id:
referencedColumnName: id
Configuration for list
manyToMany:
feeds:
targetEntity: Feed
joinTable:
name: providerlist_feed
joinColumns:
providerlist_id:
referencedColumnName: id
inverseJoinColumns:
feed_id:
referencedColumnName: id
so with that both entities now own each other, and both will persist the same way :)

Resources