Doctrine entities reference each other's primary key - php

I have an Account entity which contains a collection of Group entities. In addition, each Account must have a default Group. How can one persist the entities? When attempting to do so, $manager->flush(); complains with a Not null violation for the entity that was persisted first.
Account:
namespace App\Entity;
use Doctrine\Common\Collections\ArrayCollection;
use Doctrine\Common\Collections\Collection;
use Symfony\Bridge\Doctrine\IdGenerator\UuidV4Generator;
use Doctrine\ORM\Mapping as ORM;
/**
* #ORM\Entity()
*/
class Account
{
/**
* #ORM\Id
* #ORM\Column(type="uuid", unique=true)
* #ORM\GeneratedValue(strategy="CUSTOM")
* #ORM\CustomIdGenerator(class=UuidV4Generator::class)
*/
private $id;
/**
* #ORM\OneToOne(targetEntity=Group::class, cascade={"persist", "remove"})
* #ORM\JoinColumn(nullable=false, unique=true)
*/
private $defaultGroup;
/**
* #ORM\OneToMany(targetEntity=Group::class, mappedBy="account")
*/
private $groups;
public function __construct()
{
$this->groups = new ArrayCollection();
$this->defaultGroup = new Group();
}
// Typical getters and setters
}
Group:
namespace App\Entity;
use Doctrine\ORM\Mapping as ORM;
/**
* #ORM\Entity()
*/
class Group
{
/**
* #ORM\Id
* #ORM\GeneratedValue
* #ORM\Column(type="integer")
*/
private $id;
/**
* #ORM\ManyToOne(targetEntity=Account::class, inversedBy="groups")
* #ORM\JoinColumn(nullable=false, onDelete="CASCADE")
*/
private $account;
// Typical getters and setters
}

Your approach is not possible because Group::$account is already mapped to the one-to-many Account::$groups property. Doctrine does not know what to do when you persist a Group via Account::$defaultGroup, as this is not the property Group::$account is inversing.
You will have to enforce this Account default group invariant yourself programmatically. There's multiple approaches to this, but I suggest using the existing Account::$groups mapping in combination with the Symfony Collection validation constraint. Validating that an Account should have at least 1 Group assigned to it, which will be the default one.
Here's an example:
Because the entity name Group was giving me syntax problems in the SQL dialect I was using I renamed it to Team.
The Account implementation:
<?php declare(strict_types = 1);
namespace App\Entity;
use Doctrine\Common\Collections\ArrayCollection;
use Doctrine\Common\Collections\Collection;
use Doctrine\ORM\Mapping as ORM;
use Symfony\Component\Validator\Constraints as Assert;
/**
* #ORM\Entity
*/
class Account
{
/**
* #ORM\Id
* #ORM\GeneratedValue
* #ORM\Column(type="integer")
*/
private ?int $id = null;
/**
* #ORM\OneToMany(
* targetEntity="App\Entity\Team",
* mappedBy="account",
* cascade={"persist", "remove"},
* )
* #Assert\Count(
* min=1
* )
*/
private Collection $teams;
public function __construct()
{
$this->teams = new ArrayCollection();
$this->addTeam(new Team());
}
public function getId(): ?int
{
return $this->id;
}
public function getDefaultTeam(): ?Team
{
$team = $this->teams->first();
return $team instanceof Team ? $team : null;
}
public function getTeams(): Collection
{
return $this->teams;
}
public function setTeams(array $teams): void
{
$this->teams->clear();
foreach ($teams as $team) {
if ($team instanceof Team) {
$this->addTeam($team);
}
}
}
public function addTeam(Team $team): void
{
if (false === $this->teams->contains($team)) {
$this->teams->add($team);
$team->setAccount($this);
}
}
public function removeTeam(Team $team): void
{
if (true === $this->teams->contains($team)) {
$this->teams->removeElement($team);
}
}
}
The Team implementation:
<?php declare(strict_types = 1);
namespace App\Entity;
use Doctrine\ORM\Mapping as ORM;
/**
* #ORM\Entity
*/
class Team
{
/**
* #ORM\Id
* #ORM\GeneratedValue
* #ORM\Column(type="integer")
*/
private ?int $id = null;
/**
* #ORM\ManyToOne(
* targetEntity="App\Entity\Account",
* inversedBy="teams",
* )
* #ORM\JoinColumn(
* name="account_id",
* referencedColumnName="id",
* nullable=false
* )
*/
private ?Account $account = null;
public function getId(): ?int
{
return $this->id;
}
public function getAccount(): ?Account
{
return $this->account;
}
public function setAccount(Account $account): void
{
$this->account = $account;
}
}

