Something strange with blank fields in symfony2 forms - forms

when i send a form with a blank field i get an error SQLSTATE[23000]: Integrity constraint violation: 1048 Column 'image' cannot be null. the only way to fix it that i found is to make a default value in the entity file:
* #ORM\Column(type="string", length=100)
*/
protected $image="";
and change the setter like this:
public function setImage($image){
if(!isset($image)) {
//its really empty but it works only in this way
}
else {
$this->image = $image;
}
I think that it is very starnge...
Is there any explanation for this? And is there another way to do it?
}

If the field image is not required, you can set it as nullable so Doctrine will know that and will set the column as being nullable.
This way, constraint won't be violated since the field can be null. To make a field nullable with Doctrine annotation, just add nullable = true in the ORM\Column definition like this:
#ORM\Column(type="string", length=100, nullable=true)
By default, all columns are nullable=false so they will throw a constaint validation exception when trying to persist a null value in it.
Regards,
Matt

The why is partially answered here:
Symfony2 forms interpret blank strings as nulls
This code gets around it because when Symfony sets $image to null and calls $entity->setImage(null), this code will not change the $image member.
public function setImage($image){
if(!isset($image)) {
// $image is null, symfony was trying to set $this->image to null, prevent it
} else {
$this->image = $image;
}
}
This is more explicit (and who wants that weird empty statement anyway?). It expresses your intent, that $this->image cannot be null (which matches the database definition if you don't make it nullable)
public function setImage($image){
if(isset($image)) {
// $image not null, go ahead and use it
$this->image = $image;
}
}
Either way, you need to initialize $this->image otherwise it will default to null.

Related

In Doctrine2, cannot remove unidirectional many-to-one relation (cannot set to null)

I have an Authentication entity that works like a user entity, and a Country entity which has a property related to Authentication entity unidirectionally:
/**
* #var Authentication
*
* #ORM\ManyToOne(targetEntity="Authentication")
* #ORM\JoinColumn(name="`archived_by`", referencedColumnName="id")
*/
private $archivedBy;
/**
* #param Authentication $archivedBy
* #return Country
*/
public function setArchivedBy(Authentication $archivedBy = null)
{
$this->archivedBy = $archivedBy;
return $this;
}
As for documentation, the join column is by default nullable.
When I want to set a user to this archivedBy property in a listener, it works as expected:
public function preRemove(LifecycleEventArgs $event)
{
$entity = $event->getObject();
$entity->setArchivedBy($this->tokenStorage->getToken()->getUser());
$om = $event->getObjectManager();
$om->persist($entity);
$om->flush();
}
But when I want to set this property to null (want to remove the relation) in its controller, it does not work as expected:
$country->setArchivedBy(null);
In Doctrine2 debug of Symfony profiler, I see that the sql query does not include "SET archived_by = null", such that:
UPDATE "country" SET "archived_at" = NULL, "updated_by" = '80fa198a-3216-46cd-aedb-64ce7ff27801', "updated_at" = '2017-08-29 17:11:59+0300' WHERE "id" = '8149132e-2e28-4423-bc72-471751b5fcd3';
So the problem may occur in Doctrine's internal query builder.
When I explicitly define nullable=true such that,
* #ORM\JoinColumn(name="`archived_by`", referencedColumnName="id", nullable=true)
nothing changes, as expected, because it is already by default.
In Doctrine2 documentation, I couldn't find anything.
All I need is to set archivedBy property to null.
Details:
I use postgresql. (The archived_by column is nullable there.)
Doctrine ORM 2.5 & Doctrine Bundle 1.6
Try this:
use this annotation:
#ORM\JoinColumn(name="`archived_by`", referencedColumnName="id", nullable=true)
After launch into your command line:
bin/consone doctrine:schema:update --force
Delete the cache and retry to make this:
$country->setArchivedBy(null);
And this works fine for me
It is hard to believe that the problem is about a Doctrine limitation.
When I remove the quotes in the column name annotation, it works as expected; such that,
* #ORM\JoinColumn(name="archived_by", referencedColumnName="id")
instead of
* #ORM\JoinColumn(name="`archived_by`", referencedColumnName="id")
Then Doctrine produces SQL queries with
SET archived_by = NULL
It may be reported as a bug, but it is not a bug. In Doctrine2 documentation, it is clearly stated that:
You cannot quote join column names.
So removing unnecessary quotes solves the problem.

handle request of incomplete missing fields symfony form

I have created a small Symfony (Sf3.2 + php7) web with a Task Entity. I have my controller where I have a new action to create a new task. Everything is fine. Now, some of the form fields are not necessary to be filled (created date, for example). So I have removed from my taskType:
public function buildForm(FormBuilderInterface $builder, array $options) {
$builder->add->('name')->add('description');
}
Now my form looks exactly as I want. The surprise is that when I submit it my controller pass it to doctrine,and every missing field is write as a NULL. NO!, I don't want that, what I wanted is my default mysql value.
Ok, I read the doc. There seems to be two ways of handling data:
$form->handleRequest($request);
$form->submit($request->request->get($form->getName()));
I've found in the API, (not in the doc) that submit have a second parameter, a boolean:
API says:
bool $clearMissing Whether to set fields to NULL when they are missing in the submitted data.
Great! this is exactly what I need. Lets see it:
public function newAction(Request $request) {
$task = new Task();
$form = $this->createForm('myBundle\Form\TaskType', $task);
$form->submit($request->request->get($form->getName()), false);
if ($form->isSubmitted() && $form->isValid()) {
$em = $this->getDoctrine()->getManager();
$em->persist($task);
$em->flush();
$response = new Response();
$response->setContent(json_encode(array(
'data' => 'success',
)));
$response->headers->set('Content-Type', 'application/json');
return $response;
return $this->redirectToRoute('task_success');
}
return $this->render('task/new.html.twig', array(
'task' => $task,
'form' => $form->createView(),
));
}
Well, after tried everything I always get NULL in all missing fields. What am I doing wrong.
Thank you, Caterina.
I'm afraid there must be something more. And I was thinking that perhaps is not Doctrine the solution. The Symfony log shows me the insert and it's setting a Null in every empty field, on empty fields and missing fields. In fields like status i could set a default value, at doctrine level, but if I want to set created_at field, I suppose that must be Mysql the responsible is setting current timeStamp.
Anyway this is my ORM property status from Task Entity.
/**
* #var \scrumBundle\Entity\Taskstatus
*
* #ORM\ManyToOne(targetEntity="scrumBundle\Entity\Taskstatus")
* #ORM\JoinColumns({
* #ORM\JoinColumn(name="status", referencedColumnName="id")
* })
* #ORM\Column(name="status", type="integer", nullable=false,options={"default":1})
*/
private $status;
And this is the property id from TaskStatus:
/**
* #var integer
*
* #ORM\Column(name="id", type="integer", nullable=false, options={"default":1})
* #ORM\Id
* #ORM\GeneratedValue(strategy="IDENTITY")
*/
private $id;
As you see I tried to follow your advise of setting the option default value. I've tried to create to set the form with handleRequest and submit with same result
$task = new Task();
$form = $this->createForm('scrumBundle\Form\TaskType', $task);
//$form->handleRequest($request);
$form->submit($request->request->get($form->getName()), false);
if ($form->isSubmitted() && $form->isValid()) {
I even tried to debug step by step submit function, a real Hell, because submit is a recursive function to much complex for me.
Ok , again thank for you time.
Regards, Cate
PS. Sorry about my poor english.;^)
Short answer - when you persist a PHP object through Doctrine, you must have absolutely every value set that you want. If your PHP object has null fields, Doctrine will manually set them as null in your entity. Doctrine doesn't assume that just because a value is null in your PHP object, that you don't want it included on your INSERT statement. This is because null is a perfectly valid SQL value.
So, whatever your PHP object is at the time of insert is exactly what is going to be inserted into your database, null values and all.
When you are editing an entry, Doctrine will only update the fields that are different, so this isn't a concern. Your concern is when persisting entities.
The easiest solution is to copy your MySQL default value into your PHP entity. Like one of these many ways:
// set default value on the variable itself
public $someVar = 100;
// set default values in the constructor (so when creating a new entry)
public function __construct()
{
$this->createdAt = new \DateTime();
$this->isActive = true;
}
You can also use Lifecycle Callbacks to set when inserting a new entry:
/**
* #ORM\Entity()
* #ORM\HasLifecycleCallbacks()
*/
class Task
{
// ...
/**
* #ORM\PrePersist
*/
public function setCreatedAtValue()
{
$this->createdAt = new \DateTime();
}
}

Weird situation with a table column's value fetching

I'm retrieving data from Database like everyone else, but I'm facing a weird issue.
I'm using the slug in my table to retrieve the element's data but but I display the slug it gives me nulland using the famous dd()famous it shows up here is an example :
dd($element);
Result
dd($snippets->toArray());
Result
Table
Schema::create('elements', function (Blueprint $table) {
$table->increments('id');
$table->integer('user_id');
$table->string('title');
$table->string('slug')->unique();
$table->text('body');
$table->timestamps();
});
Eloquent
$snippets = Snippet::latest()->with('owner')->get();
Snippet Model
protected $primaryKey = 'slug';
public function owner()
{
return $this->belongsTo(User::class, 'user_id');
}
Is there any one who knows what is going on?
The problem was in the Primary Key Type, by default is set to int and have not set incrementing to false which is causing it to 'cast' it by the keyType. The keyType is set to 'int' unless set otherwise. (int)'es2015' == 0
protected $keyType = 'string';
Thanks everyone for help

Symfony 2 - Change entity's field from Admin while keeping validation using SonataAdminBundle

Using:
Symfony 2.5
SonataAdminBundle
I am trying to change one of the entity fields (title) when data is submitted / saved to database by using two fields from associated entites ex.
DocumentRevision <- Document -> CustomEntity [title] = Document[title]+DocumentRevision[number]
But title of CustomEntity has to be unique - this was the problem I was trying to solve and managed with Database constraints and UniqueEntity validation (not quite - more on this later).
Now the issue is that I change the title data on Doctrine preUpdate/Persist effectivly skipping validation for that field since it's empty at validation time. When user puts wrong data Database layer throws an error about duplicate for unique constraint.
/**
* #ORM\PrePersist
* #ORM\PreUpdate
*/
public function setTitleFromDocumentName() {
$this->setTitle($this->getDocument()->getName() . " rev. " . $this->getDocumentRevision()->getRevisionNumber());
}
The entity itself is using UniqueEntity constraint on field title, so custom constraints or validation groups are pointles from my perspective as it would only duplicate the already used constraint.
/**
* #UniqueEntity(
* fields={"title"}
* )
**/
The simplest solution as it seems would be to get somewhere between post Submit before validation, but it would have to be done from Entity.
My question is how can (can it?) be done without overriding SonataCRUD Controller or it's other parts, is it even possible?
It can be done, but there are issues:
I was able to change the title using Form Events like this:
protected function configureFormFields(FormMapper $formMapper) {
...
$builder = $formMapper->getFormBuilder();
$builder->addEventListener(FormEvents::PRE_SUBMIT, function (FormEvent $event) {
$data = $event->getData();
$form = $event->getForm();
if (!$data) {
return;
}
$data['title'] = $data['document'] . ' rev. ' . $data['documentRevision'];
$event->setData($data);
}
...
formMapper
->add('title',null,array(
...
);
The current problem is that I am getting the IDs of 'document' and 'documentRevision' and I need their names or __toString() representation at least.
Another issue is that although I can set the title using the event it shows error from DB when it should show Form error since validation should be done on FormEvents::SUBMIT - this one I don't understand.
Last thing to note is that if I try to use callback function:
$builder->addEventListener(FormEvents::PRE_SUBMIT, array($this,'onPreSubmit'))
public function onPreSubmit() {
$entity = $this->getSubject();
$entity->setTitleFromDocumentName();
}
I will get null title and errors if Entity tries to get fields from related entites - Calling function on non object.
Regarding entity data maybe this will help you to get the subject:
https://gist.github.com/webdevilopers/fef9e296e77bb879d138
Then you could use getters to get the desired data for instance:
protected function configureFormFields(FormMapper $formMapper)
{
$subject = $this->getSubject();
$formMapper->getFormBuilder()->addEventListener(FormEvents::PRE_SET_DATA,
function (FormEvent $event) use ($subject) {
$document = $subject->getDocument();
// ...
});
}
I also posted this on your issue:
https://github.com/sonata-project/SonataAdminBundle/issues/2273
To solved this when I changed the unique entity validation constraints as ones used by me where not completely valid from conceptual perspective.
Also it's important to note that functions that are marked as #PrePersist, #PreUpdate etc. must be public if they are to be used like that, marking them private will make Doctrine fail.
Note that the methods set as lifecycle callbacks need to be public and, when using these annotations, you have to apply the #HasLifecycleCallbacks marker annotation on the entity class.
See: http://doctrine-orm.readthedocs.org/en/latest/reference/events.html#lifecycle-callbacks (first paragraph after the code sample).

Symfony2 - Forced to validate inside DataTransformer because of type hint on setter

I have created an Object to ID Data Transformer. It's part of a custom ObjectIdType that allows me to enter the ID of a document instead of using a 'document' form type. It's handy for MongoDB (when there could be 100 million documents to choose from).
The Data Transformer does a query upon the ID and returns an object. If it can't find an object then it returns null. The issue is - sometimes null is an acceptable value, and sometimes it isn't.
Even if I add a NotNull validator, I get the following error -
Catchable Fatal Error: Argument 1 passed to Character::setPlayer() must be an instance of Document\Player, null given
So it's calling the setter regardless of the validation failing. I fixed this by throwing a TransformationFailedException within the transformer - but this just seems like a bad idea. I shouldn't really be using a Data Transformer to validate.
The code of the transformer is below. What I'd like is to be able to put the validator in the correct place, and intercept the setter so it doesn't get called. Generally, this seems like a bit of a code smell, I would love to know how other people have solved this issue.
class ObjectToIdTransformer implements DataTransformerInterface
{
private $objectLocator;
private $objectName;
private $optional;
/**
* #param ObjectLocator $objectLocator
* #param $objectName
*/
public function __construct(ObjectLocator $objectLocator, $objectName, $optional = false)
{
$this->objectLocator = $objectLocator;
$this->objectName = $objectName;
$this->optional = $optional;
}
/**
* {#inheritdoc}
*/
public function transform($value)
{
if (null === $value) {
return null;
}
if (!$value instanceof BaseObject) {
throw new TransformationFailedException("transform() expects an instance of BaseObject.");
}
return $value->getId();
}
/**
* {#inheritdoc}
*/
public function reverseTransform($value)
{
if (null === $value) {
return null;
}
$repo = $this->objectLocator->getRepository($this->objectName);
$object = $repo->find($value);
if (!$this->optional && !$object) {
throw new TransformationFailedException("This is probably a bad place to validate data.");
}
return $object;
}
}
Actually, it's a PHP quirk that's very unintuitive — especially for those coming from other (logical, intuitive, sane) languages like Java. If you want to be able to pass a null argument to a typehinted parameter, you have to set its default value to null:
public function setPlayer(Player $player = null)
{
// ...
}
Yea, talk about some consistency here...