How to know if a form field is translatable - symfony

Is there a shortcut to know if an entity field has the #Gedmo\Translatable property set, let say when rendering a form, or displaying entity values ?
For example, having this field :
/**
* #var string
*
* #ORM\Column(name="name", type="string", length=255)
* #Gedmo\Translatable
*/
private $name;
While displaying the entity, I'd like to know if a field is translatable, by doing something like this (pseudo-code idea of what it could be or look like in twig templates)
{% entity.title in entity.translatable.fields %}
Note : The real idea behind this is to automatically display a marker on translatable form field.
Translatable Behavior extension for Doctrine2

You can create a Twig extension:
class TranslatableTypeExtension extends AbstractTypeExtension
{
/**
* #var ObjectManager
*/
private $om;
/**
* #var TranslatableListener
*/
private $listener;
/**
* #param ObjectManager $om
*/
public function __construct(ObjectManager $om, TranslatableListener $listener )
{
$this->om = $om;
$this->listener = $listener;
}
private function isTranslatableField($object, $name)
{
$config = $this->listener->getConfiguration($this->om, get_class($object));
if (isset($config['fields']) && in_array($name, $config['fields']) )
return true;
return false;
}
public function buildView(FormView $view, FormInterface $form, array $options)
{
if ( $form->getParent() == null )
return;
if ( is_object($form->getParent()->getData())) {
if ( $this->isTranslatableField($form->getParent()->getData(), $form->getName()) )
$view->vars['field_translatable'] = true;
}
}
/**
* Returns the name of the type being extended.
*
* #return string The name of the type being extended
*/
public function getExtendedType()
{
return 'field';
}
}
Load this extension as follows:
my_extension.translatable_type_extension:
class: Acme\DemoBundle\Form\Extension\TranslatableTypeExtension
arguments: ["#doctrine.orm.entity_manager", "#gedmo.listener.translatable"]
tags:
- { name: form.type_extension, alias: field }
In your twig templates you could use something like this:
{% if field_translatable is defined and field_translatable %} Translatable field {% endif %}

In your entity repository, assuming you extend the TranslationRepository, you could create a custom function that retrieves fields that have translations. You could create a custom method in your repository along the lines of
use use Doctrine\ORM\Query;
use Gedmo\Translatable\Entity\Repository\TranslationRepository;
class MyEntityRepository extends TranslationRepository
{
public function getTranslatableFieldsByClass($className)
{
$translationMeta = $this->getClassMetadata();
$qb = $this->_em->createQueryBuilder();
$qb->select('trans.field')
->from($translationMeta->rootEntityName, 'trans')
->where('trans.objectClass = :entityClass')
->groupBy('trans.field');
$q = $qb->getQuery();
$data = $q->execute(
array('entityClass' => $className),
Query::HYDRATE_ARRAY
);
return (array) $data;
}
}
Then load the results into your template, and use a similar 'in' clause like you have mentioned above.
$translatableFields = $this->getDoctrine()->getRepository('MyBundle:MyTranslatableEntity')->getTranslatableFieldsByClass(get_class($myTranslatableEntity));

I have the same requirement altough accepted answer won't work for me since it relies on already translated fields. Since I want it even if db is empty I came up with solution based on Gedmo ExtensionMetadataFactory. (solution is written on SF 2.8 and PHP 7.1)
[...]
use Doctrine\ORM\EntityManager;
use Gedmo\Translatable\TranslatableListener;
/**
* Helper Class TranslatableFieldsHelper - allow to get array of translatable fields for given entity class.
*
* #package [...]
* #author [...]
*/
class TranslatableFieldsHelper
{
/**
* #var TranslatableListener
*/
protected $listener;
/**
* #var EntityManager
*/
protected $em;
/**
* TranslatableFieldsHelper constructor.
* #param TranslatableListener $listener
* #param EntityManager $em
*/
public function __construct(TranslatableListener $listener, EntityManager $em)
{
$this->listener = $listener;
$this->em = $em;
}
/**
* Get translatable fields list of given class
*
* #param string $class
* #return array
*/
public function getTranslatableFields(string $class): array
{
$config = $this->listener->getConfiguration($this->em, $class);
return $config && isset($config['fields']) && is_array($config['fields']) ? $config['fields'] : [];
}
}
After implementing this class simply register it as service:
_alias_:
class: _class_
arguments: ['#stof_doctrine_extensions.listener.translatable', '#doctrine.orm.default_entity_manager']
And use it:
$this->container->get(__alias__)->getTranslatableFields(__your_entity_class__);
EDIT:

