Doctrine #id null when persisted - symfony

The following works and a row in inserted into both tables:
$user = new User();
$user->setId(8484);
$user->setData("user test data");
$profile = new Profile();
$profile->setBlah(8484);
$profile->setData("profile test data");
// if I leave this out it works...
$user->setProfile($profile);
$em = $this->getDoctrine()->getEntityManager();
$em->persist($user);
$em->flush();
But if leave out $user->setProfile($profile); I get an error because User's id is null:
An exception occurred while executing 'INSERT INTO User (id, data) VALUES (?, ?)' with params {"1":null,"2":"user test data"}
How can it be?
class User
{
/**
* #ORM\Id
* #ORM\Column(type="integer")
*/
protected $id;
/**
* #ORM\Column(type="string", length=64)
*/
protected $data;
/**
* #ORM\OneToOne(targetEntity="Profile", cascade={"persist", "remove"})
* #ORM\JoinColumn(name="id", referencedColumnName="blah")
*/
protected $profile;
}
class Profile
{
/**
* #ORM\Id
* #ORM\Column(name="blah", type="integer")
*/
protected $blah;
/**
* #ORM\Column(type="string", length=64)
*/
protected $data;
}
Set Profile method:
/**
* Set Profile
*
* #param \Test\AdminBundle\Entity\Profile $profile
* #return User
*/
public function setProfile(\Test\AdminBundle\Entity\Profile $profile = null)
{
$this->profile = $profile;
return $this;
}
EDIT:
If I change the joinColumn name to something random my object looks right using a var_dump but the query fails:
/**
* #ORM\OneToOne(targetEntity="Profile", cascade={"persist"})
* #ORM\JoinColumn(name="random_test", referencedColumnName="blah")
*/
Gives:
An exception occurred while executing 'INSERT INTO User (id, data, random_test) VALUES (?, ?, ?)' with params {"1":8484,"2":"user test data","3":null}:

