Nette\DI\ServiceCreationException – neumím založit službu presenteru
- m.brecher
- Generous Backer | 871
Ahoj,
v modelové třídě jsem chtěl přidat do metody pro získání atributu src tagu <img> i cestu $basePath a tu jsem chtěl získat z presenteru. Zkusil jsem injektnout presenter do modelové třídy pomocí DI jako službu, ale nejde mě to:
Presenter:
namespace App\AdminModule\Presenters;
final class ProjectPresenter extends AdminPresenter
{
public function __construct(
private ImageModel $imageModel,
)
{}
Model:
class ImageModel
{
public function __construct(
private Explorer $database,
private ProjectPresenter $presenter,
)
{}
Konfigurace:
services:
- App\Router\RouterFactory::createRouter
- App\AdminModule\Presenters\ProjectPresenter
search:
model:
in: %appDir%/Model
adminModel:
in: %appDir%/AdminModule/Model
Přidání presenteru do služeb jsem viděl někde na fóru, doporučoval to myslím @MarekBartoš a fungovat by měl. Mě ale výše uvedený kód vyhazuje chybovou hlášku:
Nette\DI\ServiceCreationException
Service of type App\AdminModule\Model\ImageModel: Service of type App\AdminModule\Presenters\ProjectPresenter required by $presenter in ImageModel::__construct() not found. Did you add it to configuration file?
Ve službách presenter přidán je, tak bude něco vadit vytvoření služby, napadlo mě třeba:
- presenter injektuje model a model injektuje presenter – není to nepovolená smyčka?
- presenter očekává v konstruktoru nějaké závislosti, které je potřeba mu dodat v konfiguraci
Díky za jakékoli tipy co dělám špatně.
Vím, že $basePath mohu pohodlně přidat v šabloně, že to asi není úplně nejlepší nápad injektovat presenter do modelu, ba co víc dokonce asi antipattern, ale i když se asi použití budu snažit vyhnout, rád bych věděl co je špatně, abych konfiguraci konečně trochu porozuměl.
- Marek Bartoš
- Nette Blogger | 1274
ApplicationExtension u presenterů vypíná autowiring, protože není žádný důvod, proč bys je měl vyžadovat jako závislost ve svém kódu.
Nette base path získává z Nette\Http\IRequest a kód vypadá takto:
$baseUrl = rtrim($this->httpRequest->getUrl()->withoutUserInfo()->getBaseUrl(), '/')`
$basePath = preg_replace('#https?://[^/]+#A', '', $baseUrl);
Pro to chceš bych si spíš vytvořil presenter, napsal pro něj routu a
odkazy na obrázky generoval pomocí této routy. Pokud obrázek existuje pod
danou url, tak ho webserver rovnou odešle. Pokud ne, můžeš na to
zareagovat – třeba i vygenerováním onoho obrázku.
A nebudeš tak muset řešit kde získat base path.
Registraci presenterů do služeb jsem doporučoval kvůli edge cases, které u automatické registrace existují. Presenter v Nette běžně služba je, ale Nette se nemusí povést všechny presentery nalézt (v závislosti na nastavení autoloadingu a dohledávání presenterů) a tak je žádoucí je registrovat do služeb manuálně.
Editoval Marek Bartoš (28. 8. 2022 14:51)
- m.brecher
- Generous Backer | 871
@mystik Díky za vysvětlení, takže stačí autowiring nějak zapnout, zkusím to.
Zkusil jsem v modelových třídách cyklické injektování a skutečně to není povoleno:
Nette\InvalidStateException
Circular reference detected for services: application.5, 09, 08.
Takže když se mě podaří autowiring zapnout, mělo by to vyhodit podobnou hlášku.
- mystik
- Člen | 312
Ja bych se o to vubec nepokousel. Nette to vypina z dobreho duvodu. Obejit to myslim nebude vubec jednoduche (nejspis by to vyzadovalo napsat vlastni DI extension) a vysledek stejne bude cyklicka zavislost a i kdyby ne tak to je hodne spatna architektura.
Sel bych se radou co napsal Marek Bartos.
- Bulldog
- Člen | 110
Kompletní souhlas s mystik
Nicméně obejít to lze velmi jednoduše a to i cyklickou závislost a
i explicitní vypnutí autowiringu.
Díky tomu, že NetteDI umí krom registrace služeb je i rovnou decorator, tak
můžeš zapsat něco jako:
services:
-
create: App\Model\Authenticator
setup:
- setPresenter(@application.2) # application.2 je u mě v test appce ErrorPresenter
Nevýhoda je akorát ta, že nemáš servicu nastavenou už v konstruktoru, ale až kdo ví kdy :) (většinou těsně po vytvoření, ale u komponent kdo ví, jestli před attachem, nebo po?)
Autowiring to obejde, protože to je explicitní žádost a ne právě autowiring a ~~circular reference taky, jelikož to není povinná závislost, takže se nejdřív vše vytvoří a pak se zavolá ten set. :)~~ Tak hovno neumí to obejít circular reference :D Ale na to jsou ještě jiné metody. Každopádně mi to přijde jako chyba :D Decorator by to měl zvládat… :D
Nicméně zůstává pravda pravdou a to, že se tohle prostě nedělá.
Presenter je prezentační vrstva ne modelová, takže tak jak ji chceš
použít to nemá co dělat.
(pořadí presenterů se může měnit během vývoje. Tak bacha na číselko.
:) )
Editoval Bulldog (29. 8. 2022 19:58)
- mystik
- Člen | 312
@Bulldog Takhle jednoduche to prave neni. Nette DI vzdy sluzbu vytvari kompletne vcetne setupu takze rozbit circular dependency neni vubec jednoduche. Kdysi se resilo jestli kvuli tomu nemit nejaky odlozen setup, ktery se vola az kdyz sluzby existuji, ale nerealizovalo se to. Takze kdyz chces rozbit circular dependency musis trochu kouzlit s DI extension.
- Marek Bartoš
- Nette Blogger | 1274
Ani nemusíš kouzlit s DI extension, pokud se obejdeš bez kontroly při kompilaci, že služba existuje. Ale záměrně neřeknu jak. Nedávno jsem si vytvořil dost komplikovanou cyklickou závislost v DI a v logování, a i když přesně vím, co se tam děje, tak vyřešit problém správně mi zabralo několik dní. Není to nic, co by se mělo objevit jako návod na fóru.
@Bulldog Jestli dekorátorem myslíš DecoratorExtension, tak to je jen způsob, jak setup aplikovat na více služeb, vygenerovaný kód je stejný jako při konfiguraci jednotlivých služeb. Klasický decorator by problém mohl (ale nutně nemusel) vyřešit.
Presentery jsou jen nepojmenované služby. Jestli nechceš číslo, není nic snazšího než presenter do služeb registrovat sám a pojmenovat jej.
- Bulldog
- Člen | 110
mystik napsal(a):
@Bulldog Takhle jednoduche to prave neni. Nette DI vzdy sluzbu vytvari kompletne vcetne setupu takze rozbit circular dependency neni vubec jednoduche. Kdysi se resilo jestli kvuli tomu nemit nejaky odlozen setup, ktery se vola az kdyz sluzby existuji, ale nerealizovalo se to. Takze kdyz chces rozbit circular dependency musis trochu kouzlit s DI extension.
Tak obecně se dá cyklická závislost řešit velice jednoduše a to třeba
accessorem:
Pokud ClassA je závislá na ClassB a naopak,
tak pak třeba takto:
ClassA:
class ClassA {
public function __construct(ClassB $classB) { }
}
ClassB:
class ClassB {
public function __construct(ClassAAccessor $classAAccessor) { }
}
ClassAAccessor:
interface ClassAAccessor {
public function get(): ClassA;
}
A tohle už projde (Zrovna odzkoušeno)
Taky je samozřejmě možné aby obě třídy měly takovýto accessor.
Jenom je nutné nezapomenout v tomto případě, že ClassB
nesmí
nad ClassAAccessorem
volat metodu get
už
v konstruktoru, jinak by se opět jednalo o cyklickou závislost, kterou DI
nepustí.
Prosím hoši je tu někde návod na to, jak ovládat fórum? Nenašel jsem nikde jak přeškrtnou text.
EDIT:
Pozor, accessor kvůli autowiringu nelze použít na Presenter. Pokud tedy je
nutné to udělat s presenterem, tak tak, že presenter bude mít jako
závislost accessor na službu a služba bude získávat presenter přes set
metodu viz můj minulý koment..
Editoval Bulldog (29. 8. 2022 21:35)
- m.brecher
- Generous Backer | 871
Děkuji všem za podrobný náhled do problematiky DI. Ano, presenter je top level manager a modelové třídy musí být schopné pracovat nezávisle na něm. BasePath nakonec je asi nejsprávnější řešit v šabloně a nezatěžovat tím model.
Presentery Nette k „zneužívání“ tak trochu svádějí – umožňují přímý přístup k řadě dalších objektů a přes ně k dalším atd. Tedy tak trochu se dají „zneužít“ jako Service Lokator, což je Bad Practice. Člověk prostě musí servis locator pokušení odolat a hledat koncepčně správné řešení a dodržovat hierarchii řízení.