Related

best practice of Symfony controllers

I'm looking for best practice in symfony controller logic. My current code
have one controller:
<?php
namespace App\Controller;
use App\Entity\Categories;
use Symfony\Bundle\FrameworkBundle\Controller\AbstractController;
use Symfony\Component\Routing\Annotation\Route;
class Controller extends AbstractController
{
/**
* #Route("/", name="main_index")
*/
public function index()
{
$categories = $this->getDoctrine()
->getRepository(Categories::class)
->findAll();
return $this->render('index.html.twig', [
'categories' => $categories,
]);
}
}
categories Entity:
namespace App\Entity;
use Doctrine\ORM\Mapping as ORM;
/**
* Categories
*
* #ORM\Table(name="categories", indexes={#ORM\Index(name="title", columns={"title"}), #ORM\Index(name="url", columns={"url"})})
* #ORM\Entity
*/
class Categories
{
/**
* #var bool
*
* #ORM\Column(name="id", type="integer", nullable=false)
* #ORM\Id
* #ORM\GeneratedValue(strategy="IDENTITY")
*/
private $id;
/**
* #var string|null
*
* #ORM\Column(name="title", type="string", length=255, nullable=true, options={"default"="NULL"})
*/
private $title = 'NULL';
/**
* #var string|null
*
* #ORM\Column(name="url", type="string", length=255, nullable=true, options={"default"="NULL"})
*/
private $url = 'NULL';
/**
* #var string|null
*
* #ORM\Column(name="header_text", type="text", length=65535, nullable=true, options={"default"="NULL"})
*/
private $headerText = 'NULL';
/**
* #var string|null
*
* #ORM\Column(name="body_text", type="text", length=65535, nullable=true, options={"default"="NULL"})
*/
private $bodyText = 'NULL';
/**
* #var string|null
*
* #ORM\Column(name="footer_text", type="text", length=65535, nullable=true, options={"default"="NULL"})
*/
private $footerText = 'NULL';
/**
* #var \DateTime|null
*
* #ORM\Column(name="created_at", type="datetime", nullable=true, options={"default"="NULL"})
*/
private $createdAt = 'NULL';
/**
* #var \DateTime|null
*
* #ORM\Column(name="updated_at", type="datetime", nullable=true, options={"default"="NULL"})
*/
private $updatedAt = 'NULL';
public function getId(): ?bool
{
return $this->id;
}
public function getTitle(): ?string
{
return $this->title;
}
public function setTitle(?string $title): self
{
$this->title = $title;
return $this;
}
public function getUrl(): ?string
{
return $this->url;
}
public function setUrl(?string $url): self
{
$this->url = $url;
return $this;
}
public function getHeaderText(): ?string
{
return $this->headerText;
}
public function setHeaderText(?string $headerText): self
{
$this->headerText = $headerText;
return $this;
}
public function getBodyText(): ?string
{
return $this->bodyText;
}
public function setBodyText(?string $bodyText): self
{
$this->bodyText = $bodyText;
return $this;
}
public function getFooterText(): ?string
{
return $this->footerText;
}
public function setFooterText(?string $footerText): self
{
$this->footerText = $footerText;
return $this;
}
public function getCreatedAt(): ?\DateTimeInterface
{
return $this->createdAt;
}
public function setCreatedAt(?\DateTimeInterface $createdAt): self
{
$this->createdAt = $createdAt;
return $this;
}
public function getUpdatedAt(): ?\DateTimeInterface
{
return $this->updatedAt;
}
public function setUpdatedAt(?\DateTimeInterface $updatedAt): self
{
$this->updatedAt = $updatedAt;
return $this;
}
}
I want to repeat "$categories" variable to all my other pages.
So it means - extra query on every page, but I don't want to repeat code:
$categories = $this->getDoctrine()
->getRepository(Categories::class)
->findAll();
everywhere, because all the pages must to show categories all the time. Also by symfony logic all the #route should have own function. So how I suppose to make the route logic and not to repeat the categories request code? by making another outside class with this code and just reuse it in all the other route methods?
EDIT:
My solution:
templates/index.html.twig file (one place):
{{ render(controller('App\\Repository\\CategoriesListRepository::getCategories')) }}
templates/categories.html.twig (one file):
{% for category in categories %}
<li>
{{ category.getName() }}
</li>
{% endfor %}
Repository/CategoriesListRepository.php :
<?php declare(strict_types=1);
namespace App\Repository;
use App\Entity\Categories;
use Doctrine\ORM\EntityManagerInterface;
use Symfony\Bundle\FrameworkBundle\Controller\AbstractController;
use Symfony\Component\HttpFoundation\Response;
/**
* Class CategoriesListRepository
* #package App\Repository
*/
final class CategoriesListRepository extends AbstractController
{
/**
* #var \Doctrine\Common\Persistence\ObjectRepository
*/
private $repository;
/**
* CategoriesListRepository constructor.
* #param EntityManagerInterface $entityManager
*/
public function __construct(EntityManagerInterface $entityManager)
{
$this->repository = $entityManager->getRepository(Categories::class);
}
public function getCategories(): Response
{
return $this->render('categories.html.twig', ['categories' => $this->repository->findAll()]);
}
}
would be nice to hear some comments/recommendations
There are several possible solutions. The most obvious one would be to subclass AbstractController, add a protected method getCategories() to it and call that method from all the controller methods where the categories are needed.
But still: this is very repetitive. Therefore, I’d probably add a custom Twig function, so that in any template where it is needed, you just write something like {{ displayCategories() }} or {% for category in getCategories() %} ... {% endfor %}, and the Twig extension handles all that for you. (See Twig docs for more info. This is not difficult. You just have to inject Doctrine as a dependeny to the extension’s constructor and overwrite getFunctions() method from Twig_Extension).
You can call a controller from a template.
Create the controller that renders only list of categories:
public function listAllAction()
{
$categories = $this->getDoctrine()
->getRepository(Categories::class)
->findAll();
return $this->render('categories.html.twig', [
'categories' => $categories,
]);
}
and then call it with render function in a twig file:
<div id="list-of-categories">
{{ render(controller(
'App:Category:listAll',
)) }}
</div>
Take a look at How to Inject Variables into all Templates (i.e. global Variables).
You would create the following config:
# config/packages/twig.yaml
twig:
# ...
globals:
# the value is the service's id
category_service: '#App\Service\CategoryService'
And in your CategoryService you would get all categories within a getCategories() method. Later on you can call in your twig template category_service.getCategories().
You can have $categories as a container's variable by using $container->set() method and then get the variable with $container->get('categories').
If you only want to avoid repeating the code (have the a code copy on different places), the previous answers may fit. But if you wan't to not execute your repository calls over and over again with the goal to optimize database performance, in my opinion its no good practice to move code to places which are not responsible for (e.g. twig global variables).
Even if the result may be always the same over different pages, it could also be that different results return yet. But thats something you should not be worry about.
Instead i would go for using the doctrine result cache for my queries. The cache should decide whether the requested data is the same as in the previous request or not and give me the data.
As long as you are using doctrine and no DBAL you will be fine with this.

