1

I have several entities that are related, such as McDoublePrice, CheeseburgerPrice, BigMacPrice, etc, which have a common BurgerPrice entity in a Mapped Superclass Doctrine configuration.

How can I access a particular entity from my controller, action, or request handler? That is, I want the following code to work:

$burgers = array(
    'McDoublePrice' => 4,
    'CheeseburgerPrice' => 5, 
    'BigMacPrice' => 6
);
foreach($burgers as $burger => $burgerId)
{
    $repository = $this->repositoryFactory->getBurgerRepository($burger);
    $entity = $repository->getBurgerEntity($burgerId);
    echo $entity->getBurgerPrice(); // total price of current burger
}

Note: Different $burgerIds can represent different burger styles within the same type of burger. i.e. Deluxe version, plain version, etc and can use different specific prices for same type of ingredients. i.e. plain bun vs deluxe bun can have different $burderID and different bun pricing.

Question: How do I structure my code?

Do I create one repository class for each of the BurgerPrice entity? That might create a lot of extra classes and seems wasteful.

Can I get away with a single BurgerRepository class that can handle multiple burgers? If so, how would I set that up in a Doctrine-centric way?

I am looking for a code example or a clear idea on how to do this. I am interested primarily in Doctrine-centric or Doctrine-recommended approach to handling working with multiple polymorphic entities in a Mapped Superclass configuration.

My end goal is to be able to request pricing of specific burgers during runtime.

I have looked into Doctrine Entity Repository pattern, and the repository structure (where I can create custom method names) is something I would like to utilize, but the example does not show how to use it with polymorphic entities.

Sample Entities

/**
 * @ORM\Entity(repositoryClass="BurgerPriceRepository")
 * @Table(name="bigmac", indexes={@Index(name="product_id", columns={"product_id"})})
 * @Entity
 */
class BigMacPrice extends BurgerPrice
{
    /** @var float @Column(name="sauce", type="decimal", precision=6, scale=2, nullable=false) */
    private $sauce;

    function getBurgerPrice(): float
    {
        //secret sauce price
        return 5 * $this->patty + 3 * $this->bun + $this->sauce;
    }
}

/**
 * @ORM\Entity(repositoryClass="BurgerPriceRepository")
 * @Table(name="mcdouble", indexes={@Index(name="product_id", columns={"product_id"})})
 * @Entity
 */
class McDoublePrice extends BurgerPrice
{
    function getBurgerPrice(): float
    {
        //a different price for mcdouble
        return 2 * $this->patty + 2 * $this->bun;
    }
}

abstract class BurgerPrice
{
    /** @var integer @Column(name="id", type="integer", nullable=false) @Id @GeneratedValue(strategy="IDENTITY") */
    protected $id;

    /** @var integer @Column(name="product_id", type="integer", nullable=false) */
    protected $productId;

    /** @var float @Column(name="patty", type="decimal", precision=6, scale=2, nullable=false) */
    public $patty;

    /** @var float @Column(name="bun", type="decimal", precision=6, scale=2, nullable=false) */
    public $bun;

    /**
     * Computes specific product price for a given burger
     *
     * @return float
     */
    abstract function getBurgerPrice(): float;
}
Dennis
  • 7,907
  • 11
  • 65
  • 115
  • 1
    Please don't post these sorts of questions during lunchtime. Very distracting. But maybe you could post the entity code for say the BigMacPrice. Right now I am having trouble visualizing exactly what the entity would contain besides the price of a big mac. I might also add that DDD entities are a completely different concept than Doctrine entities. Conflating the two seldom ends well. – Cerad Feb 22 '18 at 19:46
  • I posted sample entities. The entities contain different formulas for price computation, based on a specific entity and specific internal ingredients. Not 100% sure what you mean by DDD entities ... you mean like ones that contain business specific formulas may be overtaxing Doctrine entities that are supposed to be simple? – Dennis Feb 22 '18 at 21:21
  • $price = $repository->getBurgerPrice(); ??? So an entity is a repository? – Cerad Feb 22 '18 at 21:53
  • when I wrote that I was thinking that repository method calls the entity's float pricing method. Let me write it a little better – Dennis Feb 22 '18 at 22:10
  • updated to remove the entity/repository conflation, but ... what I usually try to go for when writing repositories is, for example, if I want to get a "burger price", I have a `getBurgerPrice` method in my repository, and I call that method. Repository then does "whatever it needs to do" to get me that price, including calling an entity and doing operations on that entity, even if it's calling it's own `getBurgerPrice` on that entity itself – Dennis Feb 22 '18 at 22:19

1 Answers1

0

Not certain if it is a Doctrine-recommended approach, or not, but it seems congruent enough to me that I can define a single repository but use different instances of it.

On each polymorphic entity I can follow Doctrine's Entity Repository pattern, and specify

@ORM\Entity(repositoryClass="BurgerPriceRepository")

although it doesn't seem to make a difference if I do not include that line. Maybe it comes handy elsewhere.

And then I can use these classes:

class BurgerPriceRepositoryFactory
{

    /**@var EntityManager*/
    private $entityManager;

    function __construct(EntityManager $entityManager)
    {
        $this->entityManager = $entityManager;
    }

    function getRepository(string $entityName): BurgerPriceRepository
    {
        return new BurgerPriceRepository(
            $this->entityManager, 
            new ClassMetadata($entityName)
        );
    }
}

class BurgerPriceRepository extends EntityRepository
{

    function getBurgerPriceEntity(int $burgerId): BurgerPrice
    {
        return $this->getEntityManager()
            ->getRepository($this->_entityName)
            ->findOneBy(array(
            'productId' => $burgerId
        ));
    }
}

Then use them in my test like so:

/** @var ContainerInterface $container */
/* Service-creation time */
$factory = new ProductPricingRepositoryFactoryFactory();
$this->repositoryFactory = $factory($container);

/* Run-time - Big Mac */
$repository = $this->repositoryFactory->getRepository(BigMacPrice::class);
$entity = $repository->getBurgerPriceEntity($bigMacId);
$this->assertEquals(3.57, $entity->getBurgerPrice());

/* Run-time - McDouble*/
$repository = $this->repositoryFactory->getRepository(McDoublePrice::class);
$entity = $repository->getBurgerPriceEntity($mdDoubleId);
$this->assertEquals(1.39, $entity->getBurgerPrice());
Dennis
  • 7,907
  • 11
  • 65
  • 115
  • Or putting the test all on one line, `$price_of_burger = ((((new BurgerPriceRepositoryFactoryFactory())($container))->getRepository(BigMacPrice::class))->getBurgerPriceEntity(45))->getBurgerPrice();` That's a whopper of a method call. – Dennis Feb 22 '18 at 19:37