ApiPlatform entity relation iri and schema - api-platform.com

I cannot figure out how to reach this goal:
I want to have the object representation in a GET request with the related entity as json object too, while i want to send just the iri in a POST request of the parent object
/**
* #ORM\Entity(repositoryClass=CustomerRepository::class)
* #ApiResource
*/
class Customer
{
/**
* #var int|null
*
* #ORM\Id
* #ORM\GeneratedValue
* #ORM\Column(type="integer")
* #ApiProperty(
* identifier=false,
* description="ID univoco del cliente generato da un'operazione di POST"
* )
*/
private $id;
/**
* #var string
*
* #ORM\Column(name="codice_cliente", type="string", length=20, unique=true)
* #ApiProperty(
* identifier=true,
* description="Codice identificativo del cliente.<br>Si tratta di un codice univoco che identifica il cliente all'interno dell'azienda di riferimento"
* )
*/
private $codiceCliente;
/**
* #var string|null
*
* #ORM\Column(name="ragione_sociale", type="string", length=50, nullable=true)
* #ApiProperty(description="Denominazione ufficiale del cliente")
*/
private $ragioneSociale;
/**
* #var string|null
*
* #ORM\Column(name="nome", type="string", length=50, nullable=true)
* #ApiProperty(description="Nome anagrafico del cliente")
*/
private $nome;
/**
* #var string|null
*
* #ORM\Column(name="cognome", type="string", length=50, nullable=true)
* #ApiProperty(description="Cognome anagrafico del cliente")
*/
private $cognome;
/**
* #var string|null
*
* #ORM\Column(name="codice_fiscale", type="string", length=16, nullable=true)
* #ApiProperty(description="Codice fiscale del cliente.<br>Può corrispondere a `partitaIva` in caso di azienda.")
*/
private $codiceFiscale;
/**
* #var string|null
*
* #ORM\Column(name="partita_iva", type="string", length=11, nullable=true)
* #ApiProperty(description="Partita Iva del cliente in caso di azienda.<br>È un valore numerico di 11 cifre.")
*/
private $partitaIva;
/**
* #var CustomerCategory
*
* #ORM\ManyToOne(targetEntity=CustomerCategory::class)
* #ORM\JoinColumn(name="categoria_id", referencedColumnName="codice", nullable=false)
*/
private $categoria;
/**
* #return int|null
*/
public function getId(): ?int
{
return $this->id;
}
/**
* #return string
*/
public function getCodiceCliente(): ?string
{
return $this->codiceCliente;
}
/**
* #param string $codiceCliente
*
* #return Customer
*/
public function setCodiceCliente(string $codiceCliente): Customer
{
$this->codiceCliente = $codiceCliente;
return $this;
}
/**
* #return string|null
*/
public function getRagioneSociale(): ?string
{
return $this->ragioneSociale;
}
/**
* #param string|null $ragioneSociale
*
* #return Customer
*/
public function setRagioneSociale(?string $ragioneSociale): Customer
{
$this->ragioneSociale = $ragioneSociale;
return $this;
}
/**
* #return string|null
*/
public function getNome(): ?string
{
return $this->nome;
}
/**
* #param string|null $nome
*
* #return Customer
*/
public function setNome(?string $nome): Customer
{
$this->nome = $nome;
return $this;
}
/**
* #return string|null
*/
public function getCognome(): ?string
{
return $this->cognome;
}
/**
* #param string|null $cognome
*
* #return Customer
*/
public function setCognome(?string $cognome): Customer
{
$this->cognome = $cognome;
return $this;
}
/**
* #return string|null
*/
public function getCodiceFiscale(): ?string
{
return $this->codiceFiscale;
}
/**
* #param string|null $codiceFiscale
*
* #return Customer
*/
public function setCodiceFiscale(?string $codiceFiscale): Customer
{
$this->codiceFiscale = $codiceFiscale;
return $this;
}
/**
* #return string|null
*/
public function getPartitaIva(): ?string
{
return $this->partitaIva;
}
/**
* #param string|null $partitaIva
*
* #return Customer
*/
public function setPartitaIva(?string $partitaIva): Customer
{
$this->partitaIva = $partitaIva;
return $this;
}
public function getCategoria(): ?CustomerCategory
{
return $this->categoria;
}
public function setCategoria(?CustomerCategory $categoria): self
{
$this->categoria = $categoria;
return $this;
}
}
/**
* #ORM\Entity(repositoryClass=CustomerCategoryRepository::class)
* #ApiResource()
*/
class CustomerCategory
{
/**
* #var string
*
* #ORM\Id
* #ORM\Column(type="string", length=10)
* #ApiProperty(identifier=true,
* description="Codice identificativo della categoria")
*/
private $codice;
/**
* #var string
*
* #ORM\Column(type="string", length=100, nullable=true)
* #ApiProperty(description="Descrizione del codice di identificazione")
*/
private $descrizione;
public function getCodice(): ?string
{
return $this->codice;
}
public function setCodice(string $codice): self
{
$this->codice = $codice;
return $this;
}
public function getDescrizione(): ?string
{
return $this->descrizione;
}
public function setDescrizione(?string $descrizione): self
{
$this->descrizione = $descrizione;
return $this;
}
}
So when i GET the Customer i want the $categoria property to be
{
codice: "10"
descrizione: "Test"
}
While when i POST/PUT/PATCH a Customer i want to refer to the CustomerCategory as the IRI of the ID as i don't have cascade persists in this case
{
"codiceCliente": "string",
"ragioneSociale": "string",
"nome": "string",
"cognome": "string",
"codiceFiscale": "string",
"partitaIva": "string",
"categoria": "/api/customer_categories/10"
}
I want also to have the correct representation on the OPEN API docs

