Addon objektový přístup k datům z Nette pro Dibi – nORMa
- Rampa
- Člen | 65
Tak jsem se odhodlal a zkusím přispět taky svojí troškou do mlýna
:)
Dovolte mi, abych Vám představil addon nORMa :)
Pro svojí potřebu jsem si udělal takovou jednoduchou ORM nadstavbu nad Dibi
a Nette.
Celý system pracuje s Entitama a kolekcemi entit. Definice entit je (skoro)
ala Doctrine2:
(Doctrine2 mě inspirovalo, ale je to na mě moc robustní a nepřehledný
:) )
<?php
/** @table orders */
class Objednavky {
/**
* @type Integer
* @identifier // určuje primary key v tabulce
*/
public $id;
/**
* @type String
* @column nejaky_popis // mapovaní názvu property na sloupec v tabulce, pokud se nezadá, mapuje se název proměné
* @nullable // může být null
*/
public $muj_popis;
/**
* @type Collection
* @collection Polozky
* @rule(table=polozky, key=id, remoteKey=orderID)
*/
public $polozky;
/**
* @type String
* @leftjoin(table=users, key=user_id, remoteKey=id)
* @column user_name
*/
public $uzivatel;
/**
* @type Integer
*/
public $stav;
}
/** table @order_items */
class Polozky {
/**
* @identifier
* @type Integer
*/
public $id;
/**
* @type Integer
*/
public $order_id;
/**
* @type String
*/
public $nazev;
}
?>
V této třídě se definují jen jednotlivé property, nic jiného se tu nepíše. Systém se sám stará o validaci typů, dle zadané definice a mužete si sami definovat vlastní typ dat:
<?php
class TypKladnyInt extends Norma\BaseDataType{
public function validate($value){
return(is_int($value)&&$value>=0);
}
}
?>
A pak už jen zadat do definice entity @type TypKladnyInt
.
Vlastní použití je následující:
<?php
use Norma;
...
...
$em=new EntityManager($configDB);
/* $configDB je název sekce v .ini s pripojením k db
pokud se nezadá, bere se default 'database'
takto se může instancovat víc EntityManageru s připojením k různým db
*/
$em->useTransaction(true); // zatím se testuje a není plně funkční
$em->useCache(true); // zatím nepoužívat, dělá se na tom :)
$foo=$em->getOne('objednavky',5); //vytvoří entitu dle třídy Objednavka s id=5
$foo['muj_popis']='blabla'; // entita implementuje ArrayAccess takže se dá použít
$foo->muj_popis='blabla'; // nebo takhle
$foo= new Entity('objednavky'); // vytvoření nové položky v db
$foo->persist();
$foo=$em->getAll('objednavky',array('stav'=>1)); // instancování kolekce
/* dají se předat i doplňující parametry: */
$options = array(
'orderBy' => array(
'id' => 'asc',
),
'limit' => 3,
'offset' => 2,
'assoc' => 'id',
);
$foo=$em->getAll('objednavky',array('stav'=>1),$options);
/* vypsaní kolekce */
foreach($foo as $objednavka){
echo $objednavka->muj_popis;
}
echo $foo[1]->muj_popis;
?>
Čtení a zápis do db se snažím optimalizovat (stalé na tom pracuju), takže třeba:
<?php
$kolekce=$em->getAll('objednavky',array('stav'=>1)); // předpokladejme, že se vrati objednavky s id (1,2,3,4,5)
$entita=$em->getOne('objednavky',2); // už se znova do db nesahá - id 2 už je načteno
// to same při zapisu
$kolekce->muj_popis='blabla';
$entita->muj_popis='bubu';
$em->flush(); // provede dva zapisy s (where stav=1) a (where id=2)
// ale:
$entita->muj_popis='bubu';
$kolekce->muj_popis='blabla';
$em->flush() // provede jen jeden zápis (where stav=1), protože kolekce přepsala dříve zadanou hodnotu
?>
Kolekce může být i součástí entity, např entita Objednávky obsahuje kolekci položky objednavky (viz definice v úvodu):
<?php
foreach($entita->polozky as $polozka){
echo $polozka->nazev;
}
// do kolekcí se dá vkládat:
$entita->polozky->add(); // vloží novou prázdnou
$foo=new Entity('polozky');
$foo->nazev='blabla';
// nebo $foo=$em->getOne('polozky',5); // přesune už existují entity do této kolekce
$entita->polozky->add($foo); // vloží už předvyplněnou
// !!! pokud zvolíte jinou asociaci v poli $options než primary key, musíte zajistit naplnění teto property před předáním entity kolekci
// nebo mazat
$entita->polozky->delete(5); // smaže položku s id=5
$entita->delete(); // smaže entitu. Zatím nejsem rozhodnutý, jestli se májí máznout i data subkolekcí, ale asi jo...
$entita->polozky->clear(); // vyprázdní kolekci - položku budou smazány v db!!!
?>
Všechny UPDATE a DELETE se provádějí až po příkazu
$em->flush(). Akorát INSERT se provede okamžitě při
persistování, kvůli získání primárního klíče k identifikaci.
System umí vyhazovat vyjímky při různých událostech např: zápis do
neexistující property, chyba typu, zápis do readOnly, pokus o přepsání
identifikátoru…
Ještě je na tom spousta práce a hlavně optimalizace kodu (a vymazání
různého balastu v kodu), ale už mi to běhá v jednou projektu a zatím
jsem spokojen :)
Rychlosti jsem neměřil, ale jak říkám – je to před optimalizací.
Momentálně jsou rozpracovaný ještě dva doplňky a to sice:
TableComponent
– vytvoří komponentu ze
zadané entity(kolekce) a zobrazí tabulku, včetně paginátoru, změny
řazení, rozklikávací sub tabulky s vloženýma kolekcema… – vývoj
zamrznul, potřebuji dynamické snippety :(
FormComponent
– vytvoří komponentu
formůláře včetně validace dat a správného zobrazení prvku (vazba
article->category se zobrazí jako rozbalovací seznam katergorií atd.)
Určitě jsem ještě zapoměl na něco co to děla a umí, tak jestli by to
někoho zajímalo, klidně se ptejte.
Zdroj je k mání na gitu: git://github.com/rampa/nORMa.git (aktuální
v branchi daily)
Dokumentace ještě není, ale pracuji na ní. Použil jsem Apigen, tak už jen
dodělávám ty otravný annotace :)
Dále je rozpracovaná automatická tvorba entit z SQL dotazu, používání
cache a zpožděné čtení dat, ale dělám na tom :)
Editoval Rampa (15. 12. 2010 13:54)
- Filip Procházka
- Moderator | 4668
Takže už jsi si také napsal vlastní ORM a teď už ti nic nebrání přejít na Doctrine2 :)
- Rampa
- Člen | 65
HosipLan napsal(a):
Takže už jsi si také napsal vlastní ORM a teď už ti nic nebrání přejít na Doctrine2 :)
No… chvilku jsem se s Doctrinou zabýval, ale je to moc obsáhlý…
333 souborů a třeba to neumí vlastní associace entit (nebo jsem aspoň
nepřišel na to jak)
Např jsem potřeboval udělat něco jako
dibi…fetchAssoc(‚timestamp‘) :)
- arron
- Člen | 464
Klobouk dolu, neco takoveho bych asi nikdy nenapsal :-) Nicmene co do vizaze a pouziti je to Doctrine 2 v bledemodrem :-) (kdybych nevedel, ze je to Tvoje ORM, tak podle zapisu bych se ochotne vsadil, ze je to Doctrine 2 ;-))
Pravdicka ale je, ze mi v Doctrine 2 opravdu chybi fetchAssoc…premyslel jsem, zkusit to dopsat sam, ale zatim se mi nepodarilo rozlustit klicove casti doctrine, abych z nich mohl cerpat. Nicmene nakonec jsem dosel k tomu (kdyz jsem premyslel, k cemu vlastne fetchAssoc je), ze to v podstate ani neni potreba, protoze data v podobnem formatu dostavam z Doctrine 2 nativne a na vsechno ostatni je INDEX BY :-)
- Filip Procházka
- Moderator | 4668
fetchAssoc si můžeš přece napsat jako nějaký array tool helper (nápad) a pak si ho volat na přeskládání v potřebných funkcích v repozitáři entity :)