Symfony Doctrine Hydrator - With custom hydrator I lose my ManyToOne relation

I would like to extends ObjectHydrator to benefit of the hydration of my ManyToOne relation and add extra field to the Entity.
Here is my hydrator: StatisticsDataHydrator.php
namespace AppBundle\Hydrator\ProjectAssignment;
use AppBundle\Entity\ProjectAssignment;
use Doctrine\ORM\Internal\Hydration\ObjectHydrator;
class StatisticsDataHydrator extends ObjectHydrator
{
/**
* {#inheritdoc}
*/
protected function hydrateRowData(array $data, array &$result)
{
$hydrated_result = array();
parent::hydrateRowData($data, $hydrated_result);
/** #var ProjectAssignment $project_assignment */
$project_assignment = $hydrated_result[0][0];
$result[] = $project_assignment;
}
}
Here is my config: config.yml
doctrine:
orm:
hydrators:
project_assignment_statisticsdata_hydrator: AppBundle\Hydrator\ProjectAssignment\StatisticsDataHydrator
Where I don't use the hydrator I have no problem:
/**
* #param ProjectStage $stage
* #return array
*/
public function findByStageWithStatisticsData(ProjectStage $stage){
$qb = $this->createQueryBuilder('pa');
$qb
//->addSelect('44')
->where($qb->expr()->eq('pa.project_stage', ':stage'))
->setParameter('stage', $stage);
return $qb->getQuery()->getResult();
}
But when I use my hydrator:
/**
* #param ProjectStage $stage
* #return array
*/
public function findByStageWithStatisticsData(ProjectStage $stage){
$qb = $this->createQueryBuilder('pa');
$qb
->addSelect('1234') // referencial value
->where($qb->expr()->eq('pa.project_stage', ':stage'))
->setParameter('stage', $stage);
return $qb->getQuery()->getResult('project_assignment_statisticsdata_hydrator');
}
The strangest behavior is that the same occure with this config: config.yml
doctrine:
orm:
hydrators:
project_assignment_statisticsdata_hydrator: Doctrine\ORM\Internal\Hydration\ObjectHydrator
I have tried all kind of fetch on relation with no success:
#ORM\ManyToOne(... , fetch="EAGER")
#ORM\ManyToOne(... , fetch="LAZY")
...
Maybe I have to use a Proxy on my Entity, I really don't know :(
Thank you for any help!
Great! I found the problem, it was with my query builder. I had to manually add the joins and the select of related objects.
/**
* #param ProjectStage $stage
* #return array
*/
public function findByStageWithStatisticsData(ProjectStage $stage){
$qb = $this->createQueryBuilder('pa');
$qb
->addSelect('e') // added
->addSelect('r') // added
->addSelect('1234')
->leftJoin('pa.employee', 'e') // added
->leftJoin('pa.role', 'r') // added
->where($qb->expr()->eq('pa.project_stage', ':stage'))
->setParameter('stage', $stage);
return $qb->getQuery()->getResult('project_assignment_statisticsdata_hydrator');
}
Bonus, here is my Hydrator (it can help someone):
namespace AppBundle\Hydrator\ProjectAssignment;
use AppBundle\Entity\Hydrator\ProjectAssignment\StatisticsData;
use AppBundle\Entity\ProjectAssignment;
use Doctrine\ORM\Internal\Hydration\ObjectHydrator;
class StatisticsDataHydrator extends ObjectHydrator
{
/**
* {#inheritdoc}
*/
protected function hydrateRowData(array $data, array &$result)
{
$hydrated_result = array();
parent::hydrateRowData($data, $hydrated_result);
/** #var ProjectAssignment $project_assignment */
$project_assignment = $hydrated_result[0][0];
$keys = array_keys($hydrated_result); $key = end($keys);
$statistics_data = new StatisticsData($project_assignment);
$statistics_data->setTotalWorkedTime((int)$hydrated_result[$key][1]);
$project_assignment->setStatisticsData($statistics_data);
$result[] = $project_assignment;
}
}
In my Entity I have the folowing attribute/getter/setter
/********** NON SYNCED FIELDS **********/
/** #var StatisticsData $statistics_data */
private $statistics_data;
/**
* #return StatisticsData
*/
public function getStatisticsData()
{
return $this->statistics_data;
}
/**
* #param StatisticsData $statistics_data
*/
public function setStatisticsData($statistics_data)
{
$this->statistics_data = $statistics_data;
}
/***************************************/
The problem is that the Doctrine SqlWalker will not load meta columns, which include subclasses and associations, if the query Hydration Mode is not HYDRATE_OBJECT or if the query hint HINT_INCLUDE_META_COLUMNS is not set to true:
$addMetaColumns = ! $this->query->getHint(Query::HINT_FORCE_PARTIAL_LOAD) &&
$this->query->getHydrationMode() == Query::HYDRATE_OBJECT
||
$this->query->getHydrationMode() != Query::HYDRATE_OBJECT &&
$this->query->getHint(Query::HINT_INCLUDE_META_COLUMNS);
The problem has already been reported in this issue.
As mentioned by the author of the issue, you can either implement the suggested fix, or set the query hint HINT_INCLUDE_META_COLUMNS to true:
$query = $queryBuilder->getQuery();
$query->setHint(Query::HINT_INCLUDE_META_COLUMNS, true);
$result = $query->getResult('CustomHydrator');

