Symfony4 Doctrine Associations insert - doctrine-orm

I get xml data from external soap server, parsing data and create Object. Next I want to persist it in database but it does't work.
Company id, I get from external soap and its string unique value like 387sdfh899ohkadkfh8.
Company
/**
* #ORM\Entity
*/
class Company
{
/**
* #ORM\Id()
* #ORM\GeneratedValue(strategy="NONE")
* #ORM\Column(type="string")
*/
private $id;
/**
* #ORM\OneToMany(targetEntity="App\Entity\Address", mappedBy="company", orphanRemoval=true, cascade={"persist","remove"})
*/
private $addresses;
// ...
}
Address
/**
* #ORM\Entity
*/
class Address
{
// ...
/**
* #ORM\ManyToOne(targetEntity="App\Entity\Company",inversedBy="adresses")
* #ORM\JoinColumn(nullable=false)
*/
private $company;
// ...
}
CompanyController
class CompanyController
{
// ...
$json = $serializer->serialize($data, 'json');
$obj = $serializer->deserialize($json, 'array<App\Entity\Company>', 'json');
// ...
}
Every thing looks as expected. Object was created including two Address objects.
Update - begin
This is structure what I get from deserialize
array:1 [
0 => Company {#524
-id: "0946346d06ffe3f551a80700c2a5c534"
// ..
-addresses: ArrayCollection {#538
-elements: array:2 [
0 => Address {#1017
-id: null
// ...
-company: null
}
1 => Address {#537
-id: null
// ..
-company: null
}
]
}
-status: "Active"
}
]
Update - end
But when I try to store it in database:
CompanyController
class CompanyController
{
// ...
$em= $this->getDoctrine()->getManager();
foreach ($obj as $o) $em->persist($o);
$em->flush();
// ...
}
I'm getting error
inserting address doesn't include id of company. company_id is setting to null
Similar json data, including addresses corresponding to company I'll be getting from client with json format, parsing it via FormType and store to database but I can't manage with :/
How should I insert that related objects in proper way?

Ok, I solved the problem
Company
/**
* #ORM\OneToMany(targetEntity="App\Entity\Address", mappedBy="company", orphanRemoval=true, cascade={"persist","remove"})
* #JMS\Accessor(setter="setAddresses")
*/
private $addresses;
And added method:
/**
* #param ArrayCollection $addresses
* #return Company
*/
public function setAddresses(ArrayCollection $addresses): self
{
if ( !$this->addresses instanceof ArrayCollection ){
$this->addresses = new ArrayCollection();
};
foreach ($addresses as $address){
if (!$this->addresses->contains($address)) {
$this->addresses[] = $address;
$address->setCompany($this);
}
}
return $this;
}
I spend on this issue two days :/ and solution was so easy, #malarzm Thanks for suggestion it helped me a lot.

Related

doctrine2 entity number in month generation

