Už dlouhou dobu řeším problém u SINGLE_TABLE inheritance. Potřebuji totiž změnit typ ale Doctrine zdá se nejdříve ukládá a pak teprve maže. Protože narážím na problém s unikátností klíče. Z ukázky snad bude vše patrné.
/**
* @entity
*/
class Test
{
/**
* @id @column(type="integer")
* @generatedValue(strategy="AUTO")
*/
private $id;
/**
* @OneToOne(targetEntity="Base", mappedBy="test")
*/
private $item;
}
/**
* @Entity
* @InheritanceType("SINGLE_TABLE")
* @DiscriminatorColumn(name="type", type="string")
* @DiscriminatorMap({"foo" = "Foo", "bar" = "Bar"})
*/
class Base
{
/**
* @id @column(type="integer")
* @generatedValue(strategy="AUTO")
*/
private $id;
/**
* @OneToOne(targetEntity="Test", inversedBy="item")
* @JoinColumn(name="test_id", referencedColumnName="id")
*/
private $test;
public function __construct(Test $test)
{
$this->test = $test;
}
}
/**
* @entity
*/
class Foo extends Base { }
/**
* @entity
*/
class Bar extends Base { }
Problém nastane když mám v DB uložené:
Test(2) {
"id" private => 1
"item" private => Foo(2) {
"id" private => 1
"test" private => Test(2) { *RECURSION* }
}
}
a potřebuji Foo zaměnit za Bar. První co mě napadne je:
$em->remove($test->item);
$item = new Bar($test);
$em->persist($item);
$em->flush();
Jenomže je tu problém pokud to takhle udělám vyběhne na mě PDOException
že v tabulce Base už existuje řádek s test = 1. (Protože Doctrine
evidentně nejdříve ukládá (insert/update) a pak teprve maže (delete)).
Jediné funkční a zároveň ošklivé řešení které mě napadlo je hodit za
$em->remove($test->item); ještě jeden flush.
Napadá někoho elegantnější řešení?
Sorry my bad english.
Vrtak-CZ | Nella Project | Osobní Blog | Twitter | LinkedIn | GitHub
„Nastala chyba která neměla nastat“ aneb „Když se chce
všechno jde.“
Protože Doctrine evidentně nejdříve ukládá (insert/update) a pak teprve maže (delete)
Je to přesně tak, takto je v Doctrine 2 udělaný postup zpracování všech změn v UnitOfWork.
Jediné funkční a zároveň ošklivé řešení které mě napadlo je hodit za
$em->remove($test->item);ještě jeden flush.
Osobně mi to přijde taky trošku ošklivé, každopádně toto je best-practice pro řešení podobných problémů se špatným pořadím vykonávaných operací. Takže ano, přesně takhle – udělat to ve více postupných transakcích.
Jan Tichý | Medio Interactive | Medio blog | PHP Guru | Osobní blog | Twitter | Facebook | LinkedIn
je opravdu nutné starý objekt nahradit úplně novým?
nestačilo by změnit data objektu tak, aby se po příštím načtení podle podmínek dědičnosti entity manager rozhodl, že jej má vytvořit jako objekt jiné třídy? (např když povýším usera na admina, nebudu ho přece mazat)
$item->setType('whatever');
$em->flush();
$em->detach($item);
$item = $em->getOne(...);
@paranoiq: to není dost dobře možné
/**
* @entity
*/
class Foo extends Base
{
/** @column */
private $foo;
}
/**
* @entity
*/
class Bar extends Base
{
/** @column */
private $bar;
}
v objektu instance Foo nemám přístup k vlastnosti
$bar z Bar.
Sorry my bad english.
Vrtak-CZ | Nella Project | Osobní Blog | Twitter | LinkedIn | GitHub
„Nastala chyba která neměla nastat“ aneb „Když se chce
všechno jde.“
DiscriminatorColumn skutečně nemusí být součástí
rodičovského objektu? protože právě na něm se definuje.
UPDATE: teď mi došlo, že o DiscriminatorColumn se nebavíme
podle mě je správné řešení to co navrhuji + data specifická pro druhou třídu nastavit až poté. to že je nelze předat konstruktorem je bohužel důsledek toho jak se chová databáze vespod a nic s tím dělat nejde
postup remove … flush … insert je naprosto špatně:
@jantichy: ptám se tě jako doctrine-guru :] umí Doctrine takovouhle změnu třídy (Foo → Bar) objektu detekovat a z dříve připojené tabulky (třeba test_foo) odmazat nepotřebný řádek?
Editoval paranoiq (14. 10. 2010 12:56)
Nejsem si jist zda se dá do DiscriminatorColumn zasahovat „zvenku“ protože se jedná o interní věc Doctrine kterou si řeší sama. (Získat aktuální hodnotu umím ale změnit nikoli).
Tebou navrhované řešení je super a taky mě napadlo nicméně uvedení do praxe je problém.
Přesně z těchto důvodů se mě to nelíbí.
Pokud vím detekovat to neumí. Proto tam mám to remove. Zkoušel jsem to
totiž tak že jsem „naklonoval“ společná data (v zásadě jde hlavně
o hodnotu PK) z instance Foo, kterou jsem detachnul. Naplnil data
do instance Bar udělal merge a flushnul. (Ale nešlo to už
nevím proč).
Sorry my bad english.
Vrtak-CZ | Nella Project | Osobní Blog | Twitter | LinkedIn | GitHub
„Nastala chyba která neměla nastat“ aneb „Když se chce
všechno jde.“
ad merge() – to asi záleží na implementaci obsluhy mergovaných entit. pokud se ukládají komplet, tak by to mělo projít. pokud se při uložení natáhne z db původní verze pro porovnání (aby se ukládal jen rozdíl), tak to dopadne tak jako by jsi ji předtím nedetachoval – nebude mít stejnou připojenou tabulku a neprojde to
poznámka pro sebe:
po dlouhém přemýšlení, dvoutýdenní přestávce a dalším dlouhém
přemýšlení mi došlo následující: Doctrine to jednoduše řešit
nemůže, protože se nechává svázat pravidly jazyka – tak jako neumí
přetypování z třídy na třídu PHP samotné (btw: který jazyk tohle
umí?), neumí to ani Doctrine. podpora přetypování v ORM tedy musí
vycházet z podpory přetypování objektů v jazyce samotném (nebo
frameworku)…
jsem rád že jsem došel k tomu samému… :-)
Sorry my bad english.
Vrtak-CZ | Nella Project | Osobní Blog | Twitter | LinkedIn | GitHub
„Nastala chyba která neměla nastat“ aneb „Když se chce
všechno jde.“
došli jsme sice k tomu samému, ale já se nehodlám vzdát.
inspirovalo mě to k tomuhle: přetypování objektů v PHP
docela bych se podíval na použití, asi tam bude potřeba doplnit do životního cyklu nahrazení atd, že?
Neptej se, jestli se můžeš ptát | Blog | Twitter | GitHub | CMS Kdyby
Nette Jabber Room – nette@conf.netlab.cz , všichni jste vítáni
ve třídě Cast je háček, na který by se měla navěsit UnitOfWork. ta by pak měla implementovat nový druh updatu (jak říkáš „nahrazení“), při kterém si vytvoří/zruší záznamy v rozšiřujících tabulkách a hlavní záznam pouze updatuje (tedy žádné mazání starého a zakládání nového záznamu ve dvou transakcích, jak se to musí dělat v Doctrině)
tak daleko zatím ještě nejsem. jde jen o koncept
btw: s Doctrinou to integrovat nehodlám