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?
Related
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
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');
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
I have two entities one is main and second is additional, they joined as OneToOne. I won't show all, i think it's not necessary:
apiKey
/**
* #ORM\OneToOne(targetEntity="Eve\ApiBundle\Entity\Account\apiKeyInfo", inversedBy="apiKey_byKeyID")
* #ORM\JoinColumn(name="keyID", referencedColumnName="keyID")
*/
private $apiKeyInfo_byKeyID;
public function get_apiKeyInfo_byKeyID()
{
return $this->apiKeyInfo_byKeyID;
}
apiKeyInfo
/**
* #ORM\OneToOne(targetEntity="Eve\ProfileBundle\Entity\apiKey", mappedBy="apiKeyInfo_byKeyID")
*/
private $apiKey_byKeyID;
public function get_apiKey_byKeyID()
{
return $this->apiKey_byKeyID;
}
/**
* #ORM\Column(name="type", type="string", length=255)
*/
private $type;
/**
* #param string $type
* #return apiKeyInfo
*/
public function setType($type)
{
$this->type = $type;
return $this;
}
/**
* #return string
*/
public function getType()
{
return $this->type;
}
Relation i call in twig (apiKey is goten in php part)
apiKey.get_apiKeyInfo_byKeyID.type
It works fine when db tables is full of data, but when "apiKeyInfo" table doesn't have same keyID it throw me exception:
Entity was not found.
I understand why, because it cannot find entries with same keyID ... But i don't know how to deal with it.
So question is...
How can i make result, of this relation, can be null?
Trouble was in incorrect descriptions of entity. Problem solved.
File based translations don't work for me because clients need to change the texts.
So I am thinking about implementing this interface to fetch data from the database and cache the results in an APC cache.
Is this a good solution?
This could be what you are looking for:
Use a database as a translation provider in Symfony 2
Introduction
This article explain how to use a database as translation storage in Symfony 2. Using a database to provide translations is quite easy to do in Symfony 2, but unfortunately it’s actually not explained in Symfony 2 website.
Creating language entities
At first, we have to create database entities for language management. In my case, I’ve created three entities : the Language entity contain every available languages (like french, english, german).
The second entity is named LanguageToken. It represent every available language tokens. The token entity represent the source tag of the xliff files. Every translatable text available is a token. For example, I use home_page as a token and it’s translated as Page principale in french and as Home page in english.
The last entity is the LanguageTranslation entity : it contain the translation of a token in a specific language. In the example below, the Page principale is a LanguageTranslation entity for the language french and the token home_page.
It’s quite inefficient, but the translations are cached in a file by Symfony 2, finally it’s used only one time at Symfony 2 first execution (except if you delete Symfony 2’s cache files).
The code of the Language entity is visible here :
/**
* #ORM\Entity(repositoryClass="YourApp\YourBundle\Repository\LanguageRepository")
*/
class Language {
/**
* #ORM\Id
* #ORM\Column(type="integer")
* #ORM\GeneratedValue
*/
private $id;
/** #ORM\column(type="string", length=200) */
private $locale;
/** #ORM\column(type="string", length=200) */
private $name;
public function getId() {
return $this->id;
}
public function setId($id) {
$this->id = $id;
}
public function getLocale() {
return $this->locale;
}
public function setLocale($locale) {
$this->locale = $locale;
}
public function getName() {
return $this->name;
}
public function setName($name) {
$this->name = $name;
}
}
The code of the LanguageToken entity is visible here :
/**
* #ORM\Entity(repositoryClass="YourApp\YourBundle\Repository\LanguageTokenRepository")
*/
class LanguageToken {
/**
* #ORM\Id #ORM\Column(type="integer")
* #ORM\GeneratedValue
*/
private $id;
/** #ORM\column(type="string", length=200, unique=true) */
private $token;
public function getId() {
return $this->id;
}
public function setId($id) {
$this->id = $id;
}
public function getToken() {
return $this->token;
}
public function setToken($token) {
$this->token = $token;
}
}
And the LanguageTranslation entity’s code is visible here :
/**
* #ORM\Entity(repositoryClass="YourApp\YourBundle\Repository\LanguageTranslationRepository")
*/
class LanguageTranslation {
/**
* #ORM\Id #ORM\Column(type="integer")
* #ORM\GeneratedValue
*/
private $id;
/** #ORM\column(type="string", length=200) */
private $catalogue;
/** #ORM\column(type="text") */
private $translation;
/**
* #ORM\ManyToOne(targetEntity="YourApp\YourBundle\Entity\Language", fetch="EAGER")
*/
private $language;
/**
* #ORM\ManyToOne(targetEntity="YourApp\YourBundle\Entity\LanguageToken", fetch="EAGER")
*/
private $languageToken;
public function getId() {
return $this->id;
}
public function setId($id) {
$this->id = $id;
}
public function getCatalogue() {
return $this->catalogue;
}
public function setCatalogue($catalogue) {
$this->catalogue = $catalogue;
}
public function getTranslation() {
return $this->translation;
}
public function setTranslation($translation) {
$this->translation = $translation;
}
public function getLanguage() {
return $this->language;
}
public function setLanguage($language) {
$this->language = $language;
}
public function getLanguageToken() {
return $this->languageToken;
}
public function setLanguageToken($languageToken) {
$this->languageToken = $languageToken;
}
}
Implementing a LoaderInterface
The second step is to create a class implementing the Symfony\Component\Translation\Loader\LoaderInterface. The corresponding class is shown here :
class DBLoader implements LoaderInterface{
private $transaltionRepository;
private $languageRepository;
/**
* #param EntityManager $entityManager
*/
public function __construct(EntityManager $entityManager){
$this->transaltionRepository = $entityManager->getRepository("AppCommonBundle:LanguageTranslation");
$this->languageRepository = $entityManager->getRepository("AppCommonBundle:Language");
}
function load($resource, $locale, $domain = 'messages'){
//Load on the db for the specified local
$language = $this->languageRepository->getLanguage($locale);
$translations = $this->transaltionRepository->getTranslations($language, $domain);
$catalogue = new MessageCatalogue($locale);
/**#var $translation Frtrains\CommonbBundle\Entity\LanguageTranslation */
foreach($translations as $translation){
$catalogue->set($translation->getLanguageToken()->getToken(), $translation->getTranslation(), $domain);
}
return $catalogue;
}
}
The DBLoader class need to have every translations from the LanguageTranslationRepository (the translationRepository member). The getTranslations($language, $domain) method of the translationRepository object is visible here :
class LanguageTranslationRepository extends EntityRepository {
/**
* Return all translations for specified token
* #param type $token
* #param type $domain
*/
public function getTranslations($language, $catalogue = "messages"){
$query = $this->getEntityManager()->createQuery("SELECT t FROM AppCommonBundle:LanguageTranslation t WHERE t.language = :language AND t.catalogue = :catalogue");
$query->setParameter("language", $language);
$query->setParameter("catalogue", $catalogue);
return $query->getResult();
}
...
}
The DBLoader class will be created by Symfony as a service, receiving an EntityManager as constructor argument. All arguments of the load method let you customize the way the translation loader interface work.
Create a Symfony service with DBLoader
The third step is to create a service using the previously created class. The code to add to the config.yml file is here :
services:
translation.loader.db:
class: MyApp\CommonBundle\Services\DBLoader
arguments: [#doctrine.orm.entity_manager]
tags:
- { name: translation.loader, alias: db}
The transation.loader tag indicate to Symfony to use this translation loader for the db alias.
Create fake translation files
The last step is to create an app/Resources/translations/messages.xx.db file for every translation (with xx = en, fr, de, …).
I didn’t found the way to notify Symfony to use DBLoader as default translation loader. The only quick hack I’ve found is to create a app/Resources/translations/messages.en.db file. The db extension correspond to the db alias used in the service declaration. A corresponding file is created for every language available on the website, like messages.fr.db for french or messages.de.db for german.
When Symfony find the messages.xx.db file he load the translation.loader.db to manage this unknown extension and then the DBLoader use database content to provide translation.
I’ve also didn’t found the way to clean properly the translations cache on database modification (the cache have to be cleaned to force Symfony to recreate it). The code I actually use is visible here :
/**
* Remove language in every cache directories
*/
private function clearLanguageCache(){
$cacheDir = __DIR__ . "/../../../../app/cache";
$finder = new \Symfony\Component\Finder\Finder();
//TODO quick hack...
$finder->in(array($cacheDir . "/dev/translations", $cacheDir . "/prod/translations"))->files();
foreach($finder as $file){
unlink($file->getRealpath());
}
}
This solution isn’t the pretiest one (I will update this post if I find better solution) but it’s working ^^
Be Sociable, Share!
Take a look at the Translatable behavior extension for Doctrine 2. StofDoctrineExtensionsBundle integrates it with Symfony.
You may want to take a look into this Loader + Resource using PDO connection: https://gist.github.com/3315472
You then only need to make it cache aware, like adding a memcache, apc, .. in between.
If so, you can then disable the filecaching of the Translator itself.