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
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.
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?
(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))
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 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.