Correct Symfony-doctrine inheritance mapping - php

I recently started using Symfony2-Doctrine2. I'm not getting how to save data in inheritance mapping.
My requirements:
For learning exercise:
I'm making a library application for testing (Requirements might not be practical).
At high level, library may contain many different type of items like books, articles, manuals as example for now.
They have some common fields like name, publish year etc and some item specific details like book has IDBN, publisher; Manual have company, product.
Again to make problem little more complex, there is another 'item_content' table to have some description in different language.
To quickly visualize, I've following structure:
I achieved above structure as per doctrine docs for inheritance mapping & Bidirectional one to many relation
My Question: How to save data using Symfony2 (I've proper routing/actions running, just need code to write in controller or better in repository). While saving data (say for manual) I want to save data in Item, Manual and ItemContect table but getting confused due to discr field in database. I didn't find code for saving data in above structure. I don't need full code, just few hints will be sufficient. My Item class is as follow (Other classes have proper inverse as mentioned in doctrine docs):
/**
* Article
*
* #ORM\Table(name="item")
* #ORM\Entity(repositoryClass="Test\LibraryBundle\Entity\ItemRepository")
* #ORM\InheritanceType("JOINED")
* #ORM\DiscriminatorColumn(name="discr", type="string")
* #ORM\DiscriminatorMap({"book" = "Book", "manual" = "Manual", "article" = "Article"})
*/
class Item
{
//...
/**
* For joining with ItemContent
*
* #ORM\OneToMany(targetEntity="ItemContent", mappedBy="item")
**/
private $itemContents;
public function __construct()
{
$this->itemContents = new ArrayCollection();
}
//...
}

The discriminator field will be automatically filled by Doctrine
$em = $this->getDoctrine()->getManager();
$item = new Manual(); // discr field = "manual"
$itemContent = new ItemContent();
$item->addItemContent($itemContent);
$itemContent->setItem($item);
$em->persist($item);
$em->persist($itemContent);
$em->flush();
Is that the answer you're waiting ?

Related

How do I set up a Rails "has_one :through" association with Doctrine?

I come from a Rails background and am not really familiar with Doctrine. I am trying to set up a similar association to this one in Rails.
I have a UserRelation entity that contains a combination of user_id and company_id with a primary key. The same user can belong to multiple companies, and most data is stored with user_relation_id.
So, in this example I have a Template that has the following association set up which works fine:
/**
* The UserRelation entity who created the template.
*
* #ORM\ManyToOne(targetEntity="UserRelation")
* #ORM\JoinColumn(name="user_relation_id", referencedColumnName="id", nullable=false)
*/
protected $creator;
In this example I know I can just add a method to my template entity along the lines of:
public function getUser(): User
{
return $this->creator->getUser();
}
but I need it to be filterable so that I can get all Template entities by user_id or company_id in a repository or controller like this:
$company = $entityManager->getRepository('Company')->find($company_id);
$templatesForCompany = $entityManager->getRepository('Template')->findBy('company' => $company);
Is there any way to set up this relationship so I can query it like above, instead of having to resort to raw SQL?

Doctrine merging entity with unidirectional OneToMany not clearing database entries

First off, I use Doctrine v2.6.2 with Symfony v4.1.7.
I have an entity Product which (among others) has a unidirectional one-to-many relation with another entity AlternativeDuration. Following Doctrine's documentation, the mapping in my product class looks like this:
/**
* #ORM\ManyToMany(
* targetEntity="AlternativeDuration",
* cascade={"persist", "merge", "remove"},
* orphanRemoval=true
* )
* #ORM\JoinTable(name="ProductAlternativeDurations",
* joinColumns={#ORM\JoinColumn(name="product_id", referencedColumnName="id", onDelete="CASCADE", nullable=false)},
* inverseJoinColumns={#ORM\JoinColumn(name="alternative_duration_id", referencedColumnName="id", unique=true, onDelete="CASCADE", nullable=false)}
* )
*/
protected $alternativeDurations;
My application recently started using React, this means I now submit a JSON representation of my product (along with an array of alternative durations) which I need to deserialize into the Product entity in the back-end. I use the JMS serializer with default configuration for this.
Now the problem I'm having happens when editing an existing product, the product already has an alternative duration which I delete. The submitted JSON looks like this:
{
"id": 1, # the ID of the existing product here
"alternativeDurations": [] # empty array because the entry is removed
}
In the back-end I successfully deserialize the JSON string:
$serializedProduct = $this->serializer->deserialize($jsonString, Product::class, 'json');
I verified here that the $serializedProduct has no alternative durations. Then I follow with a merge + flush. I expect the merge to fetch the existing product and supplement it with the $serializedProduct.
$em->merge($serializedProduct); # $em being the EntityManager.
$em->flush();
Now I would expect the AlternativeDuration entry, along with the ProductAlternativeDurations join table entry being removed. The result, however, is that the entry in ProductAlternativeDurations is removed but the AlternativeDuration is still there.
I'm at a loss now, anyone can give some pointers on why the AlternativeDuration entry is not deleted?
EDIT 19-11-2018:
It seems this is a known bug in Doctrine: #2542
Also merge will be removed in Doctrine3 so I will probably need to rethink this approach in general.

TYPO3: Extend model and map to existing table

I need to create a custom FE user with some custom fields.
Also, it needs to be assignable through the frontend to different user groups.
You can find my first approach here. Didn't work out that well.
Second approach was to create another extension and follow the guide which is shown here.
First thing I did was to add \TYPO3\CMS\Extbase\Domain\Model\FrontendUser into the Extend existing model class-field for my CustomFEU-model.
Then I created another model which I named FEgroup and I mapped it to the table fe_groups. After that, I connected an n:m relation to the CustomFEU.
When I try to create a new CustomFEU with the new action, it returns a white empty page after submitting the form and no user is being added.
The only strange thing I found was that the /Classes/Domain/Repository/ folder is empty.
TYPO3 7.6.8
Although I didn't edit the files yet, here they are:
Model / Controller / Setup
Did anyone encounter similar problems?
First you need to create the repositories that handle the new user and usergroup models.
Second you try to save the user with $this->customFEURepository->add($newCustomFEU); and the variable customFEURepository does not exist. It would be the best to inject it, it has to be the repository that you should create first. You can inject it like that:
/**
* CustomFEUController
*/
class CustomFEUController extends \TYPO3\CMS\Extbase\Mvc\Controller\ActionController
{
/**
* #var \Vendor\Feregistration\Repository\CustomFEURepository
* #inject
*/
protected $customFEURepository;
// other code ...
}
Don't forget to clear the system cache after adding inject annotations, otherwise it wont work.
Last but not least i can't see the mapping to the database table for your model. You need to add it to your TypoScript (setup.txt)
config.tx_extbase.persistence.classes {
Vendor\Feregistration\Domain\Model\CustomFEU {
mapping {
recordType = 0
tableName = fe_users
}
}
Vendor\Feregistration\Domain\Model\FEGroups {
mapping {
recordType = 0
tableName = fe_groups
}
}
}

Doctrine get confused when generating plural method of a field that ends with a letter "s"

I have an rntity called Account, which can have many phone numbers (or Dnis, as I have named the related Entity ).
The definition for Account using yml is:
models\Account:
type: entity
table: account
oneToMany:
dnis:
targetEntity: models\Dnis
mappedBy: account
The problem is when I generate the entities classes with the following command:
doctrine orm:generate:entities
Since it is a OneToMany relation, the Account entity has a dnis collection, the problem is that the "add" method gets named as "addDni".
/**
* Add dni
*
* #param \application\models\Dnis $dni
*
* #return CreditAccount
*/
public function addDni(\application\models\Dnis $dni)
{
$this->dnis[] = $dni;
return $this;
}
/**
* Remove dni
*
* #param \application\models\Dnis $dni
*/
public function removeDni(\application\models\Dnis $dni)
{
$this->dnis->removeElement($dni);
}
I guess doctrine get confused because it thinks that the property "dnis" is a plural just because ends with a letter "s".
How can I let doctrine know that "dnis" is the actual name of the property? Or am I missing something here in the entity definition?
Thanks in advance.
You are defining a OneToMany association.
That means that one Account can have many Dnis.
This is why Dnis is considered as plural, because it represents the Many side of the OneToMany association, also it's normal that doctrine generates a addDni method that add a given Dnis to the collection of Dnis, same for the removeDni that remove a given Dnis and for the getDnis that fetches the whole collection of Dnis.
If you need that an account can have one Dnis, define it as OneToOne and keep it as plural. You'll have a getDnis() and setDnis().
If you doesn't like the name of your variables (i.e. Dnis $dni), just change it (i.e. Dnis $dnis).
EDIT
I found a quick way to avoid this unexpected behavior and I submitted a pull request.
I'll let you know in case of the PR is merged, for now you can use my fix.

Doctrine 2 - How to use objects retrieved from cache in relationships

I'm working in a project that use Doctrine 2 in Symfony 2 and I use MEMCACHE to store doctrine's results.
I have a problem with objects that are retrieved from MEMCACHE.
I found this post similar, but this approach not resolves my problem: Doctrine detaching, caching, and merging
This is the scenario
/**
* This is in entity ContestRegistry
* #var contest
*
* #ORM\ManyToOne(targetEntity="Contest", inversedBy="usersRegistered")
* #ORM\JoinColumn(name="contest_id", referencedColumnName="id", onDelete="CASCADE"))
*
*/
protected $contest;
and in other entity
/**
* #var usersRegistered
*
* #ORM\OneToMany(targetEntity="ContestRegistry", mappedBy="contest")
*
*/
protected $usersRegistered;
Now imagine that Contest is in cache and I want to save a ContestRegistry entry.
So I retrieve the object contest in cache as follows:
$contest = $cacheDriver->fetch($key);
$contest = $this->getEntityManager()->merge($contest);
return $contest;
And as last operation I do:
$contestRegistry = new ContestRegistry();
$contestRegistry->setContest($contest);
$this->entityManager->persist($contestRegistry);
$this->entityManager->flush();
My problem is that doctrine saves the new entity correctly, but also it makes an update on the entity Contest and it updates the column updated. The real problem is that it makes an update query for every entry, I just want to add a reference to the entity.
How I can make it possible?
Any help would be appreciated.
Why
When an entity is merged back into the EntityManager, it will be marked as dirty. This means that when a flush is performed, the entity will be updated in the database. This seems reasonable to me, because when you make an entity managed, you actually want the EntityManager to manage it ;)
In your case you only need the entity for an association with another entity, so you don't really need it to be managed. I therefor suggest a different approach.
Use a reference
So don't merge $contest back into the EntityManager, but grab a reference to it:
$contest = $cacheDriver->fetch($key);
$contestRef = $em->getReference('Contest', $contest->getId());
$contestRegistry = new ContestRegistry();
$contestRegistry->setContest($contestRef);
$em->persist($contestRegistry);
$em->flush();
That reference will be a Proxy (unless it's already managed), and won't be loaded from the db at all (not even when flushing the EntityManager).
Result Cache
In stead of using you own caching mechanisms, you could use Doctrine's result cache. It caches the query results in order to prevent a trip to the database, but (if I'm not mistaken) still hydrates those results. This prevents a lot of issues that you can get with caching entities themselves.
What you want to achieve is called partial update.
You should use something like this instead
/**
* Partially updates an entity
*
* #param Object $entity The entity to update
* #param Request $request
*/
protected function partialUpdate($entity, $request)
{
$parameters = $request->request->all();
$accessor = PropertyAccess::createPropertyAccessor();
foreach ($parameters as $key => $parameter) {
$accessor->setValue($entity, $key, $parameter);
}
}
Merge requires the whole entity to be 100% fullfilled with data.
I haven't checked the behavior with children (many to one, one to one, and so on) relations yet.
Partial update is usually used on PATCH (or PUT) on a Rest API.

Categories