I've got Invoice entity, in which I'd like to generate subsequent numbers within a given month.
Entity code:
/**
* Class Invoice
* #package App\Entity
* #ORM\Entity()
* #ORM\HasLifecycleCallbacks()
*/
class Invoice
{
(...)
/**
* #var int
* #ORM\Column(type="integer")
*/
private $year;
/**
* #var int
* #ORM\Column(type="integer")
*/
private $month;
/**
* #var int
* #ORM\Column(type="integer")
* #ORM\GeneratedValue(strategy="CUSTOM")
* #ORM\CustomIdGenerator(class="App\Helper\InvoiceNumberGenerator")
*/
private $counter;
(...)
/**
* #ORM\PrePersist
* #ORM\PreUpdate
*/
public function numberGenerator()
{
if ($this->getYear() === null) {
$this->setYear(date('Y'));
$this->setMonth(date('m'));
}
}
And App\Helper\InvoiceNumberGenerator code is:
<?php
namespace App\Helper;
use App\Entity\Invoice;
use Doctrine\Common\Persistence\ObjectRepository;
use Doctrine\ORM\EntityManager;
use Doctrine\ORM\EntityRepository;
use Doctrine\ORM\Id\AbstractIdGenerator;
use Exception;
class InvoiceNumberGenerator extends AbstractIdGenerator
{
/**
* Generates an invoice number
*
* #param EntityManager $em
* #param Invoice $entity
* #return mixed
* #throws Exception
*/
public function generate(EntityManager $em, $entity)
{
if (!$entity instanceof Invoice) {
throw new Exception('Generator służy tylko do generowania numerów faktur.');
}
/** #var ObjectRepository | EntityRepository $invoiceRepository */
$invoiceRepository = $em->getRepository(Invoice::class);
/** #var Invoice $lastInvoice */
$lastInvoice = $invoiceRepository->findOneBy(
array(
'year' => $entity->getYear(),
'month' => $entity->getMonth()
),
array(
'counter' => 'desc'
)
);
if (empty($lastInvoice)) {
return 1;
}
return $lastInvoice->getCounter() + 1;
}
}
When I dump $lastInvoice, it shows:
Invoice {#5522 ▼
-id: 1
-generated: false
-fileName: "example"
-year: 2019
-month: 11
-counter: 1
-name: "AG"
-company: "Gall"
-address: "Street 1"
-address2: "Gliwice"
-nip: "6314567890"
-reservation: Reservation {#5855 ▶}
-date: null
}
So it looks like the generator gets to selecting last one correctly, but nevertheless I got error when trying to create new Invoice:
SQLSTATE[23000]: Integrity constraint violation: 1048 Column 'counter'
cannot be null
Any advise on what I'm doing wrong?
the #CustomIdGenerator annotation is only called when the column is also marked with #Id. From the docs:
This annotations allows you to specify a user-provided class to generate identifiers. This annotation only works when both #Id and #GeneratedValue(strategy="CUSTOM") are specified.
Ids are always a special kind of thing and thus must sometimes be perfect before inserting. To solve your problem - because the counter is not an id column -, you could use lifecycle events instead (prePersist, probably) and use the event's entity manager in an event listener/subscriber to run your query.

API Platform - PATCH and ArrayCollection

I'm using API-Platform and faced an issue with updating many-to-many with an empty value.
Here is the small example:
/**
* Many Organizations have Many Followers.
* #ORM\ManyToMany(targetEntity="App\Entity\User\User", inversedBy="organizations")
* #ORM\JoinTable(name="organizations_followers",
* joinColumns={#ORM\JoinColumn(name="organization_id", referencedColumnName="id")},
* inverseJoinColumns={#ORM\JoinColumn(name="user_id", referencedColumnName="id", unique=true)}
* )
*/
protected $followers;
/**
* #return Collection
*/
public function getFollowers(): Collection
{
return $this->followers;
}
/**
* #param array $followers
*/
public function setFollowers(array $followers): void
{
$this->followers = $followers;
}
/**
* Organization constructor.
*/
public function __construct()
{
$this->id = Uuid::uuid4();
$this->followers = new ArrayCollection();
}
So, when I'm trying to delete all followers (PATCH request with empty followers in the relationships field) I always get one undeleted record. What am I doing wrong? Any Ideas?

How to set parent id on a child entity when submitting symfony3 form

How can I set the task_id on the TaskLine entity?
Similar Questions:
Doctrine: How to insert foreign key value
Best practice for inserting objects with foreign keys in symfony2
Doctrine 2 entity association does not set value for foreign key
I get this error:
Neither the property "task_id" nor one of the methods "getTaskId()", "taskId()", "isTaskId()", "hasTaskId()", "__get()" exist and have public access in class "AppBundle\Entity\TaskLine"
NOTE: Normally I work with PropelORM.
I'm trying to save a new TaskLine entity which is related to Task entity. I'm posting JSON payload which look something like this.
{
"id": null,
"task_id": 1,
"note" : "new note"
}
In the controller I json_decode the request payload and submit that to $form->submit($note_data), $form is an instance of:
class TaskNoteType extends AbstractType
{
/**
* #param FormBuilderInterface $builder
* #param array $options
*/
public function buildForm(FormBuilderInterface $builder, array $options)
{
$builder
->add( 'task_id', NumberType::class )
->add( 'note', TextType::class )
;
}
/**
* #param OptionsResolver $resolver
*/
public function configureOptions(OptionsResolver $resolver)
{
$resolver->setDefaults(array(
'data_class' => 'AppBundle\Entity\TaskLine'
));
}
}
Here is my Task entity
class Task
{
/**
* #var string
*
* #ORM\Column(name="description", type="string", length=150, nullable=true)
*/
private $description;
/**
* #var integer
*
* #ORM\Column(name="id", type="integer")
* #ORM\Id
* #ORM\GeneratedValue(strategy="IDENTITY")
*/
private $id;
}
TaskLine entity
class TaskLine
{
/**
* #var integer
*
* #ORM\Column(name="id", type="integer")
* #ORM\Id
* #ORM\GeneratedValue(strategy="IDENTITY")
*/
private $id;
/**
* #var \AppBundle\Entity\Task
*
* #ORM\ManyToOne(targetEntity="AppBundle\Entity\Task")
* #ORM\JoinColumns({
* #ORM\JoinColumn(name="task_id", referencedColumnName="id")
* })
*/
private $task;
/**
* Get id
*
* #return integer
*/
public function getId()
{
return $this->id;
}
/**
* Set task
*
* #param \AppBundle\Entity\Task $task
*
* #return TaskLine
*/
public function setTask(\AppBundle\Entity\Task $task = null)
{
$this->task = $task;
return $this;
}
/**
* Get task
*
* #return \AppBundle\Entity\Task
*/
public function getTask()
{
return $this->task;
}
}
I found my answer in here:
Best Practice for inserting objects with foreign keys in Symfony2
Answered by: Tuan nguyen
In ORM you have to set Foreign key by an object which your entity
associated with. You could use EntityManager#getReference to get a
reference to category entity without loading it from DB. Like this
$category = $entityManager->getReference('YourProject\Entity\Category', $categoryId);
$product = new Product();
$product->setCategory($category);
Similar questions:
Doctrine: How to insert foreign key value
Following function you should have already in your Slider entity (or
similar).
public function addImage(Image $image) {
$image->setSlider($this); // This is the line you're probably looking for
$this->images[] = $image;
return $this; }
What it does is if you persist the entity it writes the ID of the Slider (sid) into your Image.
Doctrine 2 entity
association does not set value for foreign key
I found something in the Doctrine 2 documentation:
Changes made only to the inverse side of an association are ignored.
Make sure to update both sides of a bidirectional association (or at
least the owning side, from Doctrine’s point of view) As in my case
the owning side is the User I must update it. Doctrine 1 was able to
manage it automatically... too bad.

Doctrine 2 Many To Many follower relationship not working

I have followed the example here doctrine 2 documentation and made the entity
<?php
namespace Account\Entity;
use Doctrine\ORM\Mapping as ORM;
use Doctrine\Common\Collections\ArrayCollection;
use Zend\Filter\Null;
/**
* #ORM\Entity
* #ORM\Table(name="accounts")
*/
class Account
{
/**
* #ORM\Id
* #ORM\GeneratedValue(strategy="AUTO")
* #ORM\Column(type="integer")
* #ORM\Column(length=11)
*/
private $id;
// ......
/**
* #ORM\ManyToMany(targetEntity="Account\Entity\Account", mappedBy="following")
*/
private $followers;
/**
* #ORM\ManyToMany(targetEntity="Account\Entity\Account", inversedBy="followers")
* #ORM\JoinTable(name="followers",
* joinColumns={#ORM\JoinColumn(name="account_id", referencedColumnName="id")},
* inverseJoinColumns={#ORM\JoinColumn(name="follower_id", referencedColumnName="id")}
* )
*/
private $following;
public function __construct(){
$this->followers = new ArrayCollection();
$this->following = new ArrayCollection();
}
/**
* #param mixed $followers
*/
public function setFollowers($followers)
{
$this->followers[] = $followers;
}
/**
* #return mixed
*/
public function getFollowers()
{
return $this->followers;
}
public function addFollowers($followers){
foreach($followers as $follower)
$this->followers->add($follower);
}
public function removeFollowers($followers){
$this->followers->removeElement($followers);
}
/**
* #param mixed $following
*/
public function setFollowing($following)
{
$this->following[] = $following;
}
/**
* #return mixed
*/
public function getFollowing()
{
return $this->following;
}
public function addFollowing($followers){
foreach($followers as $follower)
$this->following->add($follower);
}
public function removeFollowing($followers){
$this->following->removeElement($followers);
}
/**
* #param mixed $id
*/
public function setId($id)
{
$this->id = $id;
}
/**
* #return mixed
*/
public function getId()
{
return $this->id;
}
}
So I have 2 accounts (ids 1 and 2) and made it so that 1 follows (is friend to) 2.
The column is something like
user_id follower_id
2 1
By using the following code, I'm not getting any results as I should
$user = $this->entityManager()->getRepository('Account/Entity/Account')->find(1);
$followers = $user->getFollowers();
var_dump($followers);
It returns something like:
object(Doctrine\ORM\PersistentCollection)#357 (9) { ["snapshot":"Doctrine\ORM\PersistentCollection":private]=> array(0) { } ["owner":"Doctrine\ORM\PersistentCollection":private]=> NULL ["association":"Doctrine\ORM\PersistentCollection":private]=> NULL ["em":"Doctrine\ORM\PersistentCollection":private]=> NULL ["backRefFieldName":"Doctrine\ORM\PersistentCollection":private]=> NULL ["typeClass":"Doctrine\ORM\PersistentCollection":private]=> NULL ["isDirty":"Doctrine\ORM\PersistentCollection":private]=> bool(false) ["initialized":"Doctrine\ORM\PersistentCollection":private]=> bool(false) ["coll":"Doctrine\ORM\PersistentCollection":private]=> object(Doctrine\Common\Collections\ArrayCollection)#358 (1) { ["_elements":"Doctrine\Common\Collections\ArrayCollection":private]=> array(0) { } } }
The same happens if I use getFollowing and all the combinations I've tried. Am I missing something? I mean it's pretty much like the documentation code, please help me out!
I'm using Zend Framework 2, if that's of any help.
All associations are LAZY by default, which means it is populated when you first access it. PersistentCollection actually is an iterator and a var_dump will not trigger iteration, that's why you see _intialized property set to false and the count of _elements is 0.
You can use getArrayCopy or simply iterate through the collection.
var_dump($followers->getArrayCopy());
or:
foreach ($followers as $follower) {
var_dump($follower);
}

Two one-to-many relationship with reference table (Doctrine 2, ZF2)

I've a problem with my many-to-many relation. I want to have access to the reference table for a querybuilder query. With a many-to-many relation I don't have access to my reference table, so I've set up two one-to-many relationships. My structure look likes:
User ---> UserUserCategory <--- UserCategory
The above structure has two one-to-many relationships and are working fine with the database. When I have a user with the following data in the database (in UserUserCategory):
Table User
ID | Name
1 | Bart
2 | Gerard
Table Category
ID | Name
1 | Officer
2 | Medic
Table UserUserCategory
User | Category
1 | 1
2 | 2
So Bart is an Officer and Gerard is a Medic. But when I want to retrieve the data, it said that Bart is the Medic, and Gerard has a "null" value in the category.
My User-entity:
/**
* Entity Class representing a post of our User module.
*
* #ORM\Entity
* #ORM\Table(name="user")
* #ORM\Entity(repositoryClass="User\Repository\UserRepository")
*
*/
class User extends zfcUser implements UserInterface
{
/**
* Categories from user
*
* #ORM\OneToMany(targetEntity="User\Entity\UserUserCategory", mappedBy="user_id", cascade={"remove", "persist"})
* #var UserUserCategory
* #access protected
*/
protected $user_usercategories;
//name & user_id comes here
/**
* Constructor to make a new ArrayCollection for addresses
*
*
*/
public function __construct()
{
$this->user_usercategories = new ArrayCollection();
}
/**
* #param Collection $categories
*/
public function addUserUserCategories(Collection $user_usercategories)
{
foreach ($user_usercategories as $user_usercategorie) {
$user_usercategorie->setUser($this);
$this->user_usercategories->add($user_usercategorie);
}
}
/**
* #param Collection $categories
*/
public function removeUserUserCategories(Collection $user_usercategories)
{
foreach ($user_usercategories as $user_usercategorie) {
$user_usercategorie->setUser(null);
$this->user_usercategories->removeElement($user_usercategorie);
}
}
/**
* #return Collection
*/
public function getUserUserCategories()
{
return $this->categories;
}
}
My UserCategory-entity:
/**
* A User category entity.
* #ORM\Entity
* #ORM\Table(uniqueConstraints={#ORM\UniqueConstraint(name="unique_name_parentId", columns={"name", "parent_id"})})
* #ORM\HasLifecycleCallbacks
*/
class UserCategory extends Category
{
/**
* User_usercategories
*
* #ORM\OneToMany(targetEntity="User\Entity\UserUserCategory", mappedBy="category_id")
* #var UserUserCategory
* #access protected
*/
protected $user_usercategories;
/**
* Constructor
*/
public function __construct()
{
$this->user_usercategories = new ArrayCollection();
}
/**
* #param Collection $categories
*/
public function addUserUserCategories(Collection $user_usercategories)
{
foreach ($user_usercategories as $user_usercategorie) {
$user_usercategorie->setCategory($this);
$this->user_usercategories->add($user_usercategorie);
}
}
/**
* #param Collection $categories
*/
public function removeUserUserCategories(Collection $user_usercategories)
{
foreach ($user_usercategories as $user_usercategorie) {
$user_usercategorie->setCategory(null);
$this->user_usercategories->removeElement($user_usercategorie);
}
}
/**
* #return Collection
*/
public function getUserUserCategories()
{
return $this->categories;
}
}
My UserUserCategory-entity:
/**
* Entity Class representing a post of our User_UserCategory entity.
*
* #ORM\Entity
* #ORM\Table(name="user_usercategory")
*
*/
class UserUserCategory
{
/**
* User with a category
*
* #ORM\ManyToOne(targetEntity="User\Entity\User", inversedBy="user_usercategories")
* #ORM\JoinColumn(name="user_id", referencedColumnName="user_id", nullable=false, onDelete="CASCADE")
* #ORM\Id
*
* #var User
* #access protected
*/
protected $user_id;
/**
* Category from user
*
* #ORM\ManyToOne(targetEntity="User\Entity\UserCategory", inversedBy="user_usercategories")
* #ORM\JoinColumn(name="category_id", referencedColumnName="id", nullable=false, onDelete="CASCADE")
* #ORM\Id
*
* #var Category
* #access protected
*/
protected $category_id;
public function getUser()
{
return $this->user;
}
/**
* Set User
*
* #param User $user
* #return User
*/
public function setUser(User $user = null)
{
//die('setUser');
$this->user = $user;
return $this;
}
public function getCategory()
{
return $this->category;
}
/**
* Set Category
*
* #param Category $category
* #return Category
*/
public function setCategory(Category $category = null)
{
$this->category = $category;
return $this;
}
}
When I execute the following line, it gives back the wrong result. The wrong category pops up:
\Doctrine\Common\Util\Debug::dump($this->getEntityManager()->find('User\Entity\User', '49')->user_usercategories);
die;
Result:
array(1) {
[0]=>
object(stdClass)#452 (3) {
["__CLASS__"]=>
string(28) "User\Entity\UserUserCategory"
["user_id"]=>
string(16) "User\Entity\User"
["category_id"]=>
string(24) "User\Entity\UserCategory"
}
}
In the category_id is the medic printed, I expect the officer to get back.
In my other user, (id=60) the category_id field is "null". So it look likes Doctrine skips the first input in my UserUserCategory, starts with the second and can't get the last category anymore.
No offence, but I find your code very hard to read. I would suggest you to do few corrections and that might even help you in solving the problem.
1: Naming: Instead of UserCategory, rename it to Category. If your Category will have different types, create new column "type" with values from constansts like
class Category
{
const TYPE_USER = 1 ;
....
2: Instead of addCategories(Collection $array), do singular version like
public function addCategory(Category $category)
{
$reference = new UserCategory() ;
$reference->setUser($this) ;
$reference->setCategory($category) ;
$this->user_categories->add($reference) ;
}
public function removeCategory(Category $category)
{
foreach($this->user_categories as $reference) {
if ( $reference->getCategory() === $category )
$this->user_categories->removeElement($reference) ;
}
}
Symfony2 automaticaly recognizes methods like this. Even if your relation is plural (like categories), s2 will find singularified addCategory and removeCategory methods.
To get array of categories, use this:
public function getCategories()
{
$categories = new ArrayCollection() ;
foreach($this->user_categories as $reference) {
$categories->add( $reference->getCategory() ) ;
}
return $categories ;
}
If you do it like this, you will probably solve the problem you have.