managment of many-to-many tags with Doctrine2 - sql

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);
}

Related

Api-Platform ODM IRI reference gets empty objects

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.

Symfony 4 + JMS Serializer - Internal Server Error

I have a problem with serialization data.
I have two entities, which are connection relation.
These are my entities:
Task.php
use Doctrine\ORM\Mapping as ORM;
use JMS\Serializer\Annotation as JMS;
/**
* #ORM\Entity
*/
class Task
{
/**
*
* #var integer
*
* #ORM\Id
* #ORM\Column(type="integer")
* #ORM\GeneratedValue(strategy="IDENTITY")
*
*/
private $id;
/**
* #JMS\MaxDepth(1)
* #ORM\ManyToOne(targetEntity="App\Entity\User", inversedBy="task")
*
*/
private $user;
User.php
use FOS\UserBundle\Model\User as BaseUser;
use Doctrine\ORM\Mapping as ORM;
/**
* #ORM\Entity
* #ORM\Table(name="app_user")
*/
class User extends BaseUser
{
/**
* #ORM\Id
* #ORM\Column(type="integer")
* #ORM\GeneratedValue(strategy="AUTO")
*/
protected $id;
/**
* #ORM\OneToMany(targetEntity="App\Entity\Task", mappedBy="user")
*/
private $task;
public function __construct()
{
I would like to create API, and I serialization my data with JMS. I get this error:
Internal Server Error
The server encountered an internal error or misconfiguration and was unable to complete your request.
This is my controller:
public function getAllAction(): JsonResponse
{
$tasks = $this->taskService->getAll();
$serializer = SerializerBuilder::create()->build();
$data = $serializer->serialize($tasks, 'json',
SerializationContext::create()->enableMaxDepthChecks());
return new JsonResponse($data, 200, [], true);
}
Function getAll in my controller return data with table Task.
Please, help me with my problem. :)
Thanks, Friends.
I did not use JMS Serializer but i can suggest different solution like symfony serializer component.
public function getAllAction(SerializerInterface $serializer): JsonResponse
{
$data = $serializer->serialize($this->taskService->getAll(), 'json');
return new JsonResponse($data, 200, [], true);
}
Optional you can set serialization group to avoid circular reference error.
For each entity atribute that you want to serialize, set:
use Symfony\Component\Serializer\Annotation\Groups;
/**
* #ORM\Column(name="title", type="string", nullable=false, length=150)
* #Assert\Type("string")
* #Assert\NotBlank()
* #Groups({"group_name"})
*/
private $title;
/**
* #ORM\Column(name="description", type="string", nullable=true, length=255)
* #Assert\Type("string")
*/
private $description;
And then you serialize only this field with specific group:
$serializer->serialize($this->taskService->getAll(), 'json', ['groups' => ['group_name']])
Doc: https://symfony.com/doc/4.1/serializer.html#using-serialization-groups-annotations
PS. Action suffix isnt required now
https://symfony.com/doc/current/best_practices/controllers.html#controller-action-naming

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.

symfony create query builder

I'm newbie in symfony and I would like some advices for creating a query builder statement. Is pretty simple, the idea is to get the data from a third entity.
I have the entities:
/**
* Entity
* #ORM\Entity(repositoryClass="LoungepassBundle\Entity\LoungepassRepository")
* #ORM\Table(name="loungepass_loungepass")
*
*/
class Loungepass{
/**
* #ORM\Column(type="integer")
* #ORM\Id
* #ORM\GeneratedValue(strategy="AUTO")
*/
private $id;
// ...
/**
* #ORM\ManyToOne(targetEntity="\AppBundle\Entity\Agency", inversedBy="loungepasses")
* #ORM\JoinColumn(name="agency_id", referencedColumnName="iata8", nullable=false)
*/
private $agency;
//...
}
Agency Entity
class Agency {
/**
* #ORM\Id
* #ORM\Column(type="string", length=8, name="iata8")
*/
protected $id;
//...
/**
* #ORM\OneToMany(targetEntity="LoungepassBundle\Entity\Loungepass", mappedBy="agency")
*/
protected $loungepasses;
/**
* #var Market
* #ORM\ManyToOne(targetEntity="AppBundle\Entity\Market")
* #ORM\JoinColumns({
* #ORM\JoinColumn(name="sales_country", referencedColumnName="id")
* })
*/
private $market;
//...
}
I would like to do a query, like this:
public function queryBySlugInContext($slug, $user) {
$query = $this->createQueryBuilder("l")
->where('l.slug = :slug')
->setParameter('slug', $slug);
if(count($user->getAgencies()) > 0){
$query->andWhere(':agencyIds MEMBER OF l.agencies')
->setParameter('agencyIds',$user->getAgencies());
}
Able to access the information from the market attribute located in the agency entity.So basically, retrieve information from the Market Entity.
any suggestions?
Thank you in advance
I modified a bit your code but the mind is here
When you have to access to retrieve data from other tables just keep in mind to use differents mysql join accessible with doctrine
public function queryBySlugInContext($slug, $user) {
$query = $this->createQueryBuilder("l")
->where('l.slug = :slug')
->setParameter('slug', $slug);
if(0 !== count($user->getAgencies())) {
$query = $query->innerJoin('l.agency', 'a')
->andWhere('a.id IN(:userAgenciesIds)')
->setParameter('userAgenciesIds', array_map(function(Agency $agency) {
return $agency->getId();
}, $user->getAgencies());
}
You should take a time to read some content on internet like for example the doctrine query builder documentation

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 ...