You need to have your $profile persisted before Doctrine can successfully make a relationship between your user and your profile.
$em = $this->getDoctrine()->getEntityManager();
$user = new User();
$user->setId(8484);
$user->setData("user test data");
$profile = new Profile();
$profile->setBlah(8484);
$profile->setData("profile test data");
$em->persist($profile);
$user->setProfile($profile);
$em->persist($user);
$em->flush();
Think about what you are trying to do in your code in MySQL.
Originally you were saying:
Insert a user who's profile will be (8484)
Which results in (Err: Profile 8484 doesn't exist).
What you want to say is:
Insert a profile (8484).
Insert a user who's profile is (8484).

Related

Symfony - How to deserialize with default values?

I'm starting to learn Symfony 4.
I want to deserialize datas from json data. (I'm using JMSSerializer)
This is my context :
I have a Customer entity in src/App/Entity
class Customer {
/**
*
* #ORM\Id
* #ORM\Column(type="integer")
* #ORM\GeneratedValue(strategy="AUTO")
*/
private $id;
/**
* #ORM\Column(type="string", length=250)
* #Assert\NotBlank()
*/
private $name;
/**
* #ORM\Column(type="string", length=250)
* #Assert\NotNull
*/
private $comment;
public function __construct() {
$this->comment = "";
}
}
I have a CustomerController controller in src/App/Controller
class CustomerController extends Controller
{
/**
* #Route("/customers", name="customer_create")
* #Method({"POST"})
*/
public function createAction(Request $request)
{
$data = $request->getContent();
//Il faudrait valider les données avant de les mettre en base de données
$customer = $this->get('jms_serializer')->deserialize($data, 'App\Entity\Customer', 'json');
$em = $this->getDoctrine()->getManager();
$em->persist($customer);
$em->flush();
return new Response('', Response::HTTP_CREATED);
}
}
I post a request for creating a customer with the following JSON data:
{"name":"Customer Lambda"}
but I have the following error
NotNullConstraintViolationException
An exception occurred while executing 'INSERT INTO customers (id, name, comment) VALUES (?, ?, ?, ?)' with params [Resource id #99, "Customer Lambda", null]:
SQLSTATE[23000]: Integrity constraint violation: 1048 Column 'comment'
cannot be null
Is it possible to set a default value (empty string) for the comment field after deserialize? I was thinking that setting it in the Customer constructor will fix the issue but it is not the case.
1º Why are you trying to set empty string if you have defined comment as not nullable.
2º
This:
/**
* #ORM\Column(type="string", length=250)
* #Assert\NotNull
*/
private $comment = "";
public function __construct() {
}
Instead this:
/**
* #ORM\Column(type="string", length=250)
* #Assert\NotNull
*/
private $comment;
public function __construct() {
$this->comment = "";
}

Symfony 3 UniqueEntity validation on update

I am facing an issue with UniqueEntity validation.
I have a field "internal_asset_number" which should be unique and it's working fine on create. On update when i edit the existing current data with the same values, it shows "There is already an asset with that internal number!" but it shouldn't because it's the same entry.
The entity:
/**
* Asset
*
* #ORM\Table(schema="assets", name="asset", uniqueConstraints= {#ORM\UniqueConstraint(name="uk_asset_internal_asset_number_client_id", columns={"internal_asset_number", "client_id"})})
* #ORM\Entity(repositoryClass="Api\AssetBundle\Entity\AssetRepository")
* #UniqueEntity(fields={"internalAssetNumber"}, groups={"post", "put"}, message="There is already an asset with that internal number!")
*/
class Asset
{
/**
* #var guid
*
* #ORM\Column(name="id", type="string")
* #ORM\Id
* #ORM\GeneratedValue(strategy="UUID")
*/
private $id;
/**
* #var string
*
* #ORM\Column(name="client_id", type="string", length=255, nullable=false)
*/
private $clientId;
/**
* #var string
*
* #ORM\Column(name="internal_asset_number", type="string", length=255, nullable=true, unique=true)
*/
private $internalAssetNumber;
Update method:
public function putAssetAction(Request $request, $id)
{
$data = $this->deserializer('Api\AssetBundle\Entity\Asset', $request, 'put');
if ($data instanceof \Exception) {
return View::create(['error' => $data->getMessage()], 400);
}
$validator = $this->get('validator');
$errors = $validator->validate($data, null, 'put');
if (count($errors) > 0) {
$errorsResponse = [];
foreach ($errors as $error) {
$errorsResponse = $error->getMessage();
}
return View::create(array('error' => $errorsResponse), 400);
}
...
As #xabbuh commented, the problem is that the entity you persist after update is not retrieved through the entityManager so when you persist it the entity manager thinks it is a new entity.
Here is how to solve it:
$entityManager->merge($entity);
This will tell the entitymanager to merge your serialized entity with the managed one
Some more explanation on merge():
https://stackoverflow.com/a/15838232/5758328

symfony2 + doctrine: modify a child entity on `onFlush`: "Invalid parameter number: number of bound variables does not match number of tokens"

I have a 1:m relationship between Subitem and SubitemColor. Now I would like to save some data inside an onFlush to modify some data for SubitemColor. The problem: I get the error message below when executing the controller you can see below too:
An exception occurred while executing 'INSERT INTO SubitemColor
(code, precio, pvp_recommended, file_name, activado, en_stock, area,
lets_fix_width_or_height_in_list, lets_fix_width_or_height_in_show,
position_level_0, position_level_1, position_brand, subitem_id,
color_id) VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?)' with
params [2]:
SQLSTATE[HY093]: Invalid parameter number: number of bound variables
does not match number of tokens
public function onFlush(Event \OnFlushEventArgs $eventArgs)
{
$em = $eventArgs->getEntityManager();
$uow = $em->getUnitOfWork();
$updates = $uow->getScheduledEntityUpdates();
$insertions = $uow->getScheduledEntityInsertions();
/////////// NEW SUBITEM_IMAGE OR SUBITEM_COLOR UPLOADED //////////
foreach ($insertions as $entity) {
if ($entity instanceof SubitemColor) {
//$entity->setLetsFixWidthOrHeightInList("jander");
//$entity->setLetsFixWidthOrHeightInList('width');
//$entity->setLetsFixWidthOrHeightInShow('width');
$entity->setEnStock(2);
$metaSubitemColor = $em->getClassMetadata(get_class($entity));
$uow->computeChangeSet($metaSubitemColor, $entity);
$uow->persist($entity);
}
}
}
//controller - controller - controller - controller
$subitem = new Subitem();
$em = $this->getDoctrine()->getManager();
$subitem->setNombre("jls");
$subitemColor = new SubitemColor();
$subitem->addSubitemColor($subitemColor);
$em->persist($subitem);
$em->persist($subitemColor);
$metaSubitem = $em->getClassMetadata(get_class($subitem));
$em->flush();
Use recomputeSingleEntityChangeSet method instead of computeChangeSet
computeChangeSet method is supposed to be called by doctrine only and calls once for every entity that marked for persistence on flush operation.
When you load entity from database doctrine saves its data to originalEntityData array, then it checks if no original data exists for entity then this entity is new and doctrine saves its current data as original and fill change set with every field value.
On second call of computeChangeSet doctrine has original data for newly created entity and computes change set only for changed fields since last call of computeChangeSet method.
Thats why you should never call computeChangeSet.
I replicated your problem as you can see in the image below.
The problem is; persist() is being used once in your controller (which you cannot do without it) and once in your onFlush() listener (which you cannot do without it as well!!!) so for that reason you get that error.
Event onFlush is called inside EntityManager#flush() after the
changes to all the managed entities and their associations have been
computed.
You're calling persist in your controller and straight after that you're calling another persist in your listener before even flushing it in your controller.
SOLUTION
Based on what you're trying to do, onFlush is not what you need anyway so the one you should use is prePersist so look at the example below.
CONTROLLER
Please checkout entity examples I added at the bottom. As you noted it is 1:N so since child SubitemColor cannot exist without parent Subitem, we're using $subitemColor->setSubitem($subitem); oppose to your example.
public function createAction()
{
$subitem = new Subitem();
$subitemColor = new SubitemColor();
$subitem->setNombre('jls');
$subitemColor->setSubitem($subitem);
$em = $this->getDoctrine()->getManager();
$em->persist($subitem);
$em->persist($subitemColor);
$em->flush();
}
YML
services:
application_backend.event_listener.subitem:
class: Application\BackendBundle\EventListener\SubitemListener
tags:
- { name: doctrine.event_listener, event: prePersist }
LISTENER
namespace Application\BackendBundle\EventListener;
use Application\BackendBundle\Entity\SubitemColor;
use Doctrine\ORM\Event\LifecycleEventArgs;
class SubitemListener
{
public function prePersist(LifecycleEventArgs $args)
{
$entity = $args->getEntity();
if ($entity instanceof SubitemColor) {
$entity->setEnStock(2);
}
}
}
RESULT
mysql> SELECT * FROM subitem;
Empty set (0.00 sec)
mysql> SELECT * FROM subitem_color;
Empty set (0.01 sec)
mysql> SELECT * FROM subitem;
+----+------+
| id | name |
+----+------+
| 1 | jls |
+----+------+
1 row in set (0.00 sec)
mysql> SELECT * FROM subitem_color;
+----+------------+------+----------+
| id | subitem_id | code | en_stock |
+----+------------+------+----------+
| 1 | 1 | NULL | 2 |
+----+------------+------+----------+
1 row in set (0.00 sec)
SUBITEM ENTITY
namespace Application\BackendBundle\Entity;
use Application\BackendBundle\Entity\SubitemColor;
use Doctrine\Common\Collections\ArrayCollection;
use Doctrine\Common\Collections\Collection;
use Doctrine\ORM\Mapping as ORM;
/**
* #ORM\Entity
* #ORM\Table(name="subitem")
*/
class Subitem
{
/**
* #ORM\Id
* #ORM\Column(type="integer")
* #ORM\GeneratedValue(strategy="AUTO")
*/
protected $id;
/**
* #ORM\Column(name="name", type="string", length=20)
*/
protected $nombre;
/**
* #ORM\OneToMany(targetEntity="SubitemColor", mappedBy="subitem", cascade={"persist", "remove"})
*/
protected $subitemColor;
/**
* Constructor
*/
public function __construct()
{
$this->subitemColor = new ArrayCollection();
}
/**
* #return integer
*/
public function getId()
{
return $this->id;
}
/**
* #param string $nombre
* #return Subitem
*/
public function setNombre($nombre)
{
$this->nombre = $nombre;
return $this;
}
/**
* #return string
*/
public function getNombre()
{
return $this->nombre;
}
/**
* #param SubitemColor $subitemColor
* #return Subitem
*/
public function addSubitemColor(SubitemColor $subitemColor)
{
$this->subitemColor[] = $subitemColor;
return $this;
}
/**
* #param SubitemColor $subitemColor
*/
public function removeSubitemColor(SubitemColor $subitemColor)
{
$this->subitemColor->removeElement($subitemColor);
}
/**
* #return Collection
*/
public function getSubitemColor()
{
return $this->subitemColor;
}
}
SUBITEMCOLOR ENTITY
namespace Application\BackendBundle\Entity;
use Application\BackendBundle\Entity\Subitem;
use Doctrine\ORM\Mapping as ORM;
/**
* #ORM\Entity
* #ORM\Table(name="subitem_color")
*/
class SubitemColor
{
/**
* #ORM\Id
* #ORM\Column(type="integer")
* #ORM\GeneratedValue(strategy="AUTO")
*/
protected $id;
/**
* #ORM\Column(name="code", type="string", length=20, nullable=true)
*/
protected $code;
/**
* #ORM\Column(name="en_stock", type="integer", length=5, nullable=true)
*/
protected $enStock;
/**
* #ORM\ManyToOne(targetEntity="Subitem", inversedBy="subitemColor")
* #ORM\JoinColumn(name="subitem_id", referencedColumnName="id", onDelete="CASCADE", nullable=false)
*/
protected $subitem;
/**
* #return integer
*/
public function getId()
{
return $this->id;
}
/**
* #param string $code
* #return SubitemColor
*/
public function setCode($code)
{
$this->code = $code;
return $this;
}
/**
* #return string
*/
public function getCode()
{
return $this->code;
}
/**
* #param integer $enStock
* #return SubitemColor
*/
public function setEnStock($enStock)
{
$this->enStock = $enStock;
return $this;
}
/**
* #return integer
*/
public function getEnStock()
{
return $this->enStock;
}
/**
* #param Subitem $subitem
* #return SubitemColor
*/
public function setSubitem(Subitem $subitem)
{
$this->subitem = $subitem;
return $this;
}
/**
* #return Subitem
*/
public function getSubitem()
{
return $this->subitem;
}
}
This may or may not solve your problem, but when I do this in my code I call $uow->persist($entity); then I call $uow->computeChangeSet($metaSubitemColor, $entity);
Order seems important here as you have persisted changes that then have to be recalculated in the unit of work. So persisting after calling computeChangeSet seems likely to cause problems.

Save Object in table related to another

How can I create a new object when a cell is related to another table? In my case there exist a table with states, like id=1,state=active;id=2,state=inactive.
My Entity/States.php
class States
{
/**
* #var integer
*
* #ORM\Column(name="id", type="integer", nullable=false)
* #ORM\Id
* #ORM\GeneratedValue(strategy="IDENTITY")
*/
private $id;
....
Entity/User.php
....
/**
* Set state
*
* #param \Frontend\AccountBundle\Entity\States $state
* #return User
*/
public function setState(\Frontend\AccountBundle\Entity\States $state = null)
{
$this->state = $state;
return $this;
}
My AccountController:
....
$user = new User();
$em = $this->get('doctrine')->getEntityManager();
$state = $this->getDoctrine()->getRepository('FrontendAccountBundle:States')->find(1);
$user->setEmail($formData->getEmail());
$user->setStateId(1);
$em->persist($user);
$em->flush();
This is not working and way to complicated: http://symfony.com/doc/current/book/doctrine.html#relationship-mapping-metadata. It was so freaking easy in symfony1.4.
Your User entity has a method setState(), which takes a single parameter of $state. That parameter must be of type \Frontend\AccountBundle\Entity\States.
In your controller, you obtain that object through this call:
$state = $this->getDoctrine()->getRepository('FrontendAccountBundle:States')->find(1);
So when you go to set the State of the User, you don't need to bother with IDs. Rather, just set the State directly, and Doctrine will take care of the rest:
$user->setState($state);
This solution works for me:
https://stackoverflow.com/a/14131067/2400373
But in Symfony 4 change the line of getRepository:
$role = $this->getDoctrine()
->getRepository(Role::class)
->find(1);
$usuario->setRole($role);

Removing an object with em->remove($user) removes all objects in table

I have a User entity that implements UserInterface to use with a RBAC system. I have not implemented the whole system yet. However, when I try to remove a user with the following code, the action removes all the users and other associated objects in other tables and then throws me an error. I am able to remove objects from other entities without issues.
User entity
class User implements UserInterface
{
**
* #var integer $id
*
* #ORM\Column(name="id", type="smallint")
* #ORM\Id
* #ORM\GeneratedValue(strategy="AUTO")
*
protected $id;
**
* #var string $username
*
* #ORM\Column(name="username", type="string", length=20, unique=TRUE)
*
protected $username;
**
* #var string $password
*
* #ORM\Column(name="password", type="string", length=255)
*
protected $password;
**
* #var string $salt
*
* #ORM\Column(name="salt", type="string", length=255)
*
protected $salt;
**
* #var string $fullName
*
* #ORM\Column(name="full_name", type="string", length=60, unique=TRUE)
*
protected $fullName;
**
* #ORM\ManyToMany(targetEntity="Role", inversedBy="users", cascade={"persist", "remove"})
* #ORM\JoinTable(name="users_roles")
*
* #var ArrayCollection $userRoles
*
protected $userRoles;
public function __construct()
{
$this->userRoles = new ArrayCollection();
}
}
Delete action
public function deleteUserAction($id) {
$user = $em->getRepository('ACMECompanyBundle:User')->find($id);
$currentUser = $this->get('security.context')->getToken()->getUser();
if ($id == $currentUser->getId()) {
return new Response("You cannot delete the current user");
}
if (!$user) {
throw $this->createNotFoundException('No user found for id '.$id);
}
try {
$em->remove($user);
$em->flush();
$msg = "User deleted!";
$code = "OK";
} catch (DBALException $e) {
return new Response($e);
$msg = "User cannot be deleted!";
$code = "ERR";
}
$response = new Response(json_encode(array('code' => $code, 'msg' => $msg)));
$response->headers->set('Content-Type', 'application/json');
return $response;
}
The error returned after all users are removed is
InvalidArgumentException: You cannot refresh a user from the EntityUserProvider that does not contain an identifier. The user object has to be serialized with its own identifier mapped by Doctrine.
You left out the definition for em in your action... define it with
$em = $this->getDoctrine()->getEntityManager();
and it should work. Unless you set it on the class itself, then you would need $this->...
When doctrine removes the user, it also removes all rolles assigned to this user and all users assigned to these roles. So according your annotation schema this is the correct behavior because of cascade={"remove"} in $userRoles annotation and cascade={"remove"} in $users annotation in Role entity.
If you want to prevent cascade removing and want to keep cascade persistent remove "remove" argument from both user and role relations

Resources