Dynamické vyžiadanie služby
- iNviNho
- Člen | 352
Ahojte,
Tvorím jednoduchú komponentu, ktorá na základe zadaného reťazca (klasický search input) prehľadáva moduly systému a z každého modulu mi vráti nejaku šablonu dát.
Kedže každý zákazník dostáva inú konfiguráciu modulov, chcem vytvoriť search, ktorý by požiadal iba nainštalované moduly o vrátenie nejakého výsledku.
Nemôžem si teda dopredu definovať služby (premenné triedy), lebo logicky nemusia existovať (ani fyzicky)…
Je nejako možné za behu požiadať DI container o získanie služby?
<?php
// tu by som nejakú triedu požiadal o získanie mailerservice registrovanej v cfg
$mailerService = "MailerModule\Services\MailerService";
$data = $mailerService->getMailsByQuery($this->getQuery());
?>
- GEpic
- Člen | 566
iNviNho napsal(a):
Ahojte,
Tvorím jednoduchú komponentu, ktorá na základe zadaného reťazca (klasický search input) prehľadáva moduly systému a z každého modulu mi vráti nejaku šablonu dát.
Kedže každý zákazník dostáva inú konfiguráciu modulov, chcem vytvoriť search, ktorý by požiadal iba nainštalované moduly o vrátenie nejakého výsledku.
Nemôžem si teda dopredu definovať služby (premenné triedy), lebo logicky nemusia existovať (ani fyzicky)…
Je nejako možné za behu požiadať DI container o získanie služby?
<?php // tu by som nejakú triedu požiadal o získanie mailerservice registrovanej v cfg $mailerService = "MailerModule\Services\MailerService"; $data = $mailerService->getMailsByQuery($this->getQuery()); ?>
Dokonce si můžeš povolit injectování v komponentě. :)
- GEpic
- Člen | 566
iNviNho napsal(a):
@GEpic to viem, ale to by moj problem neriesilo :)
Aha, sorry. My toto řešíme configem. Prostě v configu si nadefinujeme dostupné komponenty k danému modulu s jejich továrničkou, no a podle toho jaké kdo má moduly, tak se mu taky složí seznam dostupných komponent.
Neon je super, můžeš jich mít kolik chceš, modulový, projektový a můžeš si pomocí nich nastavit každý projekt či modul přesně tak jak potřebuješ, anižby si kvůli projektu musel zasahovat do modulů.
Editoval GEpic (28. 9. 2016 18:46)
- artemevsin
- Člen | 61
jak píše @CZechBoY, viděl bych to na vytvoření nějaké třídy/služby. něco jako:
<?php
class SearchService {
/**
* @var ModuleOne
*/
private $moduleOne;
/**
* @var ModuleTwo
*/
private $moduleTwo;
/**
* SearchService constructor.
* @param ModuleOne $moduleOne
* @param ModuleTwo $moduleTwo
*/
public function __construct(ModuleOne $moduleOne, ModuleTwo $moduleTwo)
{
// autowiring komponent
$this->moduleOne = $moduleOne;
$this->moduleTwo = $moduleTwo;
}
/**
* @param string $searchPhrase
* @param \Nette\Security\User $user
* @return array
*/
public function search($searchPhrase, \Nette\Security\User $user)
{
// klidně to může být nějaká služba, která ti vrátí dostupné moduly
$modules = $user->getIdentity()->getAvailableModules();
$results = [];
foreach ($modules as $moduleName) {
$results += $this->getModule($moduleName)->search($searchPhrase);
}
return $results;
}
/**
* @param string $name
* @return IModule
*/
private function getModule($name)
{
$map = [
'moduleOne' => $this->moduleOne,
'moduleTwo' => $this->moduleTwo
];
if (!array_key_exists($name, $map)) {
throw new \InvalidArgumentException;
}
return $map[$name];
}
}
?>
- Oli
- Člen | 1215
@artemevsin tohle je v podstatě malej container
;-)
Záleží na tom, kolik těch služeb může být. Pokud jedna a vždy jiná, tak bych si injectnul interface, zakázal autowire na nich a injectul si tu, kterou potřebuju. Nevýhoda je, že tyhle služby budeš muset předávat i jinam ručně. Takže, pokud se používají často, tak je to nevhodný.
Druhou možností by bylo prohledat interfaces. Něco jako používá Kdyby/doctrine. Pokud máš každý modul jako rozšíření, tak by jsi to mohl udělat v podstatě úplně stejně.
Pokud ne, tak službám, který chceš takhle injectovat nastavíš
interface, řekněme třeba ISearchModule
. A pak v extension projedeš všechny třídy, který implementujou tu interface
a injectneš si je do toho searchFormu (Možná na to bude mít nette DI
nějakou metodu, která to umí vrátit hezčeji než takhle blbě přes
reflexi). Nedělej to nikde jinde nebo si to cachuj. V extension třídě se ti
to nacachuje samo…
- iNviNho
- Člen | 352
Dá sa aj tak, zatiaľ som to vyriešil tak, že akonáhle mám modul v konštante SEARCH_MODULES tak to znamená, že som jej vytvoril službu aj na vyhľadávanie a priamo DI container mi ju vráti a môžem ju požiadať o vrátenie už nastavenej, ready šablony. V skratke takto:
<?php
/**
* Performs search in certain module
* and renders out given result
* @param string $resourceName
*/
private function performSearch($resourceName) {
$template = $this->getTemplate();
if (in_array($resourceName, self::SEARCH_MODULES)) {
$moduleSearch = $this->container->getService($resourceName.".".$resourceName."ModuleSearch");
$moduleSearch->setup($this->getQuery());
$template = $moduleSearch->getTemplate();
$template->render();
}
}
?>
- Oli
- Člen | 1215
To mě přijde jako dost naivní přístup :) Mimojiné to předpokládá přesný název služby. Předáváš celý container (to už mě přijde lepší to řešení @artemevsin). A zároveň musíš udržovat ve kterých modulech se to prohledává v konstantě. Jinými slovy: Za 2 roky, až na tom něco změníš (např. název služby), tak ti to přestane fungovat a ty na to budeš koukat jak tele na nový vrata. Vím o čem mluvím :)
Jako, jestli máš napnutý rozpočet, tak asi good enough, ale je to technologickej dluh. Pokud si to můžeš dovolit, tak bych rozhodně doporučil udělat co modul, to extension a pak už je strašně jednoduchý to navěšení toho interface na extension a projítí jako jsem psal výš. ;-)
- iNviNho
- Člen | 352
@Oli Samozrejme, nie je to finálne a netvrdím, že najlepšie a vlastne celkovo cítim code smell v tom, že niečo musí mať presný názov :-\
Každý modul registrujem ako extension: ArticleExtension,
MailerExtension, InvoiceExtension atd atd atd…
Takisto v extensionoch definujem aj cesty pre slovníky pre
Kdyby\Translation, takže viem o čom hovoríš.
To čo vlastne popisuješ v treťom odstavci sa mi páči, celkom sa s tým návrhom stotožňujem, čiže:
1. Vytvorím si napr.:
<?php
- class ContactModuleSearch implements IModuleSearch ()
- class InvoiceModuleSearch implements IModuleSearch ()
- class MailerModuleSearch implements IModuleSearch ()
?>
2. V BaseModule, ktorý je rodičom všetkých modulov, mám vytvorenú triedu, ktorá jednoducho povedané získa z týchto searchov template z výsledkami vyhľadávania.
<?php
class GeneralSearch extends Control {
/** @var ResourceService */
private $resourceService;
/** @var User */
private $user;
/** @var Container */
private $container;
private $query;
/** List of modules that have search */
const SEARCH_MODULES = ["mailer", "contact", "invoice"];
public function __construct($query, ResourceService $resourceService, User $user, Container $container) {
$this->query = $query;
$this->resourceService = $resourceService;
$this->user = $user;
$this->container = $container;
}
/**
* Checks allowed modules for current user
* and performs search in them
* For each module it renders its search result
*/
public function render() {
$activeResources = $this->resourceService->getAllActive();
foreach ($activeResources as $activeResource) {
if ($this->user->isAllowed($activeResource->getName(), "view")) {
$this->performSearch($activeResource->getName());
}
}
}
/**
* Performs search in certain module
* and renders out given result
* @param string $resourceName
*/
private function performSearch($resourceName) {
$template = $this->getTemplate();
if (in_array($resourceName, self::SEARCH_MODULES)) {
$moduleSearch = $this->container->getService($resourceName.".".$resourceName."ModuleSearch");
$moduleSearch->setup($this->getQuery());
$template = $moduleSearch->getTemplate();
$template->render();
}
}
private function getQuery() {
return $this->query;
}
}
?>
Aby sa zachovala štruktúra a zmenil som obsah funckie performSearch, tak potrebujem v nej pracovať s triedami, ktoré majú implementované rozhranie IModuleSearch a nad nimi zavolať metodu, ktorá vráti template na základe nejakého $query stringu.
Otázkou teda je ako do tohto controlu dostať(injectnúť triedy, ktoré implementujú dané rozhranie) a ako sa s ním dá následne pracovať… Skúsim pohľadať, ale idem vybehnúť na obed :)
Zatiaľ ďakujem za nakopnutie a nápady
Editoval iNviNho (29. 9. 2016 12:07)
- newPOPE
- Člen | 648
@iNviNho co tak hladat na zaklade tagu. Kde vlastne tie moduly mas registrovane? V nejakom tvojom systeme/registri alebo v DI containeri?
- GEpic
- Člen | 566
Za mě, pokud musíš udělat něco takového:
const SEARCH_MODULES = ["mailer", "contact", "invoice"];
Pak moduly a veškerá abstrakce postrádá smysl. Navíc když už to registruješ jako extension, pak už to nějaký zápis v configu má, proč se toho nedržet?
Editoval GEpic (29. 9. 2016 12:36)
- Oli
- Člen | 1215
Trochu jsem si s tím pohrál a samotnýho by mě zajímalo, jestli to jde i jednodušeji? Na tagy, jsem zapomněl, díky @newPOPE . Takže funkční by mělo být toto:
- To co už máš. Služby, který implementujou nějakou jednu interface
- Všem třídám přidáš nějaký tag, např.
moduleSearch
- Třída, do které chceme nahrát všechny služby definovaný v kroku jedna by mohla vypadat nějak takhle (zkráceně)
class GeneralSearch extends Control {
private $searchModules = [];
public function __construct($query, ResourceService $resourceService, User $user) {
$this->query = $query;
$this->resourceService = $resourceService;
$this->user = $user;
}
public function addModule(IModuleSearch $module) {
$this->searchModules[] = $module;
}
private function performSearch($resourceName) {
$template = $this->getTemplate();
foreach($searchModules as $module) {
$module->setup($this->query);
$template = $module->getTemplate();
$template->render();
}
}
}
- a konečně místo, kde injectneme ty moduly
public function beforeCompile() {
parent::beforeCompile();
$builder = $this->getContainerBuilder();
$generalSearch = $builder->getDefinition($this->prefix('generalSearch'));
foreach ($builder->findByTag('moduleSearch') as $name => $bool)
{
$statement = new Statement($builder->getDefinition($name)->getClass());
$generalSearch->addSetup('addModule', [$statement]);
}
}
Editoval Oli (29. 9. 2016 14:28)
- iNviNho
- Člen | 352
@CZechBoY ďakujem za nakopnutie
@Oli Super super super
Ďakujem vážim si to aj váš čas.
Celkovo môže byť problém aj v tom, že nepoznám až tak dobre silu DI a čo všetko pomocou nej dokážeme. Zajtra to v robote skúsim a sám som zvedavý ako to celé funguje a čo mi vráti napr. new Statement()
btw nemôže nastať ešte nejaky problém v tom, že triedu GeneralSearch vytváram cez IGeneralSearch, kedže query jej musím vždy dodať ručne?
<?php
/**
* ....
*/
interface IGeneralSearch {
/**
* @return GeneralSearch
*/
function create($query);
}
?>