PHP Doctrine 2 ORM: Non-Entity object as attribute in entity - php

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.

Related

Add extra option to LoggableListener

I use Loggable to backup changes in Entities.
The default AbstractLogEntry does not have enough columns for my needs.
Thats why i extended the class and added extra getters and setters.
See the code below
/**
* EmployeeBackup
*
* #ORM\Table(name="employee_backup")
* #ORM\Entity(repositoryClass="Gedmo\Loggable\Entity\Repository\LogEntryRepository")
*
*/
class EmployeeBackup extends AbstractLogEntry
{
/**
* #var int
*
* #ORM\Column(name="division_id", type="integer", unique=true)
*/
private $divisionId;
/**
* #return int
*/
public function getDivisionId(): int
{
return $this->divisionId;
}
/**
* #param string $divisionId
*/
public function setDivisionId(string $divisionId): void
{
$this->divisionId = $divisionId;
}
}
The extension is using the class above. So it works.
But now i need to set the divisionId when a new version is stored.
I tried the code below
$loggable = new LoggableListener();
$loggable->setDivision($division);
$evm->addEventSubscriber($loggable);
And this is what i get:
Attempted to call an undefined method named "setDivision" of class "Gedmo\Loggable\LoggableListener".
And thats true because LoggableListener does not have a setDivision function. My question is: Do i need to override the listener and if so, how do i do that?
Thanks ;)

What is a Doctrine-centric approach to work with repositories that use entities in mapped superclass configuration?

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());

What is the best way to set/update association lookup field inside Doctrine entity

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.

Filtering on many-to-many association with Doctrine2

