I use sonata admin bundle with Symfony 3.4 and knplabs. Everything work fine excepted one thing.
I have create a Test class and the sonata list with the CRUD.
This class has a translatable title, when I'm on the edit mode of one on my test object, I can click on the flag for editing the title in many languages.
But when I'm on my list view, the flag are display but when I click on it, the list always display the title in english (default language).
I debug and find that in edit view, method setLocale and getLocal are used to change the languages but in list view, they are not call.
How can I translate my entites in my list view ?
Here my test class, translation test class and my testAdmin.
Test.php
<?php
namespace App\Entity;
use Doctrine\ORM\Mapping as ORM;
use Knp\DoctrineBehaviors\Model as ORMBehaviors;
use Sonata\TranslationBundle\Model\TranslatableInterface;
/**
* #ORM\Entity(repositoryClass="App\Repository\TestRepository")
*/
class Test implements TranslatableInterface
{
use ORMBehaviors\Translatable\Translatable;
/**
* #ORM\Id()
* #ORM\GeneratedValue()
* #ORM\Column(type="integer")
*/
private $id;
/**
* #ORM\Column(type="boolean")
*/
private $is_enable;
public function getId(): ?int
{
return $this->id;
}
public function getTitle(): ?string
{
return $this->translate(null, false)->getTitle();
}
public function setTitle(string $title): self
{
$this->translate(null, false)->setTitle($title);
return $this;
}
public function getIsEnable(): ?bool
{
return $this->is_enable;
}
public function setIsEnable(bool $is_enable): self
{
$this->is_enable = $is_enable;
return $this;
}
/**
* #param string $locale
*/
public function setLocale($locale)
{
$this->setCurrentLocale($locale);
return $this;
}
/**
* #return string
*/
public function getLocale()
{
return $this->getCurrentLocale();
}
/**
* #return string
*
* Set this to have a correct name display on BO (sonata translation add some weird id key after the name)
*/
public function __toString()
{
if (empty($this->getTitle())){
return '';
}
else{
return $this->getTitle();
}
}
}
TestTranslation.php
<?php
namespace App\Entity;
use Doctrine\ORM\Mapping as ORM;
use Knp\DoctrineBehaviors\Model as ORMBehaviors;
/**
* #ORM\Table(name="test_translation")
* #ORM\Entity
*/
class TestTranslation
{
use ORMBehaviors\Translatable\Translation;
/**
* #var string
*
* #ORM\Column(type="string", length=255)
*/
private $title;
/**
* #return integer
*/
public function getId()
{
return $this->id;
}
/**
* #return string
*/
public function getTitle()
{
return $this->title;
}
/**
* #param string $title
*
* #return TestTranslation
*/
public function setTitle($title)
{
$this->title = $title;
return $this;
}
}
TestAdmin.php
<?php
namespace App\Admin;
use Sonata\AdminBundle\Admin\AbstractAdmin;
use Sonata\AdminBundle\Datagrid\ListMapper;
use Sonata\AdminBundle\Form\FormMapper;
use Symfony\Component\Form\Extension\Core\Type\TextType;
use Sonata\AdminBundle\Datagrid\DatagridMapper;
class TestAdmin extends AbstractAdmin
{
protected $baseRoutePattern = 'test';
protected $baseRouteName = 'test';
protected function configureFormFields(FormMapper $formMapper)
{
$formMapper
->add('title', TextType::class)
->add('is_enable', TextType::class)
;
}
protected function configureListFields(ListMapper $listMapper)
{
$listMapper
->addIdentifier('id')
->add('title')
->add('is_enable')
->add('_action', 'actions', array(
'actions' => array(
'edit' => array(),
'delete' => array(),
)
))
;
}
protected function configureDatagridFilters(DatagridMapper $datagridMapper)
{
$datagridMapper->add('id');
$datagridMapper->add('translations.title', null, array('label' => 'Title'));
$datagridMapper->add('is_enable');
}
}
return (string)$this->getTranslations()->get($locale);
use above code in __toString method Test.php
Related
I'm trying to show preview image with Sonata Admin Bundle 3 version but I can't do it. I get this error in RecipeAdmin.php: Could not load type "file": class does not exist.
RecipeAdmin.php
<?php
declare(strict_types=1);
namespace App\Admin;
use Sonata\AdminBundle\Admin\AbstractAdmin;
use Sonata\AdminBundle\Datagrid\DatagridMapper;
use Sonata\AdminBundle\Datagrid\ListMapper;
use Sonata\AdminBundle\Form\FormMapper;
use Sonata\AdminBundle\Show\ShowMapper;
use Sonata\Form\Type\CollectionType;
use Sonata\AdminBundle\Form\Type\ModelListType;
final class RecipeAdmin extends AbstractAdmin
{
protected function configureDatagridFilters(DatagridMapper $datagridMapper): void
{
$datagridMapper
->add('title',null,['label' =>'Título'])
->add('image',null,['label' =>'Imagen'])
->add('description',null,['label' =>'Descripción'])
->add('score',null,['label' =>'Puntuación'])
->add('visible')
;
}
protected function configureListFields(ListMapper $listMapper): void
{
$listMapper
->add('id')
->add('user', CollectionType::class,['label' =>'Usuario'])
->add('title',null,['label' =>'Título'])
->add('image',null,['label' =>'Imagen'])
->add('description',null,['label' =>'Descripción'])
->add('score',null,['label' =>'Puntuación'])
->add('visible',null,['label' =>'Visible'])
->add('_action', null, [
'label' => 'Acciones',
'actions' => [
'show' => [],
'edit' => [],
'delete' => [],
],
]);
}
protected function configureFormFields(FormMapper $formMapper): void
{
$image = $this->getSubject();
// use $fileFormOptions so we can add other options to the field
$fileFormOptions = ['required' => false];
if ($image && ($webPath = $image->getImage())) {
// get the request so the full path to the image can be set
$request = $this->getRequest();
$fullPath = $request->getBasePath().'/'.$webPath;
// add a 'help' option containing the preview's img tag
$fileFormOptions['help'] = '<img src="'.$fullPath.'" class="admin-preview"/>';
$fileFormOptions['help_html'] = true;
}
$formMapper
->add('title',null,['label' =>'Título'])
->add('image', 'file', $fileFormOptions)
->add('description',null,['label' =>'Descripción'])
->add('score',null,['label' =>'Puntuación'])
->add('visible')
;
}
protected function configureShowFields(ShowMapper $showMapper): void
{
$showMapper
->add('title',null,['label' =>'Título'])
->add('image',null,['label' =>'Imagen'])
->add('description',null,['label' =>'Descripción'])
->add('user', CollectionType::class)
->add('score',null,['label' =>'Puntuaciones'])
->add('visible')
;
}
}
Recipe.php Entity
<?php
namespace App\Entity;
use App\Repository\RecipeRepository;
use Doctrine\Common\Collections\ArrayCollection;
use Doctrine\Common\Collections\Collection;
use Symfony\Component\HttpFoundation\File\UploadedFile;
use Doctrine\ORM\Mapping as ORM;
/**
* #ORM\Entity(repositoryClass=RecipeRepository::class)
*/
class Recipe
{
public function __construct() {
$this->categories = new \Doctrine\Common\Collections\ArrayCollection();
}
/**
* #ORM\Id
* #ORM\GeneratedValue
* #ORM\Column(type="integer")
*/
private $id;
/**
* #ORM\Column(type="string", length=255)
*/
private $title;
/**
* #ORM\Column(type="string", length=255)
*/
private $image;
/**
* #ORM\Column(type="string", length=255)
*/
private $description;
/**
* #ORM\ManyToOne(targetEntity="User", inversedBy="recipes")
* #ORM\JoinColumn(nullable=false)
*/
private $user;
/**
* #ORM\Column(type="integer", nullable=true)
*/
private $score;
/**
* #ORM\Column(type="boolean")
*/
private $visible;
/**
* #ORM\ManyToMany(targetEntity="Category", inversedBy="recipe", cascade={"persist"})
* #ORM\JoinTable(name="recipes_categories")
*/
private $categories;
public function getId(): ?int
{
return $this->id;
}
public function setId(int $id)
{
$this->id = $id;
return $this;
}
public function getTitle(): ?string
{
return $this->title;
}
public function setTitle(string $title): self
{
$this->title = $title;
return $this;
}
public function getImage(): ?string
{
return $this->image;
}
public function setImage(string $image): self
{
$this->image = $image;
return $this;
}
public function getDescription(): ?string
{
return $this->description;
}
public function setDescription(string $description): self
{
$this->description = $description;
return $this;
}
public function getUser()
{
return $this->user;
}
public function setUser($user)
{
$this->user = $user;
return $this;
}
public function getScore(): ?int
{
return $this->score;
}
public function setScore(?int $score): self
{
$this->score = $score;
return $this;
}
public function getVisible(): ?bool
{
return $this->visible;
}
public function setVisible(bool $visible): self
{
$this->visible = $visible;
return $this;
}
public function getCategories()
{
return $this->categories;
}
public function setCategories($categories)
{
$this->categories = $categories;
return $this;
}
public function __toString()
{
return $this->getTitle();
}
public function addCategory(Category $category): self
{
if (!$this->categories->contains($category)) {
$this->categories[] = $category;
}
return $this;
}
public function removeCategory(Category $category): self
{
$this->categories->removeElement($category);
return $this;
}
}
This is the link about how do it: https://symfony.com/doc/current/bundles/SonataAdminBundle/cookbook/recipe_image_previews.html
https://sonata-project.org/bundles/admin/master/doc/cookbook/recipe_image_previews.html#showing-image-previews
In the documentation explains that I have to use 'file' type fields but when I use it in my proyect doesn't runs.
This is an error in the doc, instead of file you should use FileType::class and adding :
use Symfony\Component\Form\Extension\Core\Type\FileType;
$formMapper
->add('title',null,['label' =>'Título'])
->add('image', FileType::class, $fileFormOptions)
You will still have error such as :
The form's view data is expected to be an instance of class Symfony\Component\HttpFoundation\File\File, but is a(n) string. You can avoid this error by setting the "data_class" option to null or by adding a view transformer that transforms a(n) string to an instance of Symfony\Component\HttpFoundation\File\File.
In the cookbook they tell you to create an Image Entity so you should add it and follow all the steps :
https://sonata-project.org/bundles/admin/master/doc/cookbook/recipe_file_uploads.html
I suggest instead of following this cookbook, you should install and use the sonata media, the integration is easier and it have some nice features such as making different formats for your upload.
the error is:
Could not determine access type for property "offerOrders" in class "App\Form\OrderType": Neither the property "offerOrders" nor one of the methods "addOfferOrder()"/"removeOfferOrder()", "setOfferOrders()", "offerOrders()", "__set()" or "__call()" exist and have public access in class "App\Form\OrderType"..
PhotoSession Class
<?php
namespace App\Entity;
use Doctrine\Common\Collections\ArrayCollection;
use Doctrine\Common\Collections\Collection;
use Doctrine\ORM\Mapping as ORM;
/**
* #ORM\Entity(repositoryClass="App\Repository\PhotoSessionRepository")
*/
class PhotoSession
{
/**
* #ORM\Id()
* #ORM\GeneratedValue()
* #ORM\Column(type="integer")
*/
private $id;
/**
* #ORM\OneToOne(targetEntity="App\Entity\Order", inversedBy="photoSession", cascade={"persist", "remove"})
* #ORM\JoinColumn(nullable=false)
*/
private $purchaseOrder;
Order Class
<?php
namespace App\Entity;
use Doctrine\Common\Collections\ArrayCollection;
use Doctrine\Common\Collections\Collection;
use Doctrine\ORM\Mapping as ORM;
/**
* #ORM\Entity(repositoryClass="App\Repository\OrderRepository")
* #ORM\Table(name="`order`")
*/
class Order
{
/**
* #ORM\Id()
* #ORM\GeneratedValue()
* #ORM\Column(type="integer")
*/
private $id;
/**
* #ORM\OneToOne(targetEntity="App\Entity\PhotoSession", mappedBy="purchaseOrder", cascade={"persist", "remove"})
*/
private $photoSession;
/**
* #ORM\OneToMany(targetEntity="App\Entity\OfferOrder", mappedBy="purchaseOrder", cascade={"persist"})
*/
private $offerOrders;
public function __construct()
{
$this->offerOrders = new ArrayCollection();
}
public function getId(): ?int
{
return $this->id;
}
public function getPhotoSession(): ?PhotoSession
{
return $this->photoSession;
}
public function setPhotoSession(PhotoSession $photoSession): self
{
$this->photoSession = $photoSession;
// set the owning side of the relation if necessary
if ($photoSession->getPurchaseOrder() !== $this) {
$photoSession->setPurchaseOrder($this);
}
return $this;
}
/**
* #return Collection|OfferOrder[]
*/
public function getOfferOrders(): Collection
{
return $this->offerOrders;
}
public function addOfferOrder(OfferOrder $offerOrder): self
{
if (!$this->offerOrders->contains($offerOrder)) {
$this->offerOrders[] = $offerOrder;
$offerOrder->setPurchaseOrder($this);
}
return $this;
}
public function removeOfferOrder(OfferOrder $offerOrder): self
{
if ($this->offerOrders->contains($offerOrder)) {
$this->offerOrders->removeElement($offerOrder);
// set the owning side to null (unless already changed)
if ($offerOrder->getPurchaseOrder() === $this) {
$offerOrder->setPurchaseOrder(null);
}
}
return $this;
}
}
offerOrder Class
<?php
namespace App\Entity;
use Doctrine\ORM\Mapping as ORM;
/**
* #ORM\Entity(repositoryClass="App\Repository\OfferOrderRepository")
*/
class OfferOrder
{
/**
* #ORM\Id()
* #ORM\GeneratedValue()
* #ORM\Column(type="integer")
*/
private $id;
/**
* #ORM\ManyToOne(targetEntity="App\Entity\Offer", inversedBy="offerOrders")
* #ORM\JoinColumn(nullable=false)
*/
private $offer;
/**
* #ORM\ManyToOne(targetEntity="App\Entity\Order", inversedBy="offerOrders")
* #ORM\JoinColumn(nullable=false)
*/
private $purchaseOrder;
/**
* #ORM\Column(type="float", nullable=true)
*/
private $quantity;
/**
* #ORM\Column(type="float", nullable=true)
*/
private $totalPriceHt;
public function getId(): ?int
{
return $this->id;
}
public function getOffer(): ?Offer
{
return $this->offer;
}
public function setOffer(?Offer $offer): self
{
$this->offer = $offer;
return $this;
}
public function getPurchaseOrder(): ?Order
{
return $this->purchaseOrder;
}
public function setPurchaseOrder(?Order $purchaseOrder): self
{
$this->purchaseOrder = $purchaseOrder;
return $this;
}
public function getQuantity(): ?float
{
return $this->quantity;
}
public function setQuantity(?float $quantity): self
{
$this->quantity = $quantity;
return $this;
}
public function getTotalPriceHt(): ?float
{
return $this->totalPriceHt;
}
public function setTotalPriceHt(?float $totalPriceHt): self
{
$this->totalPriceHt = $totalPriceHt;
return $this;
}
}
offer class
<?php
namespace App\Entity;
use Doctrine\Common\Collections\ArrayCollection;
use Doctrine\Common\Collections\Collection;
use Doctrine\ORM\Mapping as ORM;
/**
* #ORM\Entity(repositoryClass="App\Repository\OfferRepository")
*/
class Offer
{
/**
* #ORM\Id()
* #ORM\GeneratedValue()
* #ORM\Column(type="integer")
*/
private $id;
/**
* #ORM\Column(type="float")
*/
private $price;
/**
* #var Tag
* #ORM\ManyToOne(targetEntity="App\Entity\Tag")
* #ORM\JoinColumn(nullable=false)
*/
private $tag;
/**
* #var integer|null
* #ORM\Column(type="integer")
*/
private $version = 0;
/**
* #ORM\OneToMany(targetEntity="App\Entity\OfferOrder", mappedBy="offer")
*/
private $offerOrders;
public function __construct()
{
$this->offerOrders = new ArrayCollection();
}
public function getId(): ?int
{
return $this->id;
}
public function getPrice(): ?float
{
return $this->price;
}
public function setPrice(float $price): self
{
$this->price = $price;
return $this;
}
/**
* #return Tag|null
*/
public function getTag(): ?Tag
{
return $this->tag;
}
/**
* #param Tag $tag
* #return Offer
*/
public function setTag(Tag $tag): self
{
$this->tag = $tag;
return $this;
}
/**
* #return int|null
*/
public function getVersion(): ?int
{
return $this->version;
}
/**
* #param int|null $version
* #return Offer
*/
public function setVersion(?int $version): self
{
$this->version = $version;
return $this;
}
/**
* #return Collection|OfferOrder[]
*/
public function getOfferOrders(): Collection
{
return $this->offerOrders;
}
public function addOfferOrder(OfferOrder $offerOrder): self
{
if (!$this->offerOrders->contains($offerOrder)) {
$this->offerOrders[] = $offerOrder;
$offerOrder->setOffer($this);
}
return $this;
}
public function removeOfferOrder(OfferOrder $offerOrder): self
{
if ($this->offerOrders->contains($offerOrder)) {
$this->offerOrders->removeElement($offerOrder);
// set the owning side to null (unless already changed)
if ($offerOrder->getOffer() === $this) {
$offerOrder->setOffer(null);
}
}
return $this;
}
}
SessionPhotoType
<?php
namespace App\Form;
use App\Entity\Family;
use App\Entity\PhotoSession;
use Symfony\Bridge\Doctrine\Form\Type\EntityType;
use Symfony\Component\Form\AbstractType;
use Symfony\Component\Form\FormBuilderInterface;
use Symfony\Component\OptionsResolver\OptionsResolver;
class PhotoSessionType extends AbstractType
{
public function buildForm(FormBuilderInterface $builder, array $options)
{
$builder
->add('name')
->add('description')
->add('family', EntityType::class, [
'class' => Family::class,
'choice_label' => 'name',
])
->add('purchaseOrder', OrderType::class, [
'data_class' => OrderType::class
])
;
}
public function configureOptions(OptionsResolver $resolver)
{
$resolver->setDefaults([
'data_class' => PhotoSession::class,
]);
}
}
OrderType
<?php
namespace App\Form;
use App\Entity\Order;
use Symfony\Component\Form\AbstractType;
use Symfony\Component\Form\Extension\Core\Type\CollectionType;
use Symfony\Component\Form\FormBuilderInterface;
use Symfony\Component\OptionsResolver\OptionsResolver;
class OrderType extends AbstractType
{
public function buildForm(FormBuilderInterface $builder, array $options)
{
$builder
->add('offerOrders', CollectionType::class, [
'entry_type' => OfferOrderType::class,
'by_reference' => false,
'allow_add' => true,
'allow_delete' => true,
])
->add('totalPriceHt')
->add('tva')
->add('finalPrice')
;
}
public function configureOptions(OptionsResolver $resolver)
{
$resolver->setDefaults([
'data_class' => Order::class,
]);
}
}
OfferOrderType
class OfferOrderType extends AbstractType
{
public function buildForm(FormBuilderInterface $builder, array $options)
{
$builder
->add('offer', EntityType::class, [
'class' => Offer::class,
'choice_label' => function(Offer $offer) {
return sprintf('%s %f €', $offer->getTag()->getName(), $offer->getPrice());
},
'placeholder' => 'Choissiez une offre'
])
->add('quantity', NumberType::class)
->add('totalPriceHt', NumberType::class);
}
public function configureOptions(OptionsResolver $resolver)
{
$resolver->setDefaults([
'widget' => 'single_text',
'data_class' => OfferOrder::class,
]);
}
}
I'm sorry for the long code blocks, but for the people who will try to solve the bug I think that there is the necessary one.
The error is returned when I submit the form.
The question I ask myself is why symfony is looking for accessors in the App\Form\OrderType class?
I put dumps everywhere in PropertyAccessor.php PropertyPathMapper.php and vendor/symfony/form/Form.php, and when it goes here:
if (FormUtil::isEmpty($viewData)) {
$emptyData = $this->config->getEmptyData();
if ($emptyData instanceof \Closure) {
$emptyData = $emptyData($this, $viewData);
}
$viewData = $emptyData;
dump($viewData);
}
in Form.php it sets $viewData to App\Form\OrderType but I don't know why
The answer was:
For the form to pass, it was necessary to change purchaseOrder in photoSessionType like this:
-> add ('purchaseOrder', OrderType :: class)
Thanks again to #Ihor Kostrov
Try to change
->add('purchaseOrder', OrderType::class, [
'data_class' => OrderType::class
])
To
->add('purchaseOrder', OrderType::class, [
'data_class' => Order::class
])
Or just remove data_class option
I am trying to develop my first symfony web-app and I have decided to use the bundle EasyAdmin.
In this web-app, I would like to define the following model : an Event with several dates.
In order to create this, I have create 2 entities with the help of the symfony console : Event and EventDate:
<?php
namespace App\Entity;
use Doctrine\Common\Collections\ArrayCollection;
use Doctrine\Common\Collections\Collection;
use Doctrine\ORM\Mapping as ORM;
/**
* #ORM\Entity(repositoryClass="App\Repository\EventRepository")
*/
class Event
{
/**
* #ORM\Id()
* #ORM\GeneratedValue()
* #ORM\Column(type="integer")
*/
private $id;
/**
* #ORM\Column(type="string", length=255)
*/
private $name;
/**
* #ORM\OneToMany(targetEntity="App\Entity\EventDate", mappedBy="event", orphanRemoval=true)
*/
private $dates;
public function __construct()
{
$this->dates = new ArrayCollection();
}
public function getId(): ?int
{
return $this->id;
}
public function getName(): ?string
{
return $this->name;
}
public function setName(string $name): self
{
$this->name = $name;
return $this;
}
/**
* #return Collection|EventDate[]
*/
public function getDates(): Collection
{
return $this->dates;
}
public function addDate(EventDate $date): self
{
if (!$this->dates->contains($date)) {
$this->dates[] = $date;
$date->setEvent($this);
}
return $this;
}
public function removeDate(EventDate $date): self
{
if ($this->dates->contains($date)) {
$this->dates->removeElement($date);
// set the owning side to null (unless already changed)
if ($date->getEvent() === $this) {
$date->setEvent(null);
}
}
return $this;
}
public function __toString(): String
{
return $this->name;
}
}
<?php
namespace App\Entity;
use Doctrine\ORM\Mapping as ORM;
/**
* #ORM\Entity(repositoryClass="App\Repository\EventDateRepository")
*/
class EventDate
{
/**
* #ORM\Id()
* #ORM\GeneratedValue()
* #ORM\Column(type="integer")
*/
private $id;
/**
* #ORM\Column(type="date")
*/
private $date;
/**
* #ORM\ManyToOne(targetEntity="App\Entity\Event", inversedBy="dates")
* #ORM\JoinColumn(nullable=false)
*/
private $event;
public function getId(): ?int
{
return $this->id;
}
public function getDate(): ?\DateTimeInterface
{
return $this->date;
}
public function setDate(\DateTimeInterface $date): self
{
$this->date = $date;
return $this;
}
public function getEvent(): ?Event
{
return $this->event;
}
public function setEvent(?Event $event): self
{
$this->event = $event;
return $this;
}
}
In order to be user-friendly, I would like to "customize" the form of an Event in order to allow the user to create in the same form the event and its dates.
In order to do this, I have define the Event entity's form like that:
easy_admin:
entities:
Event:
class: App\Entity\Event
form:
fields:
- {property: 'name'}
- {property: 'dates', type: 'collection'}
The render of the collection is right because I can add or remove a date:
But As you can see, the field that represent the EventDate is an edit text. I think it's because the field represent the EventDate class and not only the date attribute.
The aim is to have the date selector that I have if I add a new EventDate in EasyAdmin:
So the question is: How to custom EasyAdmin in order to add an Event and its dates in a single form?
Thank you for your help!
I found the way to do it.
I need to modify my yaml EasyAdmin file in order to introduce an entry_type:
easy_admin:
entities:
Event:
class: App\Entity\Event
form:
fields:
- {property: 'name'}
- {property: 'dates', type: 'collection', type_options: {entry_type: 'App\Form\EventDateForm', by_reference: false}}
Then, I have to create the EventDateForm class:
<?php
namespace App\Form;
use Symfony\Component\Form\AbstractType;
use Symfony\Component\Form\FormBuilderInterface;
use Symfony\Component\OptionsResolver\OptionsResolver;
class EventDateForm extends AbstractType
{
public function buildForm(FormBuilderInterface $builder, array $options)
{
$builder->add('date');
}
public function configureOptions(OptionsResolver $resolver)
{
$resolver->setDefaults(array(
'data_class' => 'App\Entity\EventDate'
));
}
}
I also need to update the $date attribut of my Event entity like this:
/**
* #ORM\OneToMany(targetEntity="App\Entity\EventDate", mappedBy="event", orphanRemoval=true, cascade={"persist", "remove"})
*/
private $dates;
The render is not very beautiful but it works:
I'm trying to use the DoctrineBehaviors translatable extension in Symfony 4.Just setup a test following the documentation example:
translatable entity:
<?php
namespace App\Entity;
use Doctrine\ORM\Mapping as ORM;
use Knp\DoctrineBehaviors\Model as ORMBehaviors;
/**
* #ORM\Entity(repositoryClass="App\Repository\FAQRepository")
*/
class FAQ
{
use ORMBehaviors\Translatable\Translatable;
/**
* #ORM\Id()
* #ORM\GeneratedValue()
* #ORM\Column(type="integer")
*/
protected $id;
/.**
* #ORM\Column(type="datetime", nullable=true)
*/
protected $updatedAt;
public function getId(): ?int
{
return $this->id;
}
public function getUpdatedAt(): ?\DateTimeInterface
{
return $this->updatedAt;
}
public function setUpdatedAt(?\DateTimeInterface $updatedAt): self
{
$this->updatedAt = $updatedAt;
return $this;
}
}
translation entity:
<?php
namespace App\Entity;
use Doctrine\ORM\Mapping as ORM;
use Knp\DoctrineBehaviors\Model as ORMBehaviors;
/**
* #ORM\Entity
*/
class FAQTranslation
{
use ORMBehaviors\Translatable\Translation;
/**
* #ORM\Column(type="text")
*/
protected $question;
/**
* #ORM\Column(type="text")
*/
protected $answer;
/**
* #ORM\Column(type="integer", nullable=true)
*/
protected $category;
public function getQuestion(): ?string
{
return $this->question;
}
public function setQuestion(string $question): self
{
$this->question = $question;
return $this;
}
public function getAnswer(): ?string
{
return $this->answer;
}
public function setAnswer(string $answer): self
{
$this->answer = $answer;
return $this;
}
public function getCategory(): ?int
{
return $this->category;
}
public function setCategory(?int $category): self
{
$this->category = $category;
return $this;
}
}
Testing the translatable entity:
/**
* #Route("/test", name="test")
*/
public function testfaq()
{
$em = $this->getDoctrine()->getManager();
$faq = new FAQ();
$faq->translate('fr')->setQuestion('Quelle est la couleur ?');
$faq->translate('en')->setQuestion('What is the color ?');
$em->persist($faq);
$faq->mergeNewTranslations();
$em->flush();
return $this->render('app/test.html.twig', [
]);
}
A new ID is added in the faq table.
But nothing is persisted in the faqtranslation table.
Bundles.php :
Knp\DoctrineBehaviors\Bundle\DoctrineBehaviorsBundle::class => ['all' => true],
All the documentations I found seem to refer to Symfony 3 or even Symfony 2, is it possible to use DoctrineBehaviors translatable in Symfony 4 ?
I don't know if you found your answer since (I hope you did), but yes you can use KnpLabs/DoctrineBehaviors with Symfony 4. Maybe, you just needed to wait a little longer for an update.
I created a new entity in an entity folder where I already have some other used entity. However this new entity is not detected by doctrine:mapping:info nor doctrine:schema:validate
It appears the file is simply not taken into account (if I write an error inside symfony is executed without issue).
I was thinking about a VM system issue but then I tried to create other new files (such as a new YML,a new symfony form) and it works...
I also cleared the cache:clear and doctrine:cache:all options
here is the class:
<?php
namespace NRtworks\BusinessDimensionBundle\Entity;
use Doctrine\ORM\Mapping as ORM;
use Doctrine\Common\Collections\ArrayCollection;
use JsonSerializable;
use Symfony\Component\Validator\Constraints as Assert;
/**
* #ORM\Table(name="test")
*/
class test implements JsonSerializable
{
/**
* #ORM\Id
* #ORM\Column(type="integer")
* #ORM\GeneratedValue(strategy="AUTO")
*/
protected $id;
/**
* #ORM\Column(type="string", length=50)
*/
protected $name;
/**
* #ORM\Column(type="string", length=100)
*/
protected $description;
/**
* Constructor
*/
public function __construct($id = NULL)
{
$this->id = $id;
$this->name = "Chart Of Accounts";
$this->description = "Default chart of accounts";
}
//this method returns an array with default values
public function getDefaultObject()
{
$result = Array();
$result['id'] = $this->id;
$result['name'] = $this->name;
$result['description'] = $this->code;
return $result;
}
/**
* Get id
*
* #return integer
*/
public function getId()
{
return $this->id;
}
public function setId($id)
{
$this->id = $id;
}
/**
* Set name
*/
public function setName($name)
{
$this->name = $name;
}
/**
* Get name
*/
public function getName()
{
return $this->name;
}
/**
* Set code
*/
public function setDescription($description)
{
$this->description = $description;
}
/**
* Get description
*/
public function getDescription()
{
return $this->description;
}
public function jsonSerialize()
{
return array(
'id' => $this->id,
'name' => $this->name,
'description' => $this->description
);
}
}
?>
where could this come from ?
You need to define #ORM\Entity annotation for your class:
/**
* #ORM\Entity
* #ORM\Table(name="test")
*/
class test implements JsonSerializable
{