query entities contained in a composite entity in doctrine2 - symfony

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());

Related

Doctrine OneToMany not return relationships Collection

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?

Relation with composite unique constraint (symfony + doctrine)

I'm trying to create relation where foreign key reference NOT to primary key but to composite unique constraint.
Why? Denormalize database schema for decrease join's count.
#[ORM\Entity(repositoryClass: CurrencyRepository::class)]
#[ORM\UniqueConstraint(fields: ['slug', 'type'])]
#[UniqueEntity(
fields: ['type', 'slug'],
message: 'This slug is already in use on that type.',
errorPath: 'slug',
)]
class Currency
{
#[ORM\Id]
#[ORM\GeneratedValue]
#[ORM\Column(type: 'integer')]
private ?int $id;
#[ORM\Column(type: 'smallint', length: 1)]
private ?int $type;
#[ORM\Column(type: 'string', length: 25)]
private ?string $slug;
// ...
}
#[ORM\Entity(repositoryClass: ExchangeRateHistoryTypeRepository::class)]
class ExchangeRateHistoryType
{
#[ORM\Id]
#[ORM\GeneratedValue]
#[ORM\Column(type: 'integer')]
private int $id;
#[ORM\ManyToOne(targetEntity: Currency::class)]
#[ORM\JoinColumn(name: 'currency_slug', referencedColumnName: 'slug', nullable: false)]
#[ORM\JoinColumn(name: 'currency_type', referencedColumnName: 'type', nullable: false)]
private ?Currency $currency;
php bin/console make:migration
php bin/console doctrine:migrations:migrate
All good. But when i try to add data to ExchangeRateHistoryType - error.
Client code:
$exchangeRateHistoryType = new ExchangeRateHistoryType();
$exchangeRateHistoryType->setCurrency($currency);
// ...
$this->entityManager->persist($exchangeRateHistoryType);
$this->entityManager->flush();
In BasicEntityPersister.php line 674: Warning: Undefined array key "slug"
What i'm doing wrong?
Doctrine's documentation:
It is not possible to use join columns pointing to non-primary keys. Doctrine will think these are the primary keys and create lazy-loading proxies with the data, which can lead to unexpected results. Doctrine can for performance reasons not validate the correctness of this settings at runtime but only through the Validate Schema command.
Source: https://www.doctrine-project.org/projects/doctrine-orm/en/2.10/reference/limitations-and-known-issues.html#join-columns-with-non-primary-keys

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. :)

How to associate the database record with the object

I follow the docs from here http://symfony.com/doc/current/book/doctrine.html
and I have a issue.
I created a relation between the category and the product model. The database has been updated correctly, model classes have right getters and setters, but if I try to run following code:
$product = $this->getDoctrine()->getRepository('AcmeStoreBundle:Product')
->findById(1);
$categoryName = $product->getCategory()->getName();
I gets an error:
FatalErrorException: Error: Call to a member function getName() on a
non-object in
D:\www\Symfony\src\Acme\StoreBundle\Controller\DefaultController.php
line 28
I checked it out and the category model class has getName() method and it is a public method.
My Product.orm.yml looks like
Acme\StoreBundle\Entity\Product:
type: entity
manyToOne:
category:
targetEntity: Category
inversedBy: products
joinColumn:
name: category_id
referencedColumnName: id
table: null
fields:
id:
type: integer
id: true
generator:
strategy: AUTO
name:
type: string
length: 255
price:
type: decimal
lifecycleCallbacks: { }
Category.orm.yml
Acme\StoreBundle\Entity\Category:
type: entity
oneToMany:
products:
targetEntity: Product
mappedBy: category
table: null
fields:
id:
type: integer
id: true
generator:
strategy: AUTO
name:
type: string
length: '255'
lifecycleCallbacks: { }
Product.php
...
/**
* #ORM\ManyToOne(targetEntity="Category", inversedBy="products")
* #ORM\JoinColumn(name="category_id", referencedColumnName="id")
*/
protected $category;
...
Category.php
...
/**
* #ORM\OneToMany(targetEntity="Product", mappedBy="category")
*/
protected $products;
public function __construct() {
$this->products = new ArrayCollection();
}
...
What's the problem?
You are retrieving a $category instead of a $product in your sample code.
$product = $this->getDoctrine()->getManager()->getRepository('AcmeStoreBundle:Product')->findOneById(1);
$categoryName = $product->getCategory()->getName();
note that you forgot the getManager()
Edit : You need to use findOneById() instead of findById. The first method will return only one single result (your object), the findBy* will return you an array of results on which you can't call getCategory() without traversing it inside a loop.

How can i load an entity from another bundle in an entity?

Let's say i have 2 entities. Card and User.
User it's in UserBundle and Card is in PublicBundle.
In user i have this function:
#namespace Kanban\UserBundle\Entity;
/**
* Add assignment
*
* #param \Kanban\PublicBundle\Entity\Card $assignment
* #return User
*/
public function addAssignment(\Kanban\PublicBundle\Entity\Card $assignment)
{
$this->assignment[] = $assignment;
return $this;
}
Every time i execute this command:
php app/console doctrine:schema:update --dump-sql
It throws this error:
[Doctrine\ORM\Mapping\MappingException]
The target-entity Kanban\UserBundle\Entity\Card cannot be found in 'Kanban\UserBundle\Entity\User#assignm
ent'.
I've tried the statement:
use Kanban\PublicBundle\Entity;
use Kanban\PublicBundle\Entity\Card;
At the beginning of the file but shows the same error.
Any ideas on what i'm doing wrong?
NM!,
Was a problem with the orm.yml file, changed:
manyToMany:
assignment:
targetEntity: Card
cascade: { }
mappedBy: customer
inversedBy: null
joinTable: null
orderBy: null
into
manyToMany:
assignment:
targetEntity: Kanban\PublicBundle\Entity\Card
cascade: { }
mappedBy: customer
inversedBy: null
joinTable: null
orderBy: null
And that solved the problem!

Resources