I have an Account entity which has a collection of Section entities. Each Section entity has a collection of Element entities (OneToMany association). My problem is that instead of fetching all elements belonging to a section, I want to fetch all elements that belong to a section and are associated with a specific account. Below is my database model.
Thus, when I fetch an account, I want to be able to loop through its associated sections (this part is no problem), and for each section, I want to loop through its elements that are associated with the fetched account. Right now I have the following code.
$repository = $this->objectManager->getRepository('MyModule\Entity\Account');
$account = $repository->find(1);
foreach ($account->getSections() as $section) {
foreach ($section->getElements() as $element) {
echo $element->getName() . PHP_EOL;
}
}
The problem is that it fetches all elements belonging to a given section, regardless of which account they are associated with. The generated SQL for fetching a section's elements is as follows.
SELECT t0.id AS id1, t0.name AS name2, t0.section_id AS section_id3
FROM mydb.element t0
WHERE t0.section_id = ?
What I need it to do is something like the below (could be any other approach). It is important that the filtering is done with SQL.
SELECT e.id, e.name, e.section_id
FROM element AS e
INNER JOIN account_element AS ae ON (ae.element_id = e.id)
WHERE ae.account_id = ?
AND e.section_id = ?
I do know that I can write a method getElementsBySection($accountId) or similar in a custom repository and use DQL. If I can do that and somehow override the getElements() method on the Section entity, then that would be perfect. I would just very much prefer if there would be a way to do this through association mappings or at least by using existing getter methods. Ideally, when using an account object, I would like to be able to loop like in the code snippet above so that the "account constraint" is abstracted when using the object. That is, the user of the object does not need to call getElementsByAccount() or similar on a Section object, because it seems less intuitive.
I looked into the Criteria object, but as far as I remember, it cannot be used for filtering on associations.
So, what is the best way to accomplish this? Is it possible without "manually" assembling the Section entity with elements through the use of DQL queries? My current (and shortened) source code can be seen below. Thanks a lot in advance!
/**
* #ORM\Entity
*/
class Account
{
/**
* #var int
* #ORM\Column(type="integer")
* #ORM\Id
* #ORM\GeneratedValue
*/
protected $id;
/**
* #var string
* #ORM\Column(type="string", length=50, nullable=false)
*/
protected $name;
/**
* #var ArrayCollection
* #ORM\ManyToMany(targetEntity="MyModule\Entity\Section")
* #ORM\JoinTable(name="account_section",
* joinColumns={#ORM\JoinColumn(name="account_id", referencedColumnName="id")},
* inverseJoinColumns={#ORM\JoinColumn(name="section_id", referencedColumnName="id")}
* )
*/
protected $sections;
public function __construct()
{
$this->sections = new ArrayCollection();
}
// Getters and setters
}
/**
* #ORM\Entity
*/
class Section
{
/**
* #var int
* #ORM\Id
* #ORM\GeneratedValue
* #ORM\Column(type="integer")
*/
protected $id;
/**
* #var string
* #ORM\Column(type="string", length=50, nullable=false)
*/
protected $name;
/**
* #var ArrayCollection
* #ORM\OneToMany(targetEntity="MyModule\Entity\Element", mappedBy="section")
*/
protected $elements;
public function __construct()
{
$this->elements = new ArrayCollection();
}
// Getters and setters
}
/**
* #ORM\Entity
*/
class Element
{
/**
* #var int
* #ORM\Id
* #ORM\GeneratedValue
* #ORM\Column(type="integer")
*/
protected $id;
/**
* #var string
* #ORM\Column(type="string", length=50, nullable=false)
*/
protected $name;
/**
* #var Section
* #ORM\ManyToOne(targetEntity="MyModule\Entity\Section", inversedBy="elements")
* #ORM\JoinColumn(name="section_id", referencedColumnName="id")
*/
protected $section;
/**
* #var \MyModule\Entity\Account
* #ORM\ManyToMany(targetEntity="MyModule\Entity\Account")
* #ORM\JoinTable(name="account_element",
* joinColumns={#ORM\JoinColumn(name="element_id", referencedColumnName="id")},
* inverseJoinColumns={#ORM\JoinColumn(name="account_id", referencedColumnName="id")}
* )
*/
protected $account;
// Getters and setters
}
If I understand correctly, you want to be able to retrieve all Elements of all Sections of an Account, but only if those Elements are associated with that Account, and this from a getter in Account.
First off: An entity should never know of repositories. This breaks a design principle that helps you swap out the persistence layer. That's why you cannot simple access a repository from within an entity.
Getters only
If you only want to use getters in the entities, you can solve this by adding to following 2 methods:
class Section
{
/**
* #param Account $accout
* #return Element[]
*/
public function getElementsByAccount(Account $accout)
{
$elements = array();
foreach ($this->getElements() as $element) {
if ($element->getAccount() === $account) {
$elements[] = $element->getAccount();
}
}
return $elements;
}
}
class Account
{
/**
* #return Element[]
*/
public function getMyElements()
{
$elements = array()
foreach ($this->getSections() as $section) {
foreach ($section->getElementsByAccount($this) as $element) {
$elements[] = $element;
}
}
return $elements;
}
}
Repository
The solution above is likely to perform several queries, the exact amount depending on how many Sections and Elements are associated to the Account.
You're likely to get a performance boost when you do use a Repository method, so you can optimize the query/queries used to retrieve what you want.
An example:
class ElementRepository extends EntityRepository
{
/**
* #param Account $account [description]
* #return Element[]
*/
public function findElementsByAccount(Account $account)
{
$dql = <<< 'EOQ'
SELECT e FROM Element e
JOIN e.section s
JOIN s.accounts a
WHERE e.account = ?1 AND a.id = ?2
EOQ;
$q = $this->getEntityManager()->createQuery($dql);
$q->setParameters(array(
1 => $account->getId(),
2 => $account->getId()
));
return $q->getResult();
}
}
PS: For this query to work, you'll need to define the ManyToMany association between Section and Account as a bidirectional one.
Proxy method
A hybrid solution would be to add a proxy method to Account, that forwards the call to the repository you pass to it.
class Account
{
/**
* #param ElementRepository $repository
* #return Element[]
*/
public function getMyElements(ElementRepository $repository)
{
return $repository->findElementsByAccount($this);
}
}
This way the entity still doesn't know of repositories, but you allow one to be passed to it.
When implementing this, don't have ElementRepository extend EntityRepository, but inject the EntityRepository upon creation. This way you can still swap out the persistence layer without altering your entities.

calculation of the area of a rectangle

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.

Categories