I have an abstract parent class called Divers which is extended by few other classes.
So, I use inheritance mapping with D2 using Single Table Inheritance strategy.
namespace MyBundle\Entity;
use Doctrine\ORM\Mapping as ORM;
/**
* ParentClass
*
* #ORM\Table(name="PARENTCLASS")
* #ORM\Entity
* #ORM\InheritanceType("SINGLE_TABLE")
* #ORM\DiscriminatorColumn(name="idtable", type="string")
* #ORM\DiscriminatorMap({
* "CHILD-CLASS1" = "ChildClassOne",
* "CHILD-CLASS2" = "ChildClassTwo",
* "CHILD-CLASS3" = "ChildClassThree",
* "CHILD-CLASS4" = "ChildClassFour"
* })
*/
abstract class ParentClass
{
...
}
What I want to achieve is to display the discriminator in the browser with a little description that explains what is it to the user.
I googled for a solution like putting the discriminator in a joined table but found nothing.
Do you have any advice to achieve my goal ?
Thanks by advance for your help.
The discriminator column has special meaning for Doctrine 2 and thus cannot be part of a relation.
But there is an easy work around. Just add another column and give it the same value that your discriminator column has. The value will never change so it's easy enough to do. You can then of course use your new column in the same way as any other column.
I know having two columns with the same value is not ideal from a database perspective. But from an object perspective, it's no big deal since the discriminator column is never exposed as a property. And it's just the way doctrine works. It wants that column all to itself.
You can achieve it using PHP, whitout adding another field in the db as long as you don't need the field in a SQL query.
Since the discriminator is an abstract class, just adding a public abstract method returning your hard-coded discriminator value would do the trick. Then you can use your entity in twig or a json serializer.
abstract class ParentClass {
public abstract function getDiscriminator(): string; // The discriminator type
}
class ChildClassOne extends ParentClass
{
public function getDiscriminator(): string
{
return 'CHILD-CLASS1';
}
}
If you need to fetch in SQL, use $qb->andWhere($qb->isInstanceOf(ChildClassOne::class)) since the method or discriminator attribute is not available in sql.
Related
I have been doing some research on this topic but so far I couldn't find anything helpful for my scenario.
In a brief: I have two tables Quote (table name: quote) and QuoteArchive (table name: quote_archive). Both share exactly the same columns and types. As far as I have read this turn into a Doctrine MappedSuper Class ex: MappedSuperclassQuote.
After that Quote and QuoteArchive entities will extend from the MappedSuperclassQuote and both will share exactly the same structure.
Quote has a custom Repository with some functions. QuoteArchive needs exactly the same Repository functions as in Quote with the only difference being the table name and the PK.
I have two doubts in this scenario:
How to extend Doctrine entities when the PK (#Id) is different in the child classes?
How to extend or share the same repository between entities when the only change is the table name.
For get a better idea this is how my current entities looks like:
/**
* #ORM\Entity
* #ORM\Table(name="quote")
* #ORM\Entity(repositoryClass="QuoteBundle\Entity\Repository\QuoteRepository")
*/
class Quote
{
/**
* #ORM\Id
* #ORM\Column(type="integer",unique=true,nullable=false)
* #ORM\GeneratedValue
*/
private $quoteId;
// ...
}
/**
* #ORM\Entity
* #ORM\Table(name="quote_archive")
* #ORM\Entity(repositoryClass="QuoteBundle\Entity\Repository\QuoteArchiveRepository")
*/
class QuoteArchive
{
/**
* #ORM\Id
* #ORM\Column(type="integer",unique=true,nullable=false)
*/
private $archiveId;
// ...
}
Last but not least:
class QuoteRepository extends EntityRepository
{
public function getCurrentQuoteId(int $OrigQuoteId)
{
$em = $this->getEntityManager();
$qb = $em->createQueryBuilder();
return $qb->select('q')
->from('QuoteBundle:Quote')
->where('q.origQuoteId =:origQuoteId')
->setParameter('origQuoteId', $OrigQuoteId)
->andWhere('q.quoteType =:quoteType')
->setParameter('quoteType', 'current')
->getQuery()
->getResult();
}
}
What is the problem here? I need to repeat the same exact function in QuoteArchiveRepository and change the table from quote to quote_archive and it's exactly what I am trying to avoid if possible.
Can any give me some ideas? Code example would be great :)
References:
Can we extend entities in Doctrine?
Doctrine: extending entity class
Doctrine How to extend custom repository and call the extended repository from doctrine entity manager
I think you're mistaking doing a MappedSuperclassQuote entity.
You have to inherit the Archive from the Quote.
Example : you have your Quote entity
The definition should be something like :
/**
* #ORM\Table(name="app_quote")
* #ORM\InheritanceType("JOINED")
* #ORM\DiscriminatorColumn(name="quote_type", fieldName="quoteType", type="string")
* #ORM\DiscriminatorMap({
* "quote":"YourBundle\Entity\Quote",
* "quote_archive":"YourBundle\Entity\QuoteArchive"
* })
* #Gedmo\Loggable
* #ORM\Entity(repositoryClass="YourBundle\Repository\QuoteRepository")
*/
Why a JOINED inheritance ? Cause you want two separate tables (what SINGLE_TABLE is not doing) and you don't have a really abstract class (cause Quote AND QuoteArchive means something for you)
After, your table QuoteArchive should extends the first one :
/**
* #ORM\Entity
* #ORM\Table(name="app_quote_archive")
*/
class QuoteArchive extends Quote
{
...
}
Your column quote_type in app_quote will help you to know if this is an archived quote or not.
It provides you all you want :
- QuoteArchive will have access to functions inside QuoteRepository
- Each table has separated ids
One thing could be annoying for you : if you want to set a quote has archived, it's not so easy to change an entity type for now in Doctrine. In that case, it's better for you to use single_table joining type. All the datas are stored in a same table in database, making type change easy but you keep two different entities.
I am learning Symfony 3 with Doctrine 2.
When I have OneToMany relationship in Doctrine entity, what exactly I have to put into mappedBy annotation?
Is it the table name of current entity?
Or is it the entity shortcut?
Or is it the actual class name?
Imagine this simple example:
<?php
namespace AppBundle\Entity;
/**
* #ORM\Entity
* #ORM\Table(name="blog_category")
*/
class Category
{
// ...
/**
* #ORM\OneToMany(targetEntity="Article", mappedBy="category")
*/
private $articles;
// ...
}
Why is the "category" correct value for mappedBy? Why isn't it "blog_category" or "Category" (uppercase "C")? Or "AppBundle:Category"?
Now I figured it out. It is the name of related's entity class variable :-)
To give a complement, almost everything you could do in doctrine (could surely be applied for any other ORM/ODM), in almost every contexts (QueryBuilder, findBy methods, ...) you'll use property names rather than column names.
The reason is quite simple, an ORM deals with objects and their properties, abstracting the real tables and their columns, no matter of the database engine or anything else.
In Symfony2, I just try recently to think in terms of traits, to create some sort of behaviors.
Let's say I have an address attribute in an entity. I externalized attributes, getters and setters related to this in an AddressableTrait.
But what if address become an entity? I started to try to define my OneToMany relation in my trait, as if it was in a regular entity :
use Doctrine\ORM\Mapping as ORM;
class AddressableTrait {
/**
* #var
* #ORM\OneToMany(targetEntity="XXXX\GlobalBundle\Entity\Address", inversedBy="What to put here" )
*/
protected $addresses;
/**
* #return ArrayCollection
*/
public function getAddresses()
{
return $this->addresses;
}
/**
* #param ArrayCollection $addresses
*/
public function setAddresses($addresses)
{
$this->addresses = $addresses;
}
}
What to put in the inversedBy? The purpose of the trait if precisely to embed all the behavior feature, so I think that at least using traditionnal annotation/YML/XML,it's not possible to achieve.
I digged a bit into it and found this very interesting link that seems to allow you to defines relation via events, but there is still logic to add to "finish" relations.
UPDATE :
Using the above link, I managed to created dynamic ManyToMany relation. the schema update works when creating, but if I comment the dynamic relation, a schema:update --dump-sql doesn't remove it. It seems to work add-only. Any clue to force the dynamic mapping to stick to the real relations addition/removal?
Thanks a lot for your answers !
Nicolas
I encountered a problem using traits in entities. For regular database values (scalar, DateTime) traits worked fine, but when I tried to define entity relations in traits the doctrine migrations bundle would convert the property to a varchar field.
The only way I could find to fix creating proper entity relation properties was by moving them out of the trait and into the entity class itself.
/**
* #ORM\Entity
*/
class Order extends BaseEntity
{
// this is trait for #Id
use Identifier;
/**
* #ORM\Column(type="integer")
*/
protected $costPerUnit;
/**
* #ORM\Column(type="integer")
*/
protected $numberOfUnits;
// i want to search by this property
protected $totalCost;
public function getTotalCost()
{
return $this->numberOfUnits * $this->costPerUnit;
}
}
I have an entity like this and I'd like to be able to do for example
$orderRepository->findOneByTotalCost('999')
$orderRepository->findBy(['totalCost' => '400']);
Is this possible in Doctrine2? Or would I go about it differently?
Like I said in my comments, it's likely you're wrestling with an issue that shouldn't have occurred in the first place. Still, having a SUM value mapped to a property is possible using doctrine, in a variety of ways: Check the aggregate field docs to find out which would solve your problem best.
To my eyes, is that you're using entities as more than what they really are: Entities represent records in a database, Doctrine is a DBAL. Searching data using entities (or repositories) is querying the database. You could solve the problem by adding custom methods to your entity manager or a custom repository class that'll query all of the data required to compute the totalCost value for all entities, and return only those you need. Alternatively, use the connection from your DBAL to query for the id's you're after (for example), then use those values to get to the actual entities. Or, like I said before: use aggregate fields.
The problems you have with the findOneByTotalCost and findBy examples you show is that the first requires you to write a method Called findOneByTotalCost yourself. The problem with your use of findBy is simply that your argument is malformed: the array should be associative: use the mapped column names as keys, and the values are what you want to query for:
$repo->findBy(
['totalCost' => 400]
);
is what you're looking for, not ['totalCost', 400]. As for the entity itself, you'll need to add an annotation:
Yes it is, judging by your use of #ORM\Entity annotations in the doc-blocks, this ought to do it:
/**
* #ORM\Column(type="string", length=255)
*/
protected $regioun = 'Spain';
The update the table, and you'll be able to:
$entities = $repo->findBy(
['region' => 'Spain']
);
Don't forget that this code represents a table in a DB: you can search on any of the fields, but use indexes, which you can do by adding annotations at the top of your class definition:
/**
* #ORM\Table(name="tblname", indexes={
* #ORM\Index(name="region", columns={"region"})
* })
*/
class Foo
{}
As ever: in DB's, indexes matter
You should write a method findOneByTotalCost on your entity repository, something like:
public function findOneByTotalCost ($queryParams){
$query = 'select o
from <yourEntity> o
where o.numberOfUnits * o.costPerUnit = :myParam';
$dql = $this->getEntityManager()->createQuery($query);
$dql->setParameter('myParam', $queryParams);
return $dql ->execute();
}
Them, $orderRepository->findOneByTotalCost('999') should work.
How do I create traditional polymorphic relationships with Doctrine 2?
I have read a lot of answers that suggest using Single Table Inheritance but I can't see how this would help in my situation. Here's what I'm trying to do:
I have some utility entities, like an Address, an Email and a PhoneNumber.
I have some 'contactable' entities, like a Customer, Employer, Business. Each of these should contain a OneToMany relationship with the above utility entities.
Ideally, I'd like to create an abstract base class called 'ContactableEntity' that contains these relationships, but I know it is not possible to put OneToMany relationships in mapped superclasses with doctrine-- that's fine.
However, I am still at a loss at how I can relate these without massive redundancy in code. Do I make Address an STI type, with a 'CustomerAddress' subclass that contains the relationship directly to a Customer? Is there no way to reduce the amount of repetition?
Why not just make your base ContactableEntity concrete?
EDIT:
Just did a few experiments in a project I've done that uses CTI. I don't see any reason that the same strategy wouldn't work with STI.
Basically, I have something like:
/**
* Base class for orders. Actual orders are some subclass of order.
*
* #Entity
* #Table(name="OOrder")
* #InheritanceType("JOINED")
* #DiscriminatorColumn(name="discr", type="string")
* #DiscriminatorMap({"CAOrder" = "CAOrder", "AmazonOrder" = "AmazonOrder"})
*/
abstract class Order {
/**
* CSRs can add notes to orders of any type
* #OneToMany(targetEntity = "OrderNote", mappedBy = "order", cascade={"all"})
* #OrderBy({"created" = "ASC"})
*/
protected $notes;
// ...
}
/**
* #Entity
*/
class AmazonOrder extends Order {
/**
* #Column(type="string", length="20")
*/
protected $amazonOrderId;
// ...
}
/**
* #Entity
*/
class OrderNote {
// ...
/**
* #ManyToOne(targetEntity="Order", inversedBy="notes")
*/
protected $order;
// ...
}
And it seems to work exactly as expected. I can get an OrderNote, and it's $order property will contain some subclass of Order.
Is there some restriction on using STI that makes this not possible for you? If so, I'd suggest moving to CTI. But I can't imagine why this wouldn't work with STI.
If the contactable entity shall be abstract (#MappedSuperclass) you'll need to use the ResolveTargetEntityListener provided by Doctrine 2.2+.
It basically allows you to define a relationship by specifying an interface instead of a concrete entity. (Maybe you want to define/inherit several interfaces as you speak of multiple "contactables"). For instance you then can implement the interface in your abstract class or concrete class. Finally you'll need to define/associate the concrete class (entity) to the related interface within the config.yml
An example can be found in the Symfony docs: http://symfony.com/doc/current/cookbook/doctrine/resolve_target_entity.html