You can get such results using a Serialization Group, except for the #id and #type being added to both Customer and to the $categoria property (API Platform automatically adds them).
First add the following to both your Entities:
use Symfony\Component\Serializer\Annotation\Groups;
To configure a Serialization Group only for the get operations of Customer:
/**
* #ORM\Entity(repositoryClass=CustomerRepository::class)
* #ApiResource(
* itemOperations={
* "get"={
* "normalization_context"={"groups"={"customer:get"}}
* },
* "patch",
* "put",
* "delete"
* },
* collectionOperations={
* "get"={
* "normalization_context"={"groups"={"customer:get"}}
* },
* "post"
* }
* )
*/
class Customer
//...
And then add #Groups({"customer:get"}) to the doc blocks of all props of Customer except $id, including $categoria. I only give one example here:
/**
* #var string|null
*
* #ORM\Column(name="ragione_sociale", type="string", length=50, nullable=true)
* #ApiProperty(description="Denominazione ufficiale del cliente")
* #Groups({"customer:get"})
*/
private $ragioneSociale;
Also add #Groups({"customer:get"}) to the doc blocks of $codice and $descrizione of CustomerCategory.
This should do the trick and leave the post, put and patch operations unchanged (expecting and returning just the iri of $categoria). It should automatically give a correct representation on the OPEN API docs.
Tip: The readme of branch chapter4-api of my tutorial also contains instructions for adding Serialization Groups two Entities related like yours, and the resulting code is in branch chapter5-api.

Related

orphanRemoval=true removes all related entities

