I'm working on a FOS REST API. In the underlying models I'd like to be able to define Constraints representing the form appropriate for the datastore, for example, a US Phone Number should be exactly 10 digits.
/**
* #var string
*
* #Assert\NotBlank(message="Phone is required.")
* #Assert\Regex(message="Exactly 10 digits are required.", pattern="/^\d{10}$/")
*/
private $phone;
On the other hand I'd like to be able to accept liberal values, for example a phone number formatted as:
{
"phone": "603-988-6521"
}
The ideal way to implement this would be to have some type of "conversion" or "normalization" phase where select fields could be converted to all digits etc. prior to validation.
What would be the best way to accomplish this in the FOST REST paradigm and Symfony 3?
It turns out that this is very simple. You can do any type of normalization needed in the actual setters of your model. You just need to configure JMS Serializer to use setters rather than using property reflection. Example with annotations:
/**
* #var string
*
* #JMS\Accessor(getter="getPhone", setter="setPhone")
* #Assert\Regex(message="Exactly 10 digits are required.", pattern="/^\d{10}$/")
*/
private $phone;
/**
* #param string
*/
public function setPhone($phone)
{
if ($phone === null) {
$this->phone = null;
return;
}
$this->phone = preg_replace('/[^0-9]/', '', $phone);
}
Related
So the problem is like this:
I am trying to save some data from API and I need to validate them with Symfony validation ex:
private $id;
/**
* #var
* #Assert\Length(max="255")
* #CustomAssert\OrderExternalCode()
* #CustomAssert\OrderShipNoExternalCode()
*/
private $code;
private $someId;
/**
* #var
* #Assert\NotBlank()
* #Assert\Length(max="255")
*/
private $number;
this works well but now I need to add some Assert Constrains dynamically from the controller and that is where I am stuck!
Does anyone knows how to do that or any suggestion that might help?
Currently I did an extra constraint which does extra query in the DB and I don't want to do that and I am not using FormType.
You can use groups and use (or leave out) the extra group you're talking about.
Using the CallbackConstraint should help I think, in your case :
use My\Custom\MyConstraint;
use Symfony\Component\Validator\Constraints as Assert;
use Symfony\Component\Validator\Context\ExecutionContextInterface;
// This is not tested !
class MyEntity
{
/**
* #Assert\Callback()
*/
public function validateSomeId(ExecutionContextInterface $context)
{
$constraint = new MyConstraint(['code' => $this->code]);
$violations = $context->getValidator()->validate($this->number, $constraint);
foreach ($violations as $violation) {
$context->getViolations()->add($violation);
}
}
}
See https://symfony.com/doc/current/reference/constraints/Callback.html
EDIT : I don't know what you're trying to validate so I just put some random params of your entity in there
So I wanted to dynamically validate the request data based on a condition in the controller.
I specified an extra group for that in the entity like so:
/**
* #var
* #Assert\NotBlank(groups={"extra_check"})
* #Assert\Length(max="255")
*/
private $externalId;
Then in the controller I just did the condition to validate with the extra group or not.
$groups = $order->getExternalCode() != null ? ['Default'] : ['Default', 'extra_check'];
$this->validateRequest($request, null, $groups);
The Default group is the one without group specified and the other one is the group I specified in the field
I use this column in one of my entities:
/**
* #var float
* #ORM\Column(type="decimal", precision=20, scale=2)
*/
public $value;
According to the Doctrine docs, the decimal type is returned as a string to PHP, but I am using it as a float. Should I typehint it with #var float then, or is #var string correct?
Anyway, If I use this variable for arithmetic calcualtions, eg.
$object->value + $otherobject->value
am I risking to get undesired behaviour (like only adding the integer parts )?
Generating an entity with the command line tools provided by Symfony 4, a field of type decimal can be generated like this:
New property name (press <return> to stop adding fields):
> percent
Field type (enter ? to see all types) [string]:
> decimal
Precision (total number of digits stored: 100.00 would be 5) [10]:
> 5
Scale (number of decimals to store: 100.00 would be 2) [0]:
> 4
This results into the following property and getter/setter methods:
<?php
// ...
/**
* #ORM\Column(type="decimal", precision=5, scale=4)
*/
private $percent;
public function getPercent()
{
return $this->percent;
}
public function setPercent($percent): self
{
$this->percent = $percent;
return $this;
}
So per default Doctrine generates the getter/setter methods without type hinting.
The getter will return string, while the setter expects an argument of type double (which is just a synonym for float).
Therefore you couldn't do something like:
/**
* #var float
*/
private $value;
Since the value will be returned as string you shouldn't do calculations like the one in your question, but take care for previous transformation.
EDIT:
An alternative to type hinting with string or float can be the external library PHP decimal.
You can install it with this command:
composer require php-decimal/php-decimal
I would do like this :
/**
* #var float
*
* #ORM\Column(type="float", precision=20, scale=2)
*/
public $value;
I have the following Doctrine entity and I want to use its restriction also for validation.
/**
* #var string
*
* #ORM\Column(type="string", length=40)
* #Assert\Valid
*/
private $birthName;
I use the following validation, which works for symfony specific annotaions but not Doctrine set restrictions!
// Validate data
$validator = $this->get('validator');
$errors = $validator->validate($user);
if (count($errors) > 0) {
$response = new JsonResponse(array(
'error' => 'User could not be created.' . PHP_EOL . (string)$errors
));
$response->setStatusCode(400);
return $response;
}
What can I do to let symfony validator use the doctrine restrictions as settings?
Status quo:
I read [1] and [2] but so far I do not use forms because I have a controller returning JSON. If you know how to make this work with forms would also help a lot!
Doctrine mappings have nothing to do with validation.
The code #ORM\Column(type="string", length=40) only maps a property to a database field, and sets max length of a database field to be equal 40 characters, if you would create a schema using doctrine.
But thus doesn't take any part of the validation process.
So you need to set an assertion rule by something like
/**
* #Assert\Length(max = 40)
*/
To keep the field level constraints at a central place (not replicate it in each form), I added the constraints in the entity. Like below (lets say its one of the fields of a user entity):
/**
* #var string
*
* #ORM\Column(name="email", type="string", length=255, nullable=false)
*
* #Constraints\NotBlank(
* groups={"register", "edit"},
* message="email cannot be blank."
* )
* #Constraints\Email(
* groups={"register", "edit"},
* message="Please enter a valid email address."
* )
*
* #Expose
* #Groups({"list", "details"})
*/
private $email;
Now I need a way to expose this validation constraints for each field which is an annotation of "Symfony\Component\Validator\Constraints". Is there a way that I can get all the constraints for all fields in the entity, like:
$em->getValidationConstraints('MyBundle:EntityUser'); //em is the entity manager
//and it returns me all the fields with its name, type and any constraints
//attached to it as any array
Thanks in advance.
Gather Information
Before fixing a problem, it's good to know what you are talking about and gather some information.
Doctrine is an ORM, something that does nice things between a database and an object. It has nothing to do with validation, that is done by the Symfony2 Validator Component. So you need something else than the $em.
All constraints of a class are called 'metadata' and they are usually stored in Symfony\Component\Validator\Mapping\ClassMetadata. We have to find a class which accepts the name of a class and returns a ClassMetadata instance.
To load the constraints, the Symfony2 Validator component uses loaders.
The Solution
We can see that there is a Symfony\Component\Validator\Mapping\ClassMetadataFactory. A factory is always used to build a class from a specific argument. In this case, we know it will create a ClassMetadata and we can see that it accepts a classname. We have to call ClassMetadataFactory::getMetadataFor.
But we see it needs some loaders. We aren't going to do the big job of initializing this factory, what about using the service container? We can see that the container has a validator.mapping.class_metadata_factory service, which is exactly the class we need.
Now we have all of that, let's use it:
// ... in a controller (maybe a seperated class is beter...)
public function someAction()
{
$metadataFactory = $this->get('validator.mapping.class_metadata_factory');
$metadata = $metadataFactory->getMetadataFor('Acme\DemoBundle\Entity\EntityUser');
}
Now we have the metadata, we only need to convert that to an array:
// ...
$propertiesMetadata = $metadata->properties;
$constraints = array();
foreach ($propertiesMetadata as $propertyMetadata) {
$constraints[$propertyMetadata->name] = $property->constraints;
}
Now, $constraints is an array with all fields and their constraint data, something like:
Array (
...
[email] => Array (
[0] => <instance of NotBlank>
[1] => <instance of Email>
),
)
I'm using Symfony2 and JMSSerializerBundle to build an API. The system that JMSSerializer provides to set different ways of serializing objects using groups is quite useful, however, I'm missing a way to specify which group do you want to serialize in every parameter. Example:
I have an article that is related to a user (author). Articles as well as users can be serialized as "list" or as "details", however, I want the users to be serialized as "list" always that they are retrieved from the article (because "details" group is reserved to be used to fetch the user and just the user). The problem is that if I set the serializer as "details", then the author is also serialized as "details".
In my mind, the code should be something like:
/**
* #var SCA\APIBundle\Entity\User
* #Groups({"list" => "list", "details" => "list"})
*/
private $author;
where the key of the array indicates the way the parent should be serialized, and the value indicates the way the child should be serialized.
Any clue how can I achieve this?
It should not be done on the composed object but on the composition.
In your case, I suppose you have something like that:
class Article
{
/**
* #var User
* #Groups({"list", "details"})
*/
private $author;
}
class User
{
private $firstName;
private $lastName;
}
So if you want to expose the firstName property when serializing the composed object, you need to define the same group in the User object.
It becomes:
class Article
{
/**
* #var User
* #Groups({"list", "details"})
*/
private $author;
}
class User
{
/*
* #Groups({"list"})
*/
private $firstName;
private $lastName;
}
If you need more control, you may define more explicit groups, like "article-list", "user-firstname", "user-list-minimal", etc.
It is up to you to decide the best strategy to adopt.