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 = "";
}
Related
I'm beginner with tests and I want to test my ValidatorService which throws an InvalidDataException when an entity data are not valid.
My ValidatorServiceTest function :
public function testValidatorForUser()
{
$validatorMock = $this->createMock(ValidatorInterface::class);
$contraintViolationMock = $this->createMock(ConstraintViolationListInterface::class);
$validatorMock->expects($this->once())
->method('validate')
->with()
->willReturn($contraintViolationMock);
$validatorService = new ValidatorService($validatorMock);
$user = new User();
$user->setEmail('test');
$validatorService->validate($user);
$this->expectException(InvalidDataException::class);
}
My ValidatorService :
class ValidatorService
{
/**
* #var ValidatorInterface
*/
private ValidatorInterface $validator;
public function __construct(ValidatorInterface $validator)
{
$this->validator = $validator;
}
/**
* #param $value
* #param null $constraints
* #param null $groups
* #throws InvalidDataException
*/
public function validate($value, $constraints = null, $groups = null)
{
$errors = $this->validator->validate($value, $constraints, $groups);
if (count($errors) > 0) {
throw new InvalidDataException($errors);
}
}
}
My User entity:
/**
* #ORM\Entity(repositoryClass=UserRepository::class)
* #ORM\Table(name="`user`")
* #UniqueEntity(fields="email", errorPath="email", message="user.email.unique")
* #UniqueEntity(fields="username", errorPath="username", message="user.username.unique")
*/
class User implements UserInterface
{
/**
* #ORM\Id
* #ORM\GeneratedValue
* #ORM\Column(type="integer")
*/
private ?int $id;
/**
* #ORM\Column(type="string", length=180, unique=true)
* #JMS\Type("string")
* #JMS\Groups({"api"})
* #Assert\NotBlank(message="user.email.not_blank")
* #Assert\Email(message="user.email.email")
*/
private string $email;
/**
* #var string The hashed password
* #ORM\Column(type="string")
* #JMS\Type("string")
* #Assert\NotBlank(message="user.password.not_blank")
* #Assert\Length(min=8, minMessage="user.password.length.min")
*/
private string $password;
/**
* #JMS\Type("string")
* #Assert\NotBlank(message="user.confirm_password.not_blank")
* #Assert\EqualTo(propertyPath="password", message="user.confirm_password.equal_to")
*/
private string $confirmPassword;
...
...
I have this error :
1) App\Tests\Service\Validator\ValidatorServiceTest::testValidatorForUser
Failed asserting that exception of type "App\Exception\InvalidDataException" is thrown.
How can I test if an exception thows or not ?
SOLVED:
The problem was with my $contraintViolationMock which always returned empty data.
I have to retrieve violations before and test that it's match with the mock violations. I think, it's the more simple solution instead of creating manually a ConstraintViolationList.
If you have a better solution, i'll take it.
public function testValidatorForUser()
{
$user = new User();
$user->setEmail('test');
$validator = self::$container->get(ValidatorInterface::class);
$violations = $validator->validate($user);
$validatorMock = $this->createMock(ValidatorInterface::class);
$validatorMock->expects($this->once())
->method('validate')
->willReturn($violations);
$this->expectException(InvalidDataException::class);
$validatorService = new ValidatorService($validatorMock);
$validatorService->validate($user);
}
So, my problem is when I'm trying to update schema with command "dotrine:schema:update --force", entity are create but without any relation.
I have two entity "advert" and "category" and a relation ManyToMany on advert entity.
This is entyty advert :
<?php
namespace testBundle\Entity;
use Doctrine\Common\Collections\ArrayCollection;
use Doctrine\ORM\Mapping as ORM;
/**
* Advert
*
* #ORM\Table(name="advert")
* #ORM\Entity(repositoryClass="testBundle\Repository\AdvertRepository")
*/
class Advert
{
/**
* #var int
*
* #ORM\Column(name="id", type="integer")
* #ORM\Id
* #ORM\GeneratedValue(strategy="AUTO")
*/
private $id;
/**
* #var string
*
* #ORM\Column(name="categories", type="string", length=255)
* /**
* #ORM\ManyToMany(targetEntity="OC\PlatformBundle\Entity\Category", cascade={"persist"})
*/
private $categories;
public function __construct()
{
$this->date = new \Datetime();
$this->categories = new ArrayCollection();
}
// Notez le singulier, on ajoute une seule catégorie à la fois
public function addCategory(Category $category)
{
// Ici, on utilise l'ArrayCollection vraiment comme un tableau
$this->categories[] = $category;
}
public function removeCategory(Category $category)
{
// Ici on utilise une méthode de l'ArrayCollection, pour supprimer la catégorie en argument
$this->categories->removeElement($category);
}
/**
* Get id
*
* #return int
*/
public function getId()
{
return $this->id;
}
/**
* Set categories
*
* #param string $categories
*
* #return Advert
*/
public function setCategories($categories)
{
$this->categories = $categories;
return $this;
}
/**
* Get categories
*
* #return string
*/
public function getCategories()
{
return $this->categories;
}
}
My entity category :
<?php
namespace testBundle\Entity;
use Doctrine\ORM\Mapping as ORM;
/**
* Category
*
* #ORM\Table(name="category")
* #ORM\Entity(repositoryClass="testBundle\Repository\CategoryRepository")
*/
class Category
{
/**
* #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)
*/
private $name;
/**
* Get id
*
* #return int
*/
public function getId()
{
return $this->id;
}
/**
* Set name
*
* #param string $name
*
* #return Category
*/
public function setName($name)
{
$this->name = $name;
return $this;
}
/**
* Get name
*
* #return string
*/
public function getName()
{
return $this->name;
}
}
and this is the result of command doctrine:schema:update --force --dump-sql :
CREATE TABLE advert (id INT AUTO_INCREMENT NOT NULL, categories VARCHAR(255) NOT NULL, PRIMARY KEY(id)) DEFAULT CHARACTER SET utf8 COLLATE utf8_unicode_ci ENGINE = InnoDB;
CREATE TABLE category (id INT AUTO_INCREMENT NOT NULL, name VARCHAR(255) NOT NULL, PRIMARY KEY(id)) DEFAULT CHARACTER SET utf8 COLLATE utf8_unicode_ci ENGINE = InnoDB;
Like you can see, no relation and no table advert_category
I found some topic about this but no solution working for me.
I hope someone can help me
Thank you !
Relation declared wrong, i think it should be something like this :
/**
* #ORM\ManyToMany(targetEntity="testBundle\Entity\Category")
* #ORM\JoinTable(name="advert_category")
*/
private $categories;
So problem fixed, I have to delete this :
*
* #ORM\Column(name="categories", type="string", length=255)
/**
and it's work , thank you #thomas !
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.
I have three tables in a PostgreSQL database where I run this queries:
ALTER TABLE waccount ALTER COLUMN balance SET DEFAULT 0;
ALTER TABLE waccount ALTER COLUMN created SET DEFAULT now();
ALTER TABLE waccount ALTER COLUMN modified SET DEFAULT now();
Now when I trying to run a INSERT from Symfony2 controller using this code:
$entity = new Account();
$form = $this->createForm(new AccountType(), $entity);
$form->handleRequest($request);
if ($form->isValid()) {
$em = $this->getDoctrine()->getManager();
$em->persist($entity);
$em->flush();
return $this->redirect($this->generateUrl('wba_show', array('id' => $entity->getAccountId())));
}
I get this:
An exception occurred while executing 'INSERT INTO waccount
(account_id, account_number, bank_name, balance, created, modified,
account_type) VALUES (?, ?, ?, ?, ?, ?, ?)' with params [1,
"01234567890121345678", "BankOfAmerica", null, null, null, 6]:
SQLSTATE[23502]: Not null violation: 7 ERROR: null value in column
"balance" violates not-null constraint DETAIL: Failing row contains
(1, 01234567890121345678, BankOfAmerica, 6, null, null, null).
Why Symfony or Doctrine doesn't deal with default values? I'm doing something wrong or I miss something here?
Account Entity
This is my account entity:
namespace BankBundle\Entity;
use Doctrine\ORM\Mapping as ORM;
/**
* Account
*
* #ORM\Entity
* #ORM\Table(name="waccount")
*/
class Account {
/**
* #ORM\Id
* #ORM\Column(type="integer")
* #ORM\GeneratedValue(strategy="AUTO")
*
*/
protected $account_id;
/**
*
* #ORM\Column(type="string", length=20)
*/
protected $account_number;
/**
*
* #ORM\Column(type="string", length=150)
*/
protected $bank_name;
/**
* #ORM\OneToOne(targetEntity="BankBundle\Entity\AccountType")
* #ORM\JoinColumn(name="account_type", referencedColumnName="type_id")
*/
protected $account_type;
/**
*
* #ORM\Column(type="float")
*/
protected $balance;
/**
* #var \DateTime
* #ORM\Column(type="datetime")
*/
protected $created;
/**
* #var \DateTime
* #ORM\Column(type="datetime")
*/
protected $modified;
public function getAccountId() {
return $this->account_id;
}
public function setAccountNumber($account_number) {
$this->account_number = $account_number;
}
public function getAccountNumber() {
return $this->account_number;
}
public function setAccountType($account_type) {
$this->account_type = $account_type;
}
public function setBankName($bank_name) {
$this->bank_name = $bank_name;
}
public function getBankName() {
return $this->bank_name;
}
public function getAccountType() {
return $this->account_type;
}
public function setBalance($balance) {
$this->balance = (float) $balance;
}
public function getBalance() {
return $this->balance;
}
public function setCreated($created) {
$this->created = $created;
}
public function getCreated() {
return $this->created;
}
public function setModified($modified) {
$this->modified = $modified;
}
public function getModified() {
return $this->modified;
}
}
The table waccount exists on DB
I would like to save my users search on my website. Thaht's why i have a Class User and i would like to create Search Class.
I have done that :
class Search
{
public function __construct()
{
$this->searched_date = new \Datetime();
}
/**
* #ORM\Id
* #ORM\Column(type="integer")
* #ORM\GeneratedValue
*/
private $id;
/**
* #ORM\Id
* #ORM\ManyToOne(targetEntity="test\UserBundle\Entity\User")
*/
private $user;
/**
* #ORM\Column(name="termsearch", type="string", length=255, nullable="true")
*/
private $termsearch;
/**
* #ORM\Column(name="goodtitle", type="string", length=255, nullable="true")
*/
private $goodtitle;
/**
* #ORM\Column(name="searched_date", type="datetime")
*/
private $searched_date;
/**
* Set termsearch
*
* #param text $termsearch
*/
public function setTermsearch($termsearch)
{
$this->termsearch = $termsearch;
}
/**
* Get termsearch
*
* #return text
*/
public function getTermsearch()
{
return $this->termsearch;
}
/**
* Set searched_date
*
* #param datetime $searchedDate
*/
public function setSearchedDate($searchedDate)
{
$this->searched_date = $searchedDate;
}
/**
* Get searched_date
*
* #return datetime
*/
public function getSearchedDate()
{
return $this->searched_date;
}
/**
* Set user
*
* #param test\UserBundle\Entity\User $user
*/
public function setUser(\test\UserBundle\Entity\User $user)
{
$this->user = $user;
}
/**
* Get user
*
* #return test\UserBundle\Entity\User
*/
public function getUser()
{
return $this->user;
}
/**
* Set goodtitle
*
* #param text $goodtitle
*/
public function setGoodtitle($goodtitle)
{
$this->goodtitle = $goodtitle;
}
/**
* Get goodtitle
*
* #return text
*/
public function getGoodtitle()
{
return $this->goodtitle;
}
/**
* Get id
*
* #return integer
*/
public function getId()
{
return $this->id;
}
}
And i would like to insert like that :
$em = $this->getDoctrine()->getEntityManager();
$user = $em->getRepository('TestUserBundle:User')->find($currentuser->getID());
$search = new Search();
$search->setUser($user);
$search->setTermsearch($termsearch);
$search->setGoodtitle($goodtitle);
$em->persist($search);
$em->flush();
Unfortunately i have this error :
SQLSTATE[HY093]: Invalid parameter number: number of bound variables does not match number of tokens (500 Internal Server Error)
And in the stack we can found that :
INSERT INTO s_search (user_id, termsearch, goodtitle, searched_date) VALUES (?, ?, ?, ?) ({"1":"c2c","2":"C2C Down The Road","3":{"date":"2012-10-31 00:18:47","timezone_type":3,"timezone":"Europe\/Paris"}})
I don't know how i can create this class Search...
Thank for for you help !
Remove #ORM\Id from the $user as #ManyToOne mapping reference does not require a type. See Doctrine's Annotation Reference for details. Doctrine takes care of the correct column type to hold the reference to another entity.
Make also sure that your User query really returns a valid $user. If it's possible that Search does not have $user, use #JoinColumn annotation to claim the column nullable. See another SO question Doctrine 2 can't use nullable=false in manyToOne relation?