Symfony 3.3: unable to add and remove items of the same entity with collection form type

I am new to Symfony, so I started with the official tutorial, installed Symfony framework 3.3.2 and worked through the tutorial while customizing entities, controllers, forms and views to my specific needs.
So basically I have an entity named BasePreset, there are already a couple of rows in the DB, and I finally have managed to create a form collection type that renders a list of editable BasePreset entity fields with 'add' and 'remove' links: the 1st adds new blank fields to the list, and every 'remove' link removes the corresponding fields from DOM. Everything according to docs.
So I succeed to update the existing fields (I see the right changes in form HTML after reloading and in the DB as well).
The problem is, add/delete not working. No errors given. Checked in Chrome Dev tools: parameters sent as expected.
I used the following documentation (and a lot of googling of course) regarding the form builder:
http://symfony.com/doc/current/forms.html
https://symfony.com/doc/current/best_practices/forms.html
https://symfony.com/doc/current/form/form_collections.html
https://symfony.com/doc/current/reference/forms/types/collection.html
Now, in this doc is stated:
You have to create both addTag() and removeTag() methods, otherwise
the form will still use setTag() even if by_reference is false. You'll
learn more about the removeTag() method later in this article.
At this moment I don't have any referenced subentity as it is described in the examples. I just want be able to edit the same plain entity, including adding new items and deleting existing. Maybe I'm wrong, but this seems as kind of a trivial basic goal in my mind. I don't understand how to properly add 'setBasePreset' and 'removeBasePreset' methods to the 'BasePreset' entity itself.
Of course I can skip using the form builder, but I'd like to leverage its power as a part of the framework. Any advice, example, maybe pointing to some relevant doc/tutorial that I missed - will be greatly appreciated.
Post example (with one addition and one removal):
base_presets[base_presets][5][name]:new_preset
base_presets[base_presets][5][description]:new, not really added
base_presets[base_presets][0][name]:ffffas44432df
base_presets[base_presets][0][description]:asdfffff2333
base_presets[base_presets][2][name]:ffffasdf2222
base_presets[base_presets][2][description]:asdff3fff2333
base_presets[base_presets][3][name]:yoyoshka
base_presets[base_presets][3][description]:nananaf
base_presets[base_presets][4][name]:123fffdsaasdf
base_presets[base_presets][4][description]:pop123
base_presets[_token]:H2QwRHdvZW1WAdc6VTONnspxvH1U-oC8rCEEprDdMCQ
The 'BasePresetsType' class:
<?php
namespace AppBundle\Form;
use Symfony\Component\Form\AbstractType;
use Symfony\Component\Form\FormBuilderInterface;
use Symfony\Component\OptionsResolver\OptionsResolver;
use Symfony\Component\Form\Extension\Core\Type\CollectionType;
class BasePresetsType extends AbstractType {
public function buildForm(FormBuilderInterface $builder, array $options)
{
$builder->add('base_presets', CollectionType::class, array(
'entry_type' => BasePresetType::class,
'allow_add' => true,
'allow_delete' => true,
'by_reference' => false // also tried 'true' but without any success
));
}
public function configureOptions(OptionsResolver $resolver)
{
$resolver->setDefaults(array(
'data_class' => null,
));
}
}
The 'BasePresetType' class:
<?php
namespace AppBundle\Form;
use AppBundle\Entity\BasePreset;
use Symfony\Component\Form\AbstractType;
use Symfony\Component\Form\FormBuilderInterface;
use Symfony\Component\OptionsResolver\OptionsResolver;
use Symfony\Component\Form\Extension\Core\Type\TextType;
use Symfony\Component\Form\Extension\Core\Type\TextareaType;
class BasePresetType extends AbstractType {
public function buildForm(FormBuilderInterface $builder, array $options) {
$builder
->add('name', TextType::class)
->add('description', TextareaType::class);
}
public function configureOptions(OptionsResolver $resolver) {
$resolver->setDefaults([
'data_class' => BasePreset::class,
]);
}
}
The controller method:
<?php
// ...
/**
* #Route("/admin/list_base_presets", name="list_base_presets")
*/
public function listBasePresetsAction(Request $request, EntityManagerInterface $em, LoggerInterface $logger) {
$base_presets = $em->getRepository("AppBundle\Entity\BasePreset")->findAll();
$form = $this->createForm(BasePresetsType::class, array('base_presets' => $base_presets));
$form->handleRequest($request);
$current_route = $request->get('_route');
if ($form->isSubmitted() && $form->isValid()) {
foreach ($base_presets as $base_preset) {
$em->persist($base_preset);
}
$em->flush();
// ... do other work - like sending an email, etc..
// maybe set a 'flash' success message for the user
return $this->redirectToRoute($current_route);
}
return $this->render('manage_params/list_base_presets.html.twig', array(
'form' => $form->createView()
));
}
The view:
{% extends "base.html.twig" %}
{% block body %}
{{ form_start(form) }}
{{ form_label(form.base_presets) }}
{{ form_errors(form.base_presets) }}
<ul class="base-presets" data-prototype="{{ form_widget(form.base_presets.vars.prototype)|e('html_attr') }}">
{% for base_preset in form.base_presets %}
<li class="base-preset-item">
{{ form_errors(base_preset) }}
{{ form_widget(base_preset) }}
</li>
{% endfor %}
<li class="form-submit">
<input type="submit" value="Submit" class="btn btn-default pull-right" />
</li>
</ul>
{{ form_end(form) }}
{% endblock %}
{% block javascripts %}
<script>
var $collection_holder;
// Setup an "add a base preset" link
var $add_base_preset_link = $('Add a base preset');
var $new_link_li = $('<li></li>').append($add_base_preset_link);
$(document).ready(function () {
// Get the ul that holds the collection of base presets
$collection_holder = $('ul.base-presets');
// add the "add a base preset" anchor and li to the tags ul
$collection_holder.prepend($new_link_li);
// count the current form inputs we have, use that as the new index when inserting a new item
$collection_holder.data('index', $collection_holder.find('li.base-preset-item').length);
$add_base_preset_link.on('click', function (e) {
e.preventDefault();
addBasePresetForm($collection_holder, $new_link_li);
});
// add a delete link to all of the existing base presets form li elements
$collection_holder.find('li.base-preset-item').each(function () {
addBasePresetFormDeleteLink($(this));
});
});
function addBasePresetForm($collection_holder, $new_link_li) {
// Get the data-prototype
var prototype = $collection_holder.data('prototype');
// Get the new index
var index = $collection_holder.data('index');
// Replace '__name__' in the prototype's HTML to instead be a number based on how many items we have
var new_form = prototype.replace(/__name__/g, index);
// increment the index for the next item
$collection_holder.data('index', index + 1);
// Display the form in the page in an li, before the "Add a base preset" link li
var $new_form_li = $('<li class="base-preset-item"></li>').append(new_form);
$new_link_li.after($new_form_li);
addBasePresetFormDeleteLink($new_form_li);
}
function addBasePresetFormDeleteLink($base_preset_form_li) {
var $remove_form_a = $('Delete this base preset');
$base_preset_form_li.append($remove_form_a);
$remove_form_a.on('click', function (e) {
e.preventDefault();
$base_preset_form_li.remove();
})
}
</script>
{% endblock %}
And, finally, the longest listing - the 'BasePreset' entity class:
<?php
namespace AppBundle\Entity;
use Doctrine\Common\Collections\ArrayCollection;
use Doctrine\ORM\Mapping as ORM;
use Symfony\Component\Validator\Constraints as Assert;
use Symfony\Bridge\Doctrine\Validator\Constraints\UniqueEntity;
/**
* Class BasePreset
* #package AppBundle\Entity
*
* #ORM\Entity
* #ORM\Table(name="base_presets")
* #UniqueEntity(fields="name", message="There should be only one (unique) base preset")
*/
class BasePreset {
/**
* #ORM\OneToMany(targetEntity="BaseParamsGroup", mappedBy="base_preset")
*/
private $base_params_groups;
/**
* #var int
*
* #ORM\Column(type="integer")
* #ORM\Id
* #ORM\GeneratedValue(strategy="AUTO")
*/
private $id;
/**
* #var string
*
* #ORM\Column(type="string", length=100)
*/
private $name;
/**
* #var string
*
* #Assert\NotBlank()
* #ORM\Column(type="string", length=4000)
*/
private $description;
/**
* #var \DateTime
*
* #ORM\Column(type="datetime")
*/
private $created;
/**
* #var \DateTime
*
* #ORM\Column(type="datetime", columnDefinition="TIMESTAMP on update CURRENT_TIMESTAMP")
*/
private $updated;
public function __construct() {
$this->base_params_groups = new ArrayCollection();
$this->created = new \DateTime();
$this->updated = new \DateTime();
}
/**
* Get id
*
* #return integer
*/
public function getId()
{
return $this->id;
}
/**
* Set name
*
* #param string $name
*
* #return BasePreset
*/
public function setName($name)
{
$this->name = $name;
return $this;
}
/**
* Get name
*
* #return string
*/
public function getName()
{
return $this->name;
}
/**
* Set description
*
* #param string $description
*
* #return BasePreset
*/
public function setDescription($description)
{
$this->description = $description;
return $this;
}
/**
* Get description
*
* #return string
*/
public function getDescription()
{
return $this->description;
}
/**
* Set created
*
* #param \DateTime $created
*
* #return BasePreset
*/
public function setCreated($created)
{
$this->created = $created;
return $this;
}
/**
* Get created
*
* #return \DateTime
*/
public function getCreated()
{
return $this->created;
}
/**
* Set updated
*
* #param \DateTime $updated
*
* #return BasePreset
*/
public function setUpdated($updated)
{
$this->updated = $updated;
return $this;
}
/**
* Get updated
*
* #return \DateTime
*/
public function getUpdated()
{
return $this->updated;
}
/**
* Add baseParamsGroup
*
* #param \AppBundle\Entity\BaseParamsGroup $baseParamsGroup
*
* #return BasePreset
*/
public function addBaseParamsGroup(\AppBundle\Entity\BaseParamsGroup $baseParamsGroup)
{
$this->base_params_groups[] = $baseParamsGroup;
return $this;
}
/**
* Remove baseParamsGroup
*
* #param \AppBundle\Entity\BaseParamsGroup $baseParamsGroup
*/
public function removeBaseParamsGroup(\AppBundle\Entity\BaseParamsGroup $baseParamsGroup)
{
$this->base_params_groups->removeElement($baseParamsGroup);
}
/**
* Get baseParamsGroups
*
* #return \Doctrine\Common\Collections\Collection
*/
public function getBaseParamsGroups()
{
return $this->base_params_groups;
}
}
Since you use form without Data class, you need to access submitted data directly from form object:
foreach ($form->get('base_presets')->getData() as $base_preset) {
$em->persist($base_preset);
}
Update:
That work with managing existing and persisting new ones. If you need to remove entities, you can compare entities loaded from DB with submitted ones, and then remove filtered.

