Knp\DoctrineBehaviors + a2lix_translations + Sonata Admin - symfony

I set up my entities same as https://github.com/KnpLabs/DoctrineBehaviors#translatable . Also configs same as http://a2lix.fr/bundles/translation-form/ . Also I add __call method and try to implement How to print translatable data in sonata admin with DoctrineBehaviors from kpnlabs. First I get error that $name doesn't exist at Category.php. So I add it, now I have error:
Neither the property "name" nor one of the methods "addName()"/"removeName()", "setName()", "name()", "__set()" or "__call()" exist and have public access in class.
Question is how they remove setters/getters from main Entity, for me it's caused errors. Maybe someone have proper magic for all of this?
Category.php
class MyClass
{
use \Knp\DoctrineBehaviors\Model\Translatable\Translatable;
private $name; //added after error
public function __call($method, $arguments)
{
return $this->proxyCurrentLocaleTranslation($method, $arguments);
}
public function getName() {
return $this->translate()->getName(); //added after error
}
#public function getName() {
# return ($this->getTranslations()); // also trying like this
#}
// ...
CategoryTranslation.php
use Doctrine\ORM\Mapping as ORM;
use Knp\DoctrineBehaviors\Model as ORMBehaviors;
/**
* CategoryTranslation
*/
class CategoryTranslation
{
use ORMBehaviors\Translatable\Translation;
/**
* #var string
*/
private $name;
/**
* Set name
*
* #param string $name
* #return CategoryTranslation
*/
public function setName($name)
{
$this->name = $name;
return $this;
}
/**
* Get name
*
* #return string
*/
public function getName()
{
return $this->name;
}
}
In sonata category admin:
$formMapper->add('name', 'a2lix_translations');
When I added my category(access message) I see in database "name" looks like
Doctrine\Common\Collections\ArrayCollection#000000006cb11474000000002980d54f

Remove setters and getters from main class then doctrine:schema:update.
Also in sonata category admin:
$formMapper->add('translations', 'a2lix_translations');

Related

How to use Symfony EasyAdmin with entities having a constructor?

Does EasyAdmin support entity classes with constructor arguments for properties that are meant to be not nullable? EasyAdmin instantiates the entity class even if you click the "Add " button, right? Unfortunatelly this results in an "Too few arguments to function __construct()" error. Do you have a solution for this problem?
I tend to use the constructor for entity properties that are not nullable. Unfortunatelly EasyAdmin throws errors like this one when I click on the e.g. Add FiscalYear button to create a new entity object (FiscalYear in my example):
Too few arguments to function App\Entity\FiscalYear::__construct(), 0 passed in /myProject/vendor/easycorp/easyadmin-bundle/src/Controller/AdminControllerTrait.php on line 618 and exactly 2 expected
How can I prevent these errors? As you can see in the following entity class the two constructor arguments represent the data that is meant to be submitted via the form:
<?php
declare(strict_types=1);
namespace App\Entity;
use Doctrine\ORM\Mapping as ORM;
/**
* #ORM\Entity(repositoryClass="App\Repository\FiscalYearRepository")
*/
class FiscalYear
{
/**
* #ORM\Id()
* #ORM\GeneratedValue()
* #ORM\Column(type="integer")
*/
private ?int $id = null;
/**
* #ORM\Column(type="integer")
*/
private int $title;
/**
* #ORM\Column(type="boolean", options={"default": 0})
*/
private bool $completed = false;
/**
* #ORM\ManyToOne(targetEntity="App\Entity\Company", inversedBy="fiscalYears")
* #ORM\JoinColumn(nullable=false)
*/
private Company $company;
public function __construct(int $title, Company $company)
{
$this->title = $title;
$this->company = $company;
}
public function getId(): ?int
{
return $this->id;
}
public function getTitle(): int
{
return $this->title;
}
public function setTitle(int $title): void
{
$this->title = $title;
}
public function getCompleted(): bool
{
return $this->completed;
}
public function setCompleted(bool $completed): void
{
$this->completed = $completed;
}
public function getCompany(): Company
{
return $this->company;
}
public function setCompany(Company $company): void
{
$this->company = $company;
}
}
Is there a possibility to let EasyAdmin show the "create a new entity object" form without instantiating the entity class?
No, EasyAdmin doesn't natively support constructor with argument.
To avoid this problem, you have three solution.
solution1: Override EasyAdminController
The documentation explains this method.
// src/Controller/AdminController.php
namespace App\Controller;
use EasyCorp\Bundle\EasyAdminBundle\Controller\EasyAdminController;
class FiscalYearController extends EasyAdminController
{
public function createNewFiscalYearEntity()
{
//your own logic here to retrieve title and company
return new FiscalYear($title, $company);
}
}
Depending you business model, it could be very difficult to retrieve title and company
solution2: Respect the entity pattern and help your business model with a factory pattern
Your entities should respect the entity pattern and their constructor should be edited to remove arguments.
To replace your constructor in your business model, create a factory.
class FiscalYearFactory
{
public static function create(int $title, Company $company): FiscalYear
{
$fiscalYear = new FiscalYear();
$fiscalYear->setCompany($company);
$fiscalYear->setTitle($title);
return $fiscalYear;
}
}
in your model, you have to do some updates:
//Comment code like this in your business model
$fiscalYear = new FiscalYear(2020,$company);
//Replace it, by this code:
$fiscalYear = FiscalYearFactory::create(2020,$company);
Solution3 Accept null values in your constructor.
I do NOT like this solution. Your properties shall be edited too to accept null values, your getters shall be edited to return null value. This is a solution, but I discourage you to use it.
<?php
declare(strict_types=1);
namespace App\Entity;
use Doctrine\ORM\Mapping as ORM;
/**
* #ORM\Entity(repositoryClass="App\Repository\FiscalYearRepository")
*/
class FiscalYear
{
/**
* #ORM\Id()
* #ORM\GeneratedValue()
* #ORM\Column(type="integer")
*/
private ?int $id = null;
/**
* #ORM\Column(type="integer")
*/
private ?int $title;
/**
* #ORM\Column(type="boolean", options={"default": 0})
*/
private bool $completed = false;
/**
* #ORM\ManyToOne(targetEntity="App\Entity\Company", inversedBy="fiscalYears")
* #ORM\JoinColumn(nullable=false)
*/
private Company $company;
public function __construct(?int $title = null, ?Company $company = null)
{
$this->title = $title;
$this->company = $company;
}
public function getId(): ?int
{
return $this->id;
}
public function getTitle(): ?int
{
return $this->title;
}
You should use the first solution which is a better practice

How to create an entity with several dates in a single form with EasyAdmin

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:

Weird Doctrine ODM exception when using references together with inheritance

I've got three classes. The File-Class has a reference to Foobar and Game inherits from Foobar. There are some other Classes which also inherit from Foobar but i left them out as they aren't relevant here. I also left out some unrelevant fields and their getters and setters.
The plan is that every Game has two images, the mainImage and the secondaryImage. I've put those fields into a seperate class from which Game inherits because i need them for a few other classes too.
My problem is that if I load the games from the database as soon as i try to iterate over them I get the following exception:
Notice: Undefined index: in C:\xampp\htdocs\Symfony\vendor\doctrine\mongodb-odm\lib\Doctrine\ODM\MongoDB\Mapping\ClassMetadataInfo.php line 1293
For reference here are the lines of ClassMetadataInfo.php
public function getPHPIdentifierValue($id)
{
$idType = $this->fieldMappings[$this->identifier]['type'];
return Type::getType($idType)->convertToPHPValue($id);
}
Here are my classes
File-Class:
namespace Project\MainBundle\Document;
use Doctrine\ODM\MongoDB\Mapping\Annotations as MongoDB;
/**
* #MongoDB\Document
*/
class File
{
/**
* #MongoDB\Id(strategy="INCREMENT")
*/
protected $id;
/**
* #MongoDB\ReferenceOne(targetDocument="Foobar", inversedBy="mainImage")
*/
private $mainImage;
/**
* #MongoDB\ReferenceOne(targetDocument="Foobar", inversedBy="secondaryImage")
*/
private $secondaryImage;
/**
* Get id
*/
public function getId()
{
return $this->id;
}
public function setMainImage($mainImage)
{
$this->mainImage = $mainImage;
return $this;
}
public function getMainImage()
{
return $this->mainImage;
}
public function setSecondaryImage($secondaryImage)
{
$this->secondaryImage = $secondaryImage;
return $this;
}
public function getSecondaryImage()
{
return $this->secondaryImage;
}
}
Foobar-Class:
namespace Project\MainBundle\Document;
use Doctrine\ODM\MongoDB\Mapping\Annotations as MongoDB;
/**
* #MongoDB\MappedSuperclass
*/
abstract class Foobar
{
/**
* #MongoDB\Id(strategy="INCREMENT")
*/
protected $id;
/**
* #MongoDB\ReferenceOne(targetDocument="File", mappedBy="mainImage")
*/
protected $mainImage;
/**
* #MongoDB\ReferenceOne(targetDocument="File", mappedBy="secondaryImage")
*/
protected $secondaryImage;
/**
* Get id
*/
public function getId()
{
return $this->id;
}
/**
* Set mainImage
*/
public function setMainImage($file)
{
$file->setMainImage($this);
$this->mainImage = $file;
return $this;
}
/**
* Get mainImage
*/
public function getMainImage()
{
return $this->mainImage;
}
/**
* Set secondaryImage
*/
public function setSecondaryImage($file)
{
$file->setSecondaryImage($this);
$this->secondaryImage = $file;
return $this;
}
/**
* Get secondaryImage
*/
public function getSecondaryImage()
{
return $this->secondaryImage;
}
}
Game-Class:
namespace Project\MainBundle\Document;
use Doctrine\ODM\MongoDB\Mapping\Annotations as MongoDB;
/**
* #MongoDB\Document
*/
class Game extends Foobar
{
/**
* #MongoDB\String
*/
private $name;
/**
* Set name
*/
public function setName($name)
{
$this->name = $name;
return $this;
}
/**
* Get name
*/
public function getName()
{
return $this->name;
}
}
Though it doesn't really matter but here is my function i want to execute:
$dm = $this->get('doctrine_mongodb')->getManager();
$games_all = $dm->getRepository("ProjectMainBundle:Game")->createQueryBuilder()->sort('id', 'ASC')->getQuery()->execute();
foreach ($games_all as $singlegame) { // it breaks here
// Here i would do stuff
}
Is this a bug in Doctrine ODM or am I doing something wrong? Are the classes correct? I have tried everything but it just wont work.
I think it is too late for your question, but maybe there are other users having the same problem (as me).
The problem is related to Foobar being a MappedSuperclass. Had the same problem as described by you and at https://github.com/doctrine/mongodb-odm/issues/241.
Solution is to not reference the abstract class Foobar (=MappedSuperclass) but a concrete implementation (=Document) - as in your case - Game.
See also Doctrine ODM returns proxy object for base class instead of sub-classed document

Getters and setters using Doctrine 2

I have an Entity i.e Users. I want to make getters and setters of this entity in Doctrine, so that Doctrine can read it.
How can I do it, can someone provide me basic example? I am a beginner
How to insert data in this database table?
Here is my Users entity
<?php
/**
* #Entity
* #Table(name="users")
* Total Number of Columns : 32
*/
class Users{
/* Attributes of Users */
/**
* #Id
* #Column(type="integer")
* #GeneratedValue
* #dummy
* #Assert\NotEmpty
*/
private $id;
/**
* #Column(type="string")
* #Assert\NotEmpty
*/
private $name;
/**
* #Column(type="string")
* #Assert\NotEmpty
*/
private $email;
}
?>
Try with this command:
php app/console doctrine:generate:entities YourBundle:YourEntity
For example, if you wanted to have a setter for your email property, you would do:
public function setEmail($email)
{
$this->email = $email;
return $this;
}
public function getEmail()
{
return $this->email;
}
The first is the setter (it sets the value of email on the object) and the second is the getter (it gets the value of email from the object). Hope that helps :)
You can use magic methods if you're lazy enough not to define your own methods for each property.
public function __get($property)
{
return $this->$property;
}
public function __set($property,$value)
{
$this->$property = $value;
}
It's better to create a method for each property though
public function getName()
{
return $this->name;
}
public function setName($name)
{
$this->name = $name;
}
Have a look at the answers here Doctrine 2 Whats the Recommended Way to Access Properties?

Adding a file upload to a Symfony2 form throws an "Entity was not found" and a session is active error

I have a Symfony2 form that I want to add a file upload dialog to.
According to the Symfony docs (http://symfony.com/doc/2.0/cookbook/doctrine/file_uploads.html), I have created a Document class:
<?php
namespace Acme\AppBundle\Entity;
use Doctrine\ORM\Mapping as ORM;
use Symfony\Component\HttpFoundation\File\UploadedFile;
use Symfony\Component\Validator\Constraints as Assert;
/**
* #ORM\Entity
* #ORM\HasLifecycleCallbacks
*/
class Document
{
/**
* #ORM\Id
* #ORM\Column(type="integer")
* #ORM\GeneratedValue(strategy="AUTO")
*/
public $id;
/**
* #ORM\Column(type="string", length=255, nullable=true)
*/
public $path;
/**
* #Assert\File(maxSize="6000000")
*/
public $file;
public function getAbsolutePath()
{
return null === $this->path ? null : $this->getUploadRootDir().'/'.$this->path;
}
public function getWebPath()
{
return null === $this->path ? null : $this->getUploadDir().'/'.$this->path;
}
protected function getUploadRootDir()
{
// the absolute directory path where uploaded documents should be saved
return __DIR__.'/../../../../web/'.$this->getUploadDir();
}
protected function getUploadDir()
{
// get rid of the __DIR__ so it doesn't screw when displaying uploaded doc/image in the view.
return 'uploads/documents';
}
/**
* #ORM\PrePersist()
* #ORM\PreUpdate()
*/
public function preUpload()
{
if (null !== $this->file) {
$this->path = sha1(uniqid(mt_rand(), true)).'.'.$this->file->guessExtension();
}
}
/**
* #ORM\PostPersist()
* #ORM\PostUpdate()
*/
public function upload()
{
if (null === $this->file) {
return;
}
$this->file->move($this->getUploadRootDir(), $this->path);
unset($this->file);
}
/**
* #ORM\PostRemove()
*/
public function removeUpload()
{
if ($file = $this->getAbsolutePath()) {
unlink($file);
}
}
/**
* Get id
*
* #return integer
*/
public function getId()
{
return $this->id;
}
/**
* Set path
*
* #param string $path
*/
public function setPath($path)
{
$this->path = $path;
}
/**
* Get path
*
* #return string
*/
public function getPath()
{
return $this->path;
}
}
And a DocumentType form class:
<?php
namespace Acme\AppBundle\Form;
use Symfony\Component\Form\AbstractType;
use Symfony\Component\Form\FormBuilderInterface;
use Symfony\Component\OptionsResolver\OptionsResolverInterface;
class DocumentType extends AbstractType
{
public function buildForm(FormBuilderInterface $builder, array $options)
{
$builder
->add('file')
;
}
public function setDefaultOptions(OptionsResolverInterface $resolver)
{
$resolver->setDefaults(array(
'data_class' => 'Acme\AppBundle\Entity\Document',
));
}
public function getName()
{
return 'document_form';
}
}
However, when I add this to my existing entity and form class:
<?php
namespace Acme\AppBundle\Entity\Profile;
use Doctrine\ORM\Mapping as ORM;
use Acme\AppBundle\Entity\jDocument;
use Symfony\Component\Validator\Constraints as Assert;
/**
* #ORM\Entity
* #ORM\Table(name="UserProfile")
*/
class UserProfile extends GenericProfile
{
//... Other entity params
/**
* #ORM\OneToOne(cascade={"persist", "remove"}, targetEntity="Acme\AppBundle\Entity\Document")
* #ORM\JoinColumn(name="picture_id", referencedColumnName="id", onDelete="set null")
*/
protected $picture;
/**
* Set picture
*
* #param Acme\AppBundle\Entity\Document $picture
*/
//\Acme\AppBundle\Entity\Document
public function setPicture($picture)
{
$this->picture = $picture;
}
/**
* Get picture
*
* #return Acme\AppBundle\Entity\Document
*/
public function getPicture()
{
return $this->picture;
}
//... Other entity getters and setters
}
Whenever I submit the form, I get the following error:
ErrorException: Warning: ini_set(): A session is active. You cannot change the session module's ini settings at this time in /var/www/wolseley-integrated-services/builds/dev/app/cache/prod/classes.php line 421
But the page title is "Entity was not found. (500 Internal Server Error)".
Can anybody spot which entity it can't find? Or if that's even the issue?
I've done some googling and checked that session.auto_start is set to 0 in php.ini, I've cleared all my sessions and caches... I'm stumped!
It turns out I was getting this strange session error message because of an error page definition in my nginx config.
The entity not found issue was resolved by correcting some errors in my entities. The Symfony developer bar provided me with enough information to track the issue down.

Resources