Updating association - doctrine-orm

I have two entities with a many-to-many relationship (Scenario and Step). I needed to save the order of the steps within a scenario so i added a third entity called ScenarioToStep with a displayOrder property.
class ScenarioToStep
{
/**
* #ORM\Id
*
* #ORM\ManyToOne(targetEntity="Hakisa\Bundle\GettingStartedBundle\Entity\Scenario", inversedBy="scenarioToStep")
* #ORM\JoinColumn(name="scenario_id", referencedColumnName="id")
*
* #var Hakisa\Bundle\GettingStartedBundle\Entity\Scenario
*/
private $scenario;
/**
* #ORM\Id
*
* #ORM\ManyToOne(targetEntity="Hakisa\Bundle\GettingStartedBundle\Entity\Step", inversedBy="stepToScenario")
* #ORM\JoinColumn(name="step_id", referencedColumnName="id")
*
* #var Hakisa\Bundle\GettingStartedBundle\Entity\Step
*/
private $step;
/**
* Order of the step in the scenario
*
* #ORM\Id
*
* #ORM\Column(type="integer")
*
* #var integer
*/
private $displayOrder;
The 3 properties makes the primary key so i can have a step multiple times in a scenario
I have a UI where i can add steps via drag n drop, remove steps or sort step. This UI send an array to PHP where the index is the position and the value is the id of the step.
array(
0 => 1,
1 => 2,
2 => 3,
3 => 1,
4 => 4
)
I want to update my Scenario entity to match this new ordering. I first tried something like:
$scenario->getScenarioToStep()->clear();
foreach ($tabIds as $pos => $stepId) {
$sts = new ScenarioToStep();
$sts->setScenario($scenario)->setStep($step)->setOrder($pos);
$scenario->getScenarioToStep()->add($sts);
}
but i end up with duplicate key errors
if i do similar thing but instead of instanciating a new scenario i ask entity manager for a reference (proxy) then the association table is always empty
Thanks to anyone who has an idea on how i should perform my save

Related

Doctrine ORM insert auto increment ID to other column on same insert

In my table the id is auto increment. And cid and dsid can not be NULL. On insert i need somehow to insert in dsid the value of the auto increment cid. How can i manage this in doctrine ORM / Symfony?
Table:
--------------------------------------
cid | dsid | lid
--------------------------------------
Entity:
/**
* #var int
*
* #ORM\Column(name="CID", type="integer", nullable=false)
* #ORM\Id
* #ORM\GeneratedValue(strategy="IDENTITY")
*/
private $cid;
/**
* #var int
*
* #ORM\Column(name="DSID", type="integer", nullable=false)
*/
private $dsid;
/**
* #var int
*
* #ORM\Column(name="LID", type="integer", nullable=false)
*/
private $lid;
Id generation takes place on database side during inserting, it has nothing to do with Doctrine. You can implement trigger on database side. or make fields nullable to insert a record, and then have postFlush event that will set those fields equal to ID.

Doctrine 2 left join with in a tree

I have two entities: People and Document
A people can have n childrens:
/**
*
* #ORM\ManyToOne(targetEntity="People")
* #ORM\JoinColumns({
* #ORM\JoinColumn(name="dad_id", referencedColumnName="id")
* })
*/
private $dad;
/**
*
* #ORM\OneToMany(targetEntity="People", mappedBy="dad")
*/
private $childrens;
And a people can have n Documents:
DocumentEntity:
/**
*
* #ORM\ManyToOne(targetEntity="People")
* #ORM\JoinColumns({
* #ORM\JoinColumn(name="people_id", referencedColumnName="id")
* })
*/
private $people;
I want to get all peoples (and their children) and their documents of type License in one query:
$qb = $this->em->getRepository('People')->createQueryBuilder('p');
$qb
->select(array('p','d'))
->leftJoin('p.documents','d','WITH','d.type = :type')
->setParameter('type','license')
;
$peoples = $qb->getQuery()->getResult();
Fine, if i call $people->getDocuments() returns only Documents of type 'License'. But when i do this '$people->getChildrens()[0]->getDocuments()' returns for me all Documents of this people.
How i can do a 'left join WITH' cascading on a tree structure?

Modify formula stored in string via VBA

(I have hundreds of rows need to change. The data is like this
activity_id equation
5225518 D0312_ABC * (S3765+S3790+S3762+S3763+S3770+S3764+S4480) * (1-(S2820+S0560))*(1-S1965)*(1-C0151)
7306234 D0300_BCD * C0502 * (1-(S0191))*(1-S1965)
8293425 D0798_CDE * P0692 * (1-(S0191+S2820+S0560+S0290+S0960))*(1-S1965)
9119429 D0793_DEF * S2605 * (1-S0290)*(1-S1965)
I need to combine the two "1-something" together and there is a pattern.
When ever there is a (1-(Sxxx+Sxxxx))*(1-(Sxxx+Sxxxx)),
combine them into one which is (1-(Sxxx+Sxxxx +Sxxx+Sxxxx))
So I only need to change everything with Sxxxx, you can ignore Cxxxx, Dxxxx, Pxxxx, ...
SO I need to change it to:
activity_id equation
5225518 D0312_ABC * (S3765+S3790+S3762+S3763+S3770+S3764+S4480) * (1-(S2820+S0560+S1965))*(1-C0151)
7306234 D0300_BCD * C0502 * (1-(S0191+S1965))
8293425 D0798_CDE * P0692 * (1-(S0191+S2820+S0560+S0290+S0960+S1965))
9119429 D0793_DEF * S2605 * (1-(S0290+S1965))
The following regexp certainly may and arguably should be improved.
Option Explicit
Private m_Rex As RegExp
Private Const SEARCH_PATTERN As String = "\(1-\(?((S\d{4}\+?)+)\)?\)\*\(1-\(?((S\d{4}\+?)+)\)?\)"
' $0 $2
Private Const REPLACE_PATTERN As String = "(1-($1+$3))"
Public Function Simplify(ByVal AVeryParticularFormula As String) As String
If m_Rex Is Nothing Then
Set m_Rex = New RegExp
m_Rex.Global = False
m_Rex.MultiLine = False
m_Rex.IgnoreCase = False
m_Rex.Pattern = SEARCH_PATTERN
End If
Do
Simplify = m_Rex.Replace(AVeryParticularFormula, REPLACE_PATTERN)
If Simplify = AVeryParticularFormula Then Exit Do
AVeryParticularFormula = Simplify
Loop
End Function
? Simplify("D0312_ABC * (S3765+S3790+S3762+S3763+S3770+S3764+S4480) * (1-(S2820+S0560))*(1-S1965)*(1-C0151)")
D0312_ABC * (S3765+S3790+S3762+S3763+S3770+S3764+S4480) * (1-(S2820+S0560+S1965))*(1-C0151)
? Simplify("D0300_BCD * C0502 * (1-(S0191))*(1-S1965)")
D0300_BCD * C0502 * (1-(S0191+S1965))
? Simplify("D0798_CDE * P0692 * (1-(S0191+S2820+S0560+S0290+S0960))*(1-S1965)")
D0798_CDE * P0692 * (1-(S0191+S2820+S0560+S0290+S0960+S1965))
? Simplify("D0793_DEF * S2605 * (1-S0290)*(1-S1965)")
D0793_DEF * S2605 * (1-(S0290+S1965))

How to do Doctrine2 calculated fields ordering

I've got the following doctrine2 query working nicely, it retrieves all 'markers' within some geographical radius.
$qb->select('marker')
->from('SndSpecialistLocator\Entity\Marker', 'marker')
->where('DISTANCE(marker.location, POINT_STR(:point)) < :distance')
->setParameter('point', $point)
->setParameter('distance', $radius);
Now I want to sort them by distance.
$qb->select('marker (DISTANCE(marker.location, POINT_STR(:point))) AS distance')
->from('SndSpecialistLocator\Entity\Marker', 'marker')
->where('DISTANCE(marker.location, POINT_STR(:point)) < :distance')
->setParameter('point', $point)
->orderBy('distance', 'DESC')
->setParameter('distance', $radius);
But unfortunately this does not work, I am wondering is this possible as distance is not a real property of my entity, but a calculated one?
What is the trick here?
Alternatively, try using HIDDEN in your call to select() :
$qb->select('m as marker, (DISTANCE(m.location, POINT_STR(:point))) as HIDDEN distance')
// note HIDDEN added here --->------->------->------->------->------->---^
->from('SndSpecialistLocator\Entity\Marker', 'm')
->where('DISTANCE(m.location, POINT_STR(:point)) < :distance')
->setParameter('point', $point)
->orderBy('distance', 'ASC')
->setParameter('distance', $radius)
->setMaxResults(5);
$query = $qb->getQuery();
$result = $query->execute();
Adding HIDDEN to the SELECT clause hides it from the results but allows it to be used in the orderby clause. Your $result should then contain the objects you want without having to do the extra array_walk.
Unfortunately, ordering by aliases is not possible.
What you can do* instead is to manually repeat the function in your orderBy statement:
$qb->select('marker (DISTANCE(marker.location, POINT_STR(:point))) AS distance')
->from('SndSpecialistLocator\Entity\Marker', 'marker')
->where('DISTANCE(marker.location, POINT_STR(:point)) < :distance')
->setParameter('point', $point)
->orderBy('DISTANCE(marker.location, POINT_STR(:point))', 'DESC')
->setParameter('distance', $radius);
*We will probably all end up in dev-hell for stepping off the holy path of DRY
Found the solution.
$qb->select('m as marker, (DISTANCE(m.location, POINT_STR(:point))) as distance')
->from('SndSpecialistLocator\Entity\Marker', 'm')
->where('DISTANCE(m.location, POINT_STR(:point)) < :distance')
->setParameter('point', $point)
->orderBy('distance', 'ASC')
->setParameter('distance', $radius)
->setMaxResults(5);
$query = $qb->getQuery();
$result = $query->execute();
/**
* Convert the resulting associative array into one containing only the entity objects
*
* array (size=5)
* 0 =>
* array (size=2)
* 'marker' =>
* object(SndSpecialistLocator\Entity\Marker)[647]
* protected 'id' => int 43
* protected 'title' => string 'c5d07acdd47efbe38a6d0bf4ec64f333' (length=32)
* private 'location' =>
* object(SndSpecialistLocator\Orm\Point)[636]
* private 'lat' => float 52.2897
* private 'lng' => float 4.84268
* 'distance' => string '0.0760756823433058' (length=18)
* 1 => ...
*
* array (size=5)
* 0 =>
* object(SndSpecialistLocator\Entity\Marker)[647]
* protected 'id' => int 43
* protected 'title' => string 'c5d07acdd47efbe38a6d0bf4ec64f333' (length=32)
* private 'location' =>
* object(SndSpecialistLocator\Orm\Point)[636]
* private 'lat' => float 52.2897
* private 'lng' => float 4.84268
* 1 => ...
*
*/
array_walk($result, function (&$item, $key)
{
$item = $item['marker'];
});

How create select/options tag from find('threaded') in CakePHP

How to create select/option html tag from find('threaded') data in CakePHP?
Function find() return results like this:
Array
(
[0] => Array
(
[Forum] => Array
(
[id] => 1
[name] => Forum
)
[children] => Array
(
[0] => Array
(
[Forum] => Array
(
[id] => 3
[name] => Programowanie
[parent_id] => 1
)
)
[1] => Array
(
[Thread] => Array
(
[id] => 11
[name] => Nowe forumowisko
[parent_id] => 1
)
)
)
)
[1] => Array
(
[Forum] => Array
(
[id] => 4
[name] => Nauka
[parent_id] => 0
)
[children] => Array
(
)
)
)
How?
there is a build in method for tree like results to be made into a list for select option tags:
$this->Model->generateTreeList($conditions, null, null, ' - ', $recursive);
instead of using find(threaded)
that is if you attached the Tree behavior to it (which you probably should have since its obviously a tree like model).
But if you want to keep your find(threaded) method you need to manually transform it via recursive method.
thanks DSkinner, very informative..
i have modified it to be more generic:
/**
* Returns an indented html select based on children depth
*
* #param array $data_array - Array of data passed in from cake's find('threaded') feature
* #param array $model - the model name
* #param array $key - the key field on the model
* #param array $value - the value field on the model
* #param array $list - Used internally, contains array to be returned
* #param int $counter - Used Internally, counter for depth
* #return array
*/
public function threaded_to_list($data_array, $model=null, $key='id', $value='name',
&$list = array(), $counter = 0, $separator='__')
{
if ( ! is_array($data_array))
return array();
foreach ($data_array AS $data)
{
$list[$data[$model][$key]] = str_repeat($separator, $counter).$data[$model][$value];
if ( ! empty($data['children']))
{
$this->threaded_to_list($data['children'], $model, $key, $value, $list, $counter + 1);
}
}
return $list;
}
Here's what worked for me.
Make sure you replace:
{SELECT_ID} with the value of the drop down
{SELECT_LABEL} with what is displayed as the option
{MODEL_NAME}
with your model name
/**
* Returns an indented html select based on children depth
*
* #param array $data_array - Array of data passed in from cake's find('threaded') feature
* #param array $list - Used internally, contains array to be returned
* #param int $counter - Used Internally, counter for depth
* #return array
*/
public function drop_down_from_threaded($data_array, &$list = array(), $counter = 0)
{
if ( ! is_array($data_array))
return array();
foreach ($data_array AS $data)
{
$list[$data[{SELECT_ID}]] = str_repeat(' ', $counter).$data[{SELECT_LABEL}];
if ( ! empty($data['children']))
{
$this->drop_down_from_threaded($data['children'], $list, $counter + 1);
}
}
return $list;
}
/**
* Get the data from the find('threaded') and pass it to our new function
*/
$results = $this->{MODEL_NAME}->find('threaded');
$results = $this->drop_down_from_threaded($results);
This may not work 100% for everyone, it works for me, but it should help give you something to start with.
What are "children"? Seems like your tree is spawned across one-to-many relations
It is not necessary to use the Tree behavior to use this method - but all desired results must be possible to be found in a single query.