I have this assertion inside my entity :
/**
* #Assert\Choice(choices = {"fiction", "non-fiction"}, message = "Choose a valid genre.")
*/
protected $genre;
And the documentation says for the message parameter :
type: string
default: The value you selected is not a valid choice.
This is the message that you will receive if the multiple option is set to false and the underlying value is not in the valid array of choices.
And in my form type, I have :
->add('genre', ChoiceType::class, [
'multiple' => false,
])
But, when the value of gender field isn't in the valid array of choices ("fiction", "non-fiction"), the message error is "This value is not valid."
It isn't not the default value, nor the value I set in the entity.
I know I can override it in the form type with the "invalid_message" parameter,
but I want to if there is an explanation on this ?
Why the "message" parameter oth the Assert/Choice doesn't work?
How can I make it work like its expected in the symfony doc ?
In the docs, it's not speficied that you can pass the possible values in this place.
You need to create a function in the class (for example "getGender()") that returns an array with the valid values.
Then, with the assert, you can call this function like this :
class Author
{
/**
* #Assert\Choice(callback = "getGender", message = "Choose a valid genre.")
*/
protected $genre;
public function getGender() {
return array("fiction", "non-fiction");
}
}
And be carefull, you used "choices" instead of "callback" in the annotation ;)
My first remark would be that
/**
* #Assert\Choice(choices = {"fiction", "non-fiction"}, message = "Choose a valid genre.")
*/
protected $genre;
attribute's name is genre but on the form type you're calling it gender.
I guess this is just mistyping problem.
Just rename the attribute gender instead of genre .
Related
I create a form to edit an existing object in database.
One of the field is a string, and cannot be blank or empty.
In order to display a validation error message if the value is blank at form submit, I add the following NotBlank constraint into the form type class:
...
->add(
'firstname',
null,
[
'label' => 'user.firstname',
'constraints' => [
new Assert\NotBlank()
]
]
)
...
I also specify the attribute type (string) into the entity:
...
private string $firstname;
...
public function setFirstname(string $firstname) {
$this->firstname = $firstname;
}
...
public function getFirstname(): string {
return $this->firstname;
}
When I submit the form with a blank value, I expect to see a normal form validation error message telling me the value cannot be blank. Instead, I get the following Symfony error:
Expected argument of type "string", "null" given at property path "firstname".
If I remove the type string from the entity attribute declaration, then I do not get that Symfony error anymore and the validation error message is displayed as expected.
Is there anyway to keep declaring attributes types while applying a NotBlank constraint on it ?
I have noticed that unexpected behavior only occurs when I use a form to edit an existing object but not when I create a brand new object in database. In that last case, setting a blank value with a NotBlank constraint and declaring the attribute type into the entity (private string $firstname) does not cause any issue and the form validation error message (telling the field cannot be blank) is displayed correctly.
I am writing a client that makes requests to the DA Ledger. I am following the advice I received in a previous post Doing CRUD on the DA Ledger through a gRPC client.
I need to run the 'GetTransactions' rpc. Doing so requires GetTransactionsRequest object. The GetTransactionsRequest object has a required property called 'filter' which is of type TransactionFilter. I am having trouble creating a transaction filter to meet my needs. The .proto file for it is:
// Used for filtering Transaction and Active Contract Set streams.
// Determines which on-ledger events will be served to the client.
message TransactionFilter {
// Keys of the map determine which parties' on-ledger transactions are being queried.
// Values of the map determine which events are disclosed in the stream per party.
// At the minimum, a party needs to set an empty Filters message to receive any events.
// Required
map<string, Filters> filters_by_party = 1;
}
the one and only field of 'filters_by_party' is required.
Setting this field in php requires the following function:
/**
* Keys of the map determine which parties' on-ledger transactions are being queried.
* Values of the map determine which events are disclosed in the stream per party.
* At the minimum, a party needs to set an empty Filters message to receive any events.
* Required
*
* Generated from protobuf field <code>map<string, .com.digitalasset.ledger.api.v1.Filters> filters_by_party = 1;</code>
* #param array|\Google\Protobuf\Internal\MapField $var
* #return $this
*/
public function setFiltersByParty($var)
{
$arr = GPBUtil::checkMapField($var, \Google\Protobuf\Internal\GPBType::STRING, \Google\Protobuf\Internal\GPBType::MESSAGE, \Com\Digitalasset\Ledger\Api\V1\Filters::class);
$this->filters_by_party = $arr;
return $this;
}
The php function for setting up a values in a mapFiled object is:
/**
* Assign the element at the given key.
*
* This will also be called for: $arr[$key] = $value
*
* #param object $key The key of the element to be fetched.
* #param object $value The element to be assigned.
* #return void
* #throws ErrorException Invalid type for key.
* #throws ErrorException Invalid type for value.
* #throws ErrorException Non-existing key.
*/
public function offsetSet($key, $value)
{
$this->checkKey($this->key_type, $key);
switch ($this->value_type) {
case GPBType::SFIXED32:
case GPBType::SINT32:
case GPBType::INT32:
case GPBType::ENUM:
GPBUtil::checkInt32($value);
break;
case GPBType::FIXED32:
case GPBType::UINT32:
GPBUtil::checkUint32($value);
break;
case GPBType::SFIXED64:
case GPBType::SINT64:
case GPBType::INT64:
GPBUtil::checkInt64($value);
break;
case GPBType::FIXED64:
case GPBType::UINT64:
GPBUtil::checkUint64($value);
break;
case GPBType::FLOAT:
GPBUtil::checkFloat($value);
break;
case GPBType::DOUBLE:
GPBUtil::checkDouble($value);
break;
case GPBType::BOOL:
GPBUtil::checkBool($value);
break;
case GPBType::STRING:
GPBUtil::checkString($value, true);
break;
case GPBType::MESSAGE:
if (is_null($value)) {
trigger_error("Map element cannot be null.", E_USER_ERROR);
}
GPBUtil::checkMessage($value, $this->klass);
break;
default:
break;
}
$this->container[$key] = $value;
}
How do I, for example, set up the parties name 'dealer1' and 'dealer2' as my parties for filters_by_party. I tried the following code:
$parties= new Google\Protobuf\Internal\MapField(Google\Protobuf\Internal\GPBType::STRING,Google\Protobuf\Internal\GPBType::MESSAGE);
$parties->offsetSet(0,"dealer1");
$parties->offsetSet(1,"dealer2");
results in the following error:
PHP Fatal error: Given value is not message. in /home/vantage/damlprojects/loaner_car/php/ledger_client.php on line 85
I don't understand why a 'message'
is required by the filter_by_party 'set' function. I don't know how to write the dealer name in the form of a 'meessage'. It seems doing something that should be simple is very complicated. What is the correct way to set up the input $var to the 'setFiltersByParty' function?
Maybe you can find some information here:
https://developers.google.com/protocol-buffers/docs/reference/php-generated#fields
For map field, I guess it would be something looks like:
$m->getFiltersByParty()["string"] = new Filters();
The code for setting up 'dealer1' and dealer2' should look as follows:
$parties= new Google\Protobuf\Internal\MapField(Google\Protobuf\Internal\GPBType::STRING,
Google\Protobuf\Internal\GPBType::MESSAGE, "Com\Digitalasset\Ledger\Api\V1\Filters");
$partyIdentifier = new Com\Digitalasset\Ledger\Api\V1\Identifier();
$partyIdentifier->setPackageId($the_package_id); //last one from ListPackages()
$partyIdentifier->setEntityName("<Module>"); //from DAML code
$partyIdentifier->setModuleName("<Template>"); //from DAML code
$partyInclusiveFilters = new Com\Digitalasset\Ledger\Api\V1\InclusiveFilters();
$partyInclusiveFilters->setTemplateIds(array($partyIdentifier));
$partyFilters = new Com\Digitalasset\Ledger\Api\V1\Filters();
$partyFilters->setInclusive($partyInclusiveFilters);
$parties->offsetSet("dealer1",$partyFilters);
$parties->offsetSet("dealer2",$partyFilters);
It is important to set the third parameter of the MapField constructor to "Com\Digitalasset\Ledger\Api\V1\Filters". As the protobuf file suggests:
message TransactionFilter {
// Keys of the map determine which parties' on-ledger transactions are being queried.
// Values of the map determine which events are disclosed in the stream per party.
// At the minimum, a party needs to set an empty Filters message to receive any events.
// Required
map<string, Filters> filters_by_party = 1;
}
The filters_by_party is MapField that has a value_type of 'Filter'. 'Filter' is a type of message (see your transaction_filter.proto file), therefore the MapField constructor needs to know what type of message it is.
The dealer name is not being written in the form of a "message" but rather it is a key_value in the the 'filters_by_party' MapField that corresponds to a 'Filter' object. And a 'Filter' is a type of message. I guess 'simple' is a relative term.
I have a model with title, subtitle, date and am building a form that will allow a user to submit a change request.
How can I validate to ensure at least one edit is made comparing the input fields to database values?
I think the below would ensure the title entered is different from the value in 'different:', but how would I only do this for at least one field?
public function rules()
{
return [
'title' => [
'required',
'different:Dynamic Title name here',
'string',
'max:60',
'not_regex:/[\x{1F600}-\x{1F64F}]/u'
],
'subtitle' => [
'string',
'nullable',
'max:90',
'not_regex:/[\x{1F600}-\x{1F64F}]/u'
]
];
}
e.g.
Title, Subtitle, Date fields are shown. A user must edit at least one of them from the current set database values in order to submit.
I don't know your solution, but I'd recommend to take a look at isDirty() function.
/**
* this will return false, because after we get the record from
* database, there's no attribute of it that we changed. we just
* print if it's dirty or not. so it tells us: "I'm clean, nobody has
* changed my attributes at all.
*/
$role = Role::findOrFail(1);
return $role->isDirty();
/**
* lets say We fetched this role with id=1 and its status was 1. what
* this returns is still false, because even though we set the status
* attribute equal to 1, we still didn't change it. It was 1 when we
* received it from the database and it's still 1.
*/
$role = Role::findOrFail(1);
$role->status = 1;
return $role->isDirty();
/**
* now if the status was 1 in the db, and we set it to 2, it will
* print the true.
*/
$role = Role::findOrFail(1);
$role->status = 2;
return $role->isDirty();
You can also pass an argument to isDirty() function which will only check that specific column value.
Long story short, in Symfony 2.8 I've got Movie entity with actors field, which is ArrayCollection of entity Actor (ManyToMany) and I wanted the field to be ajax-loaded Select2.
When I don't use Ajax, the form is:
->add('actors', EntityType::class, array(
'class' => Actor::class,
'label' => "Actors of the work",
'multiple' => true,
'attr' => array(
'class' => "select2-select",
),
))
And it works.
I tried to put there an empty Select field:
->add('actors', ChoiceType::class, array(
'mapped' => false,
'multiple' => true,
'attr'=>array(
'class' => "select2-ajax",
'data-entity'=>"actor"
)
))
The Select2 Ajax works, everything in DOM looks the same as in previous example, but on form submit I get errors in the profiler: This value is not valid.:
Symfony\Component\Validator\ConstraintViolation
Object(Symfony\Component\Form\Form).children[actors] = [0 => 20, 1 => 21]
Caused by:
Symfony\Component\Form\Exception\TransformationFailedException
Unable to reverse value for property path "actors": Could not find all matching choices for the given values
Caused by:
Symfony\Component\Form\Exception\TransformationFailedException
Could not find all matching choices for the given values
The funny part is the data received is the same as they were when it was an EntityType: [0 => 20, 1 => 21]
I marked field as not mapped, I even changed field name to other than Movie entity's field name. I tried adding empty choices, I tried to leave it as EntityType but with custom query_builder, returning empty collection. Now I'm out of ideas.
How should I do it?
EDIT after Raymond's answer:
I added DataTransformer:
use Doctrine\Common\Persistence\ObjectManager;
use CompanyName\Common\CommonBundle\Entity\Actor;
use Symfony\Component\Form\DataTransformerInterface;
use Symfony\Component\Form\Exception\TransformationFailedException;
class ActorToNumberTransformer implements DataTransformerInterface
{
private $manager;
public function __construct(ObjectManager $objectManager)
{
$this->manager = $objectManager;
}
public function transform($actors)
{
if(null === $actors)
return array();
$actorIds = array();
foreach($actors as $actor)
$actorIds[] = $actor->getId();
return $actorIds;
}
public function reverseTransform($actorIds)
{
if($actorIds === null)
return array();
foreach($actorIds as $actorId)
{
$actor = $this->manager->getRepository('CommonBundle:Actor')->find($actorId);
if(null === $actor)
throw new TransformationFailedException(sprintf('An actor with id "%s" does not exist!', $actorId));
$actors[] = $actor;
}
return $actors;
}
}
Added it at the end of the MovieType buildForm():
$builder->get('actors')
->addModelTransformer(new ActorToNumberTransformer($this->manager));
$builder->get('actors')
->addViewTransformer(new ActorToNumberTransformer($this->manager));
And added service:
common.form.type.work:
class: CompanyName\Common\CommonBundle\Form\Type\MovieType
arguments: ["#doctrine.orm.entity_manager"]
tags:
- { name: form.type }
Nothing changed. On form submit, reverseTransform() gets the proper data, but profiler shows the same error. That's a big mistery for me now...
You'll need to add a DTO (Data Transformer ) to transform the value received from your form and return the appropriate object .
Since you're calling the value from Ajax it doesn't recognized it anymore as a an object but a text value.
Examples :
Symfony2 -Use of DTO
Form with jQuery autocomplete
The correct way isn't Data Transformer but Form Events, look here:
http://symfony.com/doc/current/form/dynamic_form_modification.html#form-events-submitted-data
In the example you have the field sport (an entity, like your Movie) and the field position (another entity, like actors).
The trick is to use ajax in order to reload entirely the form and use
PRE_SET_DATA and POST_SUBMIT.
I'm using Symfony 3.x but I think it's the same with 2.8.x
When you add data transformers and nothing seems to change, it sounds like the data never goes through your data transformers. The transformation probably fails before your new data transformers are called. Try to add a few lines to your code:
$builder->get('actors')->resetViewTransformers();
$builder->get('actors')->resetModelTransformers();
// and then add your own
In Symfony 2.6, I am using an entity form type that is unmapped:
$form
->add(
'myEntity', // Form field name
'entity',
[
'mapped' => false, // Not mapped
'class' => 'MyVendor\MyBundle\Entity\MyEntity',
'choices' => $MyEntityCollection, // list of MyEntity
'property' => 'name',
'empty_value' => 'Please select MyEntity',
'empty_data' => null,
'attr' => [
'label' => 'My label'
]
]
);
This allows user to properly select an item of MyEntity or leave it blank. According to that, I am adding a EventSubscriber to modify the preSubmitted data if any value is selected, and leave it as it is if no choice has been made.
Here is the eventSubscriber:
/**
* {#inheritdoc}
*/
public static function getSubscribedEvents()
{
return [
FormEvents::PRE_SUBMIT => 'preSubmitData'
];
}
/**
* #param FormEvent $event
*/
public function preSubmitData(FormEvent $event)
{
if( null === ($entity = $event->getForm()->get( 'myEntity' )->getData() ) ){
return;
}
// Set value if field has been defined
$event
->getForm()
->setData( $entity )
;
}
If user selects a choice other than blank, when I debug the preSubmitData function:
$event->getForm()->get('entity')->getData() gives null
$event->getData() gives an array having as 'entity' key the selected entity ID (just the scalar value)
My questions are:
Shouldn't $event->getForm()->get('entity')->getData() have the selected entity?
Why is $event->getForm()->get('entity')->getData() giving null if $event->getData() has at least the entity ID in it?
Is there any way to get the entity here (as it happens with the mapped entities) without having to call the entity manager and querying the entity via its ID?
Thanks in advance!
Edit
For the big picture, in my global form (other fields not described here) I have 2 depending fields:
A select A (not described here) with some options from a tree. This option does exist in the global form entity as a property.
A second B select named myEntity (described here). It doesn't exist as the global form entity as a property, thus the mapped = false. If any choice is made here, then the first select (A)'s option is overridden by this one. Else the first choice remains as the entity property value.
Hope is clearer now.
Ok, it was giving null because we are on the preSubmit event, and here data sent is not yet mapped within an entity.
Changing the event to submit gives the mapped entity as needed.