I struggle organising my code, and I would like to share my "problem" with you by using a simple example: the calculation of the area of a rectangle. I put the code for example, but reading the first intro on each class section explains the situation easily.
Entity Rectangle:
The entity Rectangle contains two inportant properties $length and $width.
// src/Acme/CalculationBundle/Entity/Rectangle.php
/**
* #ORM\Entity(repositoryClass="Acme\CalculationBundle\Repository\RectangleRepository")
* #ORM\Table(name="rectangle")
*/
class Rectangle
{
/**
* #ORM\Id
* #ORM\Column(type="integer")
* #ORM\GeneratedValue(strategy="AUTO")
*/
protected $id;
/**
* #ORM\Column(type="integer")
*/
protected $length;
/**
* #ORM\Column(type="integer")
*/
protected $width;
FORM
Of course, the user can set length and width via a form
CONTROLLER
CreateRectangleAction: renders the form on GET request and work on the data on a POST request.
ViewRectangleAction: shows the rectangle.
RECTANGLE MANAGER
Now, to make sure the controller doesn't do too much stuff, I use a RectangleManager to realise common operation on Rectangle objects and use it as a service (injecting the appropriate elements).
// src/Acme/CalculationBundle/Entity/RectangleManager.php
use Doctrine\ORM\EntityManager;
use Doctrine\ORM\EntityRepository;
use Acme\EmployerBundle\Entity\Manager;
class rectangleManager
{
/**
* Doctrine entity manager
* #var EntityManager
*/
protected $em;
/**
* #var EntityRepository
*/
protected $repository;
/**
*
* #var string
*/
protected $class;
public function __construct(EntityManager $em, $class)
{
$this->em = $em;
$this->class = $class;
$this->repository = $em->getRepository($class);
}
/**
* #param $id
* #return Rectangle
*/
public function find($id)
{
$rectangle = $this->repository->find($id);
}
THE PROBLEM: WHAT IF?
What if I need to do some calculations on the rectangle? For example, if I need to add an area property so that I can render the area directly in the template without doing the calculation (length*width) in the template?
Not knowing how to do this properly, I went for this pretty bad solution:
I created a RectangleDisplay class (where I inject the rectangle entity) and display that entity instead of the Rectangle entity when calling ViewRectangleAction in the controller.
// src/Acme/CalculationBundle/Entity/
class RectangleDisplay
{
/**
* #var Rectangle $rectangle
*/
protected $rectangle;
/**
* #var Integer
*/
protected $area;
/**
* #param Rectangle $rectangle
*/
public function __construct(rectangle $rectangle){
$this->rectangle = $rectangle;
//calculate are
$area = this->calculateArea($this->rectangle->getLength(),$this->rectangle->getWidth());
$this->area = $area;
}
/**
* #return Integer $area
*/
public function calculateArea($length,$width)
{
return $length * $width;
}
Now the property area is directly accessible in the template. In the case of a rectangle, that is fine, but in the case of a more complex element (cone, ...), I might want to use a service that is crazy at doing calculation. I am not gonna inject the container in my RectangleDisplayEntity, am I?
This belongs in your entity.
class Rectangle
{
// ...
public function getArea()
{
return $this->length * $this->width;
}
}
The area of a rectangle is a property of a rectangle. It's not some crazy calculation that should be delegated to another service, and it's especially not something display-related (though you may think that based on it being displayed in your application).
Now, if you were calculating something a lot more complex, it's probably worth moving to a service / another class. (Services = verbs, Entities = nouns and their properties).
Have you tried RectangleDisplay extends rectangleManager ?
This is the normal way to extend a class with what you need, having available all possible properties of the parent and possible override of it's functions (here you can read more about it). None of the classes are final, so it shouldn't be a problem. Don't forget to take into account classes namespace when you will be working on it.
Related
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;
}
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());
I have the main entity
/**
* #ORM\Entity()
*/
class Document
{
/**
* #var int
* #ORM\Id()
* #ORM\Column(type="integer")
* #ORM\GeneratedValue(strategy="AUTO")
*/
private $id;
/**
* #var DocumentStatus
* #ORM\ManyToOne(targetEntity="DocumentStatus")
*/
private $status;
/**
* #var string
* #ORM\Column(type="text")
*/
private $text;
}
and the lookup "enum" entity (seeding on application deploy)
/**
* #ORM\Entity(repositoryClass="DocumentStatusRepository");
*/
class DocumentStatus
{
const DRAFT = 'draft';
const PENDING = 'pending';
const APPROVED = 'approved';
const DECLINED = 'declined';
/**
* #var int Surrogate primary key
* #ORM\Id()
* #ORM\Column(type="integer")
* #ORM\GeneratedValue(strategy="AUTO")
*/
private $id;
/**
* #var string Natural primary key (name for developers)
* #ORM\Column(type="string", unique=true)
*/
private $key;
/**
* #var string Short name for users
* #ORM\Column(type="string", unique=true)
*/
private $name;
/**
* #var string Full decription for users
* #ORM\Column(type="string", nullable=true, unique=true)
*/
private $description;
}
with simple repository
class DocumentStatusRepository extends EntityRepository
{
public function findOneByKey($key)
{
return parent::findOneBy(['key' => $key]);
}
}
I want to encapsulate domain logic of document lifecycle by intoducing methods like
public function __construct($text)
{
$this->text = $text;
$this->status = $something->getByKey(DocumentStatus::DRAFT);
}
public function approve()
{
try {
$this->doSomeDomainActions();
$this->status = $something->getByKey(DocumentSatus::DRAFT);
} catch (SomeDomainException($e)) {
throw new DocumentApproveException($e);
}
}
...
or
public function __construct($text)
{
$this->text = $text;
$this->status = $something->getDraftDocumentStatus()
}
public function approve()
{
$this->status = $something->getApprovedDocumentStatus()
}
...
without public setters. Also I want keep Document loose coupling and testable.
I see the next ways:
Permanent inject DocumentStatusRepository (or service that encapsulates it) into every instance by constructor on entity creation and by using public setter in postLoad subscriber
Permanent inject DocumentStatusRepository by static Document method on application bootstrap or on loadClassMetadata
Temporary inject DocumentStatusRepository in constructor and methods like Document::approve
Use setStatus() method with complex logic based on $status->key value
Encapsulate document domain logic in some DocumentManager and use Document entity like simple data storgae or DTO :(
Are there other ways? Which way is easier and more convenient to use in the long term?
Using generated Identities for Document.
Now you generate the identity on the side of the database. So you save Document from the domain perspective in inconsistent state. Entity/Aggregate should be identified, if it has no id it shouldn't exists.
If you really want to keep to database serials, add method to the repository which will generate id for you.
Better way is to use uuid generator for example ramsey/uuid.
And inject the id to the constructor.
DocumentStatus as Value Object
Why Document Status is Entity? It does look like a simple Value Object.
Then you can use Embeddable annotation. So it will leave within same table in the database, no need for doing inner joins.
DocumentStatus gets behaviour for example ->draftDocumentStatus(), which returns NEW DocumentStatus with draft status, so you can switch the old instance one with new one. ORM will do the rest.
DocumentStatusRepository
If you really want to keep DocumentStatus as entity, which in my opinion is wrong you shouldn't have DocumentStatusRepository.
Document is your aggregate root and the only entrance to the DocumentStatus, should be by aggregate root.
So you will have DocumentRepository only, which will be responsible for rebuilding the whole aggregate and saving it.
Also you should change the mapping.
It should have FETCH=EAGER type, so it will retrieve DocumentStatus with Document together.
Secondly you should do mapping with CASCADE=ALL and ORPHANREMOVAL=TRUE.
Otherwise, if you remove Document, DocumentStatus will stay in the database.
I'm looking for a way to extend my Symfony2 (i currently use 2.3) Entity class with a method to effectively filter its relations on demand. So, imaging i have such 2 classes with OneToMany relation:
/**
* ME\MyBundle\Entity\Kindergarten
*/
class Kindergarten
{
/**
* #var integer $id
*/
private $id;
/**
* #var ME\MyBundle\Entity\Kinder
*/
private $kinders;
public function __construct()
{
$this->kinders = new \Doctrine\Common\Collections\ArrayCollection();
}
/**
* Get kinders
*
* #return Doctrine\Common\Collections\Collection
*/
public function getKinders()
{
return $this->kinders;
}
}
/**
* ME\MyBundle\Entity\Kinder
*/
class Kinder
{
/**
* #var integer $id
*/
private $id;
/**
* #var string $name
*/
private $name;
/**
* #var integer $age
*/
private $age;
}
My goal is to have a method on Kindergarten class to get on demand all kinders with age, for instance, between 10 and 12:
$myKindergarten->getKindersByAgeInInterval(10,12);
Of course, i can do something like:
class Kindergarten
{
...
public function getKindersByAgeInInterval($start, $end)
{
return $this->getKinders()->filter(
function($kinder) use ($start, $end)
{
$kinderAge = $kinder->getAge();
if($kinderAge < $start || $kinderAge > $end)
{
return false;
}
return true;
}
);
}
...
}
The solution above will work, but it's very inefficient, since I need to iterate across ALL kinders which can be a really big list and have no way to cache such filters. I have in mind usage of Criteria class or some proxy patterns, but not sure about a way to do it nice in Symfony2 especially since they probably will need access to EntityManager.
Any ideas?
I would suggest extracting this responsibility into an EntityRepository:
<?php
class KinderRepository extends \Doctrine\ORM\EntityRepository
{
public function findByKindergartenAndAge(Kindergarten $entity, $minAge = 10, $maxAge = 20)
{
return $this->createQueryBuilder()
->... // your query logic here
}
}
All the lookups should really happen in classes where you have access to the entity manager.
This is actually the way suggested by the Doctrine architecture. You can never have access to any services from your entities, and if you ever think you need it, well, then something is wrong with your architecture.
Of course, it may occur to you that the repository method could become pretty ugly if you later decide on adding more criteria (imagine you'll be searching by kindergarten, age, weight and height too, see http://www.whitewashing.de/2013/03/04/doctrine_repositories.html). Then you should consider implementing more logic, but again, that should not be that necessary.
My problem is exactly that described in the Strategy Pattern article in Doctrine documentation :
A Page entity
A page can have some blocks
A block might be a text, an image, a form, a calendar, ... (strategy)
A page knows about blocks it contains, but doesn't know about their behaviours
Block inheritance is not possible
Described solution (Strategy pattern) seems exactly what I need (read article for further information) :
Page:
<?php
namespace Page\Entity;
class Page
{
/**
* #var int
* #Id #GeneratedValue
* #Column(type="integer")
*/
protected $id;
/**
* #var string
* #Column
*/
protected $title;
/**
* #var string
* #Column(type="text")
*/
protected $body;
/**
* #var Collection
* #OneToMany(targetEntity="Block", mappedBy="page")
*/
protected $blocks;
// ...
}
Block:
<?php
namespace Page\Entity;
class Block
{
/**
* #Id #GeneratedValue
* #Column(type="integer")
*/
protected $id;
/**
* #ManyToOne(targetEntity="Page", inversedBy="blocks")
*/
protected $page;
/**
* #Column
*/
protected $strategyClass;
/**
* Strategy object is instancied on postLoad by the BlockListener
*
* #var BlockStrategyInterface
*/
protected $strategyInstance;
// ...
}
Strategy interface:
<?php
namespace Page\BlockStrategy;
interface BlockStrategyInterface
{
public function setView($view);
public function getView();
public function setBlock(Block $block);
public function getBlock();
public function renderFrontend();
public function renderBackend();
}
I can easily imagine what would be my strategy if I would display a form or a calendar;
but what if my strategy is to display content of an other entity ?
The block needs to know about entity class/id and has to be deleted when related entity is removed.
I imagined to add entityClass and entityId properties in Block and load related entity on the postLoad event in a BlockListener.
But what if related entity doesn't exist ? I can't remove the block in postLoad.
So, I imagined to create an other listener watching for removal of related entity and remove refering Block in that listener.
But it means that I need to add a listener for every entity that can be put in a block.
It could work, but it seems very complicated... maybe someone has a better idea ?
I'm not sure if I understood well your question, but if what you want is to have entities inside entities and delete the child entities when the father is deleted, you could also treat the entity as a block and use a Composite pattern.
You basically could use the same interface on the entity and the blocks, and on the entity the display function could be something like:
foreach ($block in $blocks) {
$block->display();
}
For deleting all the children when you delete the parent entity, you could simply do it on the destructor of the entity.
function __destruct() {
foreach ($block in $blocks) {
/* call a common interface function that does all that need to be done, implemented on each block */
}
}
For more on Composite pattern:
http://en.wikipedia.org/wiki/Composite_pattern
I'm using Doctrine ORM 2.
I got the following entity class.
/**
* #Entity
* #Table(name="objects")
*/
class MyObject
{
/**
* #Id
* #GeneratedValue
* #Column(type="integer")
*/
private $id;
/**
* #var Coordinate
*/
private $coordinate;
}
class Coordinate
{
private $x;
private $y;
private $z;
}
I want to implement the 3 coordinate values in a separate class for better handling within php. But within the database I want the 3 values to be included in the database table "objects".
Does anyone know how to achieve this?
Best regards
Edit:
I found a workaround but it's not the nicest.
/**
*
* #var integer
* #Column(type="integer")
*/
private $x;
/**
*
* #var integer
* #Column(type="integer")
*/
private $y;
/**
*
* #var integer
* #Column(type="integer")
*/
private $z;
public function getCoordinate()
{
return new Coordinate($this->x, $this->y, $this->z);
}
public function setCoordinate(Coordinate $coord)
{
$this->x = $coord->getX();
$this->y = $coord->getY();
$this->z = $coord->getZ();
}
The easiest way would be to set that field to use the "object" mapping type.
/**
* #Column(type="object")
*/
private $coordinate;
Then whatever class of object you put in that field, Doctrine will automatically serialise and unserialise when it is inserted and pulled out of the database.
The other way is to make a custom mapping type - http://docs.doctrine-project.org/projects/doctrine-orm/en/latest/cookbook/custom-mapping-types.html. This uses the same method as the object type, but allows you to specify exactly how the object will be converted between PHP and SQL.
If you used this method had a mapping type named coordinate, you would then just declare this for the field:
/**
* #Column(type="coordinate")
*/
private $coordinate;
One drawback and as far as I can see there is no way around it, is that you can only use one database column for the field. So you wouldn't be able to order by x, y or z separately using DQL.
Just configure your getter and setter. Like this in your entity
public function setCoordinate (Coordinate $coordinate) {
$coordinateArr = $coordinate->getArrayCopy();
$this->coordinate = serialize($coordinateArr);
}
public function getCoordinate() {
$coordinateArr = unserialize($this->coordinate);
$coordinate = new Coordinate();
$coordinate->exchangeArray($coordinateArr);
return $coordinate;
}
But if you want sql search, you need use LIKE.