0

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.

19
  • BaseFoodType::budget should be protected not public ... show us your entities Drink and Snack please Commented Jul 1, 2013 at 16:03
  • nifr: The property is set to public for a reason. Commented Jul 1, 2013 at 16:04
  • the reason of having public properties instead of getters/setters against conventions? Commented Jul 1, 2013 at 16:05
  • Because I'm using php's "get_object_vars" function to get all of the public properties of my classes for auto-magic display in the UI. Commented Jul 1, 2013 at 16:07
  • This can be solved better but let's leave out this discussion for now. provide your entities and phrase a clear question please :) Commented Jul 1, 2013 at 16:08

2 Answers 2

3

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

Sign up to request clarification or add additional context in comments.

Comments

0

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.

Comments

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service and acknowledge you have read our privacy policy.

Start asking to get answers

Find the answer to your question by asking.

Ask question

Explore related questions

See similar questions with these tags.