I have a strange problem with a relation between two entities:
One Joboffer can have many jobofferLocations, while Many jobOfferlocations only have one joboffer:
class Joboffer
{
/**
* #ORM\Id
* #ORM\GeneratedValue(strategy="AUTO")
* #ORM\Column(name="id_joboffer", type="integer", length=255, nullable=false)
* #Groups({"api_read", "api_write"})
*/
protected $id;
/**
*
* #ORM\OneToMany(targetEntity="AppBundle\Entity\Joboffer\JobofferLocation", mappedBy="joboffer", orphanRemoval=true, cascade={"persist"})
* #Groups({"api_read", "api_write"})
* #var ArrayCollection
*/
protected $jobofferLocations;
....
/**
* #param JobofferLocation $jobofferLocation
*/
public function addJobofferLocation(JobofferLocation $jobofferLocation)
{
if ($this->jobofferLocations->contains($jobofferLocation)) {
return;
}
$this->jobofferLocations->add($jobofferLocation);
$jobofferLocation->setJoboffer($this);
}
The jobofferlocationclass:
class JobofferLocation
{
/**
* #ORM\Id
* #ORM\GeneratedValue(strategy="AUTO")
* #ORM\Column(name="id_joboffer_location", type="integer", length=255, nullable=false)
* #Groups({"api_read"})
*/
protected $id;
/**
* #return mixed
*/
public function getJoboffer()
{
return $this->joboffer;
}
/**
* #param mixed $joboffer
*/
public function setJoboffer($joboffer)
{
$this->joboffer = $joboffer;
}
On Updates I have this problem:
When I use "orphanRemoval=true,", it removes all jobofferlocation entities, when I don't use it, but "cascade=remove", it doesn't remove the ones that aren't in the relations any more.
So, is there a way to update all relations? (Remove the ones that aren't needed any more, adding new ones and updating existing ones.)
I found an answer:
first of all, the methods addJObofferLocation and removeJobofferLocation are needed and orphanRemoval must be set to true.
The trick seems to be in adding the right (not double) locations.
class Joboffer
{
...
/**
* #ORM\OneToMany(targetEntity="AppBundle\Entity\Joboffer\JobofferLocation", mappedBy="joboffer", orphanRemoval=true,cascade={"persist"})
* #Groups({"api_read", "api_write"})
* #var ArrayCollection
*/
protected $jobofferLocations;
/**
* #param JobofferLocation $jobofferLocation
*/
public function addJobofferLocation(JobofferLocation $jobofferLocation)
{
if ($this->jobofferLocations->contains($jobofferLocation)) {
return;
}
/** #var JobofferLocation $location */
foreach ($this->jobofferLocations as $location){
//check if this location exists
// it seems we need this, because of the API plattform bundle
if ($location->getIdLocation() == $jobofferLocation->getIdLocation()){
// if it exists, just copy the new jobofferlocation settings
return;
}
}
$jobofferLocation->setJoboffer($this);
$this->jobofferLocations->add($jobofferLocation);
}
public function removeJobOfferLocation(JobofferLocation $jobofferLocation)
{
if (!$this->jobofferLocations->contains($jobofferLocation)) {
return;
}
$this->jobofferLocations->removeElement($jobofferLocation);
$jobofferLocation->removeJobOffer();
}

Symfony3 Edit Entity : Error Missing value for primary key

I try to create a tree structure for a catalog of products.
A catalog can have multiple levels and levels can contain multiple products.
I manage to save my structure in database but when I want to edit it, I have this error :
Error : Missing value for primary key catalogCode on AppBundle\Entity\CatalogLevel
at OutOfBoundsException ::missingPrimaryKeyValue ('AppBundle\Entity\CatalogLevel', 'catalogCode')
in vendor\doctrine\common\lib\Doctrine\Common\Proxy\AbstractProxyFactory.php at line 125
when I do this in my CatalogController :
$form = $this->createForm(CatalogTreeType::class, $catalog);
But, just before that line, I verify if I get my levels correctly and it's looking like that's the case :
// Create an ArrayCollection of the current levels
$originalLevels = new ArrayCollection();
foreach ($catalog->getLevels() as $level) {
var_dump($level->getCatalogCode());
$originalLevels->add($level);
}
// returns
AppBundle\Controller\CatalogController.php:337:string 'TT-FTEST' (length=8)
AppBundle\Controller\CatalogController.php:337:string 'TT-FTEST' (length=8)
CatalogLevel entity has a composite key : levelId + catalogCode.
Considering the primary key catalogCode isn't empty, I don't understand this error...
Catalog Entity
/**
* #ORM\Table(name="catalogue")
* #ORM\Entity(repositoryClass="AppBundle\Entity\CatalogRepository")
* #UniqueEntity(fields="code", message="Catalog code already exists")
*/
class Catalog
{
/**
* #ORM\Column(name="Catalogue_Code", type="string", length=15)
* #ORM\Id
* #Assert\NotBlank()
* #Assert\Length(max=15, maxMessage="The code is too long ({{ limit }} characters max)")
*/
private $code;
/**
* #ORM\OneToMany(targetEntity="CatalogLevel", mappedBy="catalog", cascade={"persist", "remove"})
* #Assert\Valid
*/
private $levels;
/**
* Constructor
*/
public function __construct()
{
$this->levels = new ArrayCollection();
}
/**
* Get levels
*
* #return ArrayCollection
*/
public function getLevels()
{
return $this->levels;
}
/**
* Add level
*
* #param \AppBundle\Entity\CatalogLevel $level
*
* #return Catalog
*/
public function addLevel(\AppBundle\Entity\CatalogLevel $level)
{
$level->setCatalogCode($this->getCode());
$level->setCatalog($this);
if (!$this->getLevels()->contains($level)) {
$this->levels->add($level);
}
return $this;
}
/**
* Remove level
*
* #param \AppBundle\Entity\CatalogLevel $level
*/
public function removeLevel(\AppBundle\Entity\CatalogLevel $level)
{
$this->levels->removeElement($level);
}
}
CatalogLevel Entity
/**
* #ORM\Table(name="catalogue_niveau")
* #ORM\Entity(repositoryClass="AppBundle\Entity\CatalogLevelRepository")
*/
class CatalogLevel
{
/**
* #ORM\Column(name="Niveau_ID", type="string", length=15)
* #ORM\Id
*/
private $id;
/**
* #ORM\Column(name="Catalogue_Code", type="string", length=15)
* #ORM\Id
*/
private $catalogCode;
/**
* #ORM\ManyToOne(targetEntity="Catalog", inversedBy="levels")
* #ORM\JoinColumn(name="Catalogue_Code", referencedColumnName="Catalogue_Code")
*/
private $catalog;
/**
* Set id
*
* #param string $id
*
* #return CatalogLevel
*/
public function setId($id)
{
$this->id = $id;
return $this;
}
/**
* Get id
*
* #return string
*/
public function getId()
{
return $this->id;
}
/**
* Set catalogCode
*
* #param string $catalogCode
*
* #return CatalogLevel
*/
public function setCatalogCode($catalogCode)
{
$this->catalogCode = $catalogCode;
return $this;
}
/**
* Get catalogCode
*
* #return string
*/
public function getCatalogCode()
{
return $this->catalogCode;
}
}
I would like to remind you that this error occured on the editAction (it works very well on the addAction) when I display the pre-filled form.
Thanks for your help !
I think that is because you haven't autoincrement id in entity CatalogLevel. Try add to id this code:
#ORM\GeneratedValue(strategy="AUTO")
You have some problems in the way you've created you Entities. You should use auto generate strategy. Also the "#ORM\Id" annotation is the unique identifier.
Also, your "JoinColumn" is incorrect. You need to refer back to the "Catalog" Entity, and it's id (identifier). There is no need for 2 "#ORM\Id" entries in the class CatalogLevel.
So make these changes:
/**
* #ORM\Table(name="catalog")
* #ORM\Entity(repositoryClass="AppBundle\Entity\CatalogRepository")
*/
class Catalog
{
/**
* #ORM\Column(name="cat_id", type="integer")
* #ORM\Id
* #ORM\GeneratedValue(strategy="AUTO")
*/
private $cat_id;
/**
* #ORM\OneToMany(targetEntity="CatalogLevel", mappedBy="catalog", cascade={"persist", "remove"})
* #Assert\Valid
*/
private $levels;
...
/**
* #ORM\Table(name="catalog_level")
* #ORM\Entity(repositoryClass="AppBundle\Entity\CatalogLevelRepository")
*/
class CatalogLevel
{
/**
* #ORM\Column(name="cat_level_id", type="integer")
* #ORM\Id
* #ORM\GeneratedValue(strategy="AUTO")
*/
private $cat_level_id;
/**
* #ORM\ManyToOne(targetEntity="Catalog", inversedBy="levels")
* #ORM\JoinColumn(name="local_cat_id", referencedColumnName="cat_id")
*/
private $catalog;
...

Vich upload bundle remove image

Hi guys i have implemented vichuploadbundle to upload images but i want in the admin the user to remove the image.
<?php
namespace George\PageBundle\Entity;
use Doctrine\ORM\Mapping as ORM;
use George\UserBundle\Entity\User;
use Gedmo\Mapping\Annotation as Gedmo;
use Symfony\Component\HttpFoundation\File\File;
use Vich\UploaderBundle\Mapping\Annotation as Vich;
/**
* Page
*
* #ORM\Table()
* #ORM\Entity(repositoryClass="George\PageBundle\Entity\PageRepository")
* #Vich\Uploadable
*/
class Page
{
/**
* #var integer
*
* #ORM\Column(name="id", type="integer")
* #ORM\Id
* #ORM\GeneratedValue(strategy="AUTO")
*/
private $id;
/**
* #var string
*
* #ORM\Column(name="title", type="text")
*/
private $title;
/**
* #var string
*
* #ORM\Column(name="content", type="text")
*/
private $content;
/**
* #var boolean
*
* #ORM\Column(name="visible", type="boolean")
*/
private $visible;
/**
* #var \DateTime
*
* #ORM\Column(name="created", type="datetime")
*/
private $created;
/**
* #var \DateTime
*
* #ORM\Column(name="modefied", type="datetime")
*/
private $modefied;
/**
* #var string
*
* #ORM\Column(name="description", type="string", length=255)
*/
private $description;
/**
* #var string
*
* #ORM\Column(name="keywords", type="string", length=255)
*/
private $keywords;
/**
* #ORM\ManyToOne(targetEntity="George\UserBundle\Entity\User", inversedBy="pages")
* #ORM\JoinColumn(onDelete="CASCADE")
*/
private $owner;
//#ORM\Column(length=128, unique=true)
/**
* #Gedmo\Slug(fields={"title"})
* #ORM\Column(length=128, unique=true)
*/
private $slug;
/**
* Get id
*
* #return integer
*/
public function getId()
{
return $this->id;
}
/**
* Set title
*
* #param string $title
*
* #return Page
*/
public function setTitle($title)
{
$this->title = $title;
return $this;
}
/**
* Get title
*
* #return string
*/
public function getTitle()
{
return $this->title;
}
/**
* Set content
*
* #param string $content
*
* #return Page
*/
public function setContent($content)
{
$this->content = $content;
return $this;
}
/**
* Get content
*
* #return string
*/
public function getContent()
{
return $this->content;
}
/**
* Set visible
*
* #param boolean $visible
*
* #return Page
*/
public function setVisible($visible)
{
$this->visible = $visible;
return $this;
}
/**
* Get visible
*
* #return boolean
*/
public function getVisible()
{
return $this->visible;
}
/**
* Set created
*
* #param \DateTime $created
*
* #return Page
*/
public function setCreated($created)
{
$this->created = $created;
return $this;
}
/**
* Get created
*
* #return \DateTime
*/
public function getCreated()
{
return $this->created;
}
/**
* Set modefied
*
* #param \DateTime $modefied
*
* #return Page
*/
public function setModefied($modefied)
{
$this->modefied = $modefied;
return $this;
}
/**
* Get modefied
*
* #return \DateTime
*/
public function getModefied()
{
return $this->modefied;
}
/**
* Set description
*
* #param string $description
*
* #return Page
*/
public function setDescription($description)
{
$this->description = $description;
return $this;
}
/**
* Get description
*
* #return string
*/
public function getDescription()
{
return $this->description;
}
/**
* Set keywords
*
* #param string $keywords
*
* #return Page
*/
public function setKeywords($keywords)
{
$this->keywords = $keywords;
return $this;
}
/**
* Get keywords
*
* #return string
*/
public function getKeywords()
{
return $this->keywords;
}
/**
* #return mixed
*/
public function getOwner()
{
return $this->owner;
}
/**
* #param mixed $owner
*/
public function setOwner(User $owner)
{
$this->owner = $owner;
}
public function getSlug()
{
return $this->slug;
}
// ..... other fields
/**
* NOTE: This is not a mapped field of entity metadata, just a simple property.
*
* #Vich\UploadableField(mapping="product_image", fileNameProperty="imageName")
*
* #var File
*/
private $imageFile;
/**
* #ORM\Column(type="string", length=255)
*
* #var string
*/
private $imageName;
/**
* #ORM\Column(type="datetime")
*
* #var \DateTime
*/
private $updatedAt;
/**
* If manually uploading a file (i.e. not using Symfony Form) ensure an instance
* of 'UploadedFile' is injected into this setter to trigger the update. If this
* bundle's configuration parameter 'inject_on_load' is set to 'true' this setter
* must be able to accept an instance of 'File' as the bundle will inject one here
* during Doctrine hydration.
*
* #param File|\Symfony\Component\HttpFoundation\File\UploadedFile $image
*/
public function setImageFile(File $image = null)
{
$this->imageFile = $image;
if ($image) {
// It is required that at least one field changes if you are using doctrine
// otherwise the event listeners won't be called and the file is lost
$this->updatedAt = new \DateTime('now');
}
}
/**
* #return File
*/
public function getImageFile()
{
return $this->imageFile;
}
/**
* #param string $imageName
*/
public function setImageName($imageName)
{
$this->imageName = $imageName;
}
/**
* #return string
*/
public function getImageName()
{
return $this->imageName;
}
}
Can you advice me what is the best solution to this. I think it will be best if i have a method remove image and in the method try to use bundles build in function for removal the image (which i don't find anywhere) and update it in the database. In the front end this can be don trough ajax or on submit... Is there a build in function for the removal of file in the vichuploadbundle?
I have found how to remove the image. The bundle provides a custom form type in order to ease the upload, deletion and download of images.
When you build the form you specifies the widget:
public function buildForm(FormBuilderInterface $builder, array $options)
{
// ...
$builder->add('image', 'vich_image', array(
'required' => false,
'allow_delete' => true, // not mandatory, default is true
'download_link' => true, // not mandatory, default is true
));
}
This add a twig template for deleting the img :)

Create a createQueryBuilder with UniqueConstraint on 2 keys

i'm trying to retrive data from several tables to make a json, but i'm stuck for the table having UniqueConstraint on 2 keys.
Here's my QueryBuilder sofar :
$qb = $this->_em->createQueryBuilder()
->select('partial s.{id, activity}, partial a.{id, title}, partial p.{id, evaluationType}')
->from('Innova\PathBundle\Entity\Step', 's')
->leftJoin('s.activity', 'a') //join on Activity entity
->leftJoin('a.parameters', 'p') // join on ActivityParameters entity
->andWhere('s.path = 2')
;
but i want to also join on Evaluation entity, which is :
/**
* #ORM\Table(
* name="claro_activity_evaluation",
* uniqueConstraints={
* #ORM\UniqueConstraint(
* name="user_activity_unique_evaluation",
* columns={"user_id", "activity_parameters_id"}
* )
* }
* )
*/
class Evaluation
{
/**
* #ORM\ManyToOne(targetEntity="Claroline\CoreBundle\Entity\User")
* #ORM\JoinColumn(onDelete="CASCADE", nullable=false)
*/
protected $user;
/**
* #ORM\ManyToOne(targetEntity="Claroline\CoreBundle\Entity\Activity\ActivityParameters")
* #ORM\JoinColumn(name="activity_parameters_id", onDelete="CASCADE", nullable=false)
*/
protected $activityParameters;
/**
* #ORM\Column(name="attempts_count", type="integer", nullable=true)
*/
protected $attemptsCount;
}
the User entity :
/**
* #ORM\Table(name="claro_user")
* #ORM\Entity(repositoryClass="Claroline\CoreBundle\Repository\UserRepository")
*/
class User
{
/**
* #var integer
*
* #ORM\Id
* #ORM\Column(type="integer")
* #ORM\GeneratedValue(strategy="AUTO")
*/
protected $id;
/**
* #var string
*
* #ORM\Column(name="first_name", length=50)
* #Assert\NotBlank()
*/
protected $firstName;
}
The ActivityParameters entity
/**
* #ORM\Entity
* #ORM\Table(name="claro_activity_parameters")
*/
class ActivityParameters
{
/**
* #var integer
*
* #ORM\Id
* #ORM\Column(type="integer")
* #ORM\GeneratedValue(strategy="AUTO")
*/
protected $id;
/**
* #var \Claroline\CoreBundle\Entity\Resource\Activity
*
* #ORM\OneToOne(
* targetEntity="Claroline\CoreBundle\Entity\Resource\Activity",
* mappedBy="parameters"
* )
* #ORM\JoinColumn(name="activity_id", onDelete="CASCADE", nullable=true)
*/
protected $activity;
/**
* #var string
*
* #ORM\Column(name="evaluation_type", nullable=true)
*/
protected $evaluationType;
/**
* #return string
*/
public function getEvaluationType()
{
return $this->evaluationType;
}
}
the Activity entity
/**
* #ORM\Table(name="claro_activity")
*/
class Activity
{
/**
* #var string
* #ORM\Column(length=255, nullable=true)
*/
protected $title;
/**
* #ORM\OneToOne(
* targetEntity="Claroline\CoreBundle\Entity\Activity\ActivityParameters",
* inversedBy="activity",
* cascade={"persist"}
* )
* #ORM\JoinColumn(name="parameters_id", onDelete="cascade", nullable=true)
*/
protected $parameters;
/**
* #return string
*/
public function getTitle()
{
return $this->title;
}
}
I have no clue how to modify this querybuilder to retrieve also hte data from Evaluation entity. I want something like :
$qb = $this->_em->createQueryBuilder()
->select('partial s.{id, activity}, partial a.{id, title}, partial p.{id, evaluationType}, e')
->from('Innova\PathBundle\Entity\Step', 's')
->leftJoin('s.activity', 'a') //join on Activity entity
->leftJoin('a.parameters', 'p') // join on ActivityParameters entity
->andWhere('s.path = 2')
->leftJoin('?i dont know what?', 'e') // join on Evaluation entity
->andWhere('e.user = 3') //data for a specific user
;
Thank you for any help
I "seem" to have found a nearly working solution, reading this topic : i had to make a subquery :
$qb->select('e, partial p.{id, evaluationType}, partial a.{id, title}')
->from('Claroline\CoreBundle\Entity\Activity\Evaluation', 'e')
->leftJoin('e.activityParameters', 'p')
->leftJoin('p.activity', 'a')
->where(
$qb->expr()->in( //needs a subquery
'a.id',
$subq->select('a2.id')
->from('Innova\PathBundle\Entity\Step', 's')
->leftJoin(
's.activity',
'a2',
\Doctrine\ORM\Query\Expr\Join::WITH,
$subq->expr()->eq('s.path', '?1') //can't do a andWhere('s.path = :path'))
)
->getDQL() //needs a dql, not a querybuilder for the main query
)
)
->andWhere($qb->expr()->eq('e.user', '?2')) //can't do a andWhere('e.user = :user'))
->setParameter(1, $pid)
->setParameter(2, $uid)
;
I just would also like to retrieve the Step entity data (id, ...) in the query result. I can't add 's' to the main select : i have Error: 's' is used outside the scope of its declaration.

Symfony2 FOSRestBundle timestamp validation fails

I'm trying to send a post request including two timestamps to my REST api.
The problem is that the timestamps are marked as invalid. "This value is not valid."
What am I doing wrong?
This is the request:
POST http://localhost:8000/app_test.php/api/projects/1/tasks/1/timetrackings
Accept: application/json
Content-Type: application/json
{"timeStart":1390757625,"timeEnd":1390757625,"projectMember":1}
The Controller looks as follows:
class MemberController extends BaseApiController implements ClassResourceInterface
{
public function postAction($projectId, $taskId, Request $request)
{
/** #var EntityManager $em */
$em = $this->getDoctrine()->getManager();
$this->findProject($em, $projectId);
$task = $this->findTask($em, $projectId, $taskId);
$request->request->add(array(
'task' => $taskId,
));
$form = $this->createForm(new TimeTrackType(), new TimeTrack());
$form->submit($request->request->all());
if ($form->isValid())
{
/** #var TimeTrack $tracking */
$tracking = $form->getData();
$task->addTimeTrack($tracking);
$em->flush();
return $this->redirectView(
$this->generateUrl('api_get_project_task_timetracking', array(
'projectId' => $projectId,
'taskId' => $taskId,
'trackingId' => $tracking->getId(),
)),
Codes::HTTP_CREATED
);
}
return View::create($form, Codes::HTTP_BAD_REQUEST);
}
}
The TimeTrackType class:
namespace PMTool\ApiBundle\Form;
use Symfony\Component\Form\AbstractType;
use Symfony\Component\Form\FormBuilderInterface;
use Symfony\Component\OptionsResolver\OptionsResolverInterface;
class TimeTrackType extends AbstractType
{
/**
* #param FormBuilderInterface $builder
* #param array $options
*/
public function buildForm(FormBuilderInterface $builder, array $options)
{
$builder
->add('timeStart', 'datetime', array(
'input' => 'timestamp',
))
->add('timeEnd', 'datetime', array(
'input' => 'timestamp',
))
->add('projectMember')
->add('task')
;
}
/**
* #param OptionsResolverInterface $resolver
*/
public function setDefaultOptions(OptionsResolverInterface $resolver)
{
$resolver->setDefaults(array(
'data_class' => 'PMTool\ApiBundle\Entity\TimeTrack',
'csrf_protection' => false,
));
}
/**
* #return string
*/
public function getName()
{
return 'timetrack';
}
}
The entity class:
namespace PMTool\ApiBundle\Entity;
use Doctrine\ORM\Mapping as ORM;
use \DateTime;
/**
* TimeTrack
*
* #ORM\Table()
* #ORM\Entity(repositoryClass="PMTool\ApiBundle\Entity\TimeTrackRepository")
*/
class TimeTrack
{
/**
* #var integer
*
* #ORM\Column(name="id", type="integer")
* #ORM\Id
* #ORM\GeneratedValue(strategy="AUTO")
*/
private $id;
/**
* #var DateTime
*
* #ORM\Column(name="timeStart", type="datetime")
*/
private $timeStart;
/**
* #var DateTime
*
* #ORM\Column(name="timeEnd", type="datetime")
*/
private $timeEnd;
/**
* #var ProjectMember
*
* #ORM\ManyToOne(targetEntity="ProjectMember")
*/
private $projectMember;
/**
* #var Task
*
* #ORM\ManyToOne(targetEntity="Task", inversedBy="timeTracks")
* #ORM\JoinColumn(name="taskId", referencedColumnName="id")
*/
private $task;
/**
* Get id
*
* #return integer
*/
public function getId()
{
return $this->id;
}
/**
* Set timeStart
*
* #param DateTime $timeStart
* #return TimeTrack
*/
public function setTimeStart($timeStart)
{
$this->timeStart = $timeStart;
return $this;
}
/**
* Get timeStart
*
* #return DateTime
*/
public function getTimeStart()
{
return $this->timeStart;
}
/**
* Set timeEnd
*
* #param DateTime $timeEnd
* #return TimeTrack
*/
public function setTimeEnd($timeEnd)
{
$this->timeEnd = $timeEnd;
return $this;
}
/**
* Get timeEnd
*
* #return DateTime
*/
public function getTimeEnd()
{
return $this->timeEnd;
}
/**
* #return \PMTool\ApiBundle\Entity\Task
*/
public function getTask()
{
return $this->task;
}
/**
* #param \PMTool\ApiBundle\Entity\Task $task
* #return $this
*/
public function setTask($task)
{
$this->task = $task;
return $this;
}
/**
* #return \PMTool\ApiBundle\Entity\ProjectMember
*/
public function getProjectMember()
{
return $this->projectMember;
}
/**
* #param \PMTool\ApiBundle\Entity\ProjectMember $projectMember
* #return $this
*/
public function setProjectMember($projectMember)
{
$this->projectMember = $projectMember;
return $this;
}
}
You can use a transformer to achieve this. (See Symfony Transformers)
Here is an Example of my FormType:
use Symfony\Component\Form\Extension\Core\DataTransformer\DateTimeToTimestampTransformer;
...
$transformer = new DateTimeToTimestampTransformer();
$builder->add($builder->create('validFrom', 'text')
->addModelTransformer($transformer)
)
Be sure to use "'text'" as an input type, otherwise it didn't work for me.
the input option is just for the model side of your underlying data object. In your case it should be datetime.
Your problem is, that you want to transform the timestamp into view data that the symfony form datetime form type does recognise. And I dont know how to do that, unfortunately.
http://symfony.com/doc/current/reference/forms/types/datetime.html#input

Resources