Reputation: 1
I'm attempting to accomplish BASIC inheritance in Doctrine 2, but I'm running into several major issues. Such a task should not be so complicated. Let's get down to business...
I have three classes, BaseFoodType, Drink, and Snack. My BaseFoodType has the following class definition:
/** @ORM\MappedSuperclass */
class BaseFoodType {
/**
* @ORM\Column(type="integer", length=7)
*/
public $budget = 0;
}
Which follows the instructions for inheritance on the doctrine website: http://docs.doctrine-project.org/en/2.0.x/reference/inheritance-mapping.html
Here is what the sub-classes look like prior to generating my entities:
namespace MySite\MainBundle\Entity;
use Doctrine\ORM\Mapping as ORM;
/**
* MySite\MainBundle\Entity\EventDrink
*
* @ORM\Table(name="drink")
* @ORM\Entity
*/
class Drink extends BaseFoodType {
/**
* @ORM\Column(type="integer")
* @ORM\Id
* @ORM\GeneratedValue(strategy="AUTO")
*/
private $id;
/**
* @ORM\Column(type="integer", length=5, nullable=true)
*/
public $people_count;
}
Both Drink, and Snack inherit from this base class but I'm running into numerous issues when attempting to build my entities using the doctrine:generate:entities command. First, Symfony inserts a private "budget" property into each subclass, along with getters and setters (THIS DEFEATS THE PURPOSE INHERITANCE)
/**
* @var integer
*/
private $budget;
/**
* Set budget
*
* @param integer $budget
*/
public function setBudget($budget)
{
$this->budget = $budget;
return $this;
}
/**
* Get budget
*
* @return integer
*/
public function getBudget()
{
return $this->budget;
}
Second, I'm getting a fatal error:
Fatal error: Access level to MySite\MainBundle\Entity\Drink::$budget must be public (as in class MySite\MainBundle\Entity\BaseFoodType) in C:\xampp\htdocs\MySite\src\MySite\MainBundle\Entity\Drink.php on line 197
I could probably make the generated properties public and be on my way, but again, that defeats the purpose of inheritance!
Thanks in advance for any insight.
Upvotes: 0
Views: 1383
Reputation: 7891
Doctrine provides the means to specify the visibility of generated fields. Either protected or private. The default is private.
The problem is that the Symfony command that invokes Doctrine offers no way to change this.
Creating your own subclass of the standard Symfony command will allow you more control over the generation process. This might help you along.
namespace Foo\Bundle\FooBundle\Command;
use Doctrine\Bundle\DoctrineBundle\Command as DC;
use Doctrine\ORM\Tools\EntityGenerator;
class GenerateEntitiesDoctrineCommand extends DC\GenerateEntitiesDoctrineCommand
{
protected function configure()
{
parent::configure();
$this->setName('foo:generate:entities');
}
/**
* get a doctrine entity generator
*
* @return EntityGenerator
*/
protected function getEntityGenerator()
{
$entityGenerator = new EntityGenerator();
$entityGenerator->setGenerateAnnotations(true);
$entityGenerator->setGenerateStubMethods(true);
$entityGenerator->setRegenerateEntityIfExists(false);
$entityGenerator->setUpdateEntityIfExists(true);
$entityGenerator->setNumSpaces(4);
$entityGenerator->setAnnotationPrefix('ORM\\');
$entityGenerator->setFieldVisibility($entityGenerator::FIELD_VISIBLE_PROTECTED);
return $entityGenerator;
}
}
This does two things. It sets the property visibility to protected. This prevents php errors.
$entityGenerator->setFieldVisibility($entityGenerator::FIELD_VISIBLE_PROTECTED);
It also copies the annotations from mapped super class into the entity class.
$entityGenerator->setGenerateAnnotations(true);
Here's some example code where properties are inherited from a base class and their visibility and annotations copy correctly into the inheriting class
/** @ORM\MappedSuperclass */
class DataSuper {
/**
* @ORM\Id
* @ORM\Column(type="integer")
* @ORM\GeneratedValue(strategy="AUTO")
*/
protected $id;
/**
* @ORM\ManyToOne(targetEntity="Campaign", inversedBy="data")
* @ORM\JoinColumn(name="campaign_id", referencedColumnName="id")
* @Exclude
*/
protected $campaign;
/**
* @ORM\Column(type="text", nullable=true, name="data")
*/
protected $data;
/**
* @ORM\Column(type="datetime")
*/
protected $createdDate;
}
/**
* @ORM\Entity(repositoryClass="Foo\Bundle\FooBundle\Entity\DataRepository")
* @ORM\Table(name="data")
* @ExclusionPolicy("none")
*/
class Data extends DataSuper
{
}
After generation the Data class looks like:
class Data extends DataSuper
{
/**
* @var integer
*
* @ORM\Column(name="id", type="integer", precision=0, scale=0, nullable=false, unique=false)
* @ORM\Id
* @ORM\GeneratedValue(strategy="IDENTITY")
*/
protected $id;
/**
* @var string
*
* @ORM\Column(name="data", type="text", precision=0, scale=0, nullable=true, unique=false)
*/
protected $data;
/**
* @var \DateTime
*
* @ORM\Column(name="createdDate", type="datetime", precision=0, scale=0, nullable=false, unique=false)
*/
protected $createdDate;
/**
* @var \Foo\Bundle\FooBundle\Entity\Campaign
*
* @ORM\ManyToOne(targetEntity="Foo\Bundle\FooBundle\Entity\Campaign", inversedBy="data")
* @ORM\JoinColumns({
* @ORM\JoinColumn(name="campaign_id", referencedColumnName="id", nullable=true)
* })
*/
protected $campaign;
/**
* Get id
*
* @return integer
*/
public function getId()
{
return $this->id;
}
/**
* Set data
*
* @param string $data
* @return Data
*/
public function setData($data)
{
$this->data = $data;
return $this;
}
/**
* Get data
*
* @return string
*/
public function getData()
{
return $this->data;
}
/**
* Set createdDate
*
* @param \DateTime $createdDate
* @return Data
*/
public function setCreatedDate($createdDate)
{
$this->createdDate = $createdDate;
return $this;
}
/**
* Get createdDate
*
* @return \DateTime
*/
public function getCreatedDate()
{
return $this->createdDate;
}
/**
* Set campaign
*
* @param \Foo\Bundle\FooBundle\Entity\Campaign $campaign
* @return Data
*/
public function setCampaign(\Foo\Bundle\FooBundle\Entity\Campaign $campaign = null)
{
$this->campaign = $campaign;
return $this;
}
/**
* Get campaign
*
* @return \Foo\Bundle\FooBundle\Entity\Campaign
*/
public function getCampaign()
{
return $this->campaign;
}
}
And the table structure is correct once you do:
php app/console doctrine:schema:update --force
Upvotes: 3
Reputation: 52493
The exception is being thrown because BaseFoodType::budget
is a public property and doctrine:generate:entities
created a private property in your Drink / Snack classes extending BaseFoodType ( which is not correct but the way the command works by now ).
Property visibility in a subclass can only be the same level or more liberate ( private -> protected -> public ) but never more restrictive.
doctrine:generate:entities did not take superclass's public property into account when generating the getters/setters as the implementation with a public property is non-standard.
Therefore you will have to adjust the generated class manually.
I recommend using private/protected properties combined with getters & setters.
Upvotes: 0