Related

Make a field #Assert\NotBlank only for admin

This is my first time posting, my apologies if I don't follow some rules.
I'm using API Platform in my Symfony 5.3 project. I'm trying to make a field in one of my entities writable with some rules. The entity is called StripeAccount and must be linked to a $company object (see mapping below). Here are the rules
If the user is NOT granted ROLE_ADMIN, then the $company is not mandatory as it will be automatically filled
If the user is NOT granted ROLE_ADMIN and provide the $company, it MUST match the user's one (or else a violation is added)
If the user IS granted ROLE_ADMIN, then the $company IS mandatory but it can be any company
This is my StripeAccount entity :
<?php
namespace App\Entity;
use ApiPlatform\Core\Annotation\ApiProperty;
use ApiPlatform\Core\Annotation\ApiResource;
use App\Repository\StripeAccountRepository;
use App\Validator\IsValidCompany;
use Doctrine\Common\Collections\ArrayCollection;
use Doctrine\Common\Collections\Collection;
use Doctrine\ORM\Mapping as ORM;
use Symfony\Component\HttpFoundation\File\File;
use Symfony\Component\Serializer\Annotation\Groups;
use Symfony\Component\Serializer\Annotation\MaxDepth;
use Symfony\Component\Validator\Constraints as Assert;
use Vich\UploaderBundle\Mapping\Annotation as Vich;
/**
* #Vich\Uploadable
* #ApiResource(
* iri="http://schema.org/StripeAccount",
* normalizationContext={"groups"={"read:StripeAccount"}, "enable_max_depth"=true},
* denormalizationContext={"groups"={"write:StripeAccount"}},
* collectionOperations={
* "post"={
* "input_formats"={
* "multipart"={"multipart/form-data"}
* },
* },
* },
* itemOperations={
* "get"
* }
* )
* #ORM\Entity(repositoryClass=StripeAccountRepository::class)
*/
class StripeAccount
{
public const ACCOUNT_TYPE = 'custom';
/**
* #ORM\Id
* #ORM\GeneratedValue
* #ORM\Column(type="integer")
* #Groups({"read:StripeAccount", "write:StripeAccount"})
*/
private $id;
/**
* #ORM\ManyToOne(targetEntity=Company::class, inversedBy="stripeAccounts")
* #ORM\JoinColumn(nullable=false)
* #Groups({"read:StripeAccount", "admin:write"})
* #Assert\NotBlank(groups={"admin:write"})
* #IsValidCompany
*/
private $company;
/**
* #ORM\OneToMany(targetEntity=Brand::class, mappedBy="stripeAccount")
* #Groups({"read:StripeAccount", "write:StripeAccount"})
*/
private $brands;
// other fields
public function __construct()
{
$this->brands = new ArrayCollection();
}
public static function getType(): string
{
return self::ACCOUNT_TYPE;
}
public function getId(): ?int
{
return $this->id;
}
public function getCompany(): ?Company
{
return $this->company;
}
public function setCompany(?Company $company): self
{
$this->company = $company;
return $this;
}
// other methods
}
I followed this tutorial : https://symfonycasts.com/screencast/api-platform-security/context-builder#play (chapters 25, and 33 to 36), so I have this validator :
<?php
namespace App\Validator;
use App\Entity\{Company, User};
use Symfony\Component\Security\Core\Security;
use Symfony\Component\Validator\Constraint;
use Symfony\Component\Validator\ConstraintValidator;
class IsValidCompanyValidator extends ConstraintValidator
{
private $security;
public function __construct(Security $security)
{
$this->security = $security;
}
public function validate($value, Constraint $constraint)
{
/* #var $constraint \App\Validator\IsValidCompany */
if (null === $value || '' === $value) {
return;
}
$user = $this->security->getUser();
if (!$user instanceof User) {
$this->context->buildViolation($constraint->anonymousMessage)->addViolation();
return;
}
if ($this->security->isGranted('ROLE_ADMIN')) {
return;
}
if (!$value instanceof Company) {
throw new \InvalidArgumentException(
'#IsValidCompany constraint must be put on a property containing a Company object'
);
}
if ($value->getId() !== $user->getId()) {
$this->context->buildViolation($constraint->message)
->setParameter('%value%', $value)
->addViolation();
}
}
}
and this ContextBuilder :
<?php
namespace App\Serializer;
use ApiPlatform\Core\Serializer\SerializerContextBuilderInterface;
use Symfony\Component\HttpFoundation\Request;
use Symfony\Component\Security\Core\Authorization\AuthorizationCheckerInterface;
final class AdminGroupsContextBuilder implements SerializerContextBuilderInterface
{
private $decorated;
private $authorizationChecker;
public function __construct(
SerializerContextBuilderInterface $decorated,
AuthorizationCheckerInterface $authorizationChecker
) {
$this->decorated = $decorated;
$this->authorizationChecker = $authorizationChecker;
}
public function createFromRequest(Request $request, bool $normalization, ?array $extractedAttributes = null): array
{
$context = $this->decorated->createFromRequest($request, $normalization, $extractedAttributes);
$isAdmin = $this->authorizationChecker->isGranted('ROLE_ADMIN');
if (isset($context['groups']) && $isAdmin) {
$context['groups'][] = $normalization ? 'admin:read' : 'admin:write';
}
return $context;
}
}
Everything works fine, the group 'admin:write' is added if the user making the request is an admin, and the $company is set if the user is not an admin.
My problem is :
My #Assert\NotBlank(groups={"admin:write"}) is completly ignored. I tried a few adjustments with the #Groups annotation and even the denormalizationContext, but no, it's not applied at any moment. What am I missing here?
Btw, I'm using Postman to test my API
Thanks a lot for your help
[EDIT] Based on #TekPike's answer, here is my working code:
StripeAccount.php
<?php
namespace App\Entity;
use ApiPlatform\Core\Annotation\ApiProperty;
use ApiPlatform\Core\Annotation\ApiResource;
use App\Repository\StripeAccountRepository;
use App\Validation\AdminValidationGroupsGenerator;
use App\Validator\IsValidCompany;
use Doctrine\Common\Collections\ArrayCollection;
use Doctrine\Common\Collections\Collection;
use Doctrine\ORM\Mapping as ORM;
use Symfony\Component\Serializer\Annotation\Groups;
use Symfony\Component\Serializer\Annotation\MaxDepth;
use Symfony\Component\Validator\Constraints as Assert;
/**
* #ApiResource(
* iri="http://schema.org/StripeAccount",
* attributes={
* "validation_groups"=AdminValidationGroupsGenerator::class,
* },
* normalizationContext={"groups"={"read:StripeAccount"}, "enable_max_depth"=true},
* denormalizationContext={"groups"={"write:StripeAccount"}},
* collectionOperations={
* "post"={
* "input_formats"={
* "multipart"={"multipart/form-data"}
* },
* },
* },
* itemOperations={
* "get",
* "delete",
* }
* )
* #ORM\Entity(repositoryClass=StripeAccountRepository::class)
*/
class StripeAccount
{
/**
* #ORM\Id
* #ORM\GeneratedValue
* #ORM\Column(type="integer")
* #Groups({"read:StripeAccount", "write:StripeAccount"})
*/
private $id;
/**
* #ORM\ManyToOne(targetEntity=Company::class, inversedBy="stripeAccounts")
* #ORM\JoinColumn(nullable=false)
* #Groups({"read:StripeAccount", "admin:write"})
* #Assert\NotBlank(groups={"admin:write"})
* #IsValidCompany
*/
private $company;
/**
* #ORM\Column(type="string", length=255)
* #Groups({"read:StripeAccount", "write:StripeAccount"})
* #Assert\NotBlank
*/
private $name;
// ...
}
And my AdminValidationGroupsGenerator.php :
<?php
namespace App\Validation;
use ApiPlatform\Core\Bridge\Symfony\Validator\ValidationGroupsGeneratorInterface;
use Symfony\Component\Security\Core\Authorization\AuthorizationCheckerInterface;
final class AdminValidationGroupsGenerator implements ValidationGroupsGeneratorInterface
{
private $authorizationChecker;
public function __construct(AuthorizationCheckerInterface $authorizationChecker)
{
$this->authorizationChecker = $authorizationChecker;
}
/**
* {#inheritdoc}
*/
public function __invoke($entity): array
{
$reflect = new \ReflectionClass($entity);
$name = "write:" . $reflect->getShortName();
return $this->authorizationChecker->isGranted('ROLE_ADMIN', $entity) ? [$name, 'admin:write'] : [$name];
}
}
You confuse serialization groups with validation groups.
Currently you define serialization groups with the annotation denormalizationContext={"groups"={"write:StripeAccount"}} and the class App\SerializerAdminGroupsContextBuilder.
However, the "admin:write" group defined in the constraint #Assert\NotBlank(groups={"admin:write"}) is a validation group.
In your case, since the validation group changes depending on the user, you have to use dynamic validation groups.

