Kdyby\Events, circural reference – asi Gedmo
- Jiří Nápravník
- Člen | 710
Mám problém s Kdyby\Events. Když to hodně osekám tak mám:
final class DiscussionFacade extends \Nette\Object
{
public $onConversationAdded = [];
private $em;
public function __construct(EntityManager $em)
{
$this->em = $em;
$this->conversationDao = $em->getDao(Conversation::getClassName());
$this->conversationVotingLog = $em->getDao(ConversationVotingLog::getClassName());
}
class DiscussionListener extends Object implements Subscriber
{
public function __construct(DiscussionFacade $discussionFacade)
{
....
}
public function getSubscribedEvents()
{
return array(
'DiscussionModule\Model\Facade\DiscussionFacade::onConversationAdded',
);
}
public function onConversationAdded(Conversation $conversation)
{
...
}
}
Když dám do configu:
- DiscussionModule\Latte\Helpers\Discussion
- DiscussionModule\Model\Listeners\DiscussionListener
tak je vše v pořádku, žádný circural reference. Jakmile ale dám tomu listeneru tag kdyby.subscriber. Tak dostanu: „Circular reference detected for services: 171_CategoryModule_Model_Facade_CategoryFacade, 127, 99_DiscussionModule_Model_Facade_DiscussionFacade“
circural reference tam samozřejmě není, to by ostatně křičelo i bez toho tagu.
Když zakomentuju $this->conversationDao = $em->getDao(Conversation::getClassName()); tak circural není. Je to proto asi, protože tady volá EventManager->getListeners() a tam to hasne. Co s tím?
Klikací laděnku jsem dal sem . Chvíli vyčkejte, má 10MB, tak to trvá než půjde klikat.
- Michal Vyšinský
- Člen | 608
Zdravím,
narazil jsem nyní na stejný problém. Povedlo se ti to nějak vyřešit?
- Jiří Nápravník
- Člen | 710
Bohužel, včera než jsem to poslal jsem to řešil asi dvě hodiny a neúspěšně…
- Tomáš Votruba
- Moderator | 1114
K cemu potrebujes predavat $em?
V configu muzes predat do fasady rovnou dao.
services:
- DiscussionFacade(@doctrine.dao(Conversation))
- Michal Vyšinský
- Člen | 608
Už posílám pull-request s fixem
Edit: je to tam: https://github.com/…ents/pull/39
Editoval Michal Vyšinský (21. 3. 2014 14:00)
- Jiří Nápravník
- Člen | 710
Tomáš Votruba: Pokud mám v té fasádě aspoň dvoje DAO, tak si je vytáhnu až v contructoru, vypadal by konstruktor divně mít tam závislost na dvou EntityDAO. Nicméně asi budu i jen pro jedno DAO předávat entitymanager, nevypadá pak neon tak moc programátorsky, a je to „hezčí na pohled“, Ale vím, že EntityManager je takový menší DIC a není to úplně košér…
Michal Vyšínský: super, předběžně (než se to mergne) děkuji:-)
Editoval Jiří Nápravník (21. 3. 2014 14:07)
- Jiří Nápravník
- Člen | 710
JJ, vím, dříve jsem to tak dělal. Více se mi líbí ten EntityManager si předat, konkrétně tady tuším potřebuju i EM jako celek, kvůli celému flushnutí a tahat ho přes $dao->getEntityManager je divný. Já dokonce četl někdě i příspěvek od Filipa, že sám si začal předávat EntityManager než jednotlivé DAOs…
- Filip Procházka
- Moderator | 4668
Chyba je v Gedmu, protože je to sračka a na každej pageload inicializuje všechny listenery. Odmítám to podporovat.
- Jiří Nápravník
- Člen | 710
JJ, už jsem tu novinku četl na Githubu… Bohužel nic lepšího není. Tak mi asi nezbývá nic jiného než tady Kdyby/Events oželet:(
- Filip Procházka
- Moderator | 4668
Gedmo můžeš forknout a opravit. Je to lepší řešení než to opravovat v Kdyby, protože gedmo touhle hovadinou zabíjí výkon, protože ti na každej request inicializuje celej graf objektů kterej se dotýká listenerů.
- Jiří Nápravník
- Člen | 710
JJ, ja jen mylsel, ze to bude neco slozityho, ale kdyz jsem se na to podival tak to jde celkem. Ale je to spíše takový hack, než to poslat jako Pull Request, nenapadlo mě nic lepšího něž dát na
$em->getEventManager()->getListeners(‚prePersist‘)
a pak projet dokud nenarazím na ten Treelistener? Ušetřím tím sice inicializaci spousty eventu, ale úplně ideální by bylo kdybych mohl z eventmanager u vytáhnout podle interfacu, ale to asi EventManager neumožňuje co?
Přemýšlím, jestli by pro mě nebylo lepší napsat si ten NestedTree sám, protože nic jinýho z toho Gedma nepoužívám…
- Filip Procházka
- Moderator | 4668
Spíš jsem myslel něco jako udělat analýzu v compile-time v nějakém CompilerExtension pro gedmo a ten treerepo upravit tak aby přijímal ten listener nějakou inject metodou nebo takněco a nevolal to v konstruktoru. Tím si ušetříš to že to vytváří ty listenery runtime.
Klidně bych na to něco přidal do EventsExtension
aby sis mohl
říct o nějaké konkrétní listenery, nebo si je profiltrovat sám.
Tohle by ti mělo vrátit všechny listenery už teď, pokud to zavoláš v
beforeCompile
:
$builder->findByTag(\Kdyby\Events\DI\EventsExtension::SUBSCRIBER_TAG)
- Jiří Nápravník
- Člen | 710
Aha, takže ty myslíš spíše řešení nette-specific. Já chtěl spíše v samotném Gedmu, aby to pomohlo i těm ostatním, co to používají (mimo nette). Tohle co jsem udělal já, sice ten problém vyřešilo, ale pořád tam pravda jsou nějaké ty listenery inicializované zbytečně, ale mělo by jich být méně, jen ty v rámci toho eventu, nemýlím-li se
- Vojtěch Dobeš
- Gold Partner | 1316
Jestli jsem to dobře pochopil, tak stačí, aby ten prasácký Gedmo
TranslationRepository
(možná i další třídy, nejsem si jist)
vyžadoval ten listener jako klasickou závislost, a netahal to nechutným
způsobem z EventManageru.
- Jiří Nápravník
- Člen | 710
Jestli myslíš předání přes constructor injection, tak to nějak orzumně nejde. Protože je ta repository class volána z EntityManageru jako new *Repository(); s DI se tam asi moc nepočíta…
- Filip Procházka
- Moderator | 4668
Problém je v tom, že gedmo je určen pro Doctrine a čistá doctrine žádné lazy listenery nemá, tedy z jejich pohledu v tom není vůbec žádný problém a nejspíš to tam nikdy neprotlačíš.
- akadlec
- Člen | 1326
Sem tak trochu uvažoval, nedalo by se pořešit některé funkce co má Gedmo jen pomocí Kdyby\Events? Pověsit si eventy na pre/postpersist atd. ? Já osobně z gedma aktuálně využívám blameable (aktualziace autora/editora entity), loggable (verzování obsahu vybraných parametrů entity), translatable pro překlady a sluggable pro generování slugu složeného z parametrů entity.
- Jiří Nápravník
- Člen | 710
Tak kdyby někdo řešil podobný problém jako já, tak jsem to nakonec vyřešil takto. Šlo mi o to, abych nemusel zasahovat do jednotlivých repositories přímo do Gedma. Řeším konkrétně TreeRepositories.
Udělám si vlastní repository, která dědí od té konkrétní v Gedmu a pak ji samozřejmě nastavím těm entitám:
class NestedTreeRepository extends \Gedmo\Tree\Entity\Repository\NestedTreeRepository
{
public function __construct(EntityManager $em, ClassMetadata $class)
{
$this->_entityName = $class->name;
$this->_em = $em;
$this->_class = $class;
}
public function setTreeListener($treeListener)
{
$this->listener = $treeListener;
if (is_null($this->listener)) {
throw new \Gedmo\Exception\InvalidMappingException('Tree listener was not found on your entity manager, it must be hooked into the event manager');
}
if (!$this->validate()) {
throw new \Gedmo\Exception\InvalidMappingException('This repository cannot be used for tree type: ' . $this->getStrategy($em, $class->name)->getName());
}
$this->repoUtils = new RepositoryUtils($this->_em, $this->getClassMetadata(), $this->listener, $this);
}
}
Překryl jsem konstruktor, a ty věci z něj jsem dal až do setteru listeneru.
Potom jsem si udělal subscriber pro Kdyby\Events, které na slouchá na událost kdy se :
class GedmoSubscriber extends Object implements Subscriber
{
private $container;
public function __construct(\Nette\DI\Container $container)
{
$this->container = $container;
}
public function getSubscribedEvents()
{
return array(
'Kdyby\Doctrine\EntityManager::onDaoCreate',
);
}
public function onDaoCreate($em, $dao)
{
if($dao instanceof \JiriNapravnik\Gedmo\Tree\Entity\Repository\NestedTreeRepository){
$dao->setTreeListener($this->container->getService('gedmo.gedmo.treeable'));
}
}
}
není tam moc hezké tahání te pojmenované service, ale je to kvůli tomu, že jak Rixxi\Gedmo tak Zenify mají u listenerů (nevím proč) autowired:no. Řešením je případně si to forknout a autowiring zapnout.
Já mám tedy ještě tu TreeRepository gedmackou podedenou od EntityDao – protože chci pohodlně volat save a remove. Ale to není nutné. funguje to i bez toho. Skoda, že to entitydao nejde použít mj jako tratia, pak by se to dalo používat i k rozšíření vlastních repository class, které už dědí od Doctrine\EntityRepository
- Filip Procházka
- Moderator | 4668
Raději to předání pořeš v konfiguraci pomocí setup sekce, event
Kdyby\Doctrine\EntityManager::onDaoCreate
bych chtěl časem smazat
(ale zatím to dělat nebudu).
- Jiří Nápravník
- Člen | 710
Myslíš jako udělat si tu vlastní Repository jako službu a tam to nastavit? To jsem se pokoušel prvně, ale dělá mi tam problém to, že tu DAO (pochopitelně) vytváříš v EntityManageru přes new:
$dao = new $daoClassName($this, $metadata);
Či-li já když si udělám tu mojí treeRepository jako službu, tak ta setup se použije sice tam, ale pak když se to vytvří přes new, tak si tu už to nastavení nenese…
Nemluvě o tom, že když si to dám jako service, tak se to snaží autowirovat EntityManager (coz je v pohodě) ale pak i ClassMetadata, což nejde
- Filip Procházka
- Moderator | 4668
Aha! Už chápu, mám pocit že ten event jsem tam přidával přesně kvůli tomuto :D