I want to create my entities from an existing database, it's an N - N relation which create articlesCategories table.
I currently have 2 entities, Article and Category, and I want to have a ManyToMany bidirectionnal relationship.
But when I try to get article or category with a findByXXX or findOneByXXX method, my ManyToMany attribute is NULL or I have an ORM Exception : Entity 'App\Models\Article' has no field 'categories'. You can therefore not call 'findByCategories' on the entities' repository
Database :
Table article : idArticle, name, description, priceHT, size
Table category : idCategory, name, percentTaxe
Table articlesCategories : idArticle, idCategory
Entities :
Category
/**
* #Entity
* #Table(name="category")
*/
class Category
{
/**
* #var integer
*
* #Id
* #Column(name="idCategory", type="integer", nullable=false)
* #GeneratedValue(strategy="AUTO")
*/
private $id;
/**
* #var string
* #Column(name="name", type="string", length=45, nullable=false)
*/
private $name;
/**
* #var string
* #Column(name="percentTaxe",type="decimal", precision=10, scale=0, nullable=false)
*/
private $percentTaxe;
/*
* Many categories have many articles
* #ManyToMany(targetEntity="App\Models\Article", inversedBy="categories")
* #JoinTable(name="articlesCategories",
* joinColumns={#JoinColumn(name="idCategory", referencedColumnName="idCategory")},
* inverseJoinColumns={#JoinColumn(name="idArticle", referencedColumnName="idArticle")}
* )
*/
private $articles;
/*Constructor*/
public function __construct(){
$toto = "toto";
var_dump($toto);
$this->articles = new ArrayCollection();
}
/***************************
Getters / Setters
****************************/
public function getId(){
return $this->id;
}
public function getName(){
return $this->name;
}
public function getPercentTaxe(){
return $this->percentTaxe;
}
public function getArticles(){
return $this->articles;
}
/************************/
public function setId($id){
$this->id = $id;
}
public function setName($name){
$this->name = htmlspecialchars($name);
}
public function setPercentTaxe($percentTaxe){
$this->percentTaxe = htmlspecialchars($percentTaxe);
}
public function setArticles(\Doctrine\Common\Collections\Collection $articles)
{
$this->articles = $articles;
}
/***************************
Getters / Setters
****************************/
public function addArticle(App\Models\Article $article)
{
var_dump($article);
$article->addCategory($this); // synchronously updating inverse side
$this->articles[] = $article;
}
Article
/**
* #Entity
* #Table(name="article")
*/
class Article
{
/**
* #var integer
*
* #Id
* #Column(name="idArticle", type="integer", nullable=false)
* #GeneratedValue(strategy="AUTO")
*/
private $id;
/**
* #var string
* #Column(name="name", type="string", length=45, nullable=false)
*/
private $name;
/**
* #var string
* #Column(name="description",type="string", nullable=true)
*/
private $description;
/**
* #var string
* #Column(name="priceHT",type="decimal", precision=10, scale=3, nullable=false)
*/
private $priceHT;
/**
* #var string
* #Column(name="size", type="string", length=3, nullable=true)
*/
private $size;
/*
* Many articles have many categories
* #ManyToMany(targetEntity="App\Models\Category", inversedBy="articles")
* #JoinTable(name="articlesCategories",
* joinColumns={#JoinColumn(name="idArticle", referencedColumnName="idArticle")},
* inverseJoinColumns={#JoinColumn(name="idCategory", referencedColumnName="idCategory")}
* )
*/
private $categories;
/*Constructor*/
public function __construct(){
echo"tata";
$this->categories = new ArrayCollection();
echo"tata";
}
/***************************
Getters / Setters
****************************/
public function getId(){
return $this->id;
}
public function getName(){
return $this->name;
}
public function getDescription(){
return $this->description;
}
public function getPriceHT(){
return $this->priceHT;
}
public function getSize(){
return $this->size;
}
public function getCategories(){
return $this->categories;
}
/************************/
public function setId($id){
$this->id = $id;
}
public function setName($name){
$this->name = htmlspecialchars($name);
}
public function setDescription($description){
$this->description = htmlspecialchars($description);
}
public function setPriceHT($priceHT){
$this->priceHT = htmlspecialchars($priceHT);
}
public function setSize($size){
$this->size = htmlspecialchars($size);
}
public function setCategories($categories){
$this->categories = $categories;
}
/***************************
Getters / Setters
****************************/
public function addCategory(App\Models\Category $category)
{
$category->addArticle($this); // synchronously updating inverse side
$this->categories[] = $category;
}
/************************/
public function hydrate($data)
{
foreach($data as $key => $value)
{
// Get back the setter name wich correspond to the attribute
$method = 'set'.ucfirst($key);
// if the good setter exist.
if(methodexists($this, $method))
{
$this->$method($value);
}
}
}
}
Manager
/**
* #param category : category of article we want
* #return an array of Article object or null
*/
public function getArticlesByCategory($categoryName)
{
$articles = NULL;
$repository = $this->getEntityManager()->getRepository("App\Models\Category");
$category = $repository->findOneByName($categoryName);
var_dump($category);
if($category != NULL)
{
$articles = $category->getArticles();
}
return $articles;
}
And when I var_dump my $category, I have : class App\Models\Category#122 (4) { private $id => int(2) private $name => string(7) "clothes" private $percentTaxe => string(2) "20" private $articles => NULL }
I found my categories and articles are null instead of to be an empty array because of Doctrine create instances of mapped entities without invoking constructor but I don't understand why It doesn't populate it.
I just use Doctrine2, I don't use symfony.
First, you have an error at your annotations on categories and articles fields of both entities. To be valid they should start with /** not /*.Change:
/*
* Many categories have many articles
to
/**
* Many categories have many articles
Same for $categories field of Article Entity.
Also change one of the fields(articles or categories) to be the inverse side of the relation, e.g.
inversedBy="categories"
to
mappedBy="categories"
On the other part of your question, default EntityRepository find methods(findBy, findOneBy) of the does not support filter by many to many relation(at least not yet). You will have to do the extra effort and use createQueryBuilder, e.g.:
$qb = $doctrine->getRepository(Article::class)->createQueryBuilder('a');
$qb->select(
'a',
'cat'
)
->innerJoin( 'a.categories', 'cat' )
->where('cat.id =1');
And better create your own ArticleRepository and CategoryRepository and define your find methods there. 7.8.8. Custom repositories
Related
so, I have multiple entities in my app, between them:
Product* - *Order
User* - 1UserRole
Order
class Order
{
...
/**
* #ManyToMany(targetEntity="Product", inversedBy="orders", cascade={"persist"})
* #JoinTable(
* name="orders_products",
* joinColumns={
* #JoinColumn(name="order_id", referencedColumnName="id")
* },
* inverseJoinColumns={
* #JoinColumn(name="product_id", referencedColumnName="id")
* }
* )
*/
private Collection $products;
...
public function __construct()
{
$this->products = new ArrayCollection();
}
...
public function addProduct(Product $product):self
{
$this->products[] = $product;
return $this;
}
}
Product
class Product
{
...
/**
* #ManyToMany(targetEntity="Order", mappedBy="products")
*/
protected Collection $orders;
...
public function __construct()
{
$this->orders = new ArrayCollection();
}
public function addOrder(Order $order): self
{
$this->orders[] = $order;
return $this;
}
}
User
class User
{
/**
* #ManyToOne(targetEntity="UserRole")
* #JoinColumn(name="role_id", referencedColumnName="id")
*/
protected $role;
}
And UserRole:
class UserRole
{
/** #Id #Column(type="integer") #GeneratedValue */
protected $id;
/** #Column(type="string", nullable=false, unique=true, length=20) */
protected $name;
/** #Column(type="integer", unique=true, nullable=false) */
protected $rolePriority;
/**
* Many Users have Many Stores.
* #ManyToMany(targetEntity="UserRoleAction", inversedBy="user_role_actions", fetch="EAGER", cascade={"persist"})
*/
protected Collection $userRoleActions;
/**
* UserRole constructor.
*/
public function __construct()
{
$this->userRoleActions = new ArrayCollection();
}
...
}
Ok so far, my issue is the following, when I try to save a new order that has many selected products:
class OrderService
{
public function createFromRequest(HttpRequest $request)
{
...
$products = $this->entityManager->getRepository(Product::class)->findWhatever(); //products are coming from this without issues.
foreach ($products as $product) {
$order->addProduct($product);
}
$this->entityManager->persist($order);
$this->entityManager->flush();
return $order;
}
}
I get:
Blockquote Expected value of type "UserRole" for association field "User#$role", got "__PHP_Incomplete_Class" instead.
I mean, how is this in any way related with the other entities??? I have no clue where to go to be fair. Any recommendations? Is doctrine doing some absolutely unrelated validation behind the scenes? wth?
I am new with Symfony. I am working with version 3. I simplified my example. I have 3 Tables, for this tables I created a class for each one.
Table "Person": id | name
Table "Group": id | groupname
Table "PersonGroup": id | person | group
Every Person could be in several groups and every groups can have several persons. In the table PersonGroup I connect the persons with the groups. In my Controller I want to request in which group the selected person is. But in the Dump I only have the data from the PersonGroup table and not the details from the Group (in this example the name):
PersonGroup {#485 ▼
-id: 5
-person: Person {#456 ▼
-id: 4
-name: "Adam"
-personGroups: PersistentCollection {#445 ▶}
}
-group: Group {#486 ▼
+__isInitialized__: false
-id: 5
-name: null
-personGroups: null
…2
}
}
My Controller:
/**
* #Route("/person/{personId}", name="personController")
*/
public function showAction($personId)
{
$em = $this->getDoctrine()->getManager();
$item = $em->getRepository('AppBundle:Person')
->findOneBy(['id' => $personId]);
foreach ($person->getPersonGroup() as $personGroup) {
dump($personGroup);
}
return $this->render('person/detail.html.twig', [
'person' => $person
]);
}
Person class / entity:
class Person
{
/**
* #ORM\Id
* #ORM\GeneratedValue(strategy="AUTO")
* #ORM\Column(type="integer")
*/
private $id;
/**
* #ORM\Column(type="string")
*/
private $name;
/**
* #ORM\OneToMany(targetEntity="AppBundle\Entity\PersonGroup", mappedBy="person")
*/
private $personGroups;
/**
* Item constructor.
*/
public function __construct()
{
$this->personGroups= new ArrayCollection();
}
/**
* #return mixed
*/
public function getId()
{
return $this->id;
}
/**
* #param mixed $id
*/
public function setId($id)
{
$this->id = $id;
}
/**
* #return mixed
*/
public function getName()
{
return $this->name;
}
/**
* #param mixed $name
*/
public function setName($name)
{
$this->name = $name;
}
/**
* #return ArrayCollection*
*/
public function getPersonGroups()
{
return $this->personGroups;
}
}
Group class/Entity:
class Group
{
/**
* #ORM\Id
* #ORM\GeneratedValue(strategy="AUTO")
* #ORM\Column(type="integer")
*/
private $id;
/**
* #ORM\Column(type="string")
*/
private $name;
/**
* #ORM\OneToMany(targetEntity="AppBundle\Entity\PersonGroup", mappedBy="group")
*/
private $personGroups;
/**
* Item constructor.
*/
public function __construct()
{
$this->personGroups= new ArrayCollection();
}
/**
* #return mixed
*/
public function getId()
{
return $this->id;
}
/**
* #param mixed $id
*/
public function setId($id)
{
$this->id = $id;
}
/**
* #return mixed
*/
public function getName()
{
return $this->name;
}
/**
* #param mixed $name
*/
public function setName($name)
{
$this->name = $name;
}
/**
* #return ArrayCollection*
*/
public function getPersonGroups()
{
return $this->personGroups;
}
}
PersonGroup class / entity:
class PersonGroup
{
/**
* #ORM\Id
* #ORM\GeneratedValue(strategy="AUTO")
* #ORM\Column(type="integer")
*/
private $id;
/**
* #ORM\ManyToOne(targetEntity="AppBundle\Entity\Person", inversedBy="group")
* #ORM\JoinColumn(nullable=false)
*/
private $person;
/**
* #ORM\ManyToOne(targetEntity="AppBundle\Entity\Group", inversedBy="person")
* #ORM\JoinColumn(nullable=false)
*/
private $group;
/**
* #return mixed
*/
public function getId()
{
return $this->id;
}
/**
* #param mixed $id
*/
public function setId($id)
{
$this->id = $id;
}
/**
* #return Person
*/
public function getPerson()
{
return $this->person;
}
/**
* #param Person $person
*/
public function setPerson(Person $person)
{
$this->person = $person;
}
/**
* #return Group
*/
public function getGroup()
{
return $this->group;
}
/**
* #param Group $group
*/
public function setGroup(Group $group)
{
$this->group = $group;
}
}
You can access to your Group entity from PersonGroup
/**
* #Route("/person/{personId}", name="personController")
*/
public function showAction($personId)
{
$em = $this->getDoctrine()->getManager();
$item = $em->getRepository('AppBundle:Person')
->findOneBy(['id' => $personId]);
foreach ($person->getPersonGroup() as $personGroup) {
dump($personGroup->getGroup());
}
return $this->render('person/detail.html.twig', [
'person' => $person
]);
}
But you should create a custom function in Group repository and Person repository to retrieve the correct entity.
EDIT
If you want te retrieve all the Group entity you should add a parameter fetch="EAGER" to your relations, it will automatically do aan innerJoin on your relation.
class PersonGroup
{
/**
* #ORM\Id
* #ORM\GeneratedValue(strategy="AUTO")
* #ORM\Column(type="integer")
*/
private $id;
/**
* #ORM\ManyToOne(targetEntity="AppBundle\Entity\Person", inversedBy="group", fetch="EAGER")
* #ORM\JoinColumn(nullable=false)
*/
private $person;
/**
* #ORM\ManyToOne(targetEntity="AppBundle\Entity\Group", inversedBy="person", fetch="EAGER")
* #ORM\JoinColumn(nullable=false)
*/
private $group;
For example if you want to get All Groups from a Person you can do a custom repository function.
Ex:
<?php
namespace AppBundle\Repository;
use AppBundle\Entity\Person;
/**
* GroupRepository
*
* This class was generated by the Doctrine ORM. Add your own custom
* repository methods below.
*/
class GroupRepository extends \Doctrine\ORM\EntityRepository
{
public function findByPerson(Person $person){
return $this->createQueryBuilder('g')
->leftJoin('g.personGroups', 'pg')
->leftJoin('pg.person', 'p')
->where('p.id = :personId')
->setParameter('personId', $person->getId())
->getQuery()->getResult();
}
}
Given already persisted objects for categories. How do one reuse the existing categories in a one-to-many relationship in new objects when only the id of the cateories is known
/** #Entity() */
class Category {
/**
* #var string
* #\Doctrine\ORM\Mapping\Column(type="string")
*/
private $name;
/** #var string
* #\Doctrine\ORM\Mapping\Id()
* #\Doctrine\ORM\Mapping\Column(type="string")
*/
private $id;
/**
* Category constructor.
* #param string $name
* #param string $id
*/
public function __construct($name, $id)
{
$this->name = $name;
$this->id = $id;
}
/**
* #return string
*/
public function getName()
{
return $this->name;
}
/**
* #return string
*/
public function getId()
{
return $this->id;
}
}
And now I have lets say two categories id=1 -> fiction and id=2 -> english book.
Now I know the ids of the categories and want to save the one-to-many relations in my Book object.
/** #Entity() */
class Book {
/** #var mixed
* #\Doctrine\ORM\Mapping\OneToMany()
*/
private $categories;
/** #var string */
private $title;
/**
* Book constructor.
* #param mixed $categories
* #param string $title
*/
public function __construct($categories, $title)
{
$this->categories = $categories;
$this->title = $title;
}
/**
* #return mixed
*/
public function getCategories()
{
return $this->categories;
}
/**
* #return string
*/
public function getTitle()
{
return $this->title;
}
}
Is it possible to create and persist a book object by hand with references an existing and already persisted object from which I do only know the id?
You can't achieve this without retrieving at least a Reference proxy :
$categoriesRefs = ['english' => 1, 'fiction' => 2];
$categories = [];
foreach($categoriesRefs as $ref){
$categories[] = $em->getReference('Namespace\Entity\Category', $ref));
}
$book = new Book($categories, 'title');
$em->persist($book);
$em->flush();
You can store categories without fetching the whole Category objects.
Read more about reference proxy.
I have an application with Zend Framework2 and Doctrine2 as ORM.
I have this Entity called User:
namespace Adm\Entity;
use Doctrine\ORM\Mapping as ORM;
/**
* #ORM\Entity
* #ORM\Table(name="user")
*/
class User{
/**
* #ORM\Id
* #ORM\Column(type="integer");
* #ORM\GeneratedValue(strategy="AUTO")
*/
protected $id;
/**
* #ORM\Column(type="string")
*/
protected $name;
/**
* #ORM\Column(type="string")
*/
protected $email;
/**
* #ORM\Column(type="string")
*/
protected $password;
/**
* #ORM\ManyToMany(targetEntity="Module")
* #ORM\JoinTable(
* name="user_module",
* joinColumns={#ORM\JoinColumn(name="user_id", referencedColumnName="id")},
* inverseJoinColumns={#ORM\JoinColumn(name="module_id", referencedColumnName="id")}
* )
*/
protected $modules;
public function __construct() {
$this->modules = new \Doctrine\Common\Collections\ArrayCollection();
}
/**
* #return the $id
*/
public function getId() {
return $this->id;
}
/**
* #return the $name
*/
public function getName() {
return $this->name;
}
/**
* #return the $email
*/
public function getEmail() {
return $this->email;
}
/**
* #return the $password
*/
public function getPassword() {
return $this->password;
}
/**
* #param field_type $id
*/
public function setId($id) {
$this->id = $id;
}
/**
* #param field_type $name
*/
public function setName($name) {
$this->name = $name;
}
/**
* #param field_type $email
*/
public function setEmail($email) {
$this->email = $email;
}
/**
* #param field_type $password
*/
public function setPassword($password) {
$this->password = $password;
}
/**
* Add module
*
* #param dm\Entity\Module
* #return User
*/
public function addModules(Module $modules = null){
$this->modules[] = $modules;
}
/**
* Get modules
*
* #return \Doctrine\Common\Collections\Collection
*/
public function getModules(){
return $this->modules;
}
}
See the modules property is a relation Many to Many with a table called user_modules.
And i have the Entity Module as well:
namespace Adm\Entity;
use Doctrine\ORM\Mapping as ORM;
class Module{
/**
* #ORM\Id
* #ORM\Column(type="integer");
* #ORM\GeneratedValue(strategy="AUTO")
*/
private $id;
/**
* #ORM\Column(type="string")
*/
private $name;
/**
* #ORM\Column(type="integer")
*/
private $status;
/**
* #return the $id
*/
public function getId() {
return $this->id;
}
/**
* #return the $name
*/
public function getName() {
return $this->name;
}
/**
* #return the $status
*/
public function getStatus() {
return $this->status;
}
/**
* #param field_type $id
*/
public function setId($id) {
$this->id = $id;
}
/**
* #param field_type $name
*/
public function setName($name) {
$this->name = $name;
}
/**
* #param field_type $status
*/
public function setStatus($status) {
$this->status = $status;
}
}
I receive a array variable containing the Post from a form to insert in a table. Each post element have it's property in Entity, as expected. Together, i have a $module variable which is an array containing id's of the modules. My question is: How do i insert this data in the user_module table?
My add function is this:
public function addUser($newUser){
$user = new User();
$user->setName($newUser['name']);
...
$this->getEm()->persist($user);
$this->getEm()->flush();
}
Firstly you need to have cascade={"persist"} as mentioned by #skrilled.
Then you need to retrieve the module entities from the database. You mentioned you have the id's in the $module variable.
You need a DQL statement something like this
$builder = $this->getEntityManager()->createQueryBuilder();
$builder->select('m')
->from('Adm\Entity\Module', 'm')
->where('m.id IN (:modules)')
->setParameter('modules', $modules);
$moduleEntities= $builder->getQuery()->getResult(Query::HYDRATE_OBJECT);
and in your User entity you will need
public function addModules(Array $moduleEntities)
{
foreach ($moduleEntities as $module) {
if ($module instanceof Module) {
$this->modules[] = $module;
}
}
return $this;
}
finally in your addUser method you will need to add the array of modules from the above DQL
public function addUser($newUser, $moduleEntities)
{
$user = new User();
$user->setName($newUser['name']);
....
$user->addModules($moduleEntities);
$this->getEm()->persist($user);
$this->getEm()->flush();
}
I hope this helps
You should read about using cascade. This will allow you to save/modify/remove the associated relationships and how you expect this to work.
In this case, you would want the relationship to persist since you want the associated entities to be saved when user itself is saved.
#ORM\ManyToMany(targetEntity="Module", cascade={"persist"})
http://doctrine-orm.readthedocs.org/projects/doctrine-orm/en/latest/reference/working-with-associations.html
By default, no operations are cascaded.
The following cascade options exist:
persist : Cascades persist operations to the associated entities.
remove : Cascades remove operations to the associated entities.
merge : Cascades merge operations to the associated entities.
detach : Cascades detach operations to the associated entities.
refresh : Cascades refresh operations to the associated entities.
all : Cascades persist, remove, merge, refresh and detach operations to associated entities.
I have two classes:
News:
/** #Entity #Table(name="news") */
class News {
/**
* #Id #GeneratedValue #Column(type="integer")
* #var integer
*/
protected $id;
/**
* #Column(type="string", length=100)
* #var string
*/
protected $title;
/**
* #Column(type="text")
* #var string
*/
protected $content;
/**
* #ManyToOne(targetEntity="User", inversedBy="news")
* #JoinColumn(referencedColumnName="id")
*/
protected $author;
/**
* #ManyToOne(targetEntity="NewsCategory", inversedBy="news")
* #JoinColumn(referencedColumnName="id")
*/
protected $category;
/**
* #Column(type="datetime")
*/
protected $add_date;
# CATEGORY methods
public function setCategory($val) { if($val instanceof NewsCategory) $this->category = $val; }
public function getCategory() { return $this->category; }
}
NewsCategory:
/** #Entity #Table(name="news_category") */
class NewsCategory {
/**
* #Id #GeneratedValue #Column(type="integer")
* #var integer
*/
protected $id;
/**
* #Column(type="string", length=50, unique=TRUE)
* #var string
*/
protected $name;
/**
* #OneToMany(targetEntity="News", mappedBy="category")
*/
protected $news;
public function __construct() {
$this->news = new \Doctrine\Common\Collections\ArrayCollection;
}
public function setName($name) { $this->name = $name; }
public function getName() { return $this->name; }
public function getNews() { return $this->news; }
}
I want to download one news with this query:
$q = $this->db->createQuery("SELECT n FROM News n WHERE n.id = :id");
$q->setParameter('id', $_GET['id']);
$news = $q->getResult();
And next, I want to get id of a Category related to this news with
$news->getCategory()->getId()
With code above, I'm getting this error:
Fatal error: Call to undefined method DoctrineProxies\NewsCategoryProxy::getId() in C:\[...]\newsController.php on line 61
What's wrong? Why my NewsCategory class can't see getId() method?
It's a good practice to allways declare your class' members private and to generate getters and setters on your class members.
In your case, you don't generate getters and setters (there is no getId() method on your NewCategory class).
That's how your NewCategory class should look like :
/** #Entity #Table(name="news_category") */
class NewsCategory {
/**
* #Id #GeneratedValue #Column(type="integer")
* #var integer
*/
private $id;
/**
* #Column(type="string", length=50, unique=TRUE)
* #var string
*/
private $name;
/**
* #OneToMany(targetEntity="News", mappedBy="category")
*/
private $news;
public function __construct() {
$this->news = new \Doctrine\Common\Collections\ArrayCollection;
}
public function getId(){ return $this->id;}
public function setId($id){ $this->id = $id;}
public function setName($name) { $this->name = $name; }
public function getName() { return $this->name; }
public function setNews($news) {$this->news = $news;}
public function getNews() { return $this->news; }
}
The generated proxies will not generate magically getters and setters on every of your properties (it would break the OOP's encapsulation principle).
You can find more documentations about proxies here : http://www.doctrine-project.org/docs/orm/2.0/en/reference/configuration.html#proxy-objects