Jms serializer #JMS\Inline() annotation overrides an actual id - php

I have product and product_detail tables in database.
Every product has a product detail.
class Product
{
/**
* #var ProductDetail
* #JMS\ReadOnly()
* #ORM\OneToOne(targetEntity="ApiBundle\Entity\ProductDetail", mappedBy="product", cascade={"persist"})
* #JMS\Inline()
*/
private $detail;
}
I use #JMS\Inline() annotation to show only detail information of product_detail table.
But when I serialize a product I get wrong id. It must be product id but id returns an product detail id. Is it bug or I am doing something wrong?

You should not expose the id from the detail when using inline.
Source: https://github.com/schmittjoh/JMSSerializerBundle/issues/460#issuecomment-113440743

Related

Fetch entity without related objects doctrine

I have the following classes:
class Category {
/**
* #ORM\OneToMany(targetEntity="Product", mappedBy="category")
*/
private $products;
...
}
class Product {
...
/**
* #ORM\ManyToOne(targetEntity="Category", inversedBy="products")
* #ORM\JoinColumn(name="category_id", referencedColumnName="id")
*/
private $category;
...
}
when i try to fetch one product from my db like this:
$query = $doctrineManager->createQuery(
"
SELECT p FROM AppBundle:Product p
WHERE p.id = :id
"
)->setParameter('id', $id);
$result = $query->getSingleResult();
i get not only my product, but also get category with all products (except the one i found). So, how can i fetch only model what i want without any related model?
They are just stubs, you don't actually fetch any related entity information unless you are using fetch=EAGER.
This answer explains it pretty well.
What is the difference between fetch="EAGER" and fetch="LAZY" in doctrine
In summary, you can't get rid of the associations, but they don't load the other entities until you call the data unless you specifically request otherwise. So don't worry about it.

Symfony2 Get entities of an entity in another entity

