NotORM v Nette
Školení, která pořádám
Nejzásadnější novinkou ve vývoji NotORM je jeho zařazení do Nette Frameworku. Tedy – nejedná se o kompletní převzetí knihovny, ale o zařazení odvozené práce – Nette zatím nepodporuje některé obraty (např. definici vlastní konvence pojmenování sloupců) a využívá odlišnou syntaxi přístupu k tabulkám a sloupcům. Ta je méně magická, takže zatímco v NotORM lze napsat $db->article("url", $url)
, tak v Nette se musí psát $db->table("article")->where("url", $url)
. I když je s tím trochu víc psaní, tak je to pro neznalého člověka čitelnější.
Druhým zásadním rozdílem je sjednocení přístupu ke sloupcům a odvozeným tabulkám. Takže zatímco v NotORM znamená $article["author_id"]
číslo autora a $article->author
odkazuje na jeho záznam, v Nette lze použít oba zápisy ve stejném významu – pokud neexistuje sloupec, tak se hledá navázaná tabulka. Tohle v NotORM dost dobře udělat nejde, protože lze používat konvenci, kdy se vazební sloupec bude jmenovat stejně jako navázaná tabulka.
Komunita okolo Nette je velmi aktivní, což zpětně pomohlo i originálnímu NotORM k opravení několika chyb a přidání nové funkčnosti. Novinky v NotORM (od posledně) jsou tedy tyto:
- Zjednodušení agregace: Místo
list($count) = $table->group("COUNT(*)")
se používá $count = $table->count("*")
.
- Vznikla metoda
$table->fetchPairs($key, $val)
vracející asociativní pole.
- Metoda
through via
dovoluje určit, přes který sloupec se k tabulce má přistoupit: $row->$table()->via($column)
. Dřívější způsob spočívající v definici vlastní struktury, kde se specifikovalo, že např. $author->maintained_application()
má vést do tabulky application
přes sloupec maintainer_id
, byl poměrně krkolomný (i když lze stále použít).
- Přibyla cache ukládající data přímo formou PHP kódu místo serializace (využívá
var_export
a include
) – to je rychlejší, ale případná chyba je fatální.
- Metoda
where
nyní přijímá i pole: where(array($column => $value))
.
- Metoda
insert
nyní vrací vložený řádek a podporuje objekty typu DateTime
.
- Metody
select
a order
přijímají více parametrů, takže místo order("date, time")
lze psát i order("date", "time")
.
- Pokud metodě
insert
předáme více parametrů, tak vloží více řádků najednou.
- Pokud metoda přiřazená do
debug
vrátí false
, tak se dotaz neprovede.
- Již dříve popsané spojování tabulek je obecnější – nyní lze napsat i
application.author.name
.
- NotORM v konstruktoru přijímá objekt třídy
PDO
, který tím pádem můžete používat i v okolním kódu. Skoro nikdy to ale není na nic potřeba s výjimkou transakcí – tedy až do změny, která dovoluje transakce ovládat pomocí $db->transaction = "BEGIN"; // nebo COMMIT nebo ROLLBACK
.
- Pomocí metody
union
lze spojit více výsledků dotazů. V některých databázích je podporován i samostatný order
, takže např. $db->application()->order("title")->union( $db->author()->order("name") )->order("id")
vytvoří (SELECT * FROM application ORDER BY title) UNION (SELECT * FROM author ORDER BY name) ORDER BY id
.
- V PHP 5.3 lze místo
$table->where($condition)
psát přímo $table($condition)
.
- K jednotlivému záznamu lze nyní kromě primárního klíče (
$db->author[$id]
) přistoupit i přes jakýkoliv sloupec: $db->author[array("login" => $login)]
.
- Kvůli použití v Nette je nyní NotORM k dispozici i pod licencí GPL 2.
Páni, to jsem ani netušil, že změn bylo tolik. Všechny změny průběžně dokumentuji (vždy po zveřejnění nové verze).
NotORM také navštívilo jednu slepou uličku – kvůli integraci podpory dibi do hlavního kódu jsem vyčlenil práci s databází do samostatné třídy. Po zahrnutí podpory databází do Nette ale bude asi dibi používané čím dál tím méně, tak jsem ho přestal podporovat i v NotORM.
Diskuse
Michal:
Pěkné shrnutí.
V druhém odstavci je v
<?php $aricle["author_id"] ?> chybějící 't', tedy nejspíš a mohlo by to být matoucí, ale nechci rýpat :-).
Tomáš Fejfar:
<?php $db->transaction = "BEGIN"; ?>
Nechci rýpat, ale tohle je asi tak intuitivní a logické jako
<?php $db->action = "CONNECT" //připojí se k DB?>
Vypadá to, jakože si něco připravím a teď to nějak "odpálím". (v mám příkladu něco jako "doAction()" :) Není logické na to mít metody?
<?php $db->transactionBegin(); ?>
A ještě jeden tip - through skoro nikdo neví jak psát.
<?php
$row->author()->use('maintainer_id');
$row->author()->via('maintainer_id');
?>
Ale mimo to se NotORM jeví lépe a lépe.
Jakub Vrána :
Ovládání transakcí se mi taky moc nelíbí. Je to tak proto, že v syntaxi NotORM přistupuje $db->transactionBegin() k tabulce transactionBegin.
through() jsem přejmenoval na via(), je to i výstižnější. Díky za tip, snad to ještě moc lidí nepoužívá.
David Grudl:
Ad spojování tabulek: co přesně znamená to application.author.name?
Vladimír K. Kocourek:
Pokud dobře chápu, $row['application.author.name'] je ekvivalent k $row['application']['author']['name']...
Jakub Vrána :
Ne, $row['application.author.name'] se napsat nedá.
Jakub Vrána :
<?php $db->application_tag("application.author.name", "Jakub Vrana") ?> vrátí značky aplikací, jejichž autor je 'Jakub Vrana'. SQL dotaz:
SELECT application_tag.*
FROM application_tag
LEFT JOIN application ON application_tag.application_id = application.id
LEFT JOIN author ON application.author_id = author.id
WHERE (author.name = 'Jakub Vrana')
v6ak:
Třeba by bez podpory dibi nebylo NotORM za
členěno do Nette, takže ta slepá ulička nemusí být zbytečná :-)
Tomáš:
Database v Nette a samotné NotORM si žijú vlastným životom alebo zmeny na jednej strane sú premietnuté aj na stranu druhú (Database -> NotORM, NotORM -> Database)?
Inak povedané, novinky, o ktorých dnes píšeš budú dostupné aj v Nette alebo tam musia byť najprv vymyslené a napísané niekým iným?
Aby som nezabudol - skvelá práca, vďaka za NotORM, Jakube :)
Jakub Vrána :
David změny v NotORM sleduje a podle svého uvážení je bude dávat i do Nette. Já toho v Nette\Database moc sám dělat asi nebudu. Změn druhým směrem by mělo být minimum.
Lopo:
stav podpory dibi (neaktualizovane) v NotORM je dovod, preco som v BailIff-e NotORM zavrhol a pouzil priamo dibi (trosku upravene)
Ak by malo NotORM kompletnu podporu dibi tak by som ho uz pouzival ... aktualne vsak s ohladom na jeho portovanie do Nette fakt neviem co v BailIff-e nakoniec bude, tj ci ostanem pri dibi, alebo zacnem pouzivat Nette\Database, alebo nieco uplne ine ...
Miroslav Hruška:
Chtěl jsem se zeptat (požádat), zda by nebylo možné implementovat NotORM_Result jako "service", to znamená, že by bylo možné tuto třídu nahradit jinou (podobně jako NotORM_Row). Bylo by tak možné přidávat další funkce například pro formátování výsledků dotazů. Všechny aktuální public metody v NotORM_Result by mohli být klidné označeny jako final (aby se v tom nikdo nevrtal). Jde mi například o několikrát diskutované metody fetchAll a fetchAssoc.
<?php
$notORM->rowClass = 'Moje\Vlastni\Row\Class';
$notORM->resultClass = 'Moje\Vlastni\Result\Class';
?>
Díky.
Jakub Vrána :
To asi nebude tak jednoduché. NotORM totiž vytváří i objekty třídy NotORM_MultiResult, což je potomek NotORM_Result.
Zkus mi prosím lépe vysvětlit, na co přesně by to bylo potřeba.
Miroslav Hruška:
To je škoda, přiznám se, že samotné jádro jsem až tolik nestudoval. Rád bych přidal nějaké sofistikovanější metody pro formátování výsledku dotazu. NotORM je super ale přeci jen mi tam ještě pár věcí schází:
V NotORM je jediná pomocná metoda pro formátování výsledného pole a tou je fetchPairs, rád bych tam viděl ještě fetchAll (vím, že je to náhrada za iterator_to_array ale občas se to hodí a přiznejme si že psát iterator_to_array není příliš cool) a fetchAssoc (něco jako je v nette, ani to nemusí být tak promakané, stačí pouze néco jako:
<?php
$result = $db->application->fetcchAssoc('author.name,id=title');
foreach ($result as $author_name => $apps) {
foreach ($apps as $id => $title) {
echo $author_name . ' je autorem ' . $title;
}
}
?>
nebo
<?php
$result = $db->application->fetcchAssoc('author.name,id');
foreach ($result as $author_name => $apps) {
foreach ($apps as $id => $row) {
echo $author_name . ' je autorem ' . $row['title'];
}
}
?>
Na takové ty malé tabulky, ze kterých potřebuji jen rychle néco vytáhnout, je to naprosto super. Ostatně to asi sám znáš z dibi.
Díky za odpověď.
Jakub Vrána :
Sice to taky není moc cool, ale vždycky si to můžeš napsat zvenku: <?php fetchAssoc($db->application(), 'author.name,id=title') ?>.
Jakub Vrána :
Pokud někomu záleží na rychlosti, tak používá opcode cache. V článku je její použití sice zmíněno, ale výsledky chybí. Argument, že když už máme opcode cache, tak můžeme data ukládat rovnou do sdílené paměti, neplatí vždy – např. aktuální verze eAcceleratoru tuto funkci nemá.
talpa:
pouzivam notORM v Nette, co furt nemuzu najit ci nevim jak na to je jak na spojovaci tabulky...
konkretne umim prochazet, ale smazat zaznam ve spojovaci tabulce, to je uz problem... primary key slozene ze 2 sloupcu tam proste neni, respektive je pod jednim nazvem ID , coz je asi spatne. nebo delam neco spatne a v tom pripade bych to mazani rad videl jak na to:) nebyl by priklad?
Tomáš:
Akým spôsobom sa dajú debugovať/vypisovať SQL queries, ktoré NotORM vykonáva?
Viem, že debug mód je implementovaný cez STDERR ale vôbec neviem, ako s tým pracovať.
Ďakujem.
Jakub Vrána :
STDERR je definován při spuštění z příkazové řádky a znamená chybový výstup. Při spuštění z webu ho lze definovat na cokoliv, ale to bych raději doporučil použití callbacku.
Vyki:
Zdravím, chtěl jsem se zeptat, kdy se můžeme těšit na další tutorial video k notorm. Našel jsem u těch předešlých videí v seznamu ještě třetí s názvem "Model definition (M from MVC)", ale asi se teprve připravuje. Díky za odpověď,
Jakub Vrána :
Už jsem jednou slíbil leden, takže nic dalšího pro jistotu slibovat nebudu.
Vyki:
Rád si počkám, ale myslím, že nastala doba, kdy je hodně lidí nalomených k tomu přejít z Doctrine k NOTORM, protože způsob jakým NOTORM funguje a tvoje články, práce a ukázky jsou dost silnými argumenty.
Miroslav Hruška:
Ahoj Jakube, omlouvám se za komentování starého článku ale moc nevím, kde jinde se zeptat. Potřeboval bych při načítání dat zavolat svojí databázovou funkci na počítání cen, klasickým sql bych to zapsal jako:
SELECT name_cz, product_number, CALC_PRICE(price, currency), description_cz. Díky.
Jakub Vrána :
A otázka zní, jak se to udělá v NotORM?
<?php
foreach ($db->t()->select("name_cz, product_number, CALC_PRICE(price, currency) AS price, description_cz") as $t) {
$t["price"];
}
?>
Miroslav Hruška:
Jj, NotORM :) Tvé řešení samozřejmě funguje, akorát se tím připravuji o možnost cachovat načítané sloupce, spíše by se mi líbilo něco jako
<?php
foreach ($db->t()->addSelect('CALC_PRICE(price, currency)')->as('price') as $t) {
$t["price"];
}
?>
navíc tam mám téch sloupců vícero a nechce se mi vracet k (z pohledu NotORM zbytečnému) způsobu zápisu SELECT části SQL dotazu pokaždé, když potřebuji například naformátovat čas nebo spočítat cenu. Dalším mínusem je volání pluginů, které na to mám také napojené (nainstauli plugin, přidám sloupec do tabulky pomocí ALTER) a pak už si jen rozšířím tabulku pomocí
<?php $grid->addColumn('nazev_sloupce', 'titulek'); ?>
Tento přístup pro mě má hned 3 nevýhody, nešlo by to tedy vyřešit nějak elegantněji?
Díky za reakci
Jakub Vrána :
Už jsem o přidání této metody také uvažoval, mě by se hodila hlavně při spojování tabulek. Takže to asi časem do NotORM opravdu zařadím.
Miroslav Hruška:
Jo, to by bylo super Jakube, včera jsem na něco podobného narazil při agregaci, potřeboval jsem počítat hodnoty objednávek z různých stavů, ale pomocí klasického foreach to nešlo, objednávky jsou totiž malinko složitější a tak jsem musel psát natvrdo SUM(orders.products.price_dph) as price_dph_sum a pak opět vydatlovávat ty sloupce. Je to asi poslední věc, která mi na NotORM chybí (teda zatím, časem se ještě asi něco najde :)
Stano Paška:
Dobrý deň,
dá sa nejako zistiť počet riadkov výsledku a potom na to ešte aplikovať limit?
Problém je pre dibi riešený v článku MVC paradox, dá sa to nejako aj v NotORM?
Ďakujem za odpoveď.
Jakub Vrána :
Ano, celkem snadno:
<?php
$table = $db->table()->where($where);
$count = $table->count('*');
$table->limit($limit);
?>
Stano Paška:
Hmm, presne takto som to mal, ale zrejme to v dibi branchi nefunguje.
Count mi vráti správny počet, ale keď aplikujem limit sa už nespraví sql príkaz.
Používam dibi verziu kôli Typo3, aby som nemal dve konekcie na databázu.
Neplánujete aktualizovať dibi branch?
Stano Paška:
Aha, po novom vo funkcii limit nulujete $this->rows.
Tak si to tam idem doroobiť.
Pepko:
Po dlouhé době, jsem se odhodlak vyzkoušení této knihovny. Zezačítku jsem dost bojoval s "friend visibility emulation". Nakonec jsem zkusil zakázat eaccelerator a ejhle, ono to pomohlo.
Druhý krok, který mne ale zastavil je "Use of undefined constant STDERR - assumed 'STDERR'". Pouzivam PHP 5.2.6 na Linuxu. Co jsem na internetu koukal, tak tento problem by se mel tykat spise Windows serveru:-/
Jakub Vrána :
STDERR se používá při nastavení <?php $notORM->debug = true; ?> a hodí se při spuštění z příkazové řádky. Při spuštění z webu lze tuto vlastnost nastavit na vlastní callback. Viz http://www.notorm.com/#api
Pepko:
Omlouvám se, moje chyba. Koukal jsem na jeden postup na Nette fóru, kde to bylo napsáno že by to mělo fungovat i s NCallbackem.
Jakub Vrána :
Ohledně eAcceleratoru – jaká to byla verze, jaká verze PHP, jaký OS a jak se „bojování“ projevovalo?
Pepko:
Pokud mám zapnutý eAccelerator a do Nette projektu si přidám i NotORM knihovnu, dostávám toto:
Notice: Undefined property: NotORM::$structure
File: /www/publiclibs/NotORM/NotORM/Result.php Line: 20
Pokud v Nette Debuggeru vypnu strictMode, tak přirozeně skončí běh aplikace chybou:
Fatal Error: Call to a member function getPrimary() on a non-object
File: /www/publiclibs/NotORM/NotORM/Result.php Line: 20
Vlastně se v tomto případě pak NotORM pokouší pracovat s tabulkou structure v DB.
Po chvíli zkoumání jsem zjistil, že problém dělá zřejmě "friend visibility emulation". Zkusil jsem si tedy vytvořit jednoduchou abstrakní třídu a další dvě třídy, které z ní dědí + krátký kus kódu. Tento kód jsem spustil mimo svůj Nette projekt s tímto výsledkem.
První spuštění proběhlo v pořádku. Při druhém mi vyskočila chyba:
Fatal error: Cannot access protected property DruhaTrida::$test
Pokud vypnu eAc
Můj server:
PHP 5.2.6
eAccelerator 0.9.5.3
OS Linux CentOS
Jakub Vrána :
Mohl bys to prosím vyzkoušet s aktuální verzí PHP a eAcceleratoru? Pokud chyba přetrvá, tak eAcceleratoru nahlásit chybu?
Schmutzka:
Nešel by udělat nějakýc jednoduchý přehled, jak dělat ty základní věci? Vidím největší potíž s pouhým AND a OR aj.
Také by byla užitečná tabulka, jak na příkaz v sql, v dibi, případně i v dotrine a jak v notorm. Třeba těch základních 10, aby se lépe přecházelo na notorm. Moc by mi to pomohlo.
(Klidně graficky zpracuji zadaná data.)
Schmutzka:
Tak si sám odpovím:
Pokud s notorm začínáte, nebo potřebujete vědět, jak co napsat (OR, IN s více poli, JOIN, COUNT("*")), vytvořil jsem přehlednou tabulku příkazů, tedy helpku, jak to psát: http://bit.ly/SQLcQ
Naleznete zde i fetchSingle() a alternativy k jiným dibi-like příkazům. Pro uživatele dibi bude přechod mnohem jednodušší.
Snad vám bude nápomocna a ušetří spousty hodin a nervů :)
Honza:
Ahoj, mohl bys prosím opět zpřístupnit tvoji tabulku příkazů?
Již je nedostupná.
Díky moc!
Honza
Petr Ogurčák:
Měl bych dotaz, jak s příchodem Nette 2 a Dependency Injection používáš NotORM v Nette?
Modely stále statické? Načítáš modely přes ModelLoader?
Jakub Vrána :
Modely mám stále statické, tedy alespoň jejich většinu. Je to totiž hrozně pohodlné, v šabloně můžu rovnou volat Article::getActives(). Zároveň mě to nijak neomezuje, takže nemám racionální důvod to opouštět.
Experimentuji i s normálními metodami pro práci s jedním konkrétním objektem (např. <?php $article = new Article($row); $article->visit(); ?> ve srovnání se statickým <?php Article::visit($row); ?>), na čemž jisté výhody spatřuji.
Janáček:
Dobrý den, potřeboval bych poradit s problémem propojení více tabulek.
Mám 4 tabulky:
company
- id
- name
- address_id
address
- id
- street_id
- city_id
street
- id
- name
city
- id
- name
Potřebuji vyfiltrovat firmy, které mají v adrese (ulici nebo městě) nějaké jméno např. Dobřichovice
Když zkouším
$db->company('address.street.name like "%Dobřichovice%" or address.city.name like "%Dobřichovice%"')
Tak to samozřejmě nefunguje ...
Děkuji za radu
Jakub Vrána :
Se správně nastaveným kódováním mi to normálně funguje.
Diskuse je zrušena z důvodu spamu.