Hello,
I have a weird error I'm trying to resolve since few days, when I expected it to be managed automaticaly from scratch.
So maybe there is a bug somewhere...
I created an API, and I added a POST function.
I'm using a form and JMS serializer to do that, and for all values it works, except for my DateTime Object.
Maybe it's just a matter of format badly defined, but because the error is just : "This value is not valid" I have still no idea on the date format expected.
My entity :
<?php
namespace Exif\CoreBundle\Entity;
use Doctrine\ORM\Mapping as ORM;
use JMS\Serializer\Annotation\Expose;
use Symfony\Component\Validator\Constraints as Assert;
/**
* Photo
*
* @ORM\Table()
* @ORM\Entity(repositoryClass="Exif\CoreBundle\Repository\PhotoRepository")
*/
class Photo
{
/**
* @var integer
*
* @ORM\Column(name="id", type="integer")
* @ORM\Id
* @ORM\GeneratedValue(strategy="AUTO")
*
* @Expose
*/
private $id;
/**
* @var integer
*
* @ORM\Column(name="hash", type="integer")
* @Assert\NotBlank()
* @Expose
*/
private $hash;
/**
* @var integer
*
* @ORM\Column(name="filesize", type="integer")
* @Assert\NotBlank()
* @Expose
*/
private $filesize;
/**
* @var \DateTime
*
* @ORM\Column(name="datePicture", type="datetime")
*
* @Assert\NotBlank()
* @Expose
*/
private $datePicture;
/**
* @ORM\ManyToOne(targetEntity="Camera", inversedBy="photos")
* @ORM\JoinColumn(name="camera_id", referencedColumnName="id")
*
* @Assert\NotBlank()
* @Expose
**/
private $camera;
/**
* Get id
*
* @return integer
*/
public function getId()
{
return $this->id;
}
/**
* Set hash
*
* @param string $hash
* @return Photo
*/
public function setHash($hash)
{
$this->hash = $hash;
return $this;
}
/**
* Get hash
*
* @return string
*/
public function getHash()
{
return $this->hash;
}
/**
* Set datePicture
*
* @param \DateTime $datePicture
* @return Photo
*/
public function setDatePicture($datePicture)
{
$this->datePicture = $datePicture;
return $this;
}
/**
* Get datePicture
*
* @return \DateTime
*/
public function getDatePicture()
{
return $this->datePicture;
}
/**
* Set camera
*
* @param \Exif\CoreBundle\Entity\Camera $camera
* @return Photo
*/
public function setCamera(\Exif\CoreBundle\Entity\Camera $camera = null)
{
$this->camera = $camera;
return $this;
}
/**
* Get camera
*
* @return \Exif\CoreBundle\Entity\Camera
*/
public function getCamera()
{
return $this->camera;
}
/**
* Set filesize
*
* @param integer $filesize
* @return Photo
*/
public function setFilesize($filesize)
{
$this->filesize = $filesize;
return $this;
}
/**
* Get filesize
*
* @return integer
*/
public function getFilesize()
{
return $this->filesize;
}
}
My form :
<?php
namespace Exif\CoreBundle\Form;
use Symfony\Component\Form\AbstractType;
use Symfony\Component\Form\FormBuilderInterface;
use Symfony\Component\OptionsResolver\OptionsResolverInterface;
class PhotoType extends AbstractType
{
/**
* @param FormBuilderInterface $builder
* @param array $options
*/
public function buildForm(FormBuilderInterface $builder, array $options)
{
$builder
->add('useremail', 'email', array('mapped' => false))
->add('hash')
->add('filesize')
->add('datePicture', 'datetime', array(
'description' => 'The date when the picture was taken',
'format' => 'yyyy/MM/dd HH:mm',
))
->add('camera')
;
}
/**
* @param OptionsResolverInterface $resolver
*/
public function setDefaultOptions(OptionsResolverInterface $resolver)
{
$resolver->setDefaults(array(
'data_class' => 'Exif\CoreBundle\Entity\Photo',
'csrf_protection' => false,
));
}
/**
* @return string
*/
public function getName()
{
return '';
}
}
And my controler : (look cPostAction and processForm)
<?php
namespace Exif\CoreBundle\Controller;
use Symfony\Component\HttpFoundation\Request;
use FOS\RestBundle\Controller\FOSRestController;
use FOS\RestBundle\Routing\ClassResourceInterface;
use FOS\RestBundle\Controller\Annotations as Rest;
use Nelmio\ApiDocBundle\Annotation\ApiDoc;
use FOS\RestBundle\View\View;
use Symfony\Component\Security\Core\Exception\AccessDeniedException;
use FOS\RestBundle\Controller\Annotations\Get;
use Exif\CoreBundle\Entity\Photo;
use Exif\CoreBundle\Form\PhotoType;
class PhotosController extends FOSRestController implements ClassResourceInterface {
/**
* Collection get action
* @var Request $request
* @return array
*
* @Rest\View()
*/
public function cgetAction(Request $request)
{
$em = $this->getDoctrine()->getManager();
$entities = $em->getRepository('ExifCoreBundle:Photo')->findAll();
return array(
'entities' => $entities,
);
}
/**
* @ApiDoc(
* resource = true,
* description = "return datas about one photo",
* input = "integer",
* statusCodes = {
* 200 = "Returned when successful",
* 404 = "Returned when no photo had been found"
* }
* )
* @Rest\View()
*/
public function getAction($id) {
$photo = $this->getDoctrine()->getRepository('ExifCoreBundle:Photo')->findOneById($id);
if (!is_object($photo)) {
throw $this->createNotFoundException();
}
return $photo;
}
/**
* @Get("/photos/{hash}/{filesize}")
* @Rest\View()
*/
public function searchAction($hash, $filesize) {
$photo = $this->getDoctrine()->getRepository('ExifCoreBundle:Photo')->findOneBy(array("hash" => $hash, "filesize" => $filesize));
if (!is_object($photo)) {
throw $this->createNotFoundException();
}
return $photo;
}
/**
* @Rest\View()
*/
public function getUsersAction($id) {
$users = $this->getDoctrine()->getRepository('ExifCoreBundle:User')->findByPhoto($id);
if (empty($users)) {
throw $this->createNotFoundException();
}
return $users;
}
/**
* Collection post action
* @ApiDoc
* @var Request $request
* @return View|array
*/
public function cpostAction(Request $request)
{
return $this->processForm($request,new Photo());
}
private function processForm(Request $request, Photo $photo)
{
$statusCode = $photo->getId() ? 201 : 204;
$form = $this->createForm(new PhotoType(), $photo);
$form->submit($request);
if ($form->isValid()) {
$em = $this->getDoctrine()->getManager();
$em->persist($photo);
$em->flush();
return $this->redirectView(
$this->generateUrl(
'api_get_photo',
array('id' => $photo->getId())
),
Codes::HTTP_CREATED
);
}
return array(
'form' => $form,
);
}
/**
* Put action
* @var Request $request
* @var integer $id Id of the entity
* @return View|array
*/
public function putAction($id) {
$entity = $this->getEntity($id);
$form = $this->createForm(new PhotoType(), $entity);
$form->bind($this->getRequest());
if ($form->isValid()) {
$em = $this->getDoctrine()->getManager();
$em->persist($entity);
$em->flush();
return $this->view(null, Codes::HTTP_NO_CONTENT);
}
return array(
'form' => $form,
);
}
}
My query on this url : http://exif.dev/app_dev.php/api/photos.json
filesize=1024&hash=12345678&useremail=t&datePicture=2013%2F10%2F16%2012%3A00&camera=1
and the response :
{"code":400,"message":"Validation Failed","errors":{"children":{"useremail":[],"hash":[],"filesize":[],"datePicture":{"errors":["This value is not valid."],"children":{"date":{"children":{"year":[],"month":[],"day":[]}},"time":{"children":{"hour":[],"minute":[]}}}},"camera":[]}}}
The problem is in the validation I think, but without being able to define the format of datetime neither in form nor in entity, I have no idea what kind of value is waited.
Is it a bug, or just a dumb thing ?
Best regards,
Pierre
Hello.
I managed to do it by replacing in my form the type from datetime to text and by applying a transformer on it.
But I don't think it's the best option.
My APIdoc does not include the format expected now.
I can't reuse my form in a view.
Anyone played with form and DateTime ?
best regards,
Pierre
@pmithrandir I got the same problem...
@ousmaneNdiaye @pmithrandir Me too, same problem
A default datetime field expects the following structure:
"datePicture": {
"date": {
"year": 2014,
"month": 11,
"day": 5
},
"time": {
"hour": 23,
"minute": 11
}
}
thanks :+1:
@ousmaneNdiaye Can you please explain it to me? I don't understand what you mean. Thanks
There seems to be a bug in the format configuration option on the datetime-type. The specified format is simply ignored.
To work around this bug and get this working, you should do one of two things.
You can make your API-client send the DateTime value in the structure provided above by @JohannesKlauss.
If you don't wish to use this format on the client-side, you can specify a custom format in the formBuilder configuration. Change:
->add('datePicture', 'datetime', array(
'description' => 'The date when the picture was taken',
'format' => 'yyyy/MM/dd HH:mm',
))
into
->add('datePicture', 'datetime', array(
'description' => 'The date when the picture was taken',
'widget' => 'single_text',
'date-format' => 'yyyy/MM/dd',
))
This got it working for me. Note that specifying the time-format is not necessary because you are using the default format for this.
Thank @bveldhuis. You're absolute right : 'widget' => 'single_text' is important.
For DATE field
following format expected
birthDate: {
"year": 2014,
"month": 11,
"day": 5
}
The format provided by @JohannesKlauss works nice thanks ! However I am facing an issue because I nedd to send my datetime including seconds.
Obviously I tried :
"datePicture": {
"date": {
"year": 2014,
"month": 11,
"day": 5
},
"time": {
"hour": 23,
"minute": 11,
"second": 34
}
}
But the validation failed :
[...]
-messageTemplate: "This form should not contain extra fields."
-parameters: [â–¼
"{{ extra_fields }}" => ""second""`
[...]
]
Is this possible to send seconds ?
Most helpful comment
A default datetime field expects the following structure: