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.
Related
I have two fields : account_type_id and account_id.
How can i manually map doctrine TargetEntity to join Company Entity if accountTypeId = 1 OR join User Entity if account_type_id = 2 ?
<?php
/** #Entity */
class Accounts
{
// 1= Company, 2 = User
private $accountType;
/**
* #ManyToOne(targetEntity="Companies")
*/
private $company;
/**
* #ManyToOne(targetEntity="Users")
*/
private $user;
//...
}
Unfortunately, joining different columns on the fly cannot be done automatically, but you can have both fields set as nullable and only set the correct one when persisting the Account entity.
This would be the annotation:
/**
* #ORM\ManyToOne(targetEntity="Users", inversedBy="users")
* #ORM\JoinColumn(name="user_id", referencedColumnName="id", nullable=true)
*/
private $user;
Keep in mind that nullable=true is the default anyway, I'm just being specific here.
If you want to go defensively about this, you can have an additional check in getter
/**
* #return User
* #throws \Exception
*/
public function getUser()
{
if ($this->accountType !== 2) {
throw new \Exception("Entity is not of type 'user'");
}
return $this->user;
}
I have set up a many to one relation and want to add a new product-object linked to its category. Related to this I have 2 questions:
I get stuck on saving the category object to the new product. Tried different options and read related questions here. At this moment I am getting the error:
Attempted to call an undefined method named "getCategory" of class "AppBundle\Controller\ProductController".
I do have the method getCategory in my Product class.
What am I missing here?
Another thing I would like to know is do I need to pass the category-id in the url to get the related products for that category?
I have a category class:
namespace AppBundle\Entity;
use Doctrine\ORM\Mapping as ORM;
use Doctrine\Common\Collections\ArrayCollection;
/**
* #ORM\Entity
* #ORM\Table(name="category")
*/
class Category
{
/**
* #ORM\Id
* #ORM\GeneratedValue(strategy="AUTO")
* #ORM\Column(type="integer")
*/
private $cat_id;
...
/**
* #ORM\OneToMany(targetEntity="Product", mappedBy="category")
*/
private $products; ...
public function __construct()
{
$this->products = new ArrayCollection();
}
and product class:
namespace AppBundle\Entity;
use Doctrine\ORM\Mapping as ORM;
use AppBundle\Entity\Category;
use Doctrine\Common\Collections\ArrayCollection;
/**
* #ORM\Entity
* #ORM\Table(name="product")
*/
class Product
{
/**
* #ORM\Id
* #ORM\GeneratedValue(strategy="AUTO")
* #ORM\Column(type="integer")
*/
private $prd_id;
/**
* #var Category
*
* #ORM\ManyToOne(targetEntity="Category", inversedBy="products")
* #ORM\JoinColumn(name="cat_id", referencedColumnName="cat_id", nullable=false)
*/
private $category;
....
/**
* Set category
*
* #param \AppBundle\Entity\Category $category
*
* #return Product
*/
public function setCategory(\AppBundle\Entity\Category $category)
{
$this->category = $category;
return $this;
}
/**
* Get category
*
* #return \AppBundle\Entity\Category
*/
public function getCategory()
{
return $this->category;
}
From my list of categories "/categories" I link the categories to the productlist "/cat1/product" (<-- do I need to pass the category-id here?). There I want to add a new product and call the following action in my ProductController:
namespace AppBundle\Controller;
use Sensio\Bundle\FrameworkExtraBundle\Configuration\Route;
use Symfony\Bundle\FrameworkBundle\Controller\Controller;
use Symfony\Component\HttpFoundation\Response;
use AppBundle\Entity\Product;
use AppBundle\Form\ProductType;
use Symfony\Component\HttpFoundation\Request;
class ProductController extends Controller
{
/**
* #Route("/cat{cat_id}/product/new", name="newproduct")
*/
public function newAction(Request $request, $cat_id)
{
$product = new Product();
$form = $this->createForm(ProductType::class, $product);
$form->handleRequest($request);
if ($form->isSubmitted() && $form->isValid()) {
$category = $this->getCategory();
$product->setCategory($category);
$em = $this->getDoctrine()->getManager();
$em->persist($product);
$em->flush();
return $this->redirectToRoute('productlist');
}
return $this->render('product/new.html.twig', array(
'form' => $form->createView(),
'cat_id' => $cat_id,
));
}
Suggestions appreciated!
when you do :
$category = $this->getCategory();
$this represent your productController, it is the reason of the undefined method error. to get the category object you must do :
$categoryRepository = $this->getDoctrine()->getRepository('AppBundle:Category');
$product->setCategory($categoryRepository->find($cat_id));
hope that may help you.
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.
So I have 3 entities with their properties.
A USER which has MANY USERSKILLS with each ONE having a single corresponding SKILL
Here are the entities:
/**
* #ORM\Table(name="skills")
* #ORM\Entity()
*/
class Skill
{
/**
* #ORM\Column(name="id", type="integer")
* #ORM\Id
* #ORM\GeneratedValue(strategy="AUTO")
*/
private $id;
/**
* #ORM\Column(name="name", type="string", length=30, unique=true)
*/
private $name;
/**
* #ORM\Column(name="active", type="boolean")
* #var bool
*/
private $active = false;
/**
* #ORM\OneToMany(targetEntity="UserSkill", mappedBy="skill")
*/
private $userSkills;
}
/**
* #ORM\Table(name="user_skill")
* #ORM\Entity()
*/
class UserSkill
{
/**
* #ORM\Id
* #ORM\ManyToOne(targetEntity="User", inversedBy="skills")
* #var User
*/
private $user;
/**
* #ORM\Id
* #ORM\ManyToOne(targetEntity="Skill", inversedBy="userSkills")
* #var Skill
*/
private $skill;
/**
* #ORM\Column(type="integer")
* #var int
*/
private $level = 0;
}
/**
* StartupDriven\UserBundle\Entity\User
*
* #ORM\Table(name="users")
* #ORM\Entity(repositoryClass="StartupDriven\UserBundle\Entity\UserRepository")
*/
class User implements AdvancedUserInterface, \Serializable
{
/**
* #ORM\OneToMany(targetEntity="UserSkill", mappedBy="user")
* #var ArrayCollection
*/
private $skills;
}
They are created using 2 symfony form objects which works fantastically.
The problem is when I go to persist these objects in the controller.... I get an error
$form->handleRequest($request);
if ($form->isValid()) {
/**
* #var User $user
*/
$user = $form->getData();
/**
* #var UserSkill $userSkill
*/
foreach ($user->getSkills() as $userSkill) {
// No updating skills... Only new ones.
if (!$userSkill->getId()) {
// Check skill name match.
if ($matched_skill = $em->getRepository('StartupDrivenUserBundle:Skill')->findOneBy(array('name' => $userSkill->getName()))) {
$userSkill->setSkill($matched_skill);
}
else {
// No match. Create new generic skill.
$em->persist($userSkill->getSkill());
// THE ERROR HAPPENS ON THE FOLLOWING LINE!
$em->flush();
}
// Set the User
$userSkill->setUser($user);
// Persist the user skill.
$em->persist($userSkill);
$em->flush();
}
}
$em->persist($user);
$em->flush();
Error Message:
A new entity was found through the relationship 'StartupDriven\UserBundle\Entity\User#skills' that was not configured to cascade persist operations for entity: StartupDriven\UserBundle\Entity\UserSkill#0000000053e8628e00007f7f2fd56e1f. To solve this issue: Either explicitly call EntityManager#persist() on this unknown entity or configure cascade persist this association in the mapping for example #ManyToOne(..,cascade={"persist"}). If you cannot find out which entity causes the problem implement 'StartupDriven\UserBundle\Entity\UserSkill#__toString()' to get a clue.
I have tried every combination of persist & flush that I can think of. I have tried the cascade option above (which gives a different error that the "user id which is required by the primary key rules is not set")... I am completely lost. It seems like such a simple relationship... Where am I going wrong?
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.