I am doing a small project where I have an entity with a roles property which consists of an array.
What I am trying to do is, in some controller, find an existing entity which has a specific role inside of the roles array.
I am trying to use the findOneBy() method, but I can't seem to make it work, it always returns null even though entities with the specific role I'm trying to find exist.
Here is my entity and its properties:
/**
* #ORM\Entity(repositoryClass=SalarieRepository::class)
*/
class Salarie
{
/**
* #ORM\Id
* #ORM\GeneratedValue
* #ORM\Column(type="integer")
*/
private $id;
/**
* #ORM\Column(type="string", length=255)
*/
private $nom;
/**
* #ORM\Column(type="string", length=255)
*/
private $prenom;
/**
* #ORM\Column(type="string", length=255)
*/
private $email;
/**
* #ORM\Column(type="string", length=255, nullable=true)
*/
private $telephone;
/**
* #ORM\Column(type="string", length=255)
*/
private $service;
/**
* #ORM\Column(type="json")
*/
private $roles = [];
// Getters & setters
}
And here is an example of something I tried with findOneBy() inside a controller, that returns null:
$rolecheck = $this->salarieRepository->findOneBy(["roles" => ["ROLE_RESPONSABLE_RH"]]);
When I try with any other property of the entity which isn't an array it works well, if I do something like this:
$rolecheck = $this->salarieRepository->findOneBy(["nom" => "test"]);
dd($rolecheck);
It will show the right entity :
SalarieController.php on line 47:
App\Entity\Salarie {#1501 ▼
-id: 6
-nom: "test"
-prenom: "test"
-email: "test#test.test"
-telephone: null
-service: "Graphisme"
-roles: array:3 [▼
0 => "ROLE_RESPONSABLE_RH"
1 => "ROLE_RESPONSABLE_SERVICE"
2 => "ROLE_SALARIE"
]
}
Where we can also see it does have the roles array with the role I'm trying to find inside it.
Any clues on how I could try to find one entity which has the specific role "ROLE_RESPONSABLE_RH"?
Your $roles property is of type json, which means it is stored as this in your database:
["ROLE_RESPONSABLE_RH", "ROLE_RESPONSABLE_SERVICE", "ROLE_SALARIE"]
You need to ask Doctrine if the JSON array contains the role, but you can't do that with the findOneBy() method.
When you hit the ORM limitations you can use a Native Query with ResultSetMapping. It allows you to write a pure SQL query using specific features of your DBMS but still get entity objects.
Create this method in your SalarieRepository class:
public function findByRole(string $role): array
{
// The ResultSetMapping maps the SQL result to entities
$rsm = $this->createResultSetMappingBuilder('s');
$rawQuery = sprintf(
'SELECT %s
FROM salarie s
WHERE /* your WHERE clause depending on the DBMS */',
$rsm->generateSelectClause()
);
$query = $this->getEntityManager()->createNativeQuery($rawQuery, $rsm);
$query->setParameter('role', $role);
return $query->getResult();
}
Then you need to replace the comment I put in the WHERE clause depending on the DBMS:
MariaDB - JSON_SEARCH():
SELECT %s
FROM salarie s
WHERE JSON_SEARCH(s.roles, 'one', :role) IS NOT NULL
MySQL - JSON_CONTAINS():
SELECT %s
FROM salarie s
WHERE JSON_CONTAINS(s.roles, :role, '$')
Warning: you must enclose the role parameter with double quotes:
$query->setParameter('role', sprintf('"%s"', $role));
PostgreSQL - jsonb escaped "?" operator:
SELECT %s
FROM salarie s
WHERE s.roles::jsonb ?? :role
Warning: will require PHP 7.4+. See the RFC
CAST JSON to TEXT
class JSONText extends FunctionNode
{
private $expr1;
public function getSql(SqlWalker $sqlWalker)
{
return sprintf(
"CAST(%s AS TEXT)",
$this->expr1->dispatch($sqlWalker)
);
}
public function parse(Parser $parser)
{
$parser->match(Lexer::T_IDENTIFIER);
$parser->match(Lexer::T_OPEN_PARENTHESIS);
$this->expr1 = $parser->StringPrimary();
$parser->match(Lexer::T_CLOSE_PARENTHESIS);
}
}
Add to your Doctrine DQL:
dql:
string_functions:
JSON_TEXT: YOUR_NAMESPACE\JSONText
Use your cast function
$qb->andWhere("JSON_TEXT(d.topics) LIKE '%$role%'")
Related
i'm trying to get from the database some Tasks with the count of contents (related to task) and the assignment objects (related to task) with the count of answers (related to assignment).
So I'm using scalars for the counts and it's pretty fine but I cannot manage to have one count_of_answers by assignments...
Here if my entities relationships :
class Task
{
/**
* #ORM\OneToMany(targetEntity="App\Entity\Assignment", mappedBy="task", orphanRemoval=true)
*/
private $assignments;
/**
* #ORM\OneToMany(targetEntity="App\Entity\Content", mappedBy="task", orphanRemoval=true)
*/
private $contents;
}
class Content
{
/**
* #ORM\ManyToOne(targetEntity="App\Entity\Task", inversedBy="contents")
* #ORM\JoinColumn(nullable=false)
*/
private $task;
/**
* #ORM\OneToMany(targetEntity="App\Entity\Answer", mappedBy="content", orphanRemoval=true)
*/
private $answers;
}
class Assignment
{
/**
* #ORM\ManyToOne(targetEntity="App\Entity\Task", inversedBy="assignments")
* #ORM\JoinColumn(nullable=false)
*/
private $task;
/**
* #ORM\OneToMany(targetEntity="App\Entity\Answer", mappedBy="assignment", orphanRemoval=true)
*/
private $answers;
}
class Answer
{
/**
* #ORM\ManyToOne(targetEntity="App\Entity\Content", inversedBy="answers")
* #ORM\JoinColumn(nullable=false)
*/
private $content;
/**
* #ORM\ManyToOne(targetEntity="App\Entity\Assignment", inversedBy="answers")
* #ORM\JoinColumn(nullable=false)
*/
private $assignment;
}
I would like to get in one query (I think this is possible to avoid querying inside a loop) something like :
Array [[
Task1{
properties,
assignments [
Assignment1{
properties,
count_of_answers
},
Assignment2{
properties,
count_of_answers
},...
]
},
count_of_contents
],
[
Task2{},
count_of_contents
],...
]
So i tried this query in my task repository :
$r = $this->createQueryBuilder('t')
->innerJoin('t.assignments', 'a')
->addSelect('a')
->innerJoin('t.contents', 'c')
->addSelect('COUNT(DISTINCT c.id) AS count_of_contents')
->leftJoin('a.answers', 'an')
->addSelect('COUNT(DISTINCT an.id) AS count_of_answers')
->groupBy('a.id')
->getQuery()
->getResult();
But it's giving something like :
Array[[
Task1{}
count_of_contents,
count_of_answers
],
Task2{}
count_of_contents,
count_of_answers
]]
Could you please help ?
Maybe I should use DQL with a subquery but I'm affraid to lose performence in the sql side. I believe the data fetched are good (when I try the sql query, I do have one count_of_answers by assignment), but the hydratation is not correctly mapped and I only get the last count_of_answers associated to the task instead of assignment.
I've noticed a strange behavior on a Symfony app.
We have an entity containing a nullable date field:
/**
* #var \DateTime
*
* #ORM\Column(name="foo", type="date", nullable=true)
*/
private $foo;
The corresponding query to get only entries where it's set looks like this:
public function findFoo()
{
$qb = $this->_em->createQueryBuilder();
$qb
->select('e')
->from($this->_entityName, 'e')
->where('e.foo IS NOT NULL');
return $qb->getQuery()->getResult();
}
This query however returns all entries, even those who are set to Null in the db.
When dumping the value of the foo field, we get this:
object(DateTime)[549]
public 'date' => string '-0001-11-30 00:00:00.000000' (length=27)
public 'timezone_type' => int 3
public 'timezone' => string 'UTC' (length=3)
We tried to modify the query to use this where clause instead:
->where($qb->expr()->isNotNull('e.foo'))
But the result was still the same. We've now settled for this "hack":
->where('e.foo > :dateSet')
->setParameter('dateSet', new \DateTime('1970-01-01'))
Which does the trick but kind of feels wrong.
Question: What is the correct way to check for IS NOT NULL within a Doctrine Query, on a date field that's nullable? Is this a bug in doctrine or expected behavior? If it's expected, what's the reason for it?
Edit:
Followup question, how can we test for IS NULL? Neither ->where('e.foo IS NULL'); nor ->where('e.foo < :dateSet'); or ->where('e.foo <= :dateSet'); seams to work in this case. Note: I've also tried to use the current date for dateSet. Sure I could now use a NOT IN( ... ) and in there a query for the not null ones, but that would be even more hacky. For now I'll probably have to decide on the PHP side.
Edit: As requested, the relevant part of the class including the constructor
<?php
namespace AppBundle\Entity;
use Doctrine\ORM\Mapping as ORM;
/**
* FooBar
*
* #ORM\Table(name="foo_bar")
* #ORM\Entity(repositoryClass="AppBundle\Repository\FooBarRepository")
*/
class FooBar
{
/**
* #var int
*
* #ORM\Column(name="id", type="integer")
* #ORM\Id
* #ORM\GeneratedValue(strategy="AUTO")
*/
private $id;
// [...]
/**
* #var \DateTime
*
* #ORM\Column(name="foo", type="date", nullable=true)
*/
private $foo;
public function __construct()
{
$this->foo = null;
// [...]
}
// [...]
public function isFoo(): bool
{
return !is_null($this->foo);
}
// [...]
/**
* Set foo
*
* #param \DateTime $foo
*
* #return FooBar
*/
public function setFoo($foo)
{
$this->foo = $foo;
return $this;
}
/**
* Get foo
*
* #return \DateTime|null
*/
public function getFoo()
{
return $this->foo;
}
}
So the issue in the end was, that the field in question, used to be a not nullable boolean, which I changed into a nullable date. I thought all the values should be null, and indeed when checking with a SQL tool, it showed all values as null.
However internally it seams that they weren't null after all. I run a update foo_bar set foo = NULL; which fixed all my issues.
I'm not sure if it's a bug in doctrine per se, I rather think it's a issue on the MySQL side.
I have several classes using a Taggable trait to set up a tag system common to several doctrine entities (Project, Note, ...).
The relationship between these entities and these tags is a ManyToMany relationship that I can not make multi-directional.
My problem: When I delete a Project entity, it is removed from the project table, but the relationships in the project_tag table between this project and the tags are not deleted. Then, if I create a new Project entity, an exception is thrown.
An exception exists while executing 'INSERT INTO project_tag (project_id, tag_id) VALUES (?,?)' With params [2, 4]:
SQLSTATE [23000]: Integrity constraint violation: 19 UNIQUE constraint failed: project_tag.project_id, project_tag.tag_id
Entities :
Tag
/**
* Tag
*
* #ORM\Table(name="tag")
* #ORM\Entity(repositoryClass="AppBundle\Repository\TagRepository")
*/
class Tag
{
/**
* #var int
*
* #ORM\Column(name="id", type="integer")
* #ORM\Id
* #ORM\GeneratedValue(strategy="AUTO")
*/
private $id;
/**
* #var string
*
* #ORM\Column(name="name", type="string", length=255, unique=true)
*/
private $name;
/**
* #ORM\Column(name="last_use_at", type="datetime", nullable=false)
* #var \DateTime
*/
private $lastUseAt;
public function __construct()
{
$this->lastUseAt = new \DateTime();
}
public function __toString()
{
return $this->name;
}
/**
* Get id
*
* #return int
*/
public function getId()
{
return $this->id;
}
/**
* Set name
*
* #param string $name
*
* #return Tag
*/
public function setName($name)
{
$this->name = $name;
return $this;
}
/**
* Get name
*
* #return string
*/
public function getName(): string
{
return $this->name;
}
/**
* #return \DateTime
*/
public function getLastUseAt(): \DateTime
{
return $this->lastUseAt;
}
/**
* #param \DateTime $lastUseAt
*/
public function setLastUseAt(\DateTime $lastUseAt)
{
$this->lastUseAt = $lastUseAt;
}
}
Taggable
trait Taggable
{
/**
* #var ArrayCollection
*
* #ORM\ManyToMany(targetEntity="AppBundle\Entity\Tag", cascade={"persist"})
*/
protected $tags;
/**
* Add tag
*
* #param Tag $tag
*
* #return $this
*/
public function addTag(Tag $tag)
{
$tag->setLastUseAt(new \DateTime());
$this->tags[] = $tag;
return $this;
}
/**
* Remove tag
*
* #param Tag $tag
*/
public function removeTag(Tag $tag)
{
$this->tags->removeElement($tag);
}
/**
* Get tags
*
* #return \Doctrine\Common\Collections\Collection
*/
public function getTags()
{
return $this->tags;
}
}
Project
/**
* Project
*
* #ORM\Table(name="project")
* #ORM\Entity(repositoryClass="AppBundle\Repository\ProjectRepository")
*/
class Project
{
use Taggable;
}
Note
class Note
{
use Taggable;
}
Is this the only solution or is my annotation incomplete / incorrect?
I tried with JoinColumns, JoinTable and onDelete = "cascade" but nothing works.
In the meantime, I dodged the problem with this instruction placed before the suppresion.
$project->getTags()->clear();
Full code of the action in the controller :
/**
* #Route("/project/{id}/delete", name="project_delete")
*/
public function deleteAction($id) {
$em = $this->getDoctrine()->getManager();
$project = $em->getRepository('AppBundle:Project')->find($id);
if(!$project) {
return $this->redirectToRoute('index');
}
$project->getTags()->clear();
$em->remove($project);
$em->flush();
return $this->redirectToRoute('index');
}
I think I found a better solution: you can set the PRAGMA within Doctrine configuration. Like:
doctrine:
dbal:
# configure these for your database server
driver: 'pdo_sqlite'
#server_version: '5.7'
#charset: utf8mb4
#default_table_options:
#charset: utf8mb4
#collate: utf8mb4_unicode_ci
url: '%env(resolve:DATABASE_URL)%'
options:
'PRAGMA foreign_keys': 'ON'
I just tried it on my Symfony 4 application, re-created the database and tested using DB Browser for SQLite and it works as I expected.
Hope this helps
I managed to fix the problem. Here's my solution working for SQLite conections.
Create an eventListener listening on the kernel.request event :
namespace AppBundle\EventListener;
use Doctrine\Bundle\DoctrineBundle\Registry;
use Doctrine\Common\Persistence\ObjectManager;
use Symfony\Component\HttpKernel\Event\GetResponseEvent;
class RequestListener
{
/**
* #var Registry
*/
private $doctrine;
public function __construct(Registry $doctrine)
{
$this->doctrine = $doctrine;
}
public function onKernelRequest(GetResponseEvent $event)
{
$this->doctrine->getConnection()->exec('PRAGMA foreign_keys = ON');
}
}
Service declaration
app.event_listener.request_listener:
class: AppBundle\EventListener\RequestListener
arguments:
- '#doctrine'
tags:
- { name: kernel.event_listener, event: kernel.request }
I think the problem is that you have your trait Taggable set as the owning side of the ManyToMany relationship but your are deleting the inverse side and expecting something to happen as a result. Doctrine will only check the owning side of the relationship in order to persist any changes. See here for docs on this.
You can solve by making the Taggable the inverse side of each of your relationships, or by manually telling doctrine to delete the owning side.
The first solution will probably not work for you since you won't (easily) specify multiple inverse sides. (Are you sure a trait is the right way to go for this??)
The second solution is easy. In your entities like Project for your deleteTag($tag) function, call a delete function on the owning side (e.g., deleteProject($project). You will have to create if one does not exist.
class Project
{
use Taggable;
public function deleteTag($tag)
{
$this->tags->removeElement($tag);
// persist on the owning side
$tag->deleteProject($this);
}
}
EDIT:
After seeing full code, it looks like you are deleting correctly. Now you need to tell doctrine to carry that through. See this post for full details, but basically you can change your trait to this:
trait Taggable
{
/**
* #var ArrayCollection
*
* #ORM\ManyToMany(
* targetEntity="AppBundle\Entity\Tag",
* cascade={"persist"},
* onDelete="CASCADE"
* )
*/
protected $tags;
// ...
}
I have an Account entity which has a collection of Section entities. Each Section entity has a collection of Element entities (OneToMany association). My problem is that instead of fetching all elements belonging to a section, I want to fetch all elements that belong to a section and are associated with a specific account. Below is my database model.
Thus, when I fetch an account, I want to be able to loop through its associated sections (this part is no problem), and for each section, I want to loop through its elements that are associated with the fetched account. Right now I have the following code.
$repository = $this->objectManager->getRepository('MyModule\Entity\Account');
$account = $repository->find(1);
foreach ($account->getSections() as $section) {
foreach ($section->getElements() as $element) {
echo $element->getName() . PHP_EOL;
}
}
The problem is that it fetches all elements belonging to a given section, regardless of which account they are associated with. The generated SQL for fetching a section's elements is as follows.
SELECT t0.id AS id1, t0.name AS name2, t0.section_id AS section_id3
FROM mydb.element t0
WHERE t0.section_id = ?
What I need it to do is something like the below (could be any other approach). It is important that the filtering is done with SQL.
SELECT e.id, e.name, e.section_id
FROM element AS e
INNER JOIN account_element AS ae ON (ae.element_id = e.id)
WHERE ae.account_id = ?
AND e.section_id = ?
I do know that I can write a method getElementsBySection($accountId) or similar in a custom repository and use DQL. If I can do that and somehow override the getElements() method on the Section entity, then that would be perfect. I would just very much prefer if there would be a way to do this through association mappings or at least by using existing getter methods. Ideally, when using an account object, I would like to be able to loop like in the code snippet above so that the "account constraint" is abstracted when using the object. That is, the user of the object does not need to call getElementsByAccount() or similar on a Section object, because it seems less intuitive.
I looked into the Criteria object, but as far as I remember, it cannot be used for filtering on associations.
So, what is the best way to accomplish this? Is it possible without "manually" assembling the Section entity with elements through the use of DQL queries? My current (and shortened) source code can be seen below. Thanks a lot in advance!
/**
* #ORM\Entity
*/
class Account
{
/**
* #var int
* #ORM\Column(type="integer")
* #ORM\Id
* #ORM\GeneratedValue
*/
protected $id;
/**
* #var string
* #ORM\Column(type="string", length=50, nullable=false)
*/
protected $name;
/**
* #var ArrayCollection
* #ORM\ManyToMany(targetEntity="MyModule\Entity\Section")
* #ORM\JoinTable(name="account_section",
* joinColumns={#ORM\JoinColumn(name="account_id", referencedColumnName="id")},
* inverseJoinColumns={#ORM\JoinColumn(name="section_id", referencedColumnName="id")}
* )
*/
protected $sections;
public function __construct()
{
$this->sections = new ArrayCollection();
}
// Getters and setters
}
/**
* #ORM\Entity
*/
class Section
{
/**
* #var int
* #ORM\Id
* #ORM\GeneratedValue
* #ORM\Column(type="integer")
*/
protected $id;
/**
* #var string
* #ORM\Column(type="string", length=50, nullable=false)
*/
protected $name;
/**
* #var ArrayCollection
* #ORM\OneToMany(targetEntity="MyModule\Entity\Element", mappedBy="section")
*/
protected $elements;
public function __construct()
{
$this->elements = new ArrayCollection();
}
// Getters and setters
}
/**
* #ORM\Entity
*/
class Element
{
/**
* #var int
* #ORM\Id
* #ORM\GeneratedValue
* #ORM\Column(type="integer")
*/
protected $id;
/**
* #var string
* #ORM\Column(type="string", length=50, nullable=false)
*/
protected $name;
/**
* #var Section
* #ORM\ManyToOne(targetEntity="MyModule\Entity\Section", inversedBy="elements")
* #ORM\JoinColumn(name="section_id", referencedColumnName="id")
*/
protected $section;
/**
* #var \MyModule\Entity\Account
* #ORM\ManyToMany(targetEntity="MyModule\Entity\Account")
* #ORM\JoinTable(name="account_element",
* joinColumns={#ORM\JoinColumn(name="element_id", referencedColumnName="id")},
* inverseJoinColumns={#ORM\JoinColumn(name="account_id", referencedColumnName="id")}
* )
*/
protected $account;
// Getters and setters
}
If I understand correctly, you want to be able to retrieve all Elements of all Sections of an Account, but only if those Elements are associated with that Account, and this from a getter in Account.
First off: An entity should never know of repositories. This breaks a design principle that helps you swap out the persistence layer. That's why you cannot simple access a repository from within an entity.
Getters only
If you only want to use getters in the entities, you can solve this by adding to following 2 methods:
class Section
{
/**
* #param Account $accout
* #return Element[]
*/
public function getElementsByAccount(Account $accout)
{
$elements = array();
foreach ($this->getElements() as $element) {
if ($element->getAccount() === $account) {
$elements[] = $element->getAccount();
}
}
return $elements;
}
}
class Account
{
/**
* #return Element[]
*/
public function getMyElements()
{
$elements = array()
foreach ($this->getSections() as $section) {
foreach ($section->getElementsByAccount($this) as $element) {
$elements[] = $element;
}
}
return $elements;
}
}
Repository
The solution above is likely to perform several queries, the exact amount depending on how many Sections and Elements are associated to the Account.
You're likely to get a performance boost when you do use a Repository method, so you can optimize the query/queries used to retrieve what you want.
An example:
class ElementRepository extends EntityRepository
{
/**
* #param Account $account [description]
* #return Element[]
*/
public function findElementsByAccount(Account $account)
{
$dql = <<< 'EOQ'
SELECT e FROM Element e
JOIN e.section s
JOIN s.accounts a
WHERE e.account = ?1 AND a.id = ?2
EOQ;
$q = $this->getEntityManager()->createQuery($dql);
$q->setParameters(array(
1 => $account->getId(),
2 => $account->getId()
));
return $q->getResult();
}
}
PS: For this query to work, you'll need to define the ManyToMany association between Section and Account as a bidirectional one.
Proxy method
A hybrid solution would be to add a proxy method to Account, that forwards the call to the repository you pass to it.
class Account
{
/**
* #param ElementRepository $repository
* #return Element[]
*/
public function getMyElements(ElementRepository $repository)
{
return $repository->findElementsByAccount($this);
}
}
This way the entity still doesn't know of repositories, but you allow one to be passed to it.
When implementing this, don't have ElementRepository extend EntityRepository, but inject the EntityRepository upon creation. This way you can still swap out the persistence layer without altering your entities.
I keep getting this error with Doctrine:
PHP Catchable fatal error: Object of class User could not be converted to string in vendor/doctrine/orm/lib/Doctrine/ORM/UnitOfWork.php on line 1337
In my system users can have many permissions in a One to Many relationship. I have set up a User and Permission entity. They look like this (I removed some annotations, getters and setters to reduce clutter):
class User {
/**
* #ORM\Column(name="user_id", type="integer", nullable=false)
* #ORM\Id
* #ORM\GeneratedValue(strategy="IDENTITY")
*/
protected $id;
public function getId()
{
return $this->id;
}
/**
* #ORM\OneToMany(targetEntity="Permission", mappedBy="user", cascade={"persist"})
*/
protected $permissions;
public function getPermissions()
{
return $this->permissions;
}
}
class Permission {
/**
* #ORM\Column(name="user_id", type="integer")
* #ORM\ManyToOne(targetEntity="User", inversedBy="permissions")
*/
protected $user;
public function getUser()
{
return $this->user;
}
public function setUser( $user )
{
$this->user = $user;
return $this;
}
}
The problem occurs when I add a new Permission to a User:
$permission = new Permission();
$user->getPermissions()->add( $permission );
$em->persist( $user );
$em->flush();
This is the last bit of my stack trace:
PHP 11. Doctrine\ORM\UnitOfWork->persist() vendor/doctrine/orm/lib/Doctrine/ORM/EntityManager.php:565
PHP 12. Doctrine\ORM\UnitOfWork->doPersist() vendor/doctrine/orm/lib/Doctrine/ORM/UnitOfWork.php:1555
PHP 13. Doctrine\ORM\UnitOfWork->cascadePersist() vendor/doctrine/orm/lib/Doctrine/ORM/UnitOfWork.php:1615
PHP 14. Doctrine\ORM\UnitOfWork->doPersist() vendor/doctrine/orm/lib/Doctrine/ORM/UnitOfWork.php:2169
PHP 15. Doctrine\ORM\UnitOfWork->persistNew() vendor/doctrine/orm/lib/Doctrine/ORM/UnitOfWork.php:1597
PHP 16. Doctrine\ORM\UnitOfWork->scheduleForInsert() doctrine/orm/lib/Doctrine/ORM/UnitOfWork.php:836
PHP 17. Doctrine\ORM\UnitOfWork->addToIdentityMap() vendor/doctrine/orm/lib/Doctrine/ORM/UnitOfWork.php:1157
PHP 18. implode() vendor/doctrine/orm/lib/Doctrine/ORM/UnitOfWork.php:1337
Any insight would be greatly appreciated.
OK. I've got it working.
I haven't fully worked out the reason yet but when I add the following to my User entity it works:
class User {
public function __toString()
{
return strval( $this->getId() );
}
}
If I find out more I will post here.
Your solution gave me a clue of what is happening.
Even though you have the entities and the anotations, Doctrine is not being able to understand the relation between entities. When doctrine understands the relation between entities, it knows what methods to call (ie User::getId()) but otherwise, it tries to transform whatever you are sending to a scalar value that it can use to query the database. Thats why it is calling the __toString function of the User, and thats why if you return the id in toString, everything works from here.
This is ok, but its a patch, and probably you dont want to keep it if we can find a better solution, since it could be harder to maintain as your application grows.
What i can see, is that in Permissions you have:
/**
* #ORM\Column(name="user_id", type="integer")
* #ORM\ManyToOne(targetEntity="User", inversedBy="permissions")
*/
protected $user;
You should remove the #ORM\Column(type="integer")
About the join columns, it is not mandatory, but you have to be sure that the defauts, are what you want. As we can read here
Before we introduce all the association mappings in detail, you should
note that the #JoinColumn and #JoinTable definitions are usually
optional and have sensible default values. The defaults for a join
column in a one-to-one/many-to-one association is as follows:
name: "<fieldname>_id"
referencedColumnName: "id"
so, they will be the same as an explicit:
/**
* #ORM\ManyToOne(targetEntity="User", inversedBy="permissions", cascade={"persist"})
* #ORM\JoinColumns({
* #ORM\JoinColumn(name="user_id", referencedColumnName="id")
* })
*/
protected $user;
So it is supposed to look for a column user_id in the Permissions table, and join it with the id column of the User table. We suppose that this is ok.
If this is true, then in your User, the id shouldnt be user_id, but id:
/**
* #ORM\Column(name="id", type="integer", nullable=false)
* #ORM\Id
* #ORM\GeneratedValue(strategy="IDENTITY")
*/
protected $id;
Or if the column name is actually user_id, then the User class is ok, but you have to change the join column to #ORM\JoinColumn(name="user_id", referencedColumnName="user_id")
That much i can say. I cannot try it know, but i will be glad if you can give it a second.
I think there's a problem with the mapping of user property in permission entity. Try this one:
/**
* #ORM\ManyToOne(targetEntity="User", inversedBy="permissions")
* #JoinColumn(name="user_id", referencedColumnName="id")
*/
protected $user;
are you initializing the collection in your OneToMany side?
and also, the methods to add and remove from the collection?
class User {
/**
* #ORM\OneToMany(targetEntity="Permission", mappedBy="user", cascade={"persist"})
*/
protected $permissions;
public function getPermissions()
{
return $this->permissions;
}
public function __construct()
{
$this->permissions = new \Doctrine\Common\Collections\ArrayCollection();
}
public function addPermissions (Permission $permissions)
{
$this->permissions[] = $permissions;
return $this;
}
public function removePermissions(Permission $permissions)
{
$this->permissions->removeElement($permissions);
}
//...