Api-Platform ODM IRI reference gets empty objects - mongodb-query

I want to use Product and Price classes with one-to-many references. This is my Product class.
/**
* #ApiResource
*
* #Document
*/
class Product
{
/**
* #ODM\Id(strategy="INCREMENT", type="integer")
*/
private $id;
/**
* #ODM\Field(type="string")
* #Assert\NotBlank
*/
public $name;
/**
* #ODM\ReferenceMany(targetDocument=Price::class, mappedBy="product", cascade={"all"}, storeAs="id")
*/
public $prices ;
public function __construct()
{
$this->prices = new ArrayCollection();
}
//getter and setter of id...
}
This is Price class
/**
* #ApiResource
*
* #ODM\Document
*/
class Price
{
/**
* #ODM\Id(strategy="INCREMENT", type="integer")
*/
private $id;
/**
* #ODM\Field(type="float")
* #Assert\NotBlank
* #Assert\Range(min=0, minMessage="The price must be superior to 0.")
* #Assert\Type(type="float")
*/
public $price;
/**
* #Assert\Type(type="integer")
*/
private $discount;
/**
* #ODM\ReferenceOne(targetDocument=Product::class, inversedBy="prices", storeAs="id")
*/
public $product;
I can put and get Price with id=1 but when I want to put Product in swagger ui I use this reference.
{
"name": "productno1",
"prices": [
"/api/prices/1/"
]
}
It gives 201. I can check the db it is stored. The name of the product is productno1 but price section is empty. Can anyone help about what is wrong?

I changed mappedby tag of prices variable in Product class with inversedby, then problem solved.

Related

ApiPlatform: How to update instead of create a child entity that is not a #ApiResource nor a #ApiSubresource

I have a ThirdPartyEntity from a third party bundle that, using a ThirdPartyEntityTrait, I link to MyEntity in my project.
Now, as the ThirdPartyEntity is not set a ApiResource nor as an ApiSubresource and as I don't have any serializaton group set on MyEntity, when I get MyEntity from ApiPlatform, it returns me something like this:
{
"#id":"/api/my_entities/17",
"#type":"MyEntity",
"id":17,
"third_party_entity": {
"id":22,
"a_property":"some value"
}
}
BUT IF I PUT a changed value for a_property with this body:
{
"#id":"/api/my_entities/17",
"#type":"MyEntity",
"id":17,
"third_party_entity": {
"id":22,
"a_property":"some NEW value to update"
}
}
I get a new third_party_entity to be created and get this response:
{
"#id":"/api/my_entities/17",
"#type":"MyEntity",
"id":17,
"third_party_entity": {
"id":23,
"a_property":"some NEW value to update"
}
}
SO, HOW CAN I UPDATE third_party_entity instead of creating it each time?
HERE THERE ARE THE INVOLVED CLASSES AND TRAITS
/**
* #ORM\Table(name="app_my_entities")
* #ORM\Entity()
* #ApiResource()
*/
class MyEntity
{
// !!!!!!!!!!!!!!!!!!
// This is the trait I use to link MyEntity
// with the entity from the third-party bundle
// !!!!!!!!!!!!!!!!!!
use ThirdPartyEntityTrait;
/**
* #var int
*
* #ORM\Column(name="id", type="integer")
* #ORM\Id
* #ORM\GeneratedValue(strategy="IDENTITY")
*/
private $id;
...
}
And this is the ThirdPartyEntityTrait:
trait ThirdPartyEntityTrait
{
/**
* #ORM\OneToOne(targetEntity="Namespace\To\Bundle\Entity\ThirdPartyEntity", cascade={"all"})
* #ORM\JoinColumn(name="thirdPartyEntity", referencedColumnName="id")
*/
private $thirdPartyEntity;
/**
* #param thirdPartyEntity $thirdPartyEntity
*
* #return ThirdPartyEntity
*/
public function setThirdPartyEntity(thirdPartyEntity $thirdPartyEntity): ThirdPartyEntity
{
$this->thirdPartyEntity = $thirdPartyEntity;
/** #var ThirdPartyEntity $this */
return $this;
}
/**
* #return thirdPartyEntity
*/
public function getThirdPartyEntity(): ?thirdPartyEntity
{
return $this->thirdPartyEntity;
}
/**
* #return thirdPartyEntity
*/
public function removeThirdPartyEntity(): ?thirdPartyEntity
{
$thirdPartyEntity = $this->getThirdPartyEntity();
$this->thirdPartyEntity = null;
return $thirdPartyEntity;
}
}
As you can see, nothing more a property to save the relation and some accessors methods.
This is, instead, the linked Entity:
/**
* #ORM\Entity()
* #ORM\Table(name="third_party_entities")
*/
class ThirdPartyEntity
{
/**
* #var int
*
* #ORM\Column(name="id", type="integer")
* #ORM\Id
* #ORM\GeneratedValue(strategy="IDENTITY")
*/
private $id;
/**
* #ORM\Column(name="aProperty", type="string", nullable=true)
*/
private $aProperty;
public function getId()
{
return $this->id;
}
public function getAProperty()
{
return $this->aProperty;
}
public function setAProperty($aProperty)
{
$this->aProperty = $aProperty;
return $this;
}
}
This question is cross-posted also on GitHub.
The solution was pretty simple: use another config method!
Practically, it is possible to mix configuration types and so, it is possible to use the annotations along with the yaml configuration.
Given this, it is sufficient to create a new config file in config/api_platform/third_party_entity.yaml.
In it put the configuration required to map the entity from the third party bundle:
resources:
App\Entity\MyEntity:
properties:
remote:
subresource:
resourceClass: 'Third\Party\Bundle\TheBundle\Entity\ThirdPartyEntity'
Third\Party\Bundle\TheBundle\Entity\ThirdPartyEntity:
This way it is possible to configure as subresource the entity from the third party bundle to which we don't have access with annotations.

Sylius - Extend Product fixtures from CoreBundle

I've extended the Product model from CoreBundle (v1.0.0-alpha) and I want to change the behavior of the fixtures based on this new model. Keeping in mind that CoreBundle\Fixture\Factory\ProductExampleFactory is final, I am looking for a way to extend the fixtures so I don't have to rewrite the whole class and the whole "default" suite.
AppBundle\Entity\Product
<?php
namespace AppBundle\Entity;
use Doctrine\Common\Collections\ArrayCollection;
use Doctrine\ORM\Mapping as ORM;
use Sylius\Component\Core\Model\Product as BaseProduct;
/**
* Product
*
* #author leogout
*
* #ORM\Table(name="sylius_product")
* #ORM\Entity
*/
class Product extends BaseProduct
{
/**
* #var ArrayCollection
*
* #ORM\OneToMany(targetEntity="Package", mappedBy="component")
*/
protected $packages;
/**
* #var boolean
*
* #ORM\Column(type="boolean")
*/
protected $exposed;
/**
* #var ArrayCollection
*
* #ORM\OneToMany(targetEntity="Customization", mappedBy="product")
* #ORM\JoinColumn(name="customization_id", referencedColumnName="id")
*/
protected $customizations;
/**
* Product constructor.
*/
public function __construct()
{
parent::__construct();
$this->packages = new ArrayCollection();
$this->customizations = new ArrayCollection();
}
/**
* Get id
*
* #return integer
*/
public function getId()
{
return $this->id;
}
/**
* Set exposed
*
* #param boolean $exposed
* #return Product
*/
public function setExposed($exposed)
{
$this->exposed = $exposed;
return $this;
}
/**
* Get exposed
*
* #return boolean
*/
public function getExposed()
{
return $this->exposed;
}
/**
* Add packages
*
* #param Package $packages
* #return Product
*/
public function addPackage(Package $packages)
{
$this->packages[] = $packages;
return $this;
}
/**
* Remove packages
*
* #param Package $packages
*/
public function removePackage(Package $packages)
{
$this->packages->removeElement($packages);
}
/**
* Get packages
*
* #return ArrayCollection
*/
public function getPackages()
{
return $this->packages;
}
/**
* Add customizations
*
* #param Customization $customizations
* #return Product
*/
public function addCustomization(Customization $customizations)
{
$this->customizations[] = $customizations;
return $this;
}
/**
* Remove customizations
*
* #param Customization $customizations
*/
public function removeCustomization(Customization $customizations)
{
$this->customizations->removeElement($customizations);
}
/**
* Get customizations
*
* #return ArrayCollection
*/
public function getCustomizations()
{
return $this->customizations;
}
}
Thanks in advance !
EDIT :
After some digging, I saw that I needed to code a whole new fixture with a new ProductFixture, a new ProductExampleFactory and a new MyCustomProductFixture on top of that with their own config files which is not ideal... Do you have a better solution ?

A choice list based on database values in sonata

is it possible to add a choice list in configureformfields with choices values mapped from the database instead of configuring it manually like this :
->add('testfield', 'choice', array('choices' => array(
'1' => 'choice 1',
'2' => 'choice 2',)))
if the entity is correctly mapped then you can just use:
->add('testfield')
and Sonata admin will do the job.
Let's say you have a Product class linked to a Category class:
<?php
namespace AppBundle\Entity;
use Doctrine\ORM\Mapping as ORM;
/**
* Product
*
* #ORM\Table(name="product")
*
*/
class Product
{
/**
* #var int
*
* #ORM\Column(name="id", type="integer")
* #ORM\Id
* #ORM\GeneratedValue(strategy="AUTO")
*/
private $id;
/**
* #ORM\ManyToOne(targetEntity="Category", inversedBy="products")
*/
protected $category;
/**
* Get id
*
* #return int
*/
public function getId()
{
return $this->id;
}
/**
* Set category
*
* #param Category $category
*
* #return Product
*/
public function setCategory(Category $category = null)
{
$this->category = $category;
return $this;
}
/**
* Get category
*
* #return Category
*/
public function getCategory()
{
return $this->category;
}
}
Simply using:
->add('category')
will provide a select form field with all the categories.
You can also use SONATA_TYPE_MODEL if you want something more advanced:
<?php
// src/AppBundle/Admin/ProductAdmin.php
class ProductAdmin extends AbstractAdmin
{
protected function configureFormFields(FormMapper $formMapper)
{
$imageFieldOptions = array(); // see available options below
$formMapper
->add('category', 'sonata_type_model', $imageFieldOptions)
;
}
}
The documentation is on this page: Form Types
Hope this helps!

Doctrine 2 : OneToMany relation, Entity is not loaded

Here is my problem : I have the 3 entities Item, User and Link above (these classes also have the usual getters and setters).
class User {
//...
/*
* #ORM\OneToMany(targetEntity="Link", mappedBy="user", cascade={"persist", "remove"})
*
*/
protected $links;
//...
}
class Item {
//...
/*
* #ORM\OneToMany(targetEntity="Link", mappedBy="item", cascade={"persist", "remove"})
*
*/
protected $links;
//...
}
class Link {
/**
* #var datetime $time
*
* #ORM\Column(name="time", type="datetime")
*/
private $time;
/**
*
* #ORM\Id
* #ORM\ManyToOne(targetEntity="Item", inversedBy="links")
* #ORM\JoinColumn(name="item_id", referencedColumnName="id")
*/
private $item;
/**
*
* #ORM\Id
* #ORM\ManyToOne(targetEntity="User", inversedBy="links")
* #ORM\JoinColumn(name="user_id", referencedColumnName="id")
*/
private $user;
//...
}
I did not use a ManyToMany relationship because of the $time property in the Link class.
When I create a Link, I do it this way :
$link = getExistingLink($item, $user);
if (!$link) {
$link = new Link();
$link->setItem($item);
$link->setUser($user);
}
$link->setTime(new \DateTime());
$em = $this->getEntityManager();
$em->persist($link);
$em->flush();
The data is written in the database, however when I call $user->getLinks(), it returns NULL. I event tried to do this :
$user->addLink($link);
$em->persist($user);
$em->flush();
But the link won't be loaded the next time the $user will be loaded.
Any idea why the Link entities are not loaded ?
OK Problem Solved.
The annotations starts with /* instead of /** in both User and Item classes.
Just a silly mistake ...

managment of many-to-many tags with Doctrine2

I am interested how tagging works. My idea so far:
I have three database tables Bookmarks id|title|uri|…, Tags id|title|… and bookmarks_tags (mxm, 3NF). My first test will be only a single-user system, so i have not to deal with Tags owned by specific users.
Storing a bookmark: uri (String) + tags (String, eg. Lorem Ipsum, Hello should result in two Tags: Lorem Ipsum and Hello).
Problem: Where and how should i create the missing Tags and loading the known ones?
Creating tags in the model is possible (see Bookmark::setTags() below). Loading and linking in the Model seems not possible, because the ORM is not available inside the class (or is there a static ressource for fetching the ORM? would this be recommended?).
I could load existing tags and create the tags inside Controller, but i assume tagging should be model's work.
I am using Symfony2 with Doctrine2.
Bookmark Class/Table
* #ORM\Table()
* #ORM\Entity(repositoryClass="X\BookmarksBundle\Entity\BookmarkRepository")
*/
class Bookmark
{
/**
* #var integer $id
*
* #ORM\Column(name="id", type="integer")
* #ORM\Id
* #ORM\GeneratedValue(strategy="AUTO")
*/
private $id;
/**
* #var string $title
*
* #ORM\Column(name="title", type="string", length=255)
*/
private $title;
/**
* #var string $uri
*
* #ORM\Column(name="uri", type="string", length=255)
*/
private $uri;
/**
* #var datetime $created_at
*
* #ORM\Column(name="created_at", type="datetime")
*/
private $created_at;
/**
* #var datetime $deleted_at
*
* #ORM\Column(name="deleted_at", type="datetime")
*/
private $deleted_at;
/** #ORM\ManyToMany(targetEntity="Tag", cascade={"persist", "remove"}) */
private $tags;
public function __construct()
{
$this->tags = new ArrayCollection();
}
public function getTags () {
if ($this->tags->isEmpty()) {
return "NO TAGS";
}
// TODO load tags from db
return "TODO: TAGS FOUND";
}
public function setTags ($tags) {
// TODO create and load/link existing tags
$tag = new Tag();
$tag->setTitle("test tag");
$this->tags->add($tag);
}
/* setters and getters for other private variables here */
Tag Class/Table
* #ORM\Table()
* #ORM\Entity(repositoryClass="X\BookmarksBundle\Entity\TagRepository")
*/
class Tag
{
/**
* #var integer $id
*
* #ORM\Column(name="id", type="integer")
* #ORM\Id
* #ORM\GeneratedValue(strategy="AUTO")
*/
private $id;
/**
* #var string $title
*
* #ORM\Column(name="title", type="string", length=64)
*/
private $title;
/**
* #var datetime $created_at
*
* #ORM\Column(name="created_at", type="datetime")
*/
private $created_at;
/**
* #var datetime $deleted_at
*
* #ORM\Column(name="deleted_at", type="datetime", nullable=true)
*/
private $deleted_at;
public function __construct () {
$this->created_at = new \DateTime('now');
}
/* setters and getters for other private variables here */
When fetching entities from your database, Doctrine2 don't give you the POPO Entity but a "Proxy". This proxy has the ability to load missing elements from the database. Thus, you don't have to implement the logic of retrieving the missing data from the database.
Btw, you can also create this method:
public function addTag(Tag $tag)
{
$this->tags->add($tag);
}