tiny ‘n’ smart
database layer

Odkazy: dibi | API reference

Oznámení

Omlouváme se, provoz fóra byl ukončen

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
  1. Tuto i
  2. toto jsou vychytávky verze 2.1, zatím to klidně ignoruj a presentery piš bez NS.
  3. 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));
  }
}
  1. je nějaký důvod, proč se metody prochází pozpátku? (to je spíše dotaz ze zvědavosti)
  2. metoda je tu volána bez parametru, takže se dále zpracovává metoda autowireArguments třídy Helpers, 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á :)