KNP Doctrinebehaviors Bundle : how to show translatable entity?

I use KNP Doctrinebehaviors Bundle to translate my entity, and a2lix_translations to get i18n form,
I have no problems with those steps :
Adding entity with multi-languages.
Getting entities in cases my default locale language.
Update entity.
Delete entity.
But the probleme is how to access the propreties of my Page Entity in twig?
This is somes pictures to understand the problem :
This is my PageEntity
public function findAllByLocale($locale){
return $this->createQueryBuilder('a')
->join('a.translations', 'aTrans')
->where('aTrans.locale = :locale')
->setParameter("locale", $locale)
->addSelect('aTrans')
->getQuery()
->getResult()
;
}
use ORMBehaviors\Translatable\Translation;
/**
* #var string $title
*
* #ORM\Column(name="title", type="string", length=255)
*/
private $title;
/**
* #var string $content
*
* #ORM\Column(name="content", type="text")
*/
private $content;
/**
* #ORM\ManyToOne(targetEntity="Page", inversedBy="trans", cascade={"persist", "remove"})
* #var Collection
*/
private $object;
/**
* Get title
*
* #return string
*/
public function getTitle()
{
if( $title == $this->translate()->getTitle() ) {
return $title;
}
return '';
}
/**
* Set title
*
* #param string $title
* #return Page
*/
public function setTitle($title)
{
$this->title = $title;
return $this;
}
/**
* 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;
}
/**
* #param $method
* #param $args
*
* #return mixed
*/
public function __call($method, $args)
{
if (!method_exists(self::getTranslationEntityClass(), $method)) {
$method = 'get' . ucfirst($method);
}
return $this->proxyCurrentLocaleTranslation($method, $args);
}
and this is my query :
<!-- begin snippet: js hide: true -->
FormType
twig page : index.html.twig
Thank you
Here's the answer to what I understood:
FIRST:
If you want to display the translated fields of an entity based on guessed locale, from the doc:
proxy translations
An extra feature allows you to proxy translated fields of a translatable entity.
You can use it in the magic __call method of you translatable entity so that when you try to call getName (for example) it will return you the translated value of the name for current locale:
public function __call($method, $arguments)
{
return $this->proxyCurrentLocaleTranslation($method, $arguments);
}
Now when displaying an entity that has a translated field name in a twig template you can use:
{{ entity.getName() }}
The proxy will intercept your call and return the appropriate content by guessing the locale from the request. This is how you'll want to display most entities.
SECOND:
The other way if you specifically want to translate an entity in French then you can use the following, also in twig:
{{ entity.translate('fr').getName() }}

