A MappedSuperclass:
/** #ORM\MappedSuperclass */
abstract class AbstractMessage
{
/** #ORM\Column(type="text", nullable=true) */
protected $content;
}
And a child subclass, redefining $content to add some custom validation asserts:
/** #ORM\Entity */
class InternalMessage extends AbstractMessage
{
/**
* #ORM\Id
* #ORM\Column(type="integer")
* #ORM\GeneratedValue(strategy="AUTO")
*/
protected $id;
/**
* #Assert\NotBlank(message="Internal message title is required.")
*/
protected $content;
/** #return integer */
public function getId() { return $this->id; }
/**
* #param string $content
* #return InternalMessage
*/
public function setContent($content)
{
$this->content = $content;
return $this;
}
/** #return string */
public function getContent() { return $this->content; }
}
When $content override parent
As in my example, $content is not persisted! null field...
Removing $content from the child
If i remove $content from InternalMessage field is persisted, while validation does not work anymore.
Is this a bug or something? I opened an issue but don't know if it's the right place (i'm new to how github works).
Too bad i realized that Doctrine inheritance is buggy (starting from the generator itself...).
Afaik, it can't be done, Doctrine2 inheritance strategy doesn't allow you to re-define properties from MappedSuperClass.
This should be limitation due the Reflection system.
Your best bet is to extract your validation configuration and move it to a standalone XML or YML file.
You may also want to do it for your ORM (but you'll need to do it for all entities in your Bundle) as it will allow you more flexibility.
Related
I am making a web app using Symfony 4.
The app has (among others) a User entity, Post entity, and a PostLike entity. A user can create many posts, and a post can have many likes. So PostLike references User and Post. Below is my PostLike entity:
<?php
namespace App\Entity;
use Doctrine\ORM\Mapping as ORM;
use Gedmo\Mapping\Annotation as Gedmo;
/**
* #ORM\Entity(repositoryClass="App\Repository\PostLikeRepository")
*/
class PostLike
{
/**
* #ORM\Id()
* #ORM\GeneratedValue(strategy="AUTO")
* #ORM\Column(type="integer")
*/
private $id;
/**
* #ORM\ManyToOne(targetEntity="App\Entity\User", inversedBy="postLikes")
* #ORM\JoinColumn(nullable=true)
*/
private $user;
/**
* #ORM\ManyToOne(targetEntity="App\Entity\Post", inversedBy="postLikes")
* #ORM\JoinColumn(nullable=true)
*/
private $post;
/**
* #Gedmo\Timestampable(on="create")
* #ORM\Column(type="datetime")
*/
private $createdAt;
/**
* #return mixed
*/
public function getId()
{
return $this->id;
}
/**
* #param mixed $id
*/
public function setId($id): void
{
$this->id = $id;
}
/**
* #return mixed
*/
public function getUser()
{
return $this->user;
}
/**
* #param mixed $user
*/
public function setUser($user): void
{
$this->user = $user;
}
/**
* #return mixed
*/
public function getPost()
{
return $this->post;
}
/**
* #param mixed $post
*/
public function setPost($post): void
{
$this->post = $post;
}
public function getCreatedAt()
{
return $this->createdAt;
}
}
When I am on the view page for an individual post, how would I reference whether a user has liked this post in TWIG? This will be the ‘many’ side of the relationship, but I just need one row (if it exists), and I’m not sure how to do this...
TIA.
In the controller you can check whether such PostLike with such user and post exist or not and pass it to the view:
$liked = false;
$postLike = $this->getDoctrine()->getManager()->getRepository('AppBundle:PostLike')->findOneBy(['user'=>$user->getId(),'post'=>$post->getId()]);
if($postLike !== null){
$liked = true;
}
If you want to simply show whether Likes exist you can add a field to the Post entity:
public function hasLikes()
{
return (0 === count($this->likes)) ? false : true;
}
and include in twig something like {% if post.hasLikes %}Liked{% endif %}.
You could do something similar with a count and a badge to show the number of likes.
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() }}
I have "Project" entity which can have several Benefits, while a benefit belongs to just one Project:
To me it seems a many to one - one to many relationship.
I followed the indication here
The Project entity is:
/**
* #ORM\Entity(repositoryClass="AppBundle\Entity\ProjectRepository")
* #ORM\Table(name="projects")
*/
class Project
{
/**
* #ORM\Column(type="integer")
* #ORM\Id
* #ORM\GeneratedValue(strategy="AUTO")
*/
protected $id;
/**
* #ORM\oneToMany(targetEntity="Benefit", mappedBy="project")
*/
protected $benefits;
/**
* Constructor
*/
public function __construct()
{
$this->benefits = new \Doctrine\Common\Collections\ArrayCollection();
}
// other stuff
/**
* Add benefits
*
* #param \AppBundle\Entity\Benefit $benefits
* #return Project
*/
public function addBenefit(\AppBundle\Entity\Benefit $benefits)
{
$this->benefits[] = $benefits;
return $this;
}
/**
* Remove benefits
*
* #param \AppBundle\Entity\Benefit $benefits
*/
public function removeBenefit(\AppBundle\Entity\Benefit $benefits)
{
$this->benefits->removeElement($benefits);
}
/**
* Get benefits
*
* #return \Doctrine\Common\Collections\Collection
*/
public function getBenefits()
{
return $this->benefits;
}
}
The benefit entity is:
/**
* #ORM\Entity(repositoryClass="AppBundle\Entity\BenefitRepository")
* #ORM\Table(name="benefits")
*/
class Benefit
{
/**
* #ORM\Column(type="integer")
* #ORM\Id
* #ORM\GeneratedValue(strategy="AUTO")
*/
protected $id;
// Other relevant fields
/**
* #ORM\ManyToOne(targetEntity="Project", inversedBy="benefits")
*/
protected $project;
In my controller I was hoping to do:
$project = $em->getRepository('AppBundle:Project')->findOneById(3);
$benefit = new Benefit();
// set some fields for the new Benefit
$benefit->setProject($project);
$em->persist($benefit);
and I was hoping to see the benefits as a collection inside the project entity doing:
$benefits = $project->getBenefits();
But it did not work, so I explicitely did:
$project->addBenefit($benefit);
$em->persist($project);
$benefits = $project->getBenefits();
And I indeed see new the newly created Benefit inside the collection inside project. The problem is that if I rerun this and add a new benefit to the same project, I just get the last one. Of course if in the same portion of code I create 2 benefits and add both, I have a collection of 2, but that's not what I want. On the Benefit side everything is ok: each new Benefit is persisted, all of them correctly pointing to the same Project.
What am I missing?
EDIT:
Here are the steps I make/stuff I checked:
The DB is in sync with the current entity metadata.
The updated Project entity is:
<?php
// src/AppBundle/Entity/Project.php
namespace AppBundle\Entity;
use Doctrine\ORM\Mapping as ORM;
/**
* #ORM\Entity(repositoryClass="AppBundle\Entity\ProjectRepository")
* #ORM\Table(name="projects")
*/
class Project
{
/**
* #ORM\Column(type="integer")
* #ORM\Id
* #ORM\GeneratedValue(strategy="AUTO")
*/
protected $id;
/**
* #ORM\oneToMany(targetEntity="Benefit", mappedBy="project", cascade="persist")
*/
protected $benefits;
/**
* Constructor
*/
public function __construct()
{
$this->benefits = new \Doctrine\Common\Collections\ArrayCollection();
}
// Other irrelevant fields
/**
* Add benefits
*
* #param \AppBundle\Entity\Benefit $benefit
* #return Project
*/
public function addBenefit(\AppBundle\Entity\Benefit $benefit)
{
$this->benefits[] = $benefit;
$benefit->setProject($this);
return $this;
}
/**
* Remove benefits
*
* #param \AppBundle\Entity\Benefit $benefits
*/
public function removeBenefit(\AppBundle\Entity\Benefit $benefits)
{
$this->benefits->removeElement($benefits);
}
/**
* Get benefits
*
* #return \Doctrine\Common\Collections\Collection
*/
public function getBenefits()
{
return $this->benefits;
}
}
Note that the removeBenefit is probably not properly implemented, but for the moment it's not relevant.
I clean the Benefit table.
I create a new benefit and attach to the a Project:
$em = $this->getDoctrine()->getManager();
$project = $em->getRepository('AppBundle:Project')->findOneById(3);
$benefit = new Benefit();
$benefit->setName('Name of the benefit');
// here I set other irrelevant fields
$project->addBenefit($benefit);
$em->persist($project);
$em->flush();
The Benefit gets properly persisted to DB. It properly links to the Project:
I then comment all the code in the controller and simply perform:
$em = $this->getDoctrine()->getManager();
$project = $em->getRepository('AppBundle:Project')->findOneById(3);
$benefits = $project->getBenefits();
return $this->render('testBenefits.html.twig', array(
'benefits' => $benefits, 'project' => $project));
If I dump $project I get:
And of course if I dump $benefits I get this:
You are not setting project in your benefit class.
public function addBenefit(\AppBundle\Entity\Benefit $benefit)
{
$this->benefits[] = $benefit;
$benefit->setProject($this); // Add this
return $this;
}
Noticed that I also changed your argument from benefits to benefit as addBenefit deals with one benefit object at a time.
I'm trying to extend the default Page class from symfony simple cms bundle.
The problem:
The custom property is not persisted.
Below is the code of the class which extends from BasePage.
use Doctrine\ODM\PHPCR\Mapping\Annotations\Document;
use Doctrine\ODM\PHPCR\Mapping\Annotations as PHPCRODM;
use Symfony\Cmf\Bundle\SimpleCmsBundle\Doctrine\Phpcr\Page as BasePage;
/**
* {#inheritDoc}
* #PHPCRODM\Document(referenceable=true)
*/
class Product extends BasePage
{
public $node;
/**
* #var string(nullable=true)
*/
private $code;
/**
* Get Code
* #return string
*/
public function getCode()
{
return $this->code;
}
/**
* Set code
* #return Product
*/
public function setCode($code)
{
$this->code = $code;
return $this;
}
}
This looks almost correct, but you miss a mapping on the $code:
/**
* #PHPCRODM\String(nullable=true)
*/
private $code;
I assume that $code is not language dependant. Otherwise you would need nullable=true,translatable=true
If you also want to have the PHPCR node mapped, you need
/**
* #PHPCRODM\Node
*/
public $node;
I trying to create CRUD panel from FOSUserBundle but i have some troubles. I mean that i created User entity for FOS and made crud panel for this entity. Now when i trying to add new user i have error like below
Neither the property "expiresAt" nor one of the methods "getExpiresAt()", "isExpiresAt()", "hasExpiresAt()", "_get()" or "_call()" exist and have public access in class "Bn\UserBundle\Entity\User".
It's my first project so please understand when i will ask for simple function, some suggestion ? What is wrong ?
<?php
namespace Bn\UserBundle\Entity;
use FOS\UserBundle\Model\User as BaseUser;
use Doctrine\ORM\Mapping as ORM;
/**
* User
*
* #ORM\Table(name="fos_user")
* #ORM\Entity
*/
class User extends BaseUser
{
/**
* #ORM\Id
* #ORM\Column(type="integer")
* #ORM\GeneratedValue(strategy="AUTO")
*/
protected $id;
/**
* Get expiresAt
*
* #return \DateTime
*/
public function getExpiresAt()
{
return $this->expiresAt;
}
/**
* Get credentials_expire_at
*
* #return \DateTime
*/
public function getCredentialsExpireAt()
{
return $this->credentialsExpireAt;
}
public function __construct()
{
parent::__construct();
// your own logic
}
/**
* Get id
*
* #return integer
*/
public function getId()
{
return $this->id;
}
}
Now is working but i don't know why i must declare again function for getter.
I believe this means you need to add public accessors setExpiresAt() and getExpiresAt() to your User entity.
You need only add getExpiresAt to your User.php class. FOSUserBundle\User doesn't have getter for this field, but Sensio generator creates views for all fields.
public function getExpiresAt()
{
return $this->expiresAt;
}