Symfony doctrine join related entities to discriminator columns - mysql

RoundMatch.php
/**
* #ORM\Entity(repositoryClass="MyApp\MyBundle\Repository\RoundMatchRepository")
* #ORM\InheritanceType("JOINED")
* #ORM\DiscriminatorColumn(name="type", type="string")
* #ORM\DiscriminatorMap({"team_round_match" = "TeamRoundMatch", "player_round_match" = "PlayerRoundMatch"})
* #ORM\Table("my_round_match")
*/
abstract class RoundMatch
{
/**
* #var int
*
* #ORM\Column(name="id", type="integer")
* #ORM\Id
* #ORM\GeneratedValue(strategy="AUTO")
*/
private $id;
/**
* #var \DateTime
*
* #ORM\Column(name="match_date", type="datetime")
*/
private $matchDate;
How can I join related entities to discriminated entities?
I cannot get direct access to discriminated table columns to create joins.
I cannot get access to discriminated table columns to create joins.
How can I join children entities to discriminator entities?
I created joins like this:
RoundMatchRepository.php
public function getMatchesWithNoResultsSubmitted()
{
$qb = $this->createQueryBuilder("rm");
$qb->leftJoin("rm.round", "rnd" )
->leftJoin("rnd.group", "sg")
->leftJoin("sg.server", "ss")
->leftJoin("ss.stage", "ts")
->leftJoin("ts.tournament", "t")
->leftJoin("MyAppMyBundle:PlayerRoundMatch", "prm", "WITH", "rm.id = prm.id")
->leftJoin("prm.player1", "p1")
->leftJoin("prm.player2", "p2")
->leftJoin('p1.gameProfiles',"gp1")
->leftJoin('p2.gameProfiles',"gp2")
->leftJoin('p1.gameProfiles', 'gp1', "WITH", $qb->expr()->andX(
$qb->expr()->eq('t.game', 'gp1.game'),
$qb->expr()->eq('prm.player1', 'gp1.player')
))
->leftJoin('p1.gameProfiles', 'gp2', "WITH", $qb->expr()->andX(
$qb->expr()->eq('t.game', 'gp2.game'),
$qb->expr()->eq('prm.player2', 'gp2.player')
));
return $qb->getQuery()->getResult();
}
I want to use result object in a twig and I cannot get joined entities in returned object because they are not joined via object relation.
I don't have object relation created because they are joined one to one via discriminator.

I have achieved dynamic relationships using Single Table Inheritance. This will allow you to write DQL against the relation or against the discriminating class.
You CANNOT "query" the discriminator using DQL. You must INSTANCE OF the entity name, see possible duplicate as per #LBA's comment
In this resulting schema, group_id will mean a different relation depending on the discriminator.
Obviously, this has performance implications on the database, though.
/**
* #ORM\Entity
* #ORM\InheritanceType("SINGLE_TABLE")
* #ORM\DiscriminatorColumn(name="type", type="string", length=255)
* #ORM\DiscriminatorMap(
* {"parents" = "Parents", "children" = "Child"}
* )
* #ORM\Table("people")
*/
class Person
{
/**
* #var int
*
* #ORM\Column(name="id", type="integer")
* #ORM\Id
* #ORM\GeneratedValue(strategy="AUTO")
*/
private $id;
/**
* #var mixed
*/
private $group;
}
/*
* #ORM\Table("parents")
*/
class Parent extends Person
{
/**
* Many parents have one group.
*
* #ManyToOne(targetEntity="ParentGroups", inversedBy="parents")
* #JoinColumn(name="group_id", referencedColumnName="id")
*/
private $group;
}
/*
* #ORM\Table("children")
*/
class Child extends Person
{
/**
* Many children have one group.
*
* #ManyToOne(targetEntity="ChildGroups", inversedBy="children")
* #JoinColumn(name="group_id", referencedColumnName="id")
*/
private $group;
}
/*
* #ORM\Table("parent_groups")
*/
class ParentGroups
{
/**
* #var int
*
* #ORM\Column(name="id", type="integer")
* #ORM\Id
* #ORM\GeneratedValue(strategy="AUTO")
*/
private $id;
/**
* One group has many parents.
*
* #OneToMany(targetEntity="Parent", mappedBy="group")
*/
private $parents;
public function __construct() {
$this->parents = new ArrayCollection();
}
}
/*
* #ORM\Table("child_groups")
*/
class ChildGroups
{
/**
* #var int
*
* #ORM\Column(name="id", type="integer")
* #ORM\Id
* #ORM\GeneratedValue(strategy="AUTO")
*/
private $id;
/**
* One group has many parents.
*
* #OneToMany(targetEntity="Child", mappedBy="group")
*/
private $children;
public function __construct() {
$this->children = new ArrayCollection();
}
}
Effectively the extending tables can override the annotations (if any) of the parent.
It work's a dream and we have multiple uses of this on our monolith :)
Untested code, but it should give you an idea of how I achieved it. If you struggle from here I'll go and make sure it runs.

Related

Table partinioning (horizontal) with doctrine

I am using Doctrine 2.4 and I want to split the table one of my entity is using into several tables to improve querying that table - Horizontal partitioning, if I am not mistaken. The table has a lot of data (> 20 Mio. rows) and queries are always done on a subset (Component.Type).
Here are my entities:
Station:
/**
* #ORM\Id
* #ORM\Column(type="bigint")
* #ORM\GeneratedValue(strategy="AUTO")
*/
private $id;
Component:
/**
* #ORM\Id
* #ORM\Column(type="bigint")
* #ORM\GeneratedValue(strategy="AUTO")
*/
private $id;
/**
* #ORM\Column(type="string", length=255)
*/
private $type;
Data
/**
* #ORM\Id
* #ORM\Column(type="bigint")
* #ORM\GeneratedValue(strategy="AUTO")
*/
private $id;
/**
* #ORM\Column(type="datetime")
*/
private $datetime;
/**
* #ORM\Column(type="float", nullable=true)
*/
private $value;
/**
* #ORM\ManyToOne(targetEntity="AppBundle\Entity\Station")
* #ORM\JoinColumn(name="station_id", referencedColumnName="id")
*
* #var Station $station
*/
private $station;
/**
* #ORM\ManyToOne(targetEntity="AppBundle\Entity\Component")
* #ORM\JoinColumn(name="component_id", referencedColumnName="id")
*
* #var Component $component
*/
private $component;
At the moment I solved this manually by having one Entity Data[TYPE] for every Component.Type (e.g. DataPollutionNo). All entities Data[TYPE] extent a MappedSuperclass (Data). To query for data, I first get the corresponding Data Entity from the Component.Type and query that Repository (DataPollutionNoRepository). This works out fine but I was wondering if there is a more sophisticated/generic approach to solve this.
Thanks, Hannes

How to Filter Symfony Form Entity Output depending on unrelated relationship

I have three entities like so:
-Available-Teams (Managed by Admins)
-Player-PreConfig (Managed by Admins)
-Player-Self] (Managed by User(Player itself))
Available-Teams:
--> All available teams
Player-PreConfig:
--> Here the Administrators are able to preselect teams in which a player is allowed to play. (First-Filter - Many2Many: Available-Teams<->Player-PreConfig) - Lots of checkboxes in the view.
Player-Self:
--> Here the Player should be able to select the teams (multiple) he would like to play in. But he should not get listed ALL possible Available-Teams, but only the remaining ones.
Classes
/**
* TeamsPlayerBundle\Entity\Teams
*
* #ORM\Table(name="team")
* #ORM\Entity
*/
class Team
{
/**
* #var integer $id
*
* #ORM\Column(name="id", type="integer")
* #ORM\Id
* #ORM\GeneratedValue(strategy="AUTO")
*/
private $id;
/**
* #var string $name
*
* #ORM\Column(name="name", type="string", length=255)
*/
private $name;
/**
* #ORM\ManyToMany(targetEntity="PreConfig", mappedBy="teams", cascade={"persist", "remove"})
**/
private $configs;
/**
* #ORM\ManyToMany(targetEntity="Player", mappedBy="teams2show", cascade={"persist"})
**/
private $players;
public function __construct()
{
$this->configs = new ArrayCollection();
$this->players = new ArrayCollection();
}
(... setters and getters)
###################################################
/**
* TeamsPlayerBundle\Entity\PreConfig
*
* #ORM\Table(name="preconfig")
* #ORM\Entity
*/
class PreConfig
{
/**
* #ORM\Column(type="integer")
* #ORM\Id
* #ORM\GeneratedValue(strategy="AUTO")
*/
private $id;
/**
* #ORM\ManyToMany(targetEntity="Teams", inversedBy="configs", cascade={"persist", "remove"})
* #ORM\JoinTable(name="preconfig_teams)
**/
private $teams;
public function __construct()
{
$this->teams = new ArrayCollection();
}
(... setters and getters)
####################################################
/**
* TeamsPlayerBundle\Entity\Player
*
*
* #ORM\Table(name="player")
* #ORM\Entity
*/
class Player
{
/**
* #var integer $player_id
*
* #ORM\Column(name="player_id", type="integer")
* #ORM\Id
* #ORM\GeneratedValue(strategy="AUTO")
*/
private $player_id;
/**
* #var string $name
* #Assert\NotBlank
*
* #ORM\Column(name="name", type="string", length=64)
*/
private $name
/**
* #ORM\ManyToMany(targetEntity="Team", inversedBy="player", cascade={"persist"})
* #ORM\JoinTable(name="player_team",
* joinColumns={#ORM\JoinColumn(name="player_id", referencedColumnName="id")},
* inverseJoinColumns={#ORM\JoinColumn(name="id", referencedColumnName="id")}
* )
**/
private $teams2show;
public function __construct()
{
$this->teams2show = new ArrayCollection();
}
(... setters and getters)
Right now I have this FormType: I try to solve with Query_Builder as suggested by "Viktor77"
namespace TeamsPlayerBundle\Form;
use Symfony\Component\Form\AbstractType;
use Symfony\Component\Form\FormBuilder;
use Doctrine\ORM\EntityRepository;
use TeamsPlayerBundle\Entity\Player;
class Teams2ShowType extends AbstractType
{
public function buildForm(FormBuilder $builder, array $options)
{
$builder
->add('teams2show', 'entity', array(
'class' => 'TeamsPlayerBundle\Entity\PreConfig',
'query_builder' => function(EntityRepository $er) use ($cid) {
return $er->createQueryBuilder('c')
->add('orderBy', 'c.name ASC')
->innerJoin('c.teams', 'c2')
->where('c2.id = :configId')
->setParameter('configId', $cid);
},
'expanded' => true,
'multiple' => true,
'property_path' => 'teams2show',
'property' => 'name'
))
;
...<br />
For Your Reference: => My first Form looked like this:
class Teams2ShowType extends AbstractType
{
public function buildForm(FormBuilder $builder, array $options)
{
$builder
->add('teams2show', 'entity', array(
'multiple' => true,
'expanded' => true,
The problem was as follows:
If I render the form right now everything works fine, but a huge list of checkboxes gets rendered. The whole entity is presented.
Sure because I have no idea, how to only populate the remaining entities depending on the many-to-many relationship Available-Teams<->Player-PreConfig).
Because obviously, my actual Teams2ShowType has no idea, that only the remaining teams should show up.
I have already tried a lot and read a lot (query_builder, model transformer, etc..), but I could not get it right.
My real example (in the company) has to do something with licensors and partner configuration, but I wanted to present this question in a more comprehensible scenario.
I do not know of any best practices on how to implement this right.
Thank you so much for your help in advance I have already tried to solve that issue more than 3-4days.
Kind regards,
query_builder option is the way to go. Just use Doctrine Query Builder API to get only the entities you need to be rendered in your form and not all of them

Symfony update Entity value + 1

I would like to summate or add one up while I update an existing db entry.
For the Reason that I will have to work with MagicCalls, I just wonder how I can handle this.
In raw sql, I would do it like:
UPDATE table SET value= value + 1 WHERE ....
But in this case, I have absolutely no idea how to work it out.
My code looks like:
Entity:
class Properties
{
/**
* #var integer
*
* #ORM\Column(name="id", type="integer")
* #ORM\Id
* #ORM\GeneratedValue(strategy="AUTO")
*/
private $id;
/**
* #var string
*
* #ORM\Column(name="Sport", type="string", length=11, nullable=true)
*/
private $sport;
/**
* #var string
*
* #ORM\Column(name="Entertainment", type="string", length=11, nullable=true)
*/
private $entertainment;
/**
* #var string
*
* #ORM\Column(name="Wellness", type="string", length=11, nullable=true)
*/
private $wellness;
Now, I get those column names by
$metadata = $em->getClassMetadata($className);
$columnNames=$metadata->getColumnNames();
I recieve an array, which I can foreach and add values to each of them which I will have to write back by using an accessor:
$properties= new Properties();
$accessor = PropertyAccess::createPropertyAccessorBuilder()
->enableMagicCall()
->getPropertyAccessor();
foreach($columnNames as $merkmale) {
$accessor->setValue($properties, $merkmale, 1);
}
So how can I handle an update by counting one up ? I missed something I guess
Why don't you use Lifecycle callbacks? This way you can choose doctrine event which suits your case best and increment all values you need in one method.
/**
* #ORM\Entity()
* #ORM\HasLifecycleCallbacks()
*/
class Properties
{
/**
* #var string
*
* #ORM\Column(name="Sport", type="string", length=11, nullable=true)
*/
private $sport;
/**
* #var string
*
* #ORM\Column(name="Entertainment", type="string", length=11, nullable=true)
*/
private $entertainment;
/**
* #var string
*
* #ORM\Column(name="Wellness", type="string", length=11, nullable=true)
*/
private $wellness;
/**
* #ORM\PrePersist
*/
public function incrementValues()
{
$this->entertainment = 1; //you don't need any magic any more
$this->wellness = $this->wellness + 5; //you can access your entity values directly
}
}
All you need to do is annotate your entity with #ORM\HasLifecycleCallbacks() and add method which you also need to annotate with given event, like #ORM\PrePersist and this method will be called by doctrine each time this event occurs

dynamic creating and mapping new field in doctrine/symfony/mysql

Every quartal (each 3-month) i need into table "report" add new "column" - for example "Y2014Q1" where Y=year and Q=quartal (1,2,3,4). After creating I need to make mapping of this new column in to object.
Part of my code is:
namespace backend\backendBundle\Entity;
use Doctrine\ORM\Mapping as ORM;
/**
* FactSheetData
*
* #ORM\Table(name="report")
* #ORM\Entity
*/
class ReportData
{
/**
* #var integer
*
* #ORM\Column(name="id", type="integer")
* #ORM\Id
* #ORM\GeneratedValue(strategy="AUTO")
*/
private $id;
/**
* #var string
*
* #ORM\Column(name="Y2016Q3", type="string")
*/
private $Y2016Q3;
/**
* #param string $Y2016Q3
*/
public function setY2016Q3($Y2016Q3)
{
$this->Y2016Q3 = $Y2016Q3;
}
/**
* #return string
*/
public function getY2016Q4()
{
return $this->Y2016Q4;
}
....
I need to do something like this when new period comes:
class ReportData {
public function __construct() {
// if new perriod alter table
if (test_new_perriod) {
$new_period='Y2016Q4';
$sql="ALTER TABLE `report` ADD COLUMN `".$new_period."` VARCHAR(50) NULL DEFAULT NULL;";
$stmt = $this->getEntityManager()->getConnection()->prepare($sql);
$stmt->execute();
//and now - this is problem how to do - dynamic maping
/**
* #var string
*
* #ORM\Column(name="$new_period", type="string")
*/
private $new_period;
/**
* #param string $new_period
*/
public function set$new_period($new_period)
{
$this->$new_period =$new_period;
}
/**
* #return string
*/
public function get$new_period()
{
return $this->$new_period;
}
}
}
Hope I describe clear... Thanx for any advice.

Symfony2 - Doctrine - Join 2 tables

I'm having issues setting up my Doctrine request properly.
I have two tables (PROPRIETE and PHOTO), one PROPRIETE can have many PHOTO
Therefore, I'd like to make a SELECT that will return an array of PROPRIETE where which one includes an array of it's own PHOTOs (not sure if I'm clear though...)
This is what my Popriete class looks like
class Propriete
{
/**
* #var integer
*
* #ORM\Column(name="id", type="integer")
* #ORM\Id
* #ORM\GeneratedValue(strategy="AUTO")
*/
private $id;
/**
* #var string
*
* #ORM\Column(name="libelle", type="string", length=255)
*/
private $libelle;
/**
* #ORM\ManyToOne(targetEntity="VillaPrivee\UserBundle\Entity\User")
* #ORM\JoinColumn(onDelete="CASCADE")
*/
private $proprietaire;
/**
* #ORM\OneToMany(targetEntity="VillaPrivee\MainBundle\Entity\Photo", mappedBy="propriete")
*/
private $photo;
And then the Photo class
class Photo
{
/**
* #var integer
*
* #ORM\Column(name="id", type="integer")
* #ORM\Id
* #ORM\GeneratedValue(strategy="AUTO")
*/
private $id;
/**
* #var string
*
* #ORM\Column(name="path", type="string", length=255)
*/
private $path;
/**
* #ORM\ManyToOne(targetEntity="VillaPrivee\MainBundle\Entity\Propriete")
* #ORM\JoinColumn(onDelete="CASCADE")
*/
private $propriete;
And finally, my Doctrine request (that successfully returns a list of Propriete, but nothing about their photos)
public function getProprietesByCriteria($ville, $rooms) {
$qb = $this->createQueryBuilder('p');
$qb->where('p.commune = :ville AND p.nbChambres >= :rooms')
->setParameter('ville', $ville)
->setParameter('rooms', $rooms);
return $qb->getQuery()->getResult();
}
I've tried with a leftJoin, but it seems that I don't know how to use that stuff...
Thanks guys for your help
I thing you're mixing different concepts in your code. Using Doctrine you should forget about trying to get multiple arrays with a query.
You only need to invoke the getPhoto() method on any propriete object.
So, you can use a code similar to:
$props = $this->getProprietesByCriteria($ville, $rooms);
foreach($props as $prop)
{
$prop->getPhoto(); // <--will return an array of the `PHOTO` related to this `PROPIETE`
}