Komponenty teoreticky, vytvářet komponentu pro položku, výpis položek, apod.?
- joe
- Člen | 313
Ahoj, měl bych spíše diskusi k zamyšlení, jestli jdu správným směrem. V diskusi jsem četl, že bych mohl celý web poskládat pomocí komponent a protože už to tak dělám v CSS i JavaScriptu, rád bych i v PHP.
Mám nějaký výpis položek, ty budou prokliknutelné do detailu. Výpis může být různých podob, řádkový, blokový apod.
Je tedy správná cesta vytvořit komponentu Položka
(která by
měla signály například přidat do oblíbených, komentovat, zobrazit
komentáře) a pak další komponentu Výpis položek
(která by
měla například signál pro načtení dalších položek)?
Díky za navedení ;-)
- joe
- Člen | 313
Díky, určitě s tím souhlasím, ale trochu jsem se zasekl.
Bez komponent mám šabloně pro výpis něco takového:
{foreach $cards as $card}
<div class="Card">
<a n:href="Card:detail $card->id">
<img src="{$card->image|thumb:240,320,true}">
<span class="Card-info">{$card->name}</span>
</a>
</div>
{/foreach}
Vytvořil jsem si tedy komponentu Card
, kam jsem přesunul celý
HTML kód s <div class="Card">
,
tím mi v kódu vznikne
{foreach $cards as $card}
{control card}
{/foreach}
Ale (pravděpodobně?) potřebuji v komponentě Card
mít
instanci $card
ještě dříve, než před renderováním (proto
nemám v šabloně {control card, $card}
). Chtěl bych mít
v komponentě signály třeba jako handleAddToFavorite
. Nevím jak
tam tu instanci správně předat.
Dál mám v komponentě odkaz na Card:detail
, což se mi také
nezdá ideální, protože tím komponentě říkám, jaká je struktura
presenterů a to by ji přece nemělo zajímat… (?)
Prosím o nakopnutí, díky :-)
- greeny
- Člen | 405
protected function createComponentCard()
{
return new Multiplier(function ($name) {
$control = new CardControl; // nebo cardControlFactory->create();
$control->setCard($name);
return $control;
});
}
{foreach $cards as $card}
{control card-$card}
{/foreach}
Editoval greeny (22. 10. 2015 15:19)
- joe
- Člen | 313
Multiplier se mi bude hodit, ale v šabloně při vytváření komponenty se
používá její název a já bych té komponentě potřeboval už předat
instanci, protože v $card
mám entitu karty, mohu předat jen
její id, ale to si ji pak budu muset přes entity manager znovu vytáhnout,
i když ji už vlastně jednou na jiném místě mám nebo ne?
Takže mi možná nezbývá než si vytvořit pole komponent už v presenteru, předat je šabloně, kde je jen vypíšu (?)
Editoval joe (22. 10. 2015 14:28)
- David Matějka
- Moderator | 6445
A pokud nepouzivas doctrine, uloz si cards, ktere posilas do sablony, do nejake clenske promenne – kouknes se, jestli zaznam je tam a kdyz ne, vyberes z db.
- joe
- Člen | 313
Díky, momentálně na projektech mám LeanMapper.
@Azathoth Díky, možná to tak bude i u LeanMapperu, čekal bych to, ale nevím proč hledat něco znovu, když to v tu danou chvíli už mám k dispozici.
@Oli Tak to už by nebyla komponenta Card
, ale třeba
CardList
, ne? :-) To jsem se právě chtěl dozvědět, co by bylo
vhodnější… Refaktoruju si projekt a právě nevím jakou cestou se vydat,
ale víc logické mi přijde – karta = 1 komponenta (?)
- Jiří Nápravník
- Člen | 710
V tomhle konkretním případě mi dává smysl vytvořit komponentu, která si vytáhne z databáze i vypíše všechny ty karty najednou. Proč mít {foreach}{control}{/foreach} když mužes mít jen jeden kontrol v šabloně? Jedině pokud bys tu komponentu využíval osamoceně pro vykreslení jedné karty.
link na Card:detail – asi není úplně konkrétní ale sám to používám, je to v podstatě odkaz někam jinam. Například mám anketu a chci mít odkaz an všechny ankety pod ní, tak nějaký link tam mít musím…
- Zax
- Člen | 370
Někdy stačí komponenta která obstarává celý seznam včetně položek, jindy se víc hodí mít zvlášť komponentu na seznam a v ní multiplierem komponenty-jednotlivé položky. Záleží dost na situaci a třeba mě osobně přijde většinou lepší to mít rozdělené. Ale pro jednoduché případy může v pohodě stačit jedna komponenta, proč ne, jenom se to nesmí moc rozrůstat ;-)
@joe Zmínil jsi například že tam chceš mít signály a to je podle mě už taková ta hranice kde bych přemejšlel nad rozdělením. Pokud jdu cestou jedné komponenty, tak si musím vždy v šabloně předat idčko do odkazu a v handle metodě pak zase převádět ičko zpátky na entitu (data), což může být nepříjemné. Pokud to mám rozdělené, nemusím nic řešit, entitu najdu v privátní proměnné (rodič ji musí předat, pochopitelně). Záleží asi kolik těch signálů bude, co je tolerovatelné na údržbu, ale prostě mi to smrdí copy&paste „programováním“.
A pozor taky na další subkomponenty. Kdybys měl třeba přidávání do oblíbených jako komponentu a šel cestou jedné komponenty, tak musíš použít multiplier na přidávání do oblíbených. Pak přidáš lajkování – a zas řešíš multiplier, pro každou subkomponentu znovu a znovu to samé (a když na to dojde, tak se jede jenom přes copy&paste). Ale pokud to máš rozdělené, tak řešíš multiplier jen na jednom místě a v komponentě pro položky už nic neřešíš a používáš komponenty pro lajkování atd normálně bez multiplieru a bezbolestně ;-)
EDIT: a ještě jedna věc – AJAX. Vždy mi přišlo jednodušší
v signálu položky zavolat prostě $this->redrawControl()
a
v šabloně položky mít obyčejný snippet, než se snažit pochopit jak
fungujou dynamický snippety.
Editoval Zax (22. 10. 2015 18:11)
- joe
- Člen | 313
@JiříNápravník Ten foreach
bych sice duplikoval, ale
myslel jsem, že bych pomocí té komponenty vypisoval kartu položky
{controm item:card}
, pak například dětail položky
{control item:detail}
(teď jsem to pojmenoval jinak, než výš) a
měl bych všechny šablony a zobrazení v jedné komponentě.
Co se týká odkazu a rozšíření, chtěl bych si udělat seznam různých jednoduchých komponent, které vezmu z jednoho projektu a dám je do druhého a nebudu je muset upravovat a nijak do nich zasahovat, případně updatovat, tak, aby byly zpětně kompatibilní.
@Zax Musím přiznat, že se mi líbí víc ta možnost s více komponentama, už i kvůli tomu, co popisuješ, jenom mi pořád není jasné, jak do té komponenty zastřešující jednu položku předám entitu
entitu najdu v privátní proměnné (rodič ji musí předat, pochopitelně)
Poradil bys mi prosím, jak toho docílím? Mám tedy v cyklu v presenteru
vytvářet komponenty a do šablony si předat jen pole vytvořených komponent?
Předpokládám, že mi nebude stačit {control card-N}
, protože
potřebuju předat tu entitu z kódu a ještě před fází renderování.
- Zax
- Člen | 370
@joe Třeba si vyrob v seznamu metodu getData
, která ti
data nejdřív naloaduje do privátního pole (s idčky jako klíči) a pak už
jen tahá z něj. V položce zase vyrobíš metodu setData
,
kterou zavoláš v createComponent
a předáš si tu jednu položku
kterou získáš přes getData()[$id]
. Není to složité ;-)
- Jiří Nápravník
- Člen | 710
@joe to s tim control item:detail apod. jsem zkousel drive a byl tam problem s ajaxem, bralo to jen ten defaultni render a ne renderDetail() apod. ale mozna uz je to vyresene nejak…
Rozumim, tak pak tu mas moznost, kteoru taky nekdy vyuzivam, ze si dam setter pro ten odkaz do komponenty (pres closure), a poslu si ho tam pak z presenteru. ale neni to moc pekny, ale nic lepsiho me nenapadlo:)
- joe
- Člen | 313
@Zax Chvíli si s tím hraju a snažím se to pochopit, ale nemůžu docílit nějakého výsledku. Resp. pořád nevím, jak mám té komponentě pro položku předat tu entitu :)
Ve skutečnosti to bude ještě složitější, přibyde mi k tomu
komponenta filtr, kterou jen položkám přidám.
Teď se mi nedaří dostat do Item
entitu,
viz // @TODO Jak sem dostanu entitu?
Našel jsem na fóru Předání parametrů do komponenty při použití Interface ale s tím jsem si nějak neporadil.
Přepsal jsem to do kódu, aby bylo vidět jak to všechno teď mám. Předem všem díky za rady, začínám se v tom opět po letech trochu orientovat…
<?php
namespace FrontModule;
/* ******************** Presenter Items ******************** */
class ItemsPresenter extends BasePresenter
{
/** @var IItemListFactory @inject */
public $itemListFactory;
protected function createComponentItemList()
{
$control = $this->itemListFactory->create();
return $control;
}
}
/* ******************** Component Item List ******************** */
interface IItemListFactory {
/** @return ItemList */
public function create();
}
class ItemList extends \Nette\Application\UI\Control
{
protected $templateFile = __DIR__ . '/ItemList.default.latte';
/** @var IItemFactory */
private $itemFactory;
public function __construct(IItemFactory $itemFactory)
{
parent::__construct();
$this->itemFactory = $itemFactory;
}
public function render()
{
$this->template->setFile($this->templateFile);
$controls = [];
$controls[] = $this->getComponent('item-1');
$controls[] = $this->getComponent('item-2');
$controls[] = $this->getComponent('item-3');
$this->template->controls = $controls;
$this->template->render();
}
protected function createComponentItem()
{
return new \Nette\Application\UI\Multiplier(function() {
$control = $this->itemFactory->create();
return $control;
});
}
}
/* ******************** Component Item ******************** */
interface IItemFactory {
/** @return Item */
public function create();
}
class Item extends \Nette\Application\UI\Control
{
protected $templateFile = __DIR__ . '/Item.default.latte';
private $entity; // @TODO Jak sem dostanu entitu?
public function setEntity($entity)
{
$this->entity = $entity;
}
public function handleLike()
{
dump($this->entity);
}
public function render()
{
$this->template->setFile($this->templateFile);
$this->template->render();
}
}
Šablona pro ItemList.default.latte
<div class="ItemList">
{foreach $controls as $control}
{control $control}
{/foreach}
</div>
<style>
.ItemList {
background: lightblue;
overflow: hidden;
padding: 5px;
}
.Item {
background: lightcoral;
padding: 20px;
}
.ItemList .Item {
margin: 5px;
float: left;
}
</style>
Šablona Item.default.latte
<div class="Item">
Item
<a n:href="like!">Like</a>
</div>
Pak ještě v konfiguraci mám registrované továrny na komponenty
- FrontModule\IItemListFactory
- FrontModule\IItemFactory
- vitkutny
- Člen | 73
@joe v ItemList budeš mít kolekci entit, kterou buď příjmeš přes konstruktor, nebo se o získání bude starat sama komponenta z repozitáře, to je na tobě
v šabloně Itemlist si kolekci proiteruješ a vykreslíš zanořenou komponentu Item v multiplieru pomocí unikátního klíče, na straně PHP, podle klíče vytáhneš entitu z repozitáře (případně z pole entit, pokud je známé z konstruktoru, nebo nejpozději v attached, ale o tohle se stará identity mapa, nebál bych se tedy vždy použít získání podle id přímo z repozitáře) a předáš jí komponentě Item přes továrničku přímo do konstruktoru, kde si ji uložíš do privátní proměnné
ItemList::render
$this->template->items = $this->repository->findAll();
ItemList.default.latte
<div class="ItemList">
{foreach $items as $item}
{control item-$item->id}
{/foreach}
</div>
ItemList::createComponentItem
return new \Nette\Application\UI\Multiplier(function($id) {
return $this->itemFactory->create($this->repository->getById($id));
});
IItemFactory
interface IItemFactory {
/**
* @param Entity $entity
* @return Item
*/
public function create(Entity $entity);
}
class Item extends \Nette\Application\UI\Control
{
private $entity; // @TODO Jak sem dostanu entitu?
public function __construct(Entity $entity){
$this->entity = $entity;
}
public function handleLike()
{
dump($this->entity);
}
}
- vitkutny
- Člen | 73
@joe
v tom případě si pole entit indexované podle id připrav v attached (nebo
pokud se o to stará presenter, předej v konstruktoru) a při vytvoření
komponenty Item vytahuj přímo z tama
private $items = [];
protected function attached($presenter){
parent::attached($presenter);
$this->items = $this->repository->findAll();
}
public function render(){
$this->template->items = $this->items;
}
protected function createComponentItem(){
return new \Nette\Application\UI\Multiplier(function($id) {
return $this->itemFactory->create($this->items[$id]);
});
}
- Pavel Macháň
- Člen | 282
joe napsal(a):
@vitkutny Díky, moc pomohlo. Jen škoda, že LeanMapper mi při vybrání kolekce a pak následném tahání entity podle ID posílá dotaz do DB, ale je možné, že je problém u mě.
Ještě jednou díky všem.
LeanMapper to tahat znova bude. Máš možnost udělat toto:
EDIT: byl sem předběhnut :) @vitkutny
class MyPresenter extends \Nette\Application\UI\Presenter {
/** @var Item[] */
private $items = [];
public function actionMyAction(){
$this->items = $this->repository->getByMyAction();
}
public function renderMyAction(){
$this->template->items = $this->items;
}
public function createComponentMyComponent(){
return new \Nette\Application\UI\Multiplier(function($id) {
if(!isset($items[$id])) {
throw new MyException('Uknown item ID: '. $id);
}
return $this->itemFactory->create($items[$id]);
});
}
}
Editoval Pavel Macháň (23. 10. 2015 13:29)
- Pavel Macháň
- Člen | 282
joe napsal(a):
@vitkutny To udělám, jen musím zkontrolovat, jestli můžu globálně změnit vracející se pole z repozitáře tak, že klíče budou id :)
Pokud ti to nepůjde tak si je přeházej nebo si vytvoř find funkci, která to vtom listu najde.
Editoval Pavel Macháň (23. 10. 2015 13:32)
- Zax
- Člen | 370
@vitkutny Já to většinou řeším getterem který volám místo sahání do property:
class List extends Control {
private $items;
private function getItems() {
if($this->items === NULL) {
$this->items = $this->model->findByXyz();
}
return $this->items;
}
function render() {
$this->template->items = $this->getItems();
}
}
Přijde mi to lepší z toho důvodu, že se data načtou jen v momentě kdy jsou potřeba. Můžeš třeba volat signál, který jen provádí redirect někam pryč – v ten moment vůbec nepotřebuješ nic načítat.
Někdy se může hodit i cachování na úrovni šablon – model se zavolá pouze poprvé a pak už se jen servíruje čisté html z cache. Stačí malá úprava:
class List extends Control {
function render() {
// místo zavolání předám callback
$this->template->items = function() {
return $this->getItems();
};
}
}
{cache}
{foreach $items() as $id => $item} {* nezapomenout závorky; až tady se volá getItems() *}
{control item-$id}
{/foreach}
{/cache}
- Jan Folwarczny
- Člen | 10
joe napsal(a):
@vitkutny To udělám, jen musím zkontrolovat, jestli můžu globálně změnit vracející se pole z repozitáře tak, že klíče budou id :)
LeanMapper má defaultně ID jako klíče pole :)
Viz https://github.com/…pository.php#L298
Editoval Jan Folwarczny (23. 10. 2015 18:48)
- joe
- Člen | 313
@JanFolwarczny Tam to je :), teď debuguju rychle, tak snad dám správný řádek, přes který mi to jde a který tam ID jako klíč nedává:
https://github.com/…r/Entity.php#L771
a pak na řádku 823 to samé.
- Šaman
- Člen | 2666
Nechci tě odrazovat od LM, který momentálně sám používám, ale myslím, že už ani @Tharos ho nepoužívá a neudržuje. (?) Zkušenosti s tímto ORM má jen několik lidí a když narazíš na nějaký vážnější problém, tak pochybuji, že ti někdo fundovaně poradí. LM má stále hodně úzkou vazbu na databázi, takže i pro to kompletní rozkomponentování bych já už doporučil spíš Doctrine. Jeden větší projekt v LM teď dokončuji a právě nedokonalá abstrakce mi občas přidělává vrásky. Tharos ji ale uvedl mezi klady LM, takže to je opravdu vlastnost, nikoliv nevýhoda (LM je díky tomu o řády jednodušší než Doctrina). Na to, o co se pokoušíš, bych ale raději volil ORM s vysokou abstrakcí.
- joe
- Člen | 313
@Šaman Díky za tip, LM mi dost vyhovuje, s Doctrine ho porovnat nemůžu, protože jsem ji ještě nezkoušel a z časových důvodů a ne velkého zájmu o programování na serveru se do toho zřejmě pouštět ani nebudu :) A myslím, že k dokonalému rozkompomentování to stejně nedotáhnu, spíš tak, aby bylo jednoduché se pro mě v tom vyznat a na to mi LM zatím krásně vystačuje :)