How to inject non-default entity managers?

In Symfony2 you can work with multiple entity managers and use something like the code below:
$em = $this->get('doctrine')->getManager();
$em = $this->get('doctrine')->getManager('default');
$customerEm = $this->get('doctrine')->getManager('customer');
We can inject the default manager to any service by using:
"#doctrine.orm.entity_manager"
How can you inject non-default entity managers into services?
If your entity managers config name is non_default then you can reference it as #doctrine.orm.non_default_entity_manager
For those who are using Symfony 3+, use the console :
php bin/console debug:container
Then you should see many lines starting with : 'doctrine.orm.MY_CUSTOM_ENTITY_MANAGER_xxxxxxxxxx'
So if you want the entity manager corresponding to your custom entity manager, find the line :
'doctrine.orm.MY_CUSTOM_ENTITY_MANAGER_entity_manager'
You can insert it in your service arguments.
Hope it helps.
You should define your custom entity manager as a service:
services:
name_of_your_custom_manager:
class: %doctrine.orm.entity_manager.class%
factory_service: doctrine
factory_method: getEntityManager
arguments: ["name_of_your_custom_manager"]
Then, you can inject it in the same way as you do with every service:
#name_of_your_custom_manager
Edit:
Pay attention that factory method may differ between symfony's version (it could be getEntityManager or getManager)
Hello first of all create your manager, in my example I create the manager for my Item class that is in a CoreBundle:
<?php
// src/Sybio/Bundle/CoreBundle/Manager/ItemManager.php:
namespace Sybio\Bundle\CoreBundle\Manager;
use Sybio\Bundle\CoreBundle\Entity\Item;
class ItemManager
{
/**
* #var \Doctrine\ORM\EntityManager $em entity manager
*/
protected $em;
/**
* #var \Doctrine\ORM\EntityRepository $em repository
*/
protected $repository;
/**
* #var string $entityName
*/
protected $entityName;
/**
* Constructor
*
* #param EntityManager $em
* #param string $entityName
*
* #return void
*/
public function __construct(EntityManager $em, $entityName)
{
$this->em = $em;
$this->repository = $em->getRepository($entityName);
$this->entityName = $entityName;
}
/**
* Save a entity object
*
* #param Object $entity
*
* #return Object Entity
*/
public function save($entity)
{
$this->persistAndFlush($entity);
return $entity;
}
/**
* Remove a entity object
*
* #param Object $entity
*
* #return Object Entity
*/
public function remove($entity)
{
return $this->removeAndFlush($entity);
}
/**
* Persist object
*
* #param mixed $entity
*
* #return void
*/
protected function persistAndFlush($entity)
{
$this->em->persist($entity);
$this->em->flush();
}
/**
* Remove object
*
* #param mixed $entity entity to remove
*
* #return void
*/
protected function removeAndFlush($entity)
{
$this->em->remove($entity);
$this->em->flush();
}
/**
* Returns entity repository object
*
* #return EntityRepository
*/
public function getRepository()
{
return $this->repository;
}
/**
* Create a new object
*
* #return mixed
*/
public function createNewObject()
{
return new Item();
}
// Create your own methods to manage the object
}
If the manager structure is shared between multiple manager, you can create a BaseManager extended by all other managers !
Then register it in the services.yml (or xml) file of your bundle:
# src/Sybio/Bundle/CoreBundle/Resources/config/services.yml or xml !:
parameters:
# Managers _________________
sybio.item_manager.entity: SybioCoreBundle:Item
sybio.item_manager.class: Sybio\Bundle\CoreBundle\Manager\ItemManager
services:
# Managers _________________
sybio.item_manager:
class: %sybio.item_manager.class%
arguments: [#doctrine.orm.entity_manager, %sybio.item_manager.entity%]
That's it, you can now use it:
// Controller:
$im = $this->get('sybio.item_manager');
$item = $im->createNewObject();
$im->save($item);
You can then improve your manager, here I give an array of config parameters to my manager:
# src/Sybio/Bundle/CoreBundle/Resources/config/services.yml or xml !:
sybio.item_manager:
class: %sybio.item_manager.class%
arguments: [#doctrine.orm.entity_manager, %sybio.item_manager.entity%, {'item_removed_state': %item_removed_state%, 'item_unpublished_state': %item_unpublished_state%, 'item_published_state': %item_published_state%}]
// src/Sybio/Bundle/CoreBundle/Manager/ItemManager.php:
public function __construct(EntityManager $em, $entityName, $params = array()) {
// ...
$this->params = $params;
}
If you create a BaseManager, you can also create a usefull generic method to initialize an object:
// src/Sybio/Bundle/CoreBundle/Manager/BaseManager.php:
/**
* Create a new object
*
* #return mixed
*/
public function createNewObject()
{
$entityName = explode(":", $this->entityName);
$entityName = "Sybio\Bundle\CoreBundle\Entity\\".$entityName[1];
return new $entityName;
}

Resources