Oznámení
Lean Mapper s nette – quickstart
před 6 lety
- svezij
- Člen | 69
Ahojte, přicházím s dalším začátečnickým dotazem. Příklad
quickstart jsem úspěšně prošel bez větších problémů a na základě
jednoho z mých předchozích dotazů jsem se podíval na ORM (resp. mapper) od
Vojtěcha Kohouta. Jak jsem vypozoroval z dokumentace, je to přesně to, co
jsem si chtěl implementovat na koleni. Zkouším tedy předělat příklad
quickstart s použitím Lean Mapperu a zasekl jsem se hned na začátku.
V modelu příkladu je repozitář interpretující databázovou tabulku
(třída Repository) – právě tu bych měl, předpokládám, změnit na
LeanMapper entitu (tedy třídu \LeanMapper\Entity), je tak? Znamená to tedy,
že soubor by měl vypadat nějak takto?
namespace Todo;
use Nette;
abstract class Repository extends \LeanMapper\Entity {
/** @var \DibiConnection */
protected $connection;
public function __construct(\DibiConnection $db) {
$this->connection = $db;
}
protected function getTable() {
... nějaký kód ...
}
protected function findAll() {
... nějaký kód ...
}
protected function findBy(array $by) {
... nějaký kód ...
}
}
Mně to přijde špatně, ztratím tím veškeré výhody třídy
Nette\Object, což mi přijde zvláštní, ale zároveň jak jinak bych mohl
posléze vytvořit UserRepository jen s pomocí anotací (viz dokumentace Lean
Mapperu), potřebuji teď k něčemu tu DibiConnection?… chjo, nějak si
s tím vůbec nevím rady. Bez Nette bych prošel quickstart, který je uveden
na stránkách Lean Mapperu, ale nevím, jak to napojit na Nette :-( . Nemohl
byste mi někdo pomoct? Kdybyste měl někdo dokonce tolik času, že byste
celý quickstart předělali pro Lean Mapper, rád bych ho měl rovnou funkční
a učil se přes to. Za to bych byl velmi vděčný. Chápu ale, že nemusíte
mít tolik času, nebo mi tuto znalost nebudete chtít „dát zadarmo“,
takže prosím alespoň o radu ;-) .
Mockrát děkuji.
před 6 lety
- Etch
- Člen | 404
@**svezij**
V modelu příkladu je repozitář interpretující databázovou tabulku (třída Repository) – právě tu bych měl, předpokládám, změnit na LeanMapper entitu (tedy třídu \LeanMapper\Entity), je tak? Znamená to tedy, že soubor by měl vypadat nějak takto?
Ne repository by mělo dědit od \LeanMapper\Repository
.
Mně to přijde špatně, ztratím tím veškeré výhody třídy Nette\Object, což mi přijde zvláštní, ale zároveň jak jinak bych mohl posléze vytvořit UserRepository jen s pomocí anotací (viz dokumentace Lean Mapperu),
Tak pomocí anotací nastavuješ právě hlavně až entity. Řekl bych, že sis to trochu prohodil co je entita a co repository
Bez Nette bych prošel quickstart, který je uveden na stránkách Lean Mapperu, ale nevím, jak to napojit na Nette
No ono tam není moc co napojovat. Definice repository a entit je stejná jako v dokumentaci LeanMapperu a rozdíl je pak hlavně v tom kde dané repository sebereš.
Tedy že místo
$repo = new \Model\Repository\UserRepository($connection);
$users = $repo->findAll();
si to pravděpodobně někde injectneš do presenteru a pak budeš používat něco na způsob
$users = $this->userRepository->findAll();
EDIT:
Ale tak 2 hodiny budu mít chvíli čas, tak se kouknu na ten quickstart a
přepíšu ti ho s použitím Lean Mapperu.
Editoval Etch (10. 7. 2013 13:15)
před 6 lety
- svezij
- Člen | 69
To bys byl nejúžasnější, děkuji :-).
před 6 lety
- svezij
- Člen | 69
Pořád to nějak zkouším a prohlížím a jen se chci ujistit.
Takže \LeanMapper\Entity reprezentuje přímo tabulku z databáze –
v podstatě „jen“ definuje sloupce v tabulkách (pravděpodobně
i další věci, ale k těm teprve dojdu ;-) )? Ale k jejím hodnotám
(sloupcům) se nepřistupuje přímo, a proto každá tato
tabulka/entita musí mít repozitář (\LeanMapper\Repository), přes který pak
bude volána, ano?
Musí tedy vždy existovat třída
\LeanMapper\Entity NazevEntityZastupujiciTabulku;
a repozitář
\LeanMapper\Repository NazevEntityZastupujiciTabulkuRepository
a
tento repozitář si podle názvu třídy „automaticky odvodí“, že
k němu patří entita NazevEntityZastupujiciTabulku
a bude s ní
pracovat?
Editoval svezij (10. 7. 2013 14:01)
před 6 lety
- Šaman
- Člen | 2275
Jdeš na to ze špatné strany. Zkus si nejdřív nastudovat entitně relační diagram a zkus za ním nevidět databázi.
Tím máš popsané entity (objekty z reálného světa) systému. Takže
třeba kniha-autor-překladatel-ilustrátor-vydavatelství.
Popíšeš si vazby mezi nimi, vytvoříš si na to třídy. Teprve teď
začneš řešit, jak to uložit do db. A zjistíš, že autor, překladatel
i ilustrátor mohou být v jedné tabulce (třeba person
).
A repozitář je třída, která se stará o načítání a ukládání
dat – na jedné straně pracuje s objekty a na druhé straně s databází.
Repozitář zajistí, že když chci autora dané knihy, vrátí se mi
(z tabulky person
) objekt třídy Author
.
Zpočátku máš často jednu entitu v jedné tabulce a stará se o ni jeden repozitář. Ale to není pravidlo a není dobré to tak vnímat. Jedna entita může být ve více tabulkách a více entit může být v jedné tabulce.
před 6 lety
- Etch
- Člen | 404
@**svezij**
Možná by nebylo od věci se obecně seznámit s návrhovým vzorem data mapper.
před 6 lety
- svezij
- Člen | 69
Ok, určitě se podívám, zatím dík oběma.
@Etch
Určitě se podívám, ale stejně, kdybys měl chvilku a udělal mi ten
quickstart, budu ti vděčný. Ještě jednou díky :-).
Editoval svezij (10. 7. 2013 14:41)
před 6 lety
- Etch
- Člen | 404
Můžeš si to stáhnout zde z gitu
Ovšem nehledej v tom žádnou krásu a ani to co tam najdeš neber jako nějakou „Best Practice“. Je to prostě jen rychle přepsané, aby to používalo LeanMapper. S nějakýma věcma jako jsou filtry a či složitější perzistence jsem si tam hlavu nelámal. Navíc repository jsou napsané tak, aby se hodně podobaly těm v „původním quickstartu“.
PS: Je to napsané na Nette 2.1, včetně injektáží, takže se to poněkud liší od toho, co je v původním kódu quickstartu. Může se tedy lehce stát, že někde budeš poněkud zmaten. Dale pak bohužel quickstart není „dostatečně rozsáhlej“, aby se tam projevilo to o čem psal Šaman (Více entit v jedné tabulce atd.). Co si budeme nalhávat v quickstartu v podstatě není moc co mapovat.
Editoval Etch (10. 7. 2013 20:27)
před 6 lety
- svezij
- Člen | 69
Ahojte, tak návrhový vzor data mapper snad chápu – nepřijde mi tam nic nejasného, ale znáte to ;-).
@Šaman
Děkuji za vysvětlení, pohledal jsem toho ohledně návrhového vzoru data
mapper a návrhového vzoru repository trochu více a snad pochopil.
@Etch
Mockrát děkuji za ten příklad. Vůbec nevadí, že to je psané na Nette
2.1, já používam stable verzi 2.0.10 pro PHP 5.3 5.4. Ani nejde o to, aby
to bylo napsané nějak ideálně, šlo mi jen o jakési „popostrčení“.
Stejně moje první aplikace s Nette a LeanMapperem a Dibi atd. rozhodně
nebude ideálně napsaná, ale je to pokrok – zatím jsem vždycky všechno
dělal sám, „na koleni“, a když se na kódy podívám zpětně… no…
škoda mluvit :-( ¤SHY¤ (jako vyznat se v tom dá, ale pokud by to chtěl
někdo rozšířit, tak to není zrovna intuitivní a je to dost „low
level“). Řekl bych, že rozumím všemu až na pár maličkostí, na které
bych se rád zeptal:
1) co v konfigu znamená:
mapping:
*: App\*Module\*Presenter
a
services:
- Model\UserManager
- App\RouterFactory
router: @App\RouterFactory::createRouter
sekci mapping nechápu vůbec a v services nechápu ty pomlčky v prvních dvou řádcích
2) prezentery sis nadeklaroval do jmenného prostoru „namespace App;“ (to možná souvisí s předchozím bodem s tím mapováním v konfigu – odhaduji jen podle toho App na začátku ;-) ) a vlastně to je dobrý nápad, já si to chtěl také dát do jmenného prostoru, obecně dejme tomu „namespace Foo\Subfoo;“, mám ale pak problém, protože při spuštění aplikace dostanu výjimku Cannot load presenter ‚Homepage‘, class ‚HomepagePresenter‘ was not found, jak zařídím, aby mi to tuhle chybu nehlásilo (odstraním-li definici jmenného prostoru, tak to projde dál)?
3) když tedy odeberu jmenné prostory a dostanu se dál, Nette mi hlásí chybu No service of type Todo\Repository\EListRepository found. Make sure the type hint in Method BasePresenter::injectListRepository() is written correctly and service of this type is registered. A ve třídě BasePresenter mám:
/**
* Base presenter for all application presenters.
*/
abstract class BasePresenter extends \Nette\Application\UI\Presenter
{
/** @var \Todo\Repository\EListRepository */
private $listRepository;
public function injectListRepository(\Todo\Repository\EListRepository $listRepository)
{
$this->listRepository = $listRepository;
}
/* ... další metody ... */
}
a v konfigu v sekci services:
services:
authenticator: Todo\Authenticator
taskRepository: Todo\Repository\TaskRepository
userRepository: Todo\Repository\UserRepository
listRepository: Todo\Repository\EListRepository
před 6 lety
- Šaman
- Člen | 2275
- Tuto i
- toto jsou vychytávky verze 2.1, zatím to klidně ignoruj a presentery piš bez NS.
- Tohle by mělo fungovat. Neodebral jsi doufám NS i u toho
EListRepository
? Jinak pokud máš presenter bez NS, tak nemusíš psát lomítka na začátku.
Editoval Šaman (11. 7. 2013 11:56)
před 6 lety
- svezij
- Člen | 69
Ok, první dva body jsem odebral a až vyjde stable verze 2.1, mrknu na
dokumentaci ;-).
NS u EListRepository
mám: namespace Todo\Repository;
Jak to vlastně funguje v kódu metody injectListRepository
? V
„quickstartu“ jsem chápal, že je-li něco zaregistrovaného jako služba
(přes config.neon nebo přes metodu addService), tak si to mohu kdykoliv
vyvolat přes kontext kontejneru. Tudíž jsem použil
$this->context->myService
. Teď jsem ale použil metodu
injectMyService
, kde dělám něco, čemu tak úplně nerozumím,
konkrétně tedy:
public function injectListRepository(Todo\Repository\EListRepository $listRepository)
{
$this->listRepository = $listRepository;
}
Ve skriptu Nette\Application\PresenterFactory.php
jsem našel
kód, který to pravděpodobně obsluhuje, konkrétně v metodě
createPresenter($name)
:
foreach (array_reverse(get_class_methods($presenter)) as $method) {
if (substr($method, 0, 6) === 'inject') {
$this->container->callMethod(array($presenter, $method));
}
}
- je nějaký důvod, proč se metody prochází pozpátku? (to je spíše dotaz ze zvědavosti)
- metoda je tu volána bez parametru, takže se dále zpracovává metoda
autowireArguments
třídyHelpers
, která spadne na vytvoření služby, zkusil jsem nějak pokračovat a změnil jsem řádek
$res[$num] = $container->getByType($class, FALSE);
na
$res[$num] = $container->getByType($class, TRUE);
abych věděl, jestli se opravdu zastavím v první větvi. V metodě
getByType
jsem začal trošku echovat. Dál jsem zjistil, že moje
třída todo\repository\elistrepository
není v proměnné
classes
, kde by být měla. A dál jsem se bohužel zatím
nedostal. Nepomůže to nějak?
Děkuji.
před 6 lety
- Etch
- Člen | 404
Jak to vlastně funguje v kódu metody injectListRepository?
Injektuje to prostě podle type hintu.
V „quickstartu“ jsem chápal, že je-li něco zaregistrovaného jako služba (přes config.neon nebo přes metodu addService), tak si to mohu kdykoliv vyvolat přes kontext kontejneru. Tudíž jsem použil $this->context->myService. Teď jsem ale použil metodu injectMyService, kde dělám něco, čemu tak úplně nerozumím, konkrétně tedy:
Tam je rozdíl v tom, že závislosti injectuješ přes setter injection místo toho, aby sis ty závislosti tahal z contextu. Jak to injectování funguje jsem ti tu už posílal.
před 6 lety
- svezij
- Člen | 69
Jj, to jsem si myslel podle kódu metody autowireArguments
a za
ten druhý dotaz se omlouvám, opravdu jsi mi to už posílal, je toho na
začátku dost k pochopení a neuvědomil jsem si, že už jsem to četl.
Už jsem se ale konečně posunul o krok dál. Zjistil jsem, že systém
nenačítá žádnou službu, a tak jsem se ještě jednou vrátil ke
quickstartu. Tam jsem si uvědomil, že jsem teď config předělal podle
příkladu od Etche a v configu jsem neměl sekci common:
, což je
asi zase vychytávka Nette 2.1, přidal jsem ji tedy a pokračuji dál. Opět
vám mockrát děkuji :-)
před 6 lety
- Etch
- Člen | 404
@**svezij**
Na gitu jsem ti udělal novou větev, kde je to fixnuté pro nette 2.0.11.
Ale změn je tam opravdu naprosto minimálně. Vlastně pokud budu brát
v potaz jen věci, které jsem upravil vlastoručně, tak je to jen upálení
namespace App
+ use statementů (use Nette
)
v presenterech, výměna extension pro Dibi v bootstrapu a přepis bloků
v templatech z {#head}{/#}
na
{block head}{/block}
.
Pokud ti to tedy po upálení namespace začne vyhazovat „No service of type Todo\Repository\EListRepository found. Make sure the type hint in Method BasePresenter::injectListRepository() is written correctly and service of this type is registered.“, tak si umazal i něco co si neměl.
Editoval Etch (12. 7. 2013 10:21)
před 6 lety
- svezij
- Člen | 69
Pro dnešek naposledy :-)… mockrát děkuji, měl jsi pravdu,
u načítání config souborů v bootstrap.php jsem u načtení lokálního
konfigu smazal druhý parametr $configurator::NONE
. Vše je teď
tak, jak má být.
Už bych to sem nepsal, zatím můžu dál, ale kdyby se náhodou někdo dostal
do podobné situace jako já, ať může zkontrolovat i takovouhle nepozornost
/ chybu.
Dík moc a přeji vám všem slunečný víkend :-)
před 6 lety
- Šaman
- Člen | 2275
Aha, to je vypínání sekcí (common, production, …). Nette 2.0.x ještě
se sekcemi počítá, ale dá se to vypnout tím druhým parametrem. Nette
2.1 už defaultně sekce nepoužívá.
Pokud však nepoužíváš sekce a máš Nette 2.0, tak by to mělo hlásit, že
nejde definovat služby mimo sekce. Aspoň mě to tak ještě nedávno
hlásilo.
před 6 lety
- Mesiah
- Člen | 242
Prosím Vás, jak je možný, že tento příklad na githubu funguje? V config.local.neon jsou zadeklarované služby:
taskRepository: Model\Repository\TaskRepository
userRepository: Model\Repository\UserRepository
listRepository: Model\Repository\EListRepository
ale nemají definovanou connection. Z kama si lean mapper natáhne connection? Tohle smrdí černou magií. :/
Etch napsal(a):
Můžeš si to stáhnout zde z gitu
Ovšem nehledej v tom žádnou krásu a ani to co tam najdeš neber jako nějakou „Best Practice“. Je to prostě jen rychle přepsané, aby to používalo LeanMapper. S nějakýma věcma jako jsou filtry a či složitější perzistence jsem si tam hlavu nelámal. Navíc repository jsou napsané tak, aby se hodně podobaly těm v „původním quickstartu“.
PS: Je to napsané na Nette 2.1, včetně injektáží, takže se to poněkud liší od toho, co je v původním kódu quickstartu. Může se tedy lehce stát, že někde budeš poněkud zmaten. Dale pak bohužel quickstart není „dostatečně rozsáhlej“, aby se tam projevilo to o čem psal Šaman (Více entit v jedné tabulce atd.). Co si budeme nalhávat v quickstartu v podstatě není moc co mapovat.
před 6 lety
- Šaman
- Člen | 2275
Mesiah napsal(a):
Prosím Vás, jak je možný, že tento příklad na githubu funguje? V config.local.neon jsou zadeklarované služby:
taskRepository: Model\Repository\TaskRepository userRepository: Model\Repository\UserRepository listRepository: Model\Repository\EListRepository
ale nemají definovanou connection. Z kama si lean mapper natáhne connection? Tohle smrdí černou magií. :/
Kdepak, tohle voní autowiringem
před 6 lety
- Mesiah
- Člen | 242
Šaman napsal(a):
Kdepak, tohle voní autowiringem
Ahha, koukám, že LM prošel celkem bouřlivým vývojem – ten v GH má konstruktor s jedním parametrem definovaným jako DibiConnection, takže máš pravdu, tam magie není – type hinting, ale je ještě jedna věc, která mi je nejasná:
$configurator->onCompile[] = function ($configurator, $compiler) {
$compiler->addExtension('dibi', new DibiNette20Extension());
};
Kde je určeno, co se má v config.neon hledat za sekci pro nastavení dibi connection?
před 6 lety
- Šaman
- Člen | 2275
To je obecné Nette a tvorba extenzí. Tahle se bude jmenovat ‚dibi‘ (lze
pojmenovat jak chceš) a v ní budou záznamy, které už si přelouská ta
DibiNette20Extension (což jsou už ty jednotlivé řádky).
Takže název sekce určuješ ty v tom
addExtension($sectionName, $extensionClass)
, vnitřek je
napevno.
před 5 lety
- Tomichi
- Člen | 4
Chtěl jsem se zeptat mam dibi verzi 2.2.1 a Nette verzi 2.2.1 a Lean Mapper 2.2.0 nefunguje mi dibi profiler v tracy ikdyž dibi mám nainstalvamé a dotazy na Lean Mapperem chodí jen bych rád viděl kolik času jaký dotaz zabere přikládám zde svoje config.neony
parameters:
leanmapper:
host: localhost
username: root
driver: mysqli
password: ''
database: ''
lazy: true
charset: utf8
profiler: true
php:
date.timezone: Europe/Prague
nette:
application:
errorPresenter: Error
mapping:
*: App\*Module\Presenters\*Presenter
session:
expiration: 14 days
services:
- App\Model\UserManager
- App\RouterFactory
router: @App\RouterFactory::createRouter
# registrace Lean Mapperu
defaultMapper: LeanMapper\DefaultMapper
entityFactory: LeanMapper\DefaultEntityFactory
filterClass: Model\Filter\SqlFilter(defaultMapper)
connection:
class: LeanMapper\Connection(%leanmapper%)
setup:
- registerFilter('filterOrderByAndLimit', [@filterClass, 'filterOrderByAndLimit'])
# registrace repositářů
- Model\Repository\UserRepository
- Model\Repository\TextRepository
- Model\Repository\PricelistRepository
- Model\Repository\PricelistrowRepository
- Model\Repository\PricelistcatRepository
před 5 lety
- Tharos
- Člen | 1042
@Tomichi: Ahoj,
příčinu bych viděl v tom, že takto nadefinované
Connection
se nezaregistruje do Tracy panelu.
Já většinou Lean Mapper připojuji do aplikace pomocí extenze,
která je dokonale vykradenou dibi extenzí. Je v nich jediný rozdíl, ta
„moje“ registruje namísto DibiConnection
LeanMapper\Connection
. Věnuj pak pozornost těmto
řádků, v těch se Connection
registruje do panelu.
Pak profiler normálně funguje.
před 5 lety
- Tomichi
- Člen | 4
@Tharos Diky moc za tu extension už to funguje jak má :)