I have a field Decimal like this
/**
*
* #ORM\Column(name="foo", type="decimal", precision=0, scale=2, nullable=false, unique=false)
*/
private $foo;
When I use getFoo() or QueryBuilder my value is "159.79" instead of 159.79.
How can I get the correct type?
Even though you have declared it as a decimal in your Doctrine annotation, when it is retrived PHP will treat it as a string.
Reference here.
Values retrieved from the database are always converted to PHP’s
string type or null if no data is present.
You could change the getter to cast as a float and then return.
E.g.
public function getFoo()
{
return (float) $this->foo;
}
or
public function getFoo()
{
return floatval($this->foo);
}
Look at Type Juggling for more info.
More info on floatVal() here
Related
Despite my efforts with type hinting and #var int annotations, Api Platform’s interpretation of this property is always string.
Consider this property:
/**
* #var int $geonameId The ID of a Geonames.org city/place/town
*
* #ORM\Column(type="bigint", options={"unsigned": true}, nullable=true)
* #Assert\Type(type="int", message="Geoname id must be an integer")
*/
protected $geonameId;
public function getGeonameId(): ?int
{
return $this->geonameId;
}
public function setGeonameId(int $geonameId = null): self
{
$this->geonameId = $geonameId;
return $this;
}
I get the following schema out from Api Platform:
geonameId string
nullable: true
The ID of a Geonames.org city/place/town
And when I try submit "geonameId": 123456 I get the following error:
The type of the "geonameId" attribute for class <Entity> must be one of "string" ("int" given).
I know about the #ApiProperty annotation to customise attributes etc. but I don't want to have to verbosely define the type just to accept an integer when I have already used type hinting and/or #var annotation.
Does anybody see anything wrong with what I am doing / expecting? Thanks in advance...
Looks like Doctrine maps a BIGINT as a string.
Ref:
https://www.doctrine-project.org/projects/doctrine-orm/en/2.7/reference/basic-mapping.html
bigint: Type that maps a database BIGINT to a PHP string.
When passing an empty value to a label field in a form, I get a PHP error caused by Symfony trying to pass null into the setter.
Can I not define the argument type in the setters of an entity due to form validation?
class MyEntity {
...
/**
* #var string
*
* #Assert\NotBlank()
* #ORM\Column(type="string", length=128, nullable=false)
*/
private $label;
....
public function setLabel(string $label): void
{
$this->label = $label;
}
...
If you want your entity to be at a valid state you shouldn't link them with your form, you will have to use a DTO object representing your form then update your entity with the values when your form is valid.
The easiest way is to change your setter / getter to be able to return/set null values
public function setLabel(string $label = null): void
{
$this->label = $label;
}
public function getLabel(): ?string
{
return $this->label;
}
I have made a new FormType and it extends the entity type via
//...
public function getParent()
{
return 'entity';
}
Which lead my edit form to complain that an integer was not My/Entity/Type and I need a data transformer. So I created one. This is the abbreviated version (it's just the basic tutorial version)
//...
public function reverseTransform($val)
{
// Entity to int
return $val->getId();
}
public function transform($val)
{
// Int to Entity
return $repo->findOneBy($val);
}
//...
Then added it to my form type
//...
public function buildForm(FormBuilderInterface $builder, array $options)
{
$builder->addViewTransformer(new IdToMyModelTransformer($this->em));
}
This fixed me viewing my form, but now when I submit the form with an entity picked from my custom widget it tries to call transform not reverseTransform with the $val as an int the ->getId() fails on a non-object.
I can't figure out the correct way of doing this. If I use 'choice' as my widget parent I get a different set of issues (choice default constraints triggered saying it is invalid data?)
I need an entity passed to my widget so it can extract the meta data for display, but I can't post an entity back of course. How do I tell the form that?
Tried setting 'data_class' => null but no joy. Checking network tab shows the value is sent correctly when posting the form.
Update 1
So I re-read the DataTransformer page and that diagram got me thinking, especially after rubber-duck programming above, I ask the form for Entity but expect it to receive ints.. so I actually need a unidirectional transformer, ViewTransformer -> Get entity for display, get posted an int from widget, don't transform it just pass straight through. Which works and I just get the "invalid data" error on update.
Now I have in my Transformer:
public function transform($val)
{
// Int to Entity
return $repo->findOneBy($val);
}
public function reverseTransform($val)
{
// Do nothing
return $val;
}
Update 2
That seems to have fixed it now, although for some reason if I post int 2 in my form the string "2/" is sent to my transformer. Any ideas on that? FOr now I'm cleaning the string in transformer, but seems like it just shouldnt be happening.
By what I'm seeing in your transformer class you're not implementing the code right. This should be the correct implementation:
namespace App\YourBundle\Form\DataTransformer;
use Doctrine\ORM\EntityManager;
use Symfony\Component\Form\DataTransformerInterface;
use Symfony\Component\Form\Exception\TransformationFailedException;
class IdToMyModelTransformer implements DataTransformerInterface
{
/**
* #var EntityManager
*/
private $em;
/**
* #param EntityManager $em
*/
public function __construct(EntityManager $em) {
$this->em = $em;
}
/**
* Transforms a value from the original representation to a transformed representation.
*
* This method is called on two occasions inside a form field:
*
* 1. When the form field is initialized with the data attached from the datasource (object or array).
* 2. When data from a request is submitted using {#link Form::submit()} to transform the new input data
* back into the renderable format. For example if you have a date field and submit '2009-10-10'
* you might accept this value because its easily parsed, but the transformer still writes back
* "2009/10/10" onto the form field (for further displaying or other purposes).
*
* This method must be able to deal with empty values. Usually this will
* be NULL, but depending on your implementation other empty values are
* possible as well (such as empty strings). The reasoning behind this is
* that value transformers must be chainable. If the transform() method
* of the first value transformer outputs NULL, the second value transformer
* must be able to process that value.
*
* By convention, transform() should return an empty string if NULL is
* passed.
*
* #param mixed $object The value in the original representation
*
* #return mixed The value in the transformed representation
*
* #throws TransformationFailedException When the transformation fails.
*/
public function transform($object) {
if (null === $object) {
return null;
}
return $object->getId();
}
/**
* Transforms a value from the transformed representation to its original
* representation.
*
* This method is called when {#link Form::submit()} is called to transform the requests tainted data
* into an acceptable format for your data processing/model layer.
*
* This method must be able to deal with empty values. Usually this will
* be an empty string, but depending on your implementation other empty
* values are possible as well (such as empty strings). The reasoning behind
* this is that value transformers must be chainable. If the
* reverseTransform() method of the first value transformer outputs an
* empty string, the second value transformer must be able to process that
* value.
*
* By convention, reverseTransform() should return NULL if an empty string
* is passed.
*
* #param mixed $categoryId The value in the transformed representation
*
* #return mixed The value in the original representation
*
* #throws TransformationFailedException When the transformation fails.
*/
public function reverseTransform($id) {
if (!$id || $id <= 0) {
return null;
}
if(!ctype_digit($id)){
throw new TransformationFailedException();
}
$repo = $this->em->getRepository('...');
$result = $repo->findOneBy(array('id' => $id));
if (null === $result) {
throw new TransformationFailedException(
sprintf(
'Entity with id does not exist!',
$id
)
);
}
return $result;
}
}
In the IdToMyIntType you would have something like this:
namespace App\YourBundle\Form\Type;
use App\YourBundle\Form\DataTransformer\IdToMyModelTransformer ;
use Doctrine\ORM\EntityManager;
use Symfony\Component\Form\AbstractType;
use Symfony\Component\Form\FormBuilderInterface;
use Symfony\Component\OptionsResolver\OptionsResolverInterface;
class IdToMyModelType extends AbstractType {
/**
* #var EntityManager
*/
private $em;
/**
* #param EntityManager $em
*/
public function __construct( EntityManager $em ) {
$this->em = $em;
}
public function buildForm( FormBuilderInterface $builder, array $options ) {
$transformer = new IdToMyModelTransformer ( $this->em );
$builder->addModelTransformer( $transformer );
}
public function setDefaultOptions( OptionsResolverInterface $resolver ) {
$resolver->setDefaults(array('invalid_message' => 'Something went wrong message.'));
}
public function getParent() {
return 'entity';
}
public function getName() {
return 'id_to_model_type';
}
}
I would suggest you check out the DataTransformerInterface and read the documentation over the methods. It'll briefly explain what is that method expected to do. Also, in case you have problems implementing it, you can always check the official documentation, which contains a working example and build up from there.
As per my last update I realised, because I was only using my form data to display the currently saved entity relation (the rest is provided by ajax) and not in the same format the form would be receiving it in it lead to some confusion.
To follow the tutorials wording:
Model data
This was all to remain as-is (No model datatransformer needed)
Norm data
No changes
View data (unidirection transformation required)
Transform()
ID to Entity so widget can access other properties
ReverseTransform()
Posted ID is in correct format so we just return it
Code
Very simplified:
private $om;
public function __construct (ObjectManager om)
{
$this->om = $om;
}
public function transform($val)
{
// Int to Entity
return $om->getRepository('MyBundle:EntityName')->findOneBy($val);
}
public function reverseTransform($val)
{
// Do nothing
return $val;
}
Hopefully that helps anyone else who lets their requirements confuse them!
I have a json encoded string in one of my DB fields, e.g.
[{"name":"car","price":"10"}]
I'm using the FosRestBundle to return the DB-Values in json format and the String above is returned - as String - nothing special until here;)
How can this String be converted, so that a Json Object is returned instead?
Finally I found the solution.
My Entity contained this:
/**
* #var string
*
* #ORM\Column(name="options", type="string", nullable=true)
*/
private $options;
"options" contained the json encoded string. So I tried it with the JMS Serilizer Annotation #Accessor and wrote this specific getter:
/**
* Get optionsAsArray
*
* #return array
*/
public function getOptionsAsArray()
{
return (array)json_decode($this->options, true);
}
Still got an error "Array to string conversion". So the Solution was, to add another Annotation #type and the JMSSerializer returned nicely formatted JSON.
This is what the Entity has to look like:
use JMS\Serializer\Annotation\Accessor;
use JMS\Serializer\Annotation\Type;
/* ... */
/**
* #var string
*
* #ORM\Column(name="options", type="string", nullable=true)
* #Accessor(getter="getOptionsAsArray")
* #Type("array")
*/
private $options;
You can decode this string to an stdClass or an associative array with json_decode. Is this what you are looking for?
EDIT : This should work
public function myAction()
{
// do stuff
$string = '[{"name":"car","price":"10"}]';
$array = json_decode($string, true);
/* array is like
[
0 => [
'name' => string(3) "car"
'price' => string(2) "10"
]
]
*/
return $array;
}
My Entity:
/**
* #var \DateTime $publishedAt
*
* #ORM\Column(name="published_at", type="date")
*
* #Assert\Date()
*/
private $publishedAt;
/**
* Set publishedAt
*
* #param \DateTime $publishedAt
* #return MagazineIssue
*/
public function setPublishedAt(\DateTime $publishedAt)
{
$this->publishedAt = $publishedAt;
return $this;
}
/**
* Get published_at
*
* #return \DateTime
*/
public function getPublishedAt()
{
return $this->publishedAt;
}
My form builder:
$builder->add('publishedAt');
My view:
{{ form_widget(form) }}
When I select the date in the selects and submit the form I catche the error:
Catchable Fatal Error: Argument 1 passed to ... must be an instance of DateTime,
string given, called in .../vendor/symfony/symfony/src/Symfony/Component/Form
/Util/PropertyPath.php on line 537 and defined in ... line 214
Why it happens? If I replace the field setter with public function setPublishedAt($publishedAt) I got the error:
Fatal error: Call to a member function format() on a non-object
in .../vendor/doctrine/dbal/lib/Doctrine/DBAL/Types/DateType.php on line 44
If I change the form builder to
$builder->add('publishedAt','date')
all works fine. Why it happens? Why symfony can't guess it and pass to field setter the proper date format (\DateTime instead of string)?
EDIT: if I remove the #Assert\Date() then all works fine too. I think it's a sf2.1 bug with guessing the date field type
I used to deal with this just like Max wrote but then I discovered Data transformers. It's very efficient way and does not imply modifications to model (or it's getter/setter methods)...
EDIT: Check out the title "Using Transformers in a custom field type". They write about DateTime there...
Doctrine want to call \DateTime::format(). From a string.
You can check the argument in the setter method:
public function setPublishedAt($publishedAt)
{
if($publishedAt instanceof \DateTime) {
$this->publishedAt = $publishedAt;
} else {
$date = new \DateTime($publishedAt);
$this->publishedAt = $date;
}
}
To solve this problem you can
1.change the assert from #Assert\Date() to #Assert\Type('\DateTime')
OR
2.change the form builder to $builder->add('publishedAt','date')
OR
3.specify the input option in the form builder: $builder->add('publishedAt',null,array('input' => 'datetime'))