This question is about Symfony2 table relationships using ORM. I have three tables/entities that are related to each other. The relationship is very similar to Wordpress Posts, Categories and Categories relationship tables.
Table 1 contains posts.
Table 2 contains categories
Table 3 contains relationships between the categories and posts.
I want to be able to have the categories property in the posts table and a posts property in the categories table. So that when I call.
Categories->posts : I should get posts in that category.
Posts->categories : I should get the categories the post belongs to.
I want to have unique categories per table and I want all posts to point to a category without having to create a new entry for the category that already exists which is what ManyToOne or OneToMany is offering this is why the third table I think is necessary.
For example here is the relationships
class Category_relationship
{
/**
* #var integer
*
* #ORM\Column(name="object_id", type="bigint")
*
* #ORM\ManyToOne(targetEntity="Worksheet", inversedBy="category_relationships")
* #ORM\JoinColumn(name="worksheet_id", referencedColumnName="id", nullable=FALSE)
*/
private $objectId;
/**
* #var integer
*
* #ORM\Column(name="category_id", type="bigint")
*
* #ORM\ManyToOne(targetEntity="Category", inversedBy="categories")
* #ORM\JoinColumn(name="category_id", referencedColumnName="id", nullable=FALSE)
*/
private $categoryId;
}
Here is the Category class:
class Category
{
/**
* #ORM\OneToMany(targetEntity="Category_relationship", mappedBy="categoryId", cascade={"persist", "remove"}, orphanRemoval=TRUE)
*/
protected $posts;
}
Here is the Category class:
class Posts
{ /**
* #ORM\OneToMany(targetEntity="Category_relationship", mappedBy="objectId", cascade={"persist", "remove"}, orphanRemoval=TRUE)
*/
protected $categories;
}
I want to create a system where I can assign posts to a category but the category table can only contain 1 entry about the category. I also want to be able to use expressions link;
Post->categories
Category->posts
or
Post->AddCategory()
Category->AddPost()
Thanks for your help.
It seems that you want a simple many-to-many relationship.
Every post can have multiple categories, and every category have list of related posts. Many to many handles pivot table by itself.
So, in Post entity you have to declare relationship that way:
/**
* #ORM\ManyToMany(targetEntity="Category", inversedBy="posts")
* #ORM\JoinTable(name="PostsCategories",
* joinColumns={#ORM\JoinColumn(name="post_id", referencedColumnName="id")},
* inverseJoinColumns={#ORM\JoinColumn(name="category_id", referencedColumnName="id")}
* )
**/
protected $categories;
Remember about using Doctrine\ORM\Mapping with ORM alias (you don't have to import all subclasses separately):
use Doctrine\ORM\Mapping as ORM;
After that, you need to create a new ArrayCollection in class constructor:
public function __construct()
{
$this->categories = new ArrayCollection();
}
And add proper methods, like addCategory:
public function addCategory(Category $category)
{
$this->categories[] = $category;
return $this;
}
You can also add them automatically with:
php app/console doctrine:generate:entities BundleName:EntityName
Same thing in Category entity, but with a little different definiton:
/**
* #ORM\ManyToMany(targetEntity="Post", mappedBy="categories")
**/
protected $posts;
You can find all of these information in Doctrine docs

DQL for selecting a row in a x-to-x relationship

I have 3 different entities that are mapped to each other. To give a very simplified example.
ProductList:
class ProductList
{
/**
* #ORM\OneToMany(targetEntity="Product", mappedBy="productlist") */
protected $products;
}
Product:
class Product
{
/**
* #ORM\ManyToOne(targetEntity="Edition")
* #ORM\JoinColumn(name="edition_id", referencedColumnName="id")
*/
protected $edition;
/**
* #ORM\ManyToOne(targetEntity="ProductList")
* #ORM\JoinColumn(name="productlist_id", referencedColumnName="id")
*/
protected $productlist;
}
Edition
class Edition
{
protected $id;
}
How can I get all the productLists in my productListRepository that contain only 1 product with an specific edition id using DQL?
I know how to do it the 'lazy' way by retrieving all the items from the productList and check whether the related product entity contains an specific edition id but this seems highly ineffective if there is a large dataset to iterate through.
I know I have to use a join but I am stuck at the idea that I need to iterate through the products in productLists. ProductLists can contain more then one products but I only need the ones with only a single product and check wether they have an edition with the specific id.
Try this for extract all the productlist for a specified version that have only one edition:
SELECT pl
FROM ProductList pl
-- LEFT JOIN pl.products p
LEFT JOIN p.edition e
WHERE (sum (p1) from Product p1 where p1.productlist = pl.id) = 1
Hope this help

Implementing getters for joined doctrine 2 collection

I'm trying to create simple getter methods in original entity for specific item in doctrine collection of joined entity.
Main entity looks something like this:
class Product
{
/**
* #var integer
*
* #ORM\Column(name="id", type="integer")
* #ORM\Id
* #ORM\GeneratedValue(strategy="AUTO")
*/
private $id;
...
/**
* #ORM\OneToMany(targetEntity="File", mappedBy="product")
*/
private $files;
}
And joined entity:
class PrankFile
{
/**
* #var integer
*
* #ORM\Column(name="id", type="integer")
* #ORM\Id
* #ORM\GeneratedValue(strategy="AUTO")
*/
private $id;
/**
* #ORM\ManyToOne(targetEntity="Product", inversedBy="files")
* #ORM\JoinColumn(name="product_id", referencedColumnName="id", onDelete="CASCADE")
*/
protected $product;
/**
* #var string
*
* #ORM\Column(name="type", type="string", length=16)
*/
private $type;
...
My DQL in repository class is very simple:
return $this->getEntityManager()
->createQuery('SELECT p, f FROM AppProductBundle:Product p INNER JOIN p.files f ORDER BY p.created DESC')
->setMaxResults($limit)
->getResult();
In files entity type field tells me what kind of a file is it (image, sound, video, demo etc...)
Problem comes when I wish to print out a list of all products and display image next to product details, I would hate to loop through product files for each product displayed.
Is it possible to create some simple getter on product entity to fetch file of certain type?
Or maybe it would be better to create more complex DQL query for this, again how to do this?
I can't just fetch image record from files in DQL because I need all files for certain products.
Any help with this would be most welcome.
You can filter directly on collections using the filter api. If the collection is not loaded already Doctrine will apply your filter on a SQL level, giving you max performance. In case the collection is already eager loaded Doctrine will filter the ArrayCollection in memory.
use Doctrine\Common\Collections\Criteria;
class Product
{
public function getFilesByType($type)
{
$criteria = Criteria::create()
->where(Criteria::expr()->eq("type", $type))
return $this->files->matching($criteria);
}
}

Abstract Doctrine association mapping without discriminator (legacy DB)

I'm working with a legacy database (that means no schema changes!) and I need to create a associations between the the Doctrine entities involved. I'll describe the data structure first and then explain what I've tried.
The database has a user table with various other tables also storing user related info. Eg:
siteUser has:
contentId (PK)
firstName
lastName
username
password
...
and siteUser entities have metadata in this system which is along the lines of:
metadataId (PK)
title
description
keywords
createDate
publishDate
contentId
contentTable (discriminator)
...
Almost everything in the database can have Metadata by storing it's PK in the metadata.contentId field and the table name in the metadata.contentTable field. Note that metadata.contentId is not a foreign key, these must have been alien to the DBA as I'm yet to see a single one.
Users on the system can save information they find relevant to them so that they can come back to the system later and don't have to go hunting for the same information again.
This is done with content types called conLink, conVideo, conLeaflet stored as database entities (which have metadata).
For example a conVideo looks like this:
contentId (PK)
embedCode
The way users can store mark this information as being relevant to them is by the system storing it in a link table called userSavedContent:
userSavedContentId (PK)
userId
metadataId
Note that userSavedContent.userId and userSavedContent.metadataId are also not foreign key constraints.
THE APPROACH!
I need to get user's saved content. In SQL this is no problem!
SELECT
metadata.title,
conVideo.embedCode
FROM
userSavedContent
INNER JOIN
metadata ON userSavedContent.metadataId = metadata.metadataId
INNER JOIN
conVideo ON conVideo.contentId = metadata.contentId
WHERE userSavedContent.userId = 193745
AND metadata.contentTable = 'conVideo'
However doing this in Doctrine is more complicated because the value of metadata.contentTable could potentially be any of the conLink, conVideo, conLeaflet entities.
So my application is built using Symfony2 (and Doctrine) and I have models defined for all of the above entities.
In this Metadata is an abstract class with a discriminator on metadata.contentTable:
/**
*
* #ORM\Table(name="metadata")
* #ORM\Entity()
* #ORM\HasLifecycleCallbacks
* #ORM\InheritanceType("SINGLE_TABLE")
* #ORM\DiscriminatorColumn(name="contentTable", type="string")
* #ORM\DiscriminatorMap(
* {
* "conLink" = "MyApp\Bundle\DataApiBundle\Entity\Metadata\ConLinkMetadata",
* "conVideo" = "MyApp\Bundle\DataApiBundle\Entity\Metadata\ConVideoMetadata",
* "siteUser" = "MyApp\Bundle\DataApiBundle\Entity\Metadata\SiteUserMetadata"
* }
* )
*/
abstract class Metadata
The ConVideoMetadata class extends Metadata and adds a content property that associates the ConVideo entity to it:
/**
* #var ContentType $content
*
* #ORM\OneToOne(
* targetEntity="MyApp\Bundle\DataApiBundle\Entity\ContentType\ConVideo",
* inversedBy="metadata",
* cascade={"persist", "remove"}
* )
* #ORM\JoinColumn(name="contentId", referencedColumnName="contentId")
*/
protected $content;
Now the userSavedContent entity has metadata property to associated it to an item of metadata.
/**
* #var Metadata $metadata
*
* #ORM\ManyToOne(
* targetEntity="MyApp\Bundle\DataApiBundle\Entity\Metadata",
* inversedBy="userSavedContent"
* )
* #ORM\JoinColumn(name="id", referencedColumnName="metadataId")
*/
protected $metadata;
And finally the siteUser is related to userSavedContent by the following property on it's entity:
/**
* #ORM\OneToMany(
* targetEntity="MyApp\Bundle\DataApiBundle\Entity\UserSavedContent",
* mappedBy="siteUser",
* cascade={"persist", "remove"},
* orphanRemoval=true
* )
* #ORM\JoinColumn(name="contentId", referencedColumnName="userId")
*/
private $userSavedContentItems;
THE PROBLEM!
In my siteUserRepository class I now need to query for a siteUser and all it's saved content items:
$builder = $this->createQueryBuilder('s')
->select('s', 'm', 'usc', 'uscm', 'uscc')
->innerJoin('s.metadata', 'm')
->leftJoin('s.userSavedContentItems', 'usc')
->leftJoin('usc.metadata', 'uscm')
->leftJoin('uscm.content', 'uscc');
return $builder;
This doesn't work!
"[Semantical Error] Error: Class MyApp\Bundle\DataApiBundle\Entity\Metadata has no association named content"
This makes sense of course since MyApp\Bundle\DataApiBundle\Entity\Metadata doesn't have the content property, it's child MyApp\Bundle\DataApiBundle\Entity\Metadata\ConVideoMetadata is the one with that association. I thought Doctrine would have been able to work this out but apparently not.
So my question is:
Is this approach very wrong? And if not what can I do to make that association/query work?
The fix for this issue was to get Doctrine to eagerly fetch the concrete metadata->content entities. I could declare these explicitly but used Doctrine's MetadataFactory to get the Metadata entity's discriminator for the list of all possible content types.
$metadataFactory = $this->getEntityManager()->getMetadataFactory();
$metadataMetadata = $metadataFactory->getMetadataFor('MyApp\Bundle\DataApiBundle\Entity\Metadata');
foreach ($metadataMetadata->discriminatorMap as $contentEntity) {
$builder->getQuery()
->setFetchMode(
$contentEntity,
'content',
ClassMetadata::FETCH_EAGER
);
}

Categories