Filter an array collection relation

I try to filter the collection returned by a parent Entity base on several conditions that refer to another Entity (User).
Basicaly i want API PLATFORM to return the messages from the current connected User only.
Here is my Entity named File (very simplified for global understanding)
<?php
namespace App\Entity;
use Doctrine\ORM\Mapping as ORM;
use Symfony\Component\Validator\Constraints as Assert;
use Doctrine\Common\Collections\ArrayCollection;
use Doctrine\Common\Collections\Collection;
use Symfony\Component\Serializer\Annotation\Groups;
use ApiPlatform\Core\Annotation\ApiFilter;
use ApiPlatform\Core\Annotation\ApiResource;
use ApiPlatform\Core\Bridge\Doctrine\Orm\Filter\SearchFilter;
use ApiPlatform\Core\Bridge\Doctrine\Orm\Filter\DateFilter;
use App\Controller\FileCreatePdfController;
use Ramsey\Uuid\Uuid;
use Gedmo\Mapping\Annotation as Gedmo;
/**
* #ApiResource(
*
* normalizationContext={"groups"={"file"},"enable_max_depth"=true},
* denormalizationContext={"groups"={"file-write"},"enable_max_depth"=true},
* attributes={"force_eager"=false},
* )
* #ApiFilter(SearchFilter::class, properties={"status": "exact"})
* #ApiFilter(DateFilter::class, properties={"updatedAt"})
* #ORM\Entity
* #ORM\Table(name="cases")
*/
class File
{
public function __construct()
{
$this->id = Uuid::uuid4();
$this->messages = new ArrayCollection();
}
public function getId()
{
return $this->id;
}
/**
* #ORM\Id
* #ORM\Column(type="uuid", unique=true)
*/
private $id;
/**
* #var Collection|FileMessage[]
*
* #Groups({"file"})
* #ORM\OneToMany(targetEntity="App\Entity\FileMessage", mappedBy="file")
*/
private $messages;
/**
* #return Collection|FileMessage[]
*/
public function getMessages(): Collection
{
return $this->messages;
}
/**
* #param FileMessage $message
* #return File
*/
public function addMessage(FileMessage $message): self
{
if (false === $this->messages->contains($message)) {
$this->messages->add($message);
}
return $this;
}
}
My file contains some messages from FileMessage (very simplified for global understanding
<?php
namespace App\Entity;
use Doctrine\ORM\Mapping as ORM;
use ApiPlatform\Core\Annotation\ApiResource;
use Symfony\Component\Serializer\Annotation\Groups;
use Symfony\Component\Validator\Constraints as Assert;
use Ramsey\Uuid\Uuid;
/**
* #ApiResource(
* normalizationContext={"groups"={"file-message"}, "enable_max_depth"=true},
* denormalizationContext={"groups"={"file-message-write"}, "enable_max_depth"=true},
* attributes={"force_eager"=false}
* )
* #ORM\Entity
* #ORM\Table(name="`file_messages`")
*/
class FileMessage {
/**
*
* #ORM\Id
* #ORM\Column(type="uuid", unique=true)
*/
private $id;
/**
* #var File
*
* #ORM\ManyToOne(targetEntity="File", cascade={"persist"}, inversedBy="messages")
* #Assert\Valid
* #Groups({"file-message", "file-message-write","file"})
*/
private $file;
/**
* #var User
*
* #ORM\ManyToOne(targetEntity="User", cascade={"persist"}, inversedBy="messages")
* #Assert\Valid
* #Groups({"file-message", "file-message-write","file"})
*/
private $user;
public function __construct()
{
$this->id = Uuid::uuid4();
}
public function getId()
{
return $this->id;
}
public function getFile(): ?File
{
return $this->file;
}
public function setFile(?File $file): self
{
$this->file = $file;
return $this;
}
public function getUser(): ?User
{
return $this->user;
}
public function setUser(?User $user): self
{
$this->user = $user;
return $this;
}
}
Each message is posted by a specific user (another entity User ) i don't think it is neccesary to post the content of this entity.
When i fetch a specific file , url/files/[file_id]
All the messages for all users are displayed , i want to hide all message that not refer to the connected User
Solution I have tried so far :
Extensions , I only filter the /files that i retreive not the messages collection
Custom filters , does not seems to be appropriated
Collection filtering , seems perfect only when you filter on current class properties , in my case i need to filter the properties based on another class content (User).
Do I have any solution ? I was thinking maybe to use an event listener
Extensions are the way to go. They give you access to the Doctrine ORM Query Builder, so you can filter both the current resource and its relations by tweaking the WHERE clause of the DQL query.
It's always better to filter the data as early as possible (for performance, among other reasons), so here doing it at the DBMS level is the best option.

Symfony Doctrine ManyToOne association migration No reaction

I have two tables one is entry.php and user.php
I Want to associate $balance of user with the $balance of entry
but when I try php bin/console doctrine:migrations:diff
I get
No changes detected in your mapping information.
this is my user.php
<?php
namespace BankBundle\Entity;
use Doctrine\ORM\Mapping as ORM;
use Symfony\Component\Validator\Constraints as Assert;
/**
* #ORM\Table(name="user")
*
* #ORM\Entity(repositoryClass="BankBundle\Entity\user")
**/
class user
{
/**
* #ORM\Id
* #ORM\Column(name="id",type="integer")
* #ORM\GeneratedValue
*/
private $id;
/**
* #ORM\OneToMany(targetEntity="entry", mappedBy="balance")
* #var entry[] An ArrayCollection of entry objects.
*/
private $balance;
public function getId()
{
return $this->id;
}
public function getBalance()
{
return $this->balance;
}
}
and my entry.php
<?php
namespace BankBundle\Entity;
use Doctrine\ORM\Mapping as ORM;
use Symfony\Component\Validator\Constraints as Assert;
/**
* #ORM\Table(name="entry")
*
* #ORM\Entity(repositoryClass="BankBundle\Repository\BankRepository")
**/
class entry
{
/**
* #ORM\Id
* #ORM\Column(name="id",type="integer")
* #ORM\GeneratedValue
*/
private $id;
/**
* #ORM\Column(name="amount",type="integer")
*/
private $amount;
/**
* #ORM\Column(name="balance",type="integer")
* #ORM\ManyToOne(targetEntity="user", inversedBy="balance")
*/
private $balance;
/**
* #ORM\Column(name="created_at",type="datetime")
*/
private $created_at;
}

How to not multiply request in symfony when using association table

I have a trouble with symfony.
I have three table in my database :
Musicien, Composer and Oeuvre
Composer is an association table between Musicien and Oeuvre.
When I load a page who show a Musicien, it show his Oeuvres.
So I make a join between Musicien and Composer, and between Composer and Oeuvre.
From a Musicien I can get all his composer with one request. But, to show his Oeuvres, I need to loop on his Composers to get all Oeuvres.
It makes one request by get, so if I have n Composer it will makes n request.
It is horribly slow.
I want to make it with one request, but I don't know how to do that, can you help me ?
Maybe if I make the join between Musicien and Oeuvre directly, but I don't know how to do that too.
Thank you.
This is my code :
Musicien:
namespace App\Entity;
use Doctrine\ORM\Mapping as ORM;
use Doctrine\Common\Collections\ArrayCollection;
/**
* Musicien
*
* #ORM\Table(name="Musicien", indexes={#ORM\Index(name="IDX_AC6BE67520B77BF2", columns={"Code_Pays"}), #ORM\Index(name="IDX_AC6BE675E1990660", columns={"Code_Genre"}), #ORM\Index(name="IDX_AC6BE675D389A975", columns={"Code_Instrument"})})
* #ORM\Entity
*/
class Musicien
{
/* Some attributes here*/
/**
* #var \Composer
*
* #ORM\OneToMany(targetEntity="Composer", mappedBy="codeMusicien")
*/
private $composer;
public function __construct(){
$this->oeuvres = new ArrayCollection();
}
public function getOeuvres()
{
$oeuvres = new ArrayCollection();
$oeuvres->add($this->composer->getCodeOeuvre());
// foreach($this->composer as $c){
// $oeuvres->add($c->getCodeOeuvre());
// }
return $oeuvres;
}
}
Composer
<?php
namespace App\Entity;
use Doctrine\ORM\Mapping as ORM;
/**
* Composer
*
* #ORM\Table(name="Composer", indexes={#ORM\Index(name="IDX_6105648EE694D5AB", columns={"Code_Musicien"}), #ORM\Index(name="IDX_6105648ECB48FCBD", columns={"Code_Oeuvre"})})
* #ORM\Entity
*/
class Composer
{
/**
* #var int
*
* #ORM\Column(name="Code_Composer", type="integer", nullable=false)
* #ORM\Id
* #ORM\GeneratedValue(strategy="IDENTITY")
*/
private $codeComposer;
/**
* #var \Musicien
*
* #ORM\ManyToOne(targetEntity="Musicien", inversedBy="composer")
* #ORM\JoinColumns({
* #ORM\JoinColumn(name="Code_Musicien", referencedColumnName="Code_Musicien")
* })
*/
private $codeMusicien;
/**
* #var \Oeuvre
*
* #ORM\ManyToOne(targetEntity="Oeuvre", inversedBy="composer")
* #ORM\JoinColumns({
* #ORM\JoinColumn(name="Code_Oeuvre", referencedColumnName="Code_Oeuvre")
* })
*/
private $codeOeuvre;
public function getCodeComposer(): ?int
{
return $this->codeComposer;
}
public function getCodeMusicien(): ?Musicien
{
return $this->codeMusicien;
}
public function setCodeMusicien(?Musicien $codeMusicien): self
{
$this->codeMusicien = $codeMusicien;
return $this;
}
public function getCodeOeuvre(): ?Oeuvre
{
return $this->codeOeuvre;
}
public function setCodeOeuvre(?Oeuvre $codeOeuvre): self
{
$this->codeOeuvre = $codeOeuvre;
return $this;
}
}
Oeuvre
<?php
namespace App\Entity;
use Doctrine\ORM\Mapping as ORM;
use Doctrine\Common\Collections\ArrayCollection;
/**
* Oeuvre
*
* #ORM\Table(name="Oeuvre", indexes={#ORM\Index(name="IDX_32522BC898F61075", columns={"Code_Type"})})
* #ORM\Entity
*/
class Oeuvre
{
/**
* Some attributes here
*/
/**
* #var \Composer
*
* #ORM\OneToMany(targetEntity="Composer", mappedBy="codeOeuvre")
* #ORM\JoinTable(name="Composer")
*/
private $composer;
public function __construct(){
$this->musiciens = new ArrayCollection();
}
public function getMusiciens()
{
$musiciens = new ArrayCollection();
foreach($this->composer as $c){
$musiciens->add($c->getCodeMusicien());
}
return $musiciens;
}
}

The target-entity "some entity" cannot be found

i am using ZF2 with doctrine i am getting this error.
The target-entity Entity\User cannot be found in 'Subject\Entity\Subject#user'.
Here is the snippet to my code.
<?php
namespace Subject\Entity;
use Doctrine\ORM\Mapping as ORM;
use Zend\InputFilter\InputFilter;
use Zend\InputFilter\Factory as InputFactory;
use Zend\InputFilter\InputFilterAwareInterface;
use Zend\InputFilter\InputFilterInterface;
/**
* #ORM\Entity
* #ORM\Table(name="subject")
* #property string $subjectname
* #property int $user_id
* #property int $id
*/
class Subject implements InputFilterAwareInterface {
protected $inputFilter;
/**
* #ORM\Id
* #ORM\Column(type="integer");
* #ORM\GeneratedValue(strategy="AUTO")
*/
protected $id;
/**
* #ORM\Column(type="string")
*/
protected $subjectname;
/**
* #ORM\ManyToOne(targetEntity="Entity\User", inversedBy="subjects")
* #var User|null
*/
private $user;
/** #return User|null */
public function getUser() {
return $this->user;
}
/** #param User $user */
public function setUser(User $user) {
if($user === null || $user instanceof User) {
$this->user = $user;
} else {
throw new InvalidArgumentException('$user must be instance of Entity\User or null!');
}
}}
and then my "User" entity
namespace Subject\Entity;
use Doctrine\ORM\Mapping as ORM;
use Zend\InputFilter\InputFilter;
use Zend\InputFilter\Factory as InputFactory;
use Zend\InputFilter\InputFilterAwareInterface;
use Zend\InputFilter\InputFilterInterface;
/*
* #ORM\Entity
* #ORM\Table(name="users")
* #property string $username
* #property string $password
* #property int $id
*/
class User implements InputFilterAwareInterface {
protected $_username;
protected $_password;
/**
* #ORM\OneToMany(targetEntity="Entity\Subject", mappedBy="user")
* #var Collection
*/
private $subjects;
/** #ORM\Id() #ORM\Column(type="integer") #ORM\GeneratedValue(strategy="AUTO") #var int */
protected $_id;
public function __get($property) {
return $this->$property;
}
public function __set($property, $value) {
$this->$property = $value;
}
//Getters and setters
/** #return Collection */
public function getSubjects() {
return $this->comments;
}
/** #param Comment $comment */
public function addSubject(Subject $comment) {
$this->comments->add($comment);
$comment->setUser($this);
}
}
Your entity declaration is incorrect:
* #ORM\ManyToOne(targetEntity="Entity\User", inversedBy="subjects")
This should be either this:
* #ORM\ManyToOne(targetEntity="Subject\Entity\User", inversedBy="subjects")
Or, since the two classes share the same namespace, you can also use this:
* #ORM\ManyToOne(targetEntity="User", inversedBy="subjects")
The targetEntity has to be the fully qualified class name (FQCN), except if referring to a class in the same namespace, in which case the short name may be used (as per the last example above).
In our case, the file name was not the same as the class name: it was just a typo.
I had the same problem. The problem in my case was case-sensitivity.
Bad:
class Package {
/**
* #ORM\OneToMany(targetEntity="PackagUSA", mappedBy="package")
*/
private Collection $packageusas;
}
class Packageusa {
/**
* #ORM\ManyToOne(targetEntity="Package", inversedBy="packageusas")
* #ORM\JoinColumn(name="package_id", referencedColumnName="id")
*/
private Package $package;
}
Good:
class Package {
/**
* #ORM\OneToMany(targetEntity="PackageUSA", mappedBy="package")
*/
private Collection $packageusas;
}
class PackageUSA {
/**
* #ORM\ManyToOne(targetEntity="Package", inversedBy="packageusas")
* #ORM\JoinColumn(name="package_id", referencedColumnName="id")
*/
private Package $package;
}

Categories