koncept ORM postavenom na skvelom Dibi a inspirovanym Django Model

Upozornění: Tohle vlákno je hodně staré a informace nemusí být platné pro současné Nette.
edke
Člen | 198
+
0
-

Preco dalsie ORM

Ach jaj, tolko som o ORM cital tu na fore, v blogu a tweetoch Romana Sklenara az som tomu prepadol :) A to som sa smial, ked som cital Davidov tweet o ORM v roku 2010 :)

Django Model

Pocas prieskumu pythonu a rails som narazil na skvele Django, kde ma jeho implementacia ORM naozaj zaujala. Django vyzera fakt velmi dobre a python ma odjakziva lakal … bohuzial plati ze Django != Nette :( Jedine zeby sa Davidovi zapacil python aspon tak, ako mne … :) Ale zatial .. skusil som, ako by mohlo ORM vychadzajuce z Django Model vyzerat pod PHP a samozrejme nad Dibi (a Nette).

Povenoval som sa tomuto draftu par dni, s vysledkom by som sa chcel s Vami podelit, mozno pre dalsi impulz pokracovat, mozno pre par faciek, aby som vytriezvel :)

Na Django Model ma naozaj uputal sposob navrhu modelu aplikacie. Presiel som najznamejsie ORM kniznice pod PHP, ani jedna sa mi nezdala natolko sikovna (darmo, Nette & Dibi & Texy rozmaznava), aby som nadobudol pocit, ze naozaj pomoze a ulahci pracu. Django je navrhnute tak, ze cely model sa nadizajnuje raz, na jednom mieste a to v aplikacii. O vygenerovanie celej db struktury sa potom stara skript, rovnako sa cez skript aplikuju vsetky zmeny v modeli, pripadne dalsie operacie ako vycistenie tabuliek a pod. Staci teda nakonfigurovat pre model connect do prazdnej databazy, pustit administracny python skript s magickym syncdb a mozme rovno pisat dalej aplikaciu.

Samozrejme ze tento model asi nebude mozne aplikovat pre rozne urovne komplikacie databazoveho modelu. Ale pre potreby bezneho webu sa urcite da vystacit a uspora prace s databazou je znacna. Co sa mi paci este viac je vlastne fakt, ze takto sa model prenasa z databazy do skriptov, kde na rad prichadza verziovanie. Tym sa znovu ulahcuje synchronizacia zmeny modelu aplikacie medzi development a production modom. Staci po aktualizacii produkcnych skriptov pustit na produkcnom servri syncdb. Znovu, urcite toto nie je aplikovatelne v 100% pripadoch a revizia zasahov do databazy v production je nevyhnutna.

Ukazky pouzitia

No teda sem s nejakou ukazkou:

definicia modelu:

/**
 * Author
 *
 * @author kraken
 *
 * @property-write string $first_name
 * @property-write string $last_name
 * @property-write string $gender
 * @property-write integer $karma
 */
class Author extends DibiOrm {

    protected function setup()
    {
	$this->first_name = new CharField('max_length=200', 'notnull', 'db_column=meno');
	$this->last_name = new CharField('max_length=200', 'notnull', 'db_column=priezvisko');
	$this->gender = new CharField('max_length=1', 'null', 'default=m');
	$this->karma= new IntegerField('null');
    }

    public function __toString()
    {
	return $this->first_name .' '. $this->last_name;
    }
}


/**
 * Blog
 *
 * @author kraken
 *
 * @property-write string $title
 * @property-write integer $hits
 * @property-write string $content
 * @property-write Author $author
 */
class Blog extends DibiOrm {

    protected function setup()
    {
	$this->title = new CharField('max_length=200', 'notnull');
	$this->hits = new IntegerField('null');
	$this->content = new CharField('max_length=500', 'notnull');
	$this->author = new ForeignKeyField(new Author);
    }

    public function __toString()
    {
	return $this->first_name .' ' .$this->last_name;
    }
}

Maintenance script → syncdb:

CREATE TABLE author (
    id serial  NOT NULL,
    meno character varying(200)  NOT NULL,
    priezvisko character varying(200)  NOT NULL,
    gender character varying(1) DEFAULT 'm' NOT NULL,
    karma integer  NOT NULL
);

ALTER TABLE ONLY author
    ADD CONSTRAINT author_pkey PRIMARY KEY (id);


CREATE TABLE blog (
    id serial  NOT NULL,
    title character varying(200)  NOT NULL,
    hits integer  NOT NULL,
    content character varying(500)  NOT NULL,
    id_author integer  NOT NULL
);

ALTER TABLE ONLY blog
    ADD CONSTRAINT blog_pkey PRIMARY KEY (id);

ALTER TABLE ONLY blog
    ADD CONSTRAINT blog_fkey FOREIGN KEY (id_author) REFERENCES author(id);

Maintenance script → sqlclear:

DROP TABLE blog;

DROP TABLE author;

Interakcia s modelom:

$author = new Author();
$author->first_name= 'Martin';
$author->last_name= 'Adamek';
$author->gender= 'm';
$author->karma= 328;
$author->save(); // nepovinne, staci zavolat raz na konci $blog->save();

$blog = new Blog();
$blog->title= 'APNdroid 2.0 coming';
$blog->content= 'I am happy to announce first development builds of APNdroid 2.0.';
$blog->hits= 7542;
$blog->author= $author;
$blog->save();

A sql vygenerovane ORM:

insert into "author" ("meno", "priezvisko", "gender", "karma") VALUES ('Martin', 'Adamek', 'm', 328);

insert into "blog" ("title", "hits", "content", "id_author") VALUES ('APNdroid 2.0 coming', 7542, 'I am happy to announce first development builds of APNdroid 2.0.', 1);

Funkcne resp. takmer funckne oblasti

V skratke to, co ste si precitali :) Vsetky predch. snippety su uz vysledkom ORM.

  • ukladanie modelu (cast inserts)
  • import ⇒ $blog= new Blog(array(‚last_name‘ ⇒ '…
  • zakladna interakcia s modelom cez webrozhranie:
    • syncdb (vytvorenie tabulky; doplnenie/dropnutie existujucich/neexistujucich stlpcov v tabulke)
    • sqlall (zobrazenie celej struktury)
    • sqlclear (dropnutie celej struktury)
  • implementovane datatypy AutoField, CharField, IntegerField
  • implementacia ForeignKeyField
  • „driver“ pre psql
  • modelLoader → skript, ktory vyhlada v app/ foldri Nette aplikacie vsetky model classy

Co je v plane (och, toho je, kde zacat)

  • citanie z modelu (selects)
  • update
  • implementacia ostatnych DataTypes a Field Options v zmysle Django
  • v syncdb dokoncit syncovanie tabulky, ak je zmenena definicia existujuceho stlpca v existujucej tabulke
  • command line rozhranie pre interakciu modelu s databazou (linux cli, pripadne windows bat)
  • vztahy medzi modelmi ManyToManyField, OneToOneField a ForeignKeyField
  • pre skripty syncdb a sqlclear riesenie zavislosti pre spravnu interakciu so vztahovymi tabulkami
  • utriast „driver“ do vhodnej a lahko modifikovatelnej podoby, nasledne driver pre mysql a sqlite.

Ak sa to vsetko podari dostat do pouzitelnej podoby, uvolnit to pod vhodnou open-source licenciou, najskor cez github.

Budem vdacny za kazdy komentar.

Honza Kuchař
Člen | 1662
+
0
-

Mno, vypadá to fajn, ale v současné době v celku s úspěchem už používám Ormium. Ta synchronizace tabulek vypadá moc fajn. :)

edke
Člen | 198
+
0
-

honzakuchar wrote:

Mno, vypadá to fajn, ale v současné době v celku s úspěchem už používám Ormium. Ta synchronizace tabulek vypadá moc fajn. :)

Na toto som pri mojom prieskume nenarazil a ani google mi velmi nepomohlo :) Mas nejaky link ? :)

Jod
Člen | 701
+
0
-

Volá sa to Ormion, pozri github. Aj tu na fóre je o ňom dosť, plus o ďalších ORM.

Keď už má každý svoje ORM, tak ja budem mať „dva“. Pridá sa niekto? zn. rofl xDDD

Editoval Jod (18. 1. 2010 22:41)

romansklenar
Člen | 655
+
0
-

Zajímavě vyřešené máš ty pojmenované parametry :) škoda, že nejsou v PHP nativně, občas by se hodily.
Chystáš se obdobně poprat i s QuerySet'em? Z toho by asi vznikaly docela zajímavé konstrukce

Entry::objects()->filter('pub_date__year=2005')->order_by('-pub_date', 'headline');

Já váhám migrace vůbec zařadit, protože už vidím to fórum dotazů, kdy to lidi nebudou schopni na Windows rozjet PHP z CLI.

Honza Marek
Člen | 1664
+
0
-

https://github.com/janmarek/Ormion

Není tam teď commitnuto pár potřebných bugfixů, nemám na ně zatím unit testy. A tenhle týden nemám čas. No na inspiraci to ale stačí. Honza používá zřejmě starší verzi, která funguje (aby se teď nelekl ;-)

Jod: Neplánuješ zveřejnění nebo zveřejnění pro lidi, kteří mají vlastní ORM?

edke
Člen | 198
+
0
-

romansklenar wrote:

Zajímavě vyřešené máš ty pojmenované parametry :) škoda, že nejsou v PHP nativně, občas by se hodily.

hladal som co najjednoduchsi zapis, cez pole systemom ‚max_length‘ ⇒ 200 mi pride prilis komplikovane. uvidime …

Chystáš se obdobně poprat i s QuerySet'em? Z toho by asi vznikaly docela zajímavé konstrukce

Entry::objects()->filter('pub_date__year=2005')->order_by('-pub_date', 'headline');

Ak som uz raz zacal „klonovat“ Django, asi by bolo dobre v tom pokracovat :) Moznosti je ale viac, paci sa mi viacero pristupov, takze uvidime, ked to pride na rad.

Já váhám migrace vůbec zařadit, protože už vidím to fórum dotazů, kdy to lidi nebudou schopni na Windows rozjet PHP z CLI.

tak na linuxe je cli pre mna super komfortny sposob obsluhy v takomto pripade. ak uz bude cli pre linux, pre win nie je az taky problem. ale je mi jasne, ze vela ludom cli nevonia. uz teraz mam v podstate jednoduche webove rozhranie, teda rad by som ho neskor upravil tak, aby bolo lahke implementovat alebo do existujuceho backendu alebo ako samostatny modul.

Jod
Člen | 701
+
0
-

Až budem mať dva ,)

Mne príde ORM bez väzieb a joinov kus zbytočné, to je aj dôvod prečo som nezačal používať Ormion, aj keď sa mi celkom páči.
No moja fóbia z gigantických modelov nepustí :D

Migračné scripty sú naopak vynikajúca vec a od kedy sme ich začali v práci používať je pracovný deň kusisko krajší .. EXTRÉMNY KUSISKO :)

edke
Člen | 198
+
0
-

Co pribudlo

  • spustil som verejne demo, je tam par ukazok a zive akcie pre syncovanie db a clear. Mozte si to vyskusat, len samozrejme moze dojst ku koliziam ak rozni ludia budu skusat rozlicne ukony (mazat strukturu a plnit :-)). Ale zas taku navstevnost to urcite mat nebude, aby to bol problem. Staci si to vzdy skontrolovat cez syncdb a zmeny vykonat. rovnako prikad s querysets je zavisly na druhom priklade s cudzimi klucmi, pretoze pracuje s tym, co ten priklad vytvori.
  • cachovanie (primarne do memcache, ak to nie je mozne, potom FileStorage) modelu, prebieha transparentne pri vytvoreni modelu, ak je povolene. Na roznych servroch som sa dostal az k vysledku 10× rychlejsie oproti necachovanej verzii – ukazka
  • ukladanie modelu, model sleduje zmenene polia, pri ukladani potom generuje UPDATE len pre polia, ktore boli od posledneho save() zmenene. Tym padom je save() kompletne. ukazka
  • prva verzia QuerySets, prva metoda get. get sluzi na nacitanie modelu hodnotami, primarne je urcene na nacitanie konkretnej hodnoty cez primarny kluc. Metoda, ktora bude filtrovat data a vraciat pole modelov je dalsi krok. ukazka
Honza Kuchař
Člen | 1662
+
0
-

Paráda!

Je udělané jaké propojení s DibiDataSource? Je nějak dořešené filtrování? Tj. umí ORM vracet i více než jeden řádek? Ještě se zaptátm, jak je to syncování, pokud ty tabulky v DB jsou a jen jsou neauktuální (třeba tam není nějaký sloupeček), zachová to data, která tam už jsou a cestou nejmenšího odporu to ty tabulky zaktualizuje (přidá ten jeden sloupeček a data nechá být)?

edke
Člen | 198
+
0
-

Takze podme poporiadku.

Je udělané jaké propojení s DibiDataSource?

Model->objects->get() vlastne vytvori DibiDataSource. Vytvorit nieco ako
Model->objects->getDibiDataSource() nie je problem. To dava dalsie moznosti pracovat s dataSource, ak by moznosti Orm nepostacovali. A mozno aj to je cesta … namiesto pisania noveho syntaxu proste vratit datasource a vyuzit neobmedzene moznosti dibi a datasource. Ta neprijemna robota napisat select a joiny by uz bola spravena.

Je nějak dořešené filtrování? Tj. umí ORM vracet i více než jeden řádek?

Plan je get() pouzivat priamo ako naplnenie modelu udajmi, teda argument by mal byt primarykey. Akceptovany bude syntax get(‚pk=1‘), get(‚id=1‘), alebo aj este jednoduchsie get(1).

Pre filtrovanie a vracanie pola modelov bude metoda filter(). Moznosti, ako aplikovat syntax z Django je vela, iste by mi pomohlo, keby ste sa pozreli na dokumentaciu a dali nejake navrhy, ako by sa mohlo zapisovat filtrovanie. Django filter

Urcite budem chciet implementovat filtrovanie cez pole, co sa casto bude vyuzivat v spojeni so submitnutym formularom. Model->objects->filter(array(‚table1__field1‘ ⇒ ‚value‘, …))

Ještě se zaptátm, jak je to syncování, pokud ty tabulky v DB jsou a jen jsou neauktuální (třeba tam není nějaký sloupeček), zachová to data, která tam už jsou a cestou nejmenšího odporu to ty tabulky zaktualizuje (přidá ten jeden sloupeček a data nechá být)?

Aktualne uz je implementovane:

  • ak neexistuje tabulka, vytvori sa cely CREATE TABLE
  • ak existuje tabulka a chyba field, doplni sa ako ALTER TABLE ADD COLUMN …
  • ak existuje tabulka a obsahuje field, ktory neobsahuje model, dropne sa field ako ALTER TABLE DROP COLUMN …

Co treba doplnit:

  • ak existuje tabulka a existuje field, ale jeho definicia sa lisi. napriklad je potrebna zmena z null a not null alebo opacne. jednoduchsie budu pripady, kedy sa upravi datatyp, napriklad varchar(100) na varchar(200). ak pojde o zmenu typu, tam uz bude musiet byt hlbsia analyza, priklad ako integer → varchar je v poriadku, ale varchar → integer najskor bude znamenat stratu dat. uvitam napady.
  • dalsi tazsi oriesok je, ak sa premenuje field, mam nejake napady, ale rovnako ak ma niekto skusenost pri rieseni tejto situacie, moze sa podelit.
  • rovnako premenovanie celeho modelu a teda tabulky.

Editoval edke (23. 1. 2010 21:58)

edke
Člen | 198
+
0
-

Este co sa tyka tych zlozitejsich synchronizacnych operacii, pohram sa este s Django models, celkom ma zaujima, do akej miery maju oni poriesene modifikacie existujuceho modelu a nasledny syncdb.

Honza Kuchař
Člen | 1662
+
0
-

Co kdyby to u operací co se nedají automaticky rozhodnout, by to vykreslilo nějakého průvodce s nabídnutými možnostmi. V nějaké konfiguraci by bylo nastaveno z jaké adresy toho průvodce zobrazit (výchozí asi localhost), jinak by to vyhodilo výjimku a to by načetlo error presenter (všichni klienti). Mohlo by se to jmenovat nějak jako „Průvodce aktualizací databáze“. Myslím, že by to bylo jediné řešení svého druhu.

Jinak co se týká přejmenovávání sloupečků a tabulek, co využít komentáře co je u tabulek i sloupečků k dispozici a naplnit ho nějakým identifikátorem, podle kterého by syncdb poznal je to za objekt. Tzn. v dibiorm by měl každý objekt nějaké interní id, které by bylo uloženo i v tom komentáři. Nikde na venek by se s ním nepočítalo, takže by nebyl důvod ho měnit.

Němělo by to udělat zálohu databáze než to začne šahat na data? :)

amsys
Člen | 20
+
0
-

Luxus, boží, tohle mi úplně krásně vylepšilo náladu, Django mám velmi rád, že to někoho napadlo, celou dobu mi něco podobného leží v hlavě a on to tady někdo už realizuje. Btw rozjet něco takového by bylo hodně zajímavé: http://docs.djangoproject.com/…aggregation/

David Grudl
Nette Core | 8111
+
0
-

Přijedeš do Pardubic? A uděláš prezentaci? ;)

edke
Člen | 198
+
0
-

amsys wrote:

Luxus, boží, tohle mi úplně krásně vylepšilo náladu, Django mám velmi rád, že to někoho napadlo, celou dobu mi něco podobného leží v hlavě a on to tady někdo už realizuje. Btw rozjet něco takového by bylo hodně zajímavé: http://docs.djangoproject.com/…aggregation/

QuerySets mam zatial implementovane len velmi minimalne, buildovanie DibiDataSource cez cudzie kluce uz funguje, ako vidiet v ukazke. Momentalne sa intenzivne venujem synchronizacii modelu s databazou. Ak ked toto bude spracovane na urovni, pojde sa dalej. Napisem viac, ked vyriesim este par problemov.

romansklenar
Člen | 655
+
0
-

Vážou se ty migrace hodně na to tvé ORM? Asi jo že? Napadlo mě, že kdyby to bylo nějak rozumně přenositelné, dalo by se to používat i jinde.

edke
Člen | 198
+
0
-

romansklenar wrote:

Vážou se ty migrace hodně na to tvé ORM? Asi jo že? Napadlo mě, že kdyby to bylo nějak rozumně přenositelné, dalo by se to používat i jinde.

No prave som stravil tyzden tym, ze postupne upravujem Model tak, aby bol dostatok informacii pre porovnanie modelu a struktury v databaze. Pouzivam k tomu internu sqlite databazu, ktora tieto informacie uklada. Model sam obsahuje hash pre cely model a pre jednotlive fields, ich analyzou v porovnani s internou sql databazou generujem migracne skripty. Bez tejto pomoci som neprisiel ako spravit operacie ako napriklad detektnutie zmeny nazvu modelu alebo zmeny nazvu pola. Bez toho by som spravil „alter table drop column“ a „alter table add column“, takto budem vediet stlpec/tabulku premenovat.

Takze aby som odpovedal, aktualne to urcite nie je v stave „lahkej prenositelnosti“ :) A celkovo je este stale ten kod hodne prasacky :) Verim, ze ked sa kod trosku ustali, budem ho vediet upravit do publikovatelnej podoby bez toho aby som sa musel velmi cervenat :)

romansklenar
Člen | 655
+
0
-

Migrace jsou pěkně vyřešeny v Rails, pokud se dostanu k tomu, že je budu muset/chtít implementovat, tak bych chtěl aby to fungovalo na stejném principu.

Editoval romansklenar (29. 1. 2010 13:35)

Honza Kuchař
Člen | 1662
+
0
-

Ještě se zeptám: „driver“ pre psql měl znamenat pgsql?

edke
Člen | 198
+
0
-

honzakuchar wrote:

Ještě se zeptám: „driver“ pre psql měl znamenat pgsql?

no je to class, ktory riesi rozdiely medzi jednotlivymi databazami .. dump pre kazdu databazu vyzera inac, datatypy vyzeraju inac … tieto rozdiely by mal priesit prave „driver“.

Honza Kuchař
Člen | 1662
+
0
-

Rozdělení na drivery chápu. Tzn. aktuálně existuje driver na PgSQL? (jestli jo, tak super :) )

edke
Člen | 198
+
0
-

honzakuchar wrote:

Rozdělení na drivery chápu. Tzn. aktuálně existuje driver na PgSQL? (jestli jo, tak super :) )

Jj, primarne pracujeme s psql, takze preto prioritny driver je prave ten.

edke
Člen | 198
+
0
-

Za posledny tyzden som znovu pridal „par“ komitov, tu je kratky prehlad, co je nove:

1. model cache

Narazil som na par problemov, cez ktore som sa nevedel dostat:

  1. pre co najrychlejsie kesovanie som potreboval co najjednoduchsie sa dostat k mtime suboru, ktory definuje model. Neprijatelne riesenie bolo do modelu pisat rucne nejaku metodu, ktora bude vracat timestamp. Vdaka zapuzdrenosti objektov som sa nevedel dostat k filu a jeho mtime z triedy DibiOrm, ktoru model dedi.
  2. problem s koliziami, ak v projekte upravite definiciu modelu (ci uz rucne, alebo aktualizaciou na produkcnom servri cez versioning, ftp ..) a este neprebehol syncdb. Tym padom moze byt (a bude) rozdiel medzi modelom a strukturou db, co je hlavne v produkcnom prostredi nepripustne.
  3. pre lepsi objektovy navrh som potreboval vytvorit referenciu parent na rodicovsky model z objektu field. Ale nechcel som uzivatela nutit pri definicii fieldu pisat $this->fieldname= new CharField($this, 'max_lengt= …
  4. v netbeans krasne funguje PHPDoc tag @property-write, ktory umozni pri triede „naucit“ netbeans, aby ponukol pri autocomplete properties, ktore nevie sam vycitat z kodu. Rucne to ale pisat pre model by znovu znamenalo sa opakovat (pisat raz do definicie a raz do PHPDoc), co je nepekne :)

Vsetky tieto problemy som vyriesil niecim ako modelCache. Je to nieco ako skompilovana html sablona v Nette. V praxi to vyzera nasledovne. V lub. foldri model/models priamo v app dire alebo v niektorom z modelov v app dire je zakladny class pre model. Vyzera takto:

<?php


/**
 * Author
 *
 * @author kraken
 * @abstract
 */
abstract class BaseAuthor extends DibiOrm {

    protected function setup()
    {
	$this->karma= new IntegerField('null');
	$this->first_name = new CharField('max_length=200', 'notnull'/*, 'db_column=meno'*/);
	$this->last_name = new CharField('max_length=200', 'notnull'/*, 'db_column=priezvisko'*/);
	$this->gender = new CharField('max_length=1', 'null', 'default=m');
    }

    public function __toString()
    {
	return $this->first_name .' '. $this->last_name;
    }
}

Po spusteni syncdb je prehladany app dir, a pre kazdy model je vygenerovana jeho cache a az tato cache je nasledne pouzivana na pracu s modelom v aplikacii. Vyzera takto:

<?php


/**
 * Author
 *
 * @final
 * @filesource /home/kraken/NetBeansProjects/dibiorm-sandbox/app/models/BaseAuthor.php
 *
 * @property-write integer $karma
 * @property-write string $first_name
 * @property-write string $last_name
 * @property-write string $gender
 */
final class Author extends BaseAuthor {


    /**
     * Overloaded setup of BaseModel
     */
    protected function setup()
    {
	$this->karma= new IntegerField($this, 'null');
	$this->first_name = new CharField($this, 'max_length=200', 'notnull');
	$this->last_name = new CharField($this, 'max_length=200', 'notnull');
	$this->gender = new CharField($this, 'max_length=1', 'null', 'default=m');
    }


    /**
     * Returns last modification of model
     * @return integer
     */
    protected function getLastModification()
    {
	return '1265384678';
    }
}

Riesi vsetky moje predch. problemy:

  1. Vytvori metodu getLastModification(), ktora vracia timestamp mtime vytvoreneho suboru, mam teda lahko pristupny timestamp potrebny na vytvorenie kluca pre memcache a netreba ani dalsi hit na subor pri vykonani.
  2. Spolu s vygenerovanim cache prebehne aj syncdb, teda model aj databazova struktura su zosynchronizovane. Samozrejme to este nie je dokonale, pri procese mozu nastat nejake kolizie, este to nie je dokonale.
  3. Definicia stlpca bola doplnena o $this, ten je vzdy na prvom mieste, co ulahci parsovanie pri konstrukcii stlpca.
  4. Na zaklade definicie su vygenerovane spravne PHPDoc's aj s typmi.

Pokracovanie v dalsom prispevku.

edke
Člen | 198
+
0
-

2. DibiOrmStorage

Vytvorena trieda DibiOrmStorage, ktora sa stara o syncovanie. Udrziava aktualne informacie o modeli v databaze, ktore v databaze ulozit nemozno, teda hashe jednotlivych stlpcov a tabuliek.

Vdaka informaciam, ktore su udrziavane v Storage (interna sglite2 databaza), je mozne realizovat operacie ako premenovanie stlpcov alebo premenovanie tabuliek.

3. Nove moznosti syncdb

1. pridavanie stlpcov a uberanie stlpcov v tabulke

ALTER TABLE author
    ADD COLUMN notes character varying(500) DEFAULT 'm' NULL;

ALTER TABLE author
    DROP COLUMN gender;

2. premenovavanie stlpca vramci modelu

ALTER TABLE author
    RENAME COLUMN gender TO new_gender;

3. premenovanie celeho modelu, pre poriadok (psql related) je premenovany spolu s tabulkou aj primarny kluc, sekvencia a cudzie kluce su dropnute a vytvorene nanovo.

ALTER INDEX blog_pkey
    RENAME TO document_pkey;

ALTER INDEX blog_id_seq
    RENAME TO document_id_seq;

ALTER TABLE blog
    DROP CONSTRAINT blog_author_fkey;

ALTER TABLE blog
    DROP CONSTRAINT blog_coauthor_fkey;

ALTER TABLE blog
    RENAME TO document;

ALTER TABLE ONLY document
    ADD CONSTRAINT document_author_fkey FOREIGN KEY (author) REFERENCES author(id);

ALTER TABLE ONLY document
    ADD CONSTRAINT document_coauthor_fkey FOREIGN KEY (coauthor) REFERENCES author(id);

4. takze konecne som sa dopracoval aj k zmene parametrov samotneho fieldu. ako prve prislo na raz zmena z null na not null a naopak. pricom druha je trivialna:

ALTER TABLE author
    ALTER COLUMN gender DROP NOT NULL;

druha nie. Pred pridanim je potrebne vyriesit polia, ktore su null. Inac alter zlyha:

ALTER TABLE author
    ALTER COLUMN gender SET NOT NULL;

Vyriesil som to nasledovne. Mame 2 moznosti. Obe pred samotnym vykonanim alteru prejdu vsetky null zaznamy a naplnia ich:

  1. alebo podla hodnoty default (ak je zadana)
  2. alebo sa zavola callback, ktoremu ako argument sa posiela cely row. teda je mozne realizovat aj zlozitejsie skladanie hodnoty v zavislosti od inych hodnot v danom result-e.

Definuje sa to takto:

<?php


/**
 * PersonBase
 *
 * @author kraken
 * @abstract
 *
 */
abstract class BasePerson extends DibiOrm
{

    protected function setup()
    {
	$this->first_name = new CharField('max_length=200', 'notnull'/*, 'db_column=meno'*/);
	$this->last_name = new CharField('max_length=200', 'notnull'/*, 'db_column=priezvisko'*/);
	$this->gender = new CharField('max_length=1', 'notnull', 'default=m');
	$this->age= new IntegerField('notnull', 'default=3');
	$this->notes= new CharField('max_length=500', 'null', 'default_callback=setNotesDefaults');
    }


    public function __toString()
    {
	return $this->first_name .' ' .$this->last_name;
    }


    public function setNotesDefaults($row)
    {
	return 'new default value, with id:'.$row->id;
    }
}

teda pre stlpec sa prida option default_callback, ktory sa potom vyuziva na vytvorenie hodnoty.

tento mechanizmus (default value, alebo default callback) sa rovnako da vyuzit aj pri klasickom model.save(), ked sa pridava uplne novy zaznam.

5. Parent reference

Pre fields pridana referencia na parent, teda model. To prinieslo par problemov s circulating reference, napriklad pri vyhodnoteni zavislosti medzi modelmi a rovnako znamy memory leak. Predbezne som ale vyriesil obe.

Co dalej ?

  • refactoring kodu, lepsie vyuzitie parent reference
  • zmena datatypu znovu s pouzitim callbacku
  • zmena rozmeru, napriklad pri varchar
  • zmena default value
  • integrovat dalsie datatypy
  • integrovat dalsie vazby medzi modelmi
  • nasadenie do aplikacie a otestovanie v production
edke
Člen | 198
+
0
-

Aktualny changelog (15. februar 2010):

  • refactoring kodu DibiOrmStorage a DibiOrmSqlBuilder, vyuzitie parent reference
  • syncdb podporuje aktualne tieto operacie pri synchronizacii modelu s databazou:
    • pridanie modelu
    • odstranenie modelu
    • premenovanie modelu
    • pridanie stlpca do modelu
    • odstranie stlpca z modelu
    • premenovanie stlpca modelu
    • pridanie/odstranenie default value pre stlpec
    • pridanie/zrusenie not null pre stlpec
    • zmena datatypu stlpca resp. zmena rozmeru datatypu
  • Orm momentalne rozoznava tieto datatypy:
    • AutoField → primarny kluc s autoincrementom
    • BooleanField
    • CharField (max_length)
    • DateField (auto_now a auto_now_add) → tieto dve vlastnosti umoznuju automaticke vyuzivanie date alebo timestamp, auto_now je naplnanie aktualnym casom pri kazdom ulozeni modelu [napr. last_modification_date], auto_now_add je naplnenie aktualnym casom pri vytvoreni modelu [napr. created_date]; ukazka
    • DateTimeField (auto_now a auto_now_add)
    • DecimalField (max_digits a decimal_places)
    • ForeignKeyField
    • IntegerField
    • SmallIntegerField
    • TextField
    • TimeField (auto_now a auto_now_add)
  • vyuzivanie callbackov pri problemovych operaciach ako napriklad:
    • pretypovanie (lubovolna, napriklad z integer na text),
    • zmena rozsahu datatypu (napriklad varchar(500) → varchar(50) a pod.),
    • pridavanie noveho not null stlpca do tabulky s uz existujucimi zaznamami
    • zmena null stlpca na not null stlpec v tabulke s existujucimi zaznamami

Ukazka pouzitia callbacku pri pridani noveho notnull stlpca do modelu:

model Person potrebujem rozsirit o stlpec email, jedna sa o firemnu databazu zamestnancov, kazdemu preto viem vygenerovat email na zaklade mena a priezviska.

doplnim teda model o novy stlpec a callback:

protected function setup()
{
	...
	$this->email= new CharField('notnull', 'max_length=200', 'default_callback=generateEmail');
}


public function generateEmail($row)
{
	return strtolower(sprintf('%s.%s@firma.sk', $row->first_name, $row->last_name));
}

Syncdb vygeneruje nasledovny sql kod:

-- Adding field "email"
ALTER TABLE person
    ADD COLUMN email character varying(200)  NULL;

update "person" set "email" = 'john.doe@firma.sk' where "id" = 1;
update "person" set "email" = 'john.doe@firma.sk' where "id" = 2;

-- Changing null/not null of field "email"
ALTER TABLE person
    ALTER COLUMN email SET NOT NULL;

Ukazka pouzitia callbacku pri pretypovani stlpca modelu:

Potrebujeme upravit zly navrh modelu, CharField s krajinou povodu pretypovat na ciselnik. Model vyzera nasledovne:

protected function setup()
{
	...
	$this->country= new Charfield('null', 'max_length=200');
}

A data (select id, country from person):

id country
1 Slovensko
2 Slovakia
3 Czech Republic

Pridame teda callback a zmenime definiciu stlpca:

protected function setup()
{
	...
	$this->country= new IntegerField('notnull', 'recast_callback=recastCountry');
}

public function recastCountry($row)
{
	if ( preg_match("#^(Slovensko|Slovakia)$#i", $row->country))
	{
	    return 1;
	}
	elseif ( preg_match("#^Czech Republic$#i", $row->country))
	{
	    return 2;
	}
	else
	{
	    throw new Exception('invalid value');
	}
}

Syncdb po spusteni vygeneruje tento SQL kod:

-- Adding field "country_a49a6dda3e958e0a7bfaa1389557c681"
ALTER TABLE person
    ADD COLUMN country_a49a6dda3e958e0a7bfaa1389557c681 integer  NULL;

update "person" set "country_a49a6dda3e958e0a7bfaa1389557c681" = 1 where "id" = 1;
update "person" set "country_a49a6dda3e958e0a7bfaa1389557c681" = 1 where "id" = 2;
update "person" set "country_a49a6dda3e958e0a7bfaa1389557c681" = 2 where "id" = 3;

-- Changing null/not null of field "country_a49a6dda3e958e0a7bfaa1389557c681"
ALTER TABLE person
    ALTER COLUMN country_a49a6dda3e958e0a7bfaa1389557c681 SET NOT NULL;

-- Dropping field "person"
ALTER TABLE person
    DROP COLUMN country;

-- Renaming field "country_a49a6dda3e958e0a7bfaa1389557c681" to "country"
ALTER TABLE person
    RENAME COLUMN country_a49a6dda3e958e0a7bfaa1389557c681 TO country;

TODO:

  • command line rozhranie pre interakciu modelu s databazou
  • podpora pre sqlite (najskor sqlite2)
  • podpora pre IPAddressField
  • specialne varchar typy s kontrolou obsahu ako napriklad EmailField, SlugField a UrlField
  • integrovat dalsie vazby medzi modelmi, ManyToManyField a OneToManyField
  • nasadenie do aplikacie a otestovanie v production

Editoval edke (15. 2. 2010 16:14)

edke
Člen | 198
+
0
-

Aktualny changelog (17. februar 2010):

  • command line rozhranie pre interakciu modelu s databazou, podporovane vsetky tri zakladne operacie z weboveho rozhrania (syncdb, sqlall a sqlclear), SQL kod v konzole je highlightovany

Pre highlighting v konzole som pouzil PEAR class Console_Color a rovnako ako pre web FSHL, do ktoreho som pridal novy class pre out ANSI_UTF8_output.php, ktory generuje text so znackami pre Console_Color.

Ukazky:

Honza Kuchař
Člen | 1662
+
0
-

Teda, zatím čumím jak puk. Gratuluji! Zatím to vypadá opravdu výborně.

Jeden malý dotaz: Model definuji jako třídu, jako konfigurák nebo se to generuje z databáze?

edke
Člen | 198
+
0
-

honzakuchar wrote:

Teda, zatím čumím jak puk. Gratuluji! Zatím to vypadá opravdu výborně.

Jeden malý dotaz: Model definuji jako třídu, jako konfigurák nebo se to generuje z databáze?

Model je definovany triedou, filozofia vychadza z Django, vytvoris v app model, a databaza sa vygeneruje. Spravis zmeny v modeli, a znovu sa prenesu do db len zmeny. Ukazka:

<?php


/**
 * Author
 *
 * @author kraken
 * @abstract
 */
abstract class BaseAuthor extends DibiOrm {

    protected function setup()
    {
        $this->karma= new IntegerField('null');
        $this->first_name = new CharField('max_length=200', 'notnull'/*, 'db_column=meno'*/);
        $this->last_name = new CharField('max_length=200', 'notnull'/*, 'db_column=priezvisko'*/);
        $this->gender = new CharField('max_length=1', 'null', 'default=m');
    }

    public function __toString()
    {
        return $this->first_name .' '. $this->last_name;
    }
}
edke
Člen | 198
+
0
-

Ked teraz na tu triedu pozeram, tak vlastne uz to nie je aktualne. Doslo k premenovaniu DibiOrm. Ako impulz bol ostatny Davidov prispevok na phpfashion.com. David sice pisal o Nette ale myslim ze ma rovnaky nazor aj ohladne Dibi alebo Texy. DibiOrm, nad tym som nejako vazne nerozmyslal, bol to taky prvy pracovny nazov. Bol to taky logicky krok, kedze moje Orm je uplne zavisle na Dibi.

Preto som po precitani spominaneho prispevku prikrocil k premenovaniu. Dibi nechcem nekvalitou svojho kodu ani skodit, a rovnako sa nechcem na Dibi prizivovat :) Toto Orm je od zaciatku urcene pre potreby firmy, pre ktoru pracujem. Kedze je zavisle na Dibi a Nette, rozsirenie mimo tejto komunity je nerealne a ak niekomu z Nette/Dibi komunity pomoze, tak budem len rad.

Vymysliet nazov pre zhluk tried nie je vobec jednoduche :) Zabralo mi to viac casu ako som predpokladal. Nakoniec som sa rozhodol pre nazov PerfORM. Vzhladom na aktualny stav projektiku je tento nazov mozno trosku „prehnany“ a komicky. Ale je tam ORM a jeho vyznam je pre mna aj cielom, ktory som si vytycil, ked som zvazoval, ci sa vobec do niecoho takeho mam pustit.

Takze dufam a verim, ze ked vycistim akutalny TODO list, bude naozaj PerfROM uskutocnovat a vykonavat spolahlivo to, naco je urceny :)

v6ak
Člen | 206
+
0
-

Zajímavé je, že se k tomu ještě takto nevyjádřil.

EDIT: Je nějak podporována dědičnost?

Editoval v6ak (21. 2. 2010 16:06)

edke
Člen | 198
+
0
-

v6ak wrote:

Zajímavé je, že se k tomu ještě takto nevyjádřil.

Zjavne ho to zatial dostatocne nezaujalo, to je v poriadku :)

EDIT: Je nějak podporována dědičnost?

Vies mi to uviest na nejakom konkretnom pripade ? Mam v plane implementovat dedicnost tabuliek, napriklad vyuzitelnu dobre pri shopoch. Zakladnu tabulku sortimentu s nazvom, popisom a cenou bude dedit tabulka pre konkretny druh sortimentu, ktora bude hlavnu tabulku rozsirovat o konkretne vlastnosti daneho druhu.

v6ak
Člen | 206
+
0
-

Jo, přesně toto mám namysli. Díky za info.

edke
Člen | 198
+
0
-

v6ak wrote:

Jo, přesně toto mám namysli. Díky za info.

Ak s niecim podobnym mas skusenosti pri inom ORM, budem rad ak das nejaky link alebo nejaky navrh. Netreba znovu vymyslat koleso.

v6ak
Člen | 206
+
0
-

Trošku jsem zkoušel JPA (Java Persistence API) a JDO (Java Data Objects), ale zkušenostmi bych to nenazýval.

edke
Člen | 198
+
0
-

Aktualny changelog (5. marec 2010, 6086e78):

  • podpora pre indexy (aj unique), ukazka
  • podpora pre rozdielny nazov tabulky a modelu, definovanie nazvu tabulky, alebo prefixu
  • pridana podpora pre IPAddressField, EmailField, URLField a SlugField
  • pridany lazy loading
  • pridany null_callback
  • zmena url a prestylovanie sandboxu
  • prepracovane viacere casti, alias-building, magicke metody __get a __set pre nastavovanie a zmenu hodnot modelu, zmeneny sposob vytvarania modelu atd.

Ukazka prace s prefixami a zmenou nazvu tabulky

prefix:

abstract class PerfORMDezen extends PerfORM {

    protected $prefix= 'pneumatika_';
    ...

uplna zmena nazvu tabulky:

abstract class PerfORMMaterial extends PerfORM {

    protected $tableName= 'material_skupina';
    ...

Ukazka pouzitia SlugField-u:

Model:

protected function setup()
{
	...
	$this->title= new CharField('null','max_length=200');
	$this->slug= new SlugField('null', 'auto_source=title', 'max_length=200');
	$this->modified= new DateTimeField('auto_now', 'null');
	...

}

Praca s modelom:

$test= new Test();
$test->title= 'DibiORM - dalsi pokus o ORM';
$test->save();

$test->title= 'PerfORM - dalsi pokus o ORM';
$test->save();

A SQL:

insert into "test" ("title", "slug", "modified") VALUES ('DibiORM - dalsi
pokus o ORM', 'dibiorm-dalsi-pokus-o-orm', '2010-03-05 15:24:17');
update "test" set "title"='PerfORM - dalsi pokus o ORM',
"slug"='perform-dalsi-pokus-o-orm', "modified"='2010-03-05 15:24:17' where
"id" = 4;

Ukazka lazy loadingu:

Pri komplikovanejsich modelsetoch moze dopytovaci select nadobudnut celkom slusne rozmery. Napriklad v deme pri dopytovani na model Pneumatika() to moze vyzerat takto:

$tyre= new Pneumatika();
$tyre->objects()->get('id=1');

SQl:

SELECT *
                        FROM (
SELECT
        pneumatika.id as pneumatika__id,
        pneumatika.sap as pneumatika__sap,
        pneumatika.id_dezen as pneumatika__id_dezen,
        pneumatika_dezen.id as pneumatika_dezen__id,
        pneumatika_dezen.id_vyrobca as pneumatika_dezen__id_vyrobca,
        vyrobca.id as vyrobca__id,
        vyrobca.nazov as vyrobca__nazov,
        vyrobca.kod as vyrobca__kod,
        vyrobca.typ as vyrobca__typ,
        vyrobca.poradie as vyrobca__poradie,
        vyrobca.id_logo as vyrobca__id_logo,
        subor.id as subor__id,
        subor.popis as subor__popis,
        subor.opis as subor__opis,
        subor.subor as subor__subor,
        subor.skupina as subor__skupina,
        subor.typ as subor__typ,
        subor.nahlad as subor__nahlad,
        subor.datum_vytvorenia as subor__datum_vytvorenia,
        subor.system_nahlad as subor__system_nahlad,
        subor.id_item_kategoria as subor__id_item_kategoria,
        subor.id_kategoria as subor__id_kategoria,
        kategoria.id as kategoria__id,
        kategoria.nazov as kategoria__nazov,
        kategoria.cesta as kategoria__cesta,
        subor.datum_modifikacie as subor__datum_modifikacie,
        vyrobca.id_pozadie as vyrobca__id_pozadie,
        subor2.id as subor2__id,
        subor2.popis as subor2__popis,
        subor2.opis as subor2__opis,
        subor2.subor as subor2__subor,
        subor2.skupina as subor2__skupina,
        subor2.typ as subor2__typ,
        subor2.nahlad as subor2__nahlad,
        subor2.datum_vytvorenia as subor2__datum_vytvorenia,
        subor2.system_nahlad as subor2__system_nahlad,
        subor2.id_item_kategoria as subor2__id_item_kategoria,
        subor2.id_kategoria as subor2__id_kategoria,
        kategoria2.id as kategoria2__id,
        kategoria2.nazov as kategoria2__nazov,
        kategoria2.cesta as kategoria2__cesta,
        subor2.datum_modifikacie as subor2__datum_modifikacie,
        vyrobca.aktivny as vyrobca__aktivny,
        vyrobca.farba as vyrobca__farba,
        vyrobca.skratka as vyrobca__skratka,
        pneumatika_dezen.id_dokument as pneumatika_dezen__id_dokument,
        dokument.id as dokument__id,
        dokument.pid as dokument__pid,
        dokument.pracovny_nazov as dokument__pracovny_nazov,
        dokument.nazov as dokument__nazov,
        dokument.obsah as dokument__obsah,
        dokument.datum_vytvorenia as dokument__datum_vytvorenia,
        dokument.datum_modifikacie as dokument__datum_modifikacie,
        dokument.typ as dokument__typ,
        dokument.extra_dokumenty_nazov as dokument__extra_dokumenty_nazov,
        dokument.umiestnenie_priloh as dokument__umiestnenie_priloh,
        dokument.zobrazit_nazov as dokument__zobrazit_nazov,
        dokument.poradie as dokument__poradie,
        dokument.obsah_ascii as dokument__obsah_ascii,
        dokument.tsvector_ascii as dokument__tsvector_ascii,
        pneumatika_dezen.id_subor as pneumatika_dezen__id_subor,
        subor3.id as subor3__id,
        subor3.popis as subor3__popis,
        subor3.opis as subor3__opis,
        subor3.subor as subor3__subor,
        subor3.skupina as subor3__skupina,
        subor3.typ as subor3__typ,
        subor3.nahlad as subor3__nahlad,
        subor3.datum_vytvorenia as subor3__datum_vytvorenia,
        subor3.system_nahlad as subor3__system_nahlad,
        subor3.id_item_kategoria as subor3__id_item_kategoria,
        subor3.id_kategoria as subor3__id_kategoria,
        kategoria3.id as kategoria3__id,
        kategoria3.nazov as kategoria3__nazov,
        kategoria3.cesta as kategoria3__cesta,
        subor3.datum_modifikacie as subor3__datum_modifikacie,
        pneumatika_dezen.nazov as pneumatika_dezen__nazov,
        pneumatika_dezen.unikatny_nazov as pneumatika_dezen__unikatny_nazov,
        pneumatika_dezen.sezona as pneumatika_dezen__sezona,
        pneumatika.id_skupina as pneumatika__id_skupina,
        material_skupina.id as material_skupina__id,
        material_skupina.pid as material_skupina__pid,
        material_skupina.popis as material_skupina__popis,
        material_skupina.nazov as material_skupina__nazov,
        material_skupina.priorita as material_skupina__priorita,
        material_skupina.typ as material_skupina__typ,
        pneumatika.sirka as pneumatika__sirka,
        pneumatika.oddelovac as pneumatika__oddelovac,
        pneumatika.vyska as pneumatika__vyska,
        pneumatika.priemer as pneumatika__priemer,
        pneumatika.priemer_cislo as pneumatika__priemer_cislo,
        pneumatika.li as pneumatika__li,
        pneumatika.si as pneumatika__si,
        pneumatika.prevedenie as pneumatika__prevedenie,
        pneumatika.rozmer_skrateny as pneumatika__rozmer_skrateny,
        pneumatika.rozmer_cely as pneumatika__rozmer_cely
FROM pneumatika
        INNER JOIN pneumatika_dezen AS pneumatika_dezen ON pneumatika_dezen.id =
pneumatika.id_dezen
        INNER JOIN vyrobca AS vyrobca ON vyrobca.id = pneumatika_dezen.id_vyrobca
        INNER JOIN subor AS subor ON subor.id = vyrobca.id_logo
        INNER JOIN kategoria AS kategoria ON kategoria.id = subor.id_kategoria
        INNER JOIN subor AS subor2 ON subor2.id = vyrobca.id_pozadie
        INNER JOIN kategoria AS kategoria2 ON kategoria2.id = subor2.id_kategoria
        LEFT JOIN dokument AS dokument ON dokument.id =
pneumatika_dezen.id_dokument
        LEFT JOIN subor AS subor3 ON subor3.id = pneumatika_dezen.id_subor
        LEFT JOIN kategoria AS kategoria3 ON kategoria3.id = subor3.id_kategoria
        INNER JOIN material_skupina AS material_skupina ON material_skupina.id =
pneumatika.id_skupina) t
                         WHERE ("pneumatika__id" = 1);

Samozrejme nie vzdy je potrebne cely strom, casto pri niektorych interakciach s modelom viete dopredu, ze niektore casti modeloveho stromu potrebovat nebudete. Preto by sa pridalo, keby sa dala elegantne „vypnut“ niektora alebo viacero vetiev , alebo aj vsetky. Ale zas aby to nemalo dopad na neskorsiu pracu s modelom, ak nahodou aj s „vypnutou vetvou“ potrebujeme pracovat.

	$tyre= new Pneumatika();
	# vypnutie vetvy dezen->vyrobca a vetvy skupina
	$tyre->setLazyLoading('dezen->vyrobca', 'skupina');
	$tyre->objects()->get('id=1');

	# pri pristupe na property vyrobca sa chybajuca vetva nahra, priradi
	# k modelovemu stromu a pokracuje sa v interakcii, v tomto pripade
	# sa zobrazi nazov vyrovcu
	echo $tyre->dezen->vyrobca;

a sql:

SELECT *
FROM (
SELECT
        pneumatika.id as pneumatika__id,
        pneumatika.sap as pneumatika__sap,
        pneumatika.id_dezen as pneumatika__id_dezen,
        pneumatika_dezen.id as pneumatika_dezen__id,
        pneumatika_dezen.id_vyrobca as pneumatika_dezen__id_vyrobca,
        pneumatika_dezen.id_dokument as pneumatika_dezen__id_dokument,
        dokument.id as dokument__id,
        dokument.pid as dokument__pid,
        dokument.pracovny_nazov as dokument__pracovny_nazov,
        dokument.nazov as dokument__nazov,
        dokument.obsah as dokument__obsah,
        dokument.datum_vytvorenia as dokument__datum_vytvorenia,
        dokument.datum_modifikacie as dokument__datum_modifikacie,
        dokument.typ as dokument__typ,
        dokument.extra_dokumenty_nazov as dokument__extra_dokumenty_nazov,
        dokument.umiestnenie_priloh as dokument__umiestnenie_priloh,
        dokument.zobrazit_nazov as dokument__zobrazit_nazov,
        dokument.poradie as dokument__poradie,
        dokument.obsah_ascii as dokument__obsah_ascii,
        dokument.tsvector_ascii as dokument__tsvector_ascii,
        pneumatika_dezen.id_subor as pneumatika_dezen__id_subor,
        subor3.id as subor3__id,
        subor3.popis as subor3__popis,
        subor3.opis as subor3__opis,
        subor3.subor as subor3__subor,
        subor3.skupina as subor3__skupina,
        subor3.typ as subor3__typ,
        subor3.nahlad as subor3__nahlad,
        subor3.datum_vytvorenia as subor3__datum_vytvorenia,
        subor3.system_nahlad as subor3__system_nahlad,
        subor3.id_item_kategoria as subor3__id_item_kategoria,
        subor3.id_kategoria as subor3__id_kategoria,
        kategoria3.id as kategoria3__id,
        kategoria3.nazov as kategoria3__nazov,
        kategoria3.cesta as kategoria3__cesta,
        subor3.datum_modifikacie as subor3__datum_modifikacie,
        pneumatika_dezen.nazov as pneumatika_dezen__nazov,
        pneumatika_dezen.unikatny_nazov as pneumatika_dezen__unikatny_nazov,
        pneumatika_dezen.sezona as pneumatika_dezen__sezona,
        pneumatika.id_skupina as pneumatika__id_skupina,
        pneumatika.sirka as pneumatika__sirka,
        pneumatika.oddelovac as pneumatika__oddelovac,
        pneumatika.vyska as pneumatika__vyska,
        pneumatika.priemer as pneumatika__priemer,
        pneumatika.priemer_cislo as pneumatika__priemer_cislo,
        pneumatika.li as pneumatika__li,
        pneumatika.si as pneumatika__si,
        pneumatika.prevedenie as pneumatika__prevedenie,
        pneumatika.rozmer_skrateny as pneumatika__rozmer_skrateny,
        pneumatika.rozmer_cely as pneumatika__rozmer_cely
FROM pneumatika
        INNER JOIN pneumatika_dezen AS pneumatika_dezen ON pneumatika_dezen.id =
pneumatika.id_dezen
        LEFT JOIN dokument AS dokument ON dokument.id =
pneumatika_dezen.id_dokument
        LEFT JOIN subor AS subor3 ON subor3.id = pneumatika_dezen.id_subor
        LEFT JOIN kategoria AS kategoria3 ON kategoria3.id = subor3.id_kategoria)
t
                         WHERE ("pneumatika__id" = 1)

                         ;

SELECT *
FROM (
SELECT
        vyrobca.id as vyrobca__id,
        vyrobca.nazov as vyrobca__nazov,
        vyrobca.kod as vyrobca__kod,
        vyrobca.typ as vyrobca__typ,
        vyrobca.poradie as vyrobca__poradie,
        vyrobca.id_logo as vyrobca__id_logo,
        subor.id as subor__id,
        subor.popis as subor__popis,
        subor.opis as subor__opis,
        subor.subor as subor__subor,
        subor.skupina as subor__skupina,
        subor.typ as subor__typ,
        subor.nahlad as subor__nahlad,
        subor.datum_vytvorenia as subor__datum_vytvorenia,
        subor.system_nahlad as subor__system_nahlad,
        subor.id_item_kategoria as subor__id_item_kategoria,
        subor.id_kategoria as subor__id_kategoria,
        kategoria.id as kategoria__id,
        kategoria.nazov as kategoria__nazov,
        kategoria.cesta as kategoria__cesta,
        subor.datum_modifikacie as subor__datum_modifikacie,
        vyrobca.id_pozadie as vyrobca__id_pozadie,
        subor2.id as subor2__id,
        subor2.popis as subor2__popis,
        subor2.opis as subor2__opis,
        subor2.subor as subor2__subor,
        subor2.skupina as subor2__skupina,
        subor2.typ as subor2__typ,
        subor2.nahlad as subor2__nahlad,
        subor2.datum_vytvorenia as subor2__datum_vytvorenia,
        subor2.system_nahlad as subor2__system_nahlad,
        subor2.id_item_kategoria as subor2__id_item_kategoria,
        subor2.id_kategoria as subor2__id_kategoria,
        kategoria2.id as kategoria2__id,
        kategoria2.nazov as kategoria2__nazov,
        kategoria2.cesta as kategoria2__cesta,
        subor2.datum_modifikacie as subor2__datum_modifikacie,
        vyrobca.aktivny as vyrobca__aktivny,
        vyrobca.farba as vyrobca__farba,
        vyrobca.skratka as vyrobca__skratka
FROM vyrobca
        INNER JOIN subor AS subor ON subor.id = vyrobca.id_logo
        INNER JOIN kategoria AS kategoria ON kategoria.id = subor.id_kategoria
        INNER JOIN subor AS subor2 ON subor2.id = vyrobca.id_pozadie
        INNER JOIN kategoria AS kategoria2 ON kategoria2.id = subor2.id_kategoria)
t
                         WHERE ("vyrobca__id" = 1);

Funguje to aj v situacii, ked v nenahratej vetve modelu zmenime nejaku vlastnost a pokusime sa model ulozit:

	$tyre= new Pneumatika();
	//$tyre->setLazyLoading();
	$tyre->setLazyLoading('dezen->vyrobca', 'skupina');
	$tyre->objects()->get('id=1');
	$tyre->dezen->vyrobca->skratka= 'HNK';
	$tyre->dezen->vyrobca->save();

Sql:

SELECT *
                        FROM (
SELECT
        pneumatika.id as pneumatika__id,
        pneumatika.sap as pneumatika__sap,
        pneumatika.id_dezen as pneumatika__id_dezen,
        pneumatika_dezen.id as pneumatika_dezen__id,
        pneumatika_dezen.id_vyrobca as pneumatika_dezen__id_vyrobca,
        pneumatika_dezen.id_dokument as pneumatika_dezen__id_dokument,
        dokument.id as dokument__id,
        dokument.pid as dokument__pid,
        dokument.pracovny_nazov as dokument__pracovny_nazov,
        dokument.nazov as dokument__nazov,
        dokument.obsah as dokument__obsah,
        dokument.datum_vytvorenia as dokument__datum_vytvorenia,
        dokument.datum_modifikacie as dokument__datum_modifikacie,
        dokument.typ as dokument__typ,
        dokument.extra_dokumenty_nazov as dokument__extra_dokumenty_nazov,
        dokument.umiestnenie_priloh as dokument__umiestnenie_priloh,
        dokument.zobrazit_nazov as dokument__zobrazit_nazov,
        dokument.poradie as dokument__poradie,
        dokument.obsah_ascii as dokument__obsah_ascii,
        dokument.tsvector_ascii as dokument__tsvector_ascii,
        pneumatika_dezen.id_subor as pneumatika_dezen__id_subor,
        subor3.id as subor3__id,
        subor3.popis as subor3__popis,
        subor3.opis as subor3__opis,
        subor3.subor as subor3__subor,
        subor3.skupina as subor3__skupina,
        subor3.typ as subor3__typ,
        subor3.nahlad as subor3__nahlad,
        subor3.datum_vytvorenia as subor3__datum_vytvorenia,
        subor3.system_nahlad as subor3__system_nahlad,
        subor3.id_item_kategoria as subor3__id_item_kategoria,
        subor3.id_kategoria as subor3__id_kategoria,
        kategoria3.id as kategoria3__id,
        kategoria3.nazov as kategoria3__nazov,
        kategoria3.cesta as kategoria3__cesta,
        subor3.datum_modifikacie as subor3__datum_modifikacie,
        pneumatika_dezen.nazov as pneumatika_dezen__nazov,
        pneumatika_dezen.unikatny_nazov as pneumatika_dezen__unikatny_nazov,
        pneumatika_dezen.sezona as pneumatika_dezen__sezona,
        pneumatika.id_skupina as pneumatika__id_skupina,
        pneumatika.sirka as pneumatika__sirka,
        pneumatika.oddelovac as pneumatika__oddelovac,
        pneumatika.vyska as pneumatika__vyska,
        pneumatika.priemer as pneumatika__priemer,
        pneumatika.priemer_cislo as pneumatika__priemer_cislo,
        pneumatika.li as pneumatika__li,
        pneumatika.si as pneumatika__si,
        pneumatika.prevedenie as pneumatika__prevedenie,
        pneumatika.rozmer_skrateny as pneumatika__rozmer_skrateny,
        pneumatika.rozmer_cely as pneumatika__rozmer_cely
FROM pneumatika
        INNER JOIN pneumatika_dezen AS pneumatika_dezen ON pneumatika_dezen.id =
pneumatika.id_dezen
        LEFT JOIN dokument AS dokument ON dokument.id =
pneumatika_dezen.id_dokument
        LEFT JOIN subor AS subor3 ON subor3.id = pneumatika_dezen.id_subor
        LEFT JOIN kategoria AS kategoria3 ON kategoria3.id = subor3.id_kategoria)
t
                         WHERE ("pneumatika__id" = 1)

                         ;

                        SELECT *
                        FROM (
SELECT
        vyrobca.id as vyrobca__id,
        vyrobca.nazov as vyrobca__nazov,
        vyrobca.kod as vyrobca__kod,
        vyrobca.typ as vyrobca__typ,
        vyrobca.poradie as vyrobca__poradie,
        vyrobca.id_logo as vyrobca__id_logo,
        subor.id as subor__id,
        subor.popis as subor__popis,
        subor.opis as subor__opis,
        subor.subor as subor__subor,
        subor.skupina as subor__skupina,
        subor.typ as subor__typ,
        subor.nahlad as subor__nahlad,
        subor.datum_vytvorenia as subor__datum_vytvorenia,
        subor.system_nahlad as subor__system_nahlad,
        subor.id_item_kategoria as subor__id_item_kategoria,
        subor.id_kategoria as subor__id_kategoria,
        kategoria.id as kategoria__id,
        kategoria.nazov as kategoria__nazov,
        kategoria.cesta as kategoria__cesta,
        subor.datum_modifikacie as subor__datum_modifikacie,
        vyrobca.id_pozadie as vyrobca__id_pozadie,
        subor2.id as subor2__id,
        subor2.popis as subor2__popis,
        subor2.opis as subor2__opis,
        subor2.subor as subor2__subor,
        subor2.skupina as subor2__skupina,
        subor2.typ as subor2__typ,
        subor2.nahlad as subor2__nahlad,
        subor2.datum_vytvorenia as subor2__datum_vytvorenia,
        subor2.system_nahlad as subor2__system_nahlad,
        subor2.id_item_kategoria as subor2__id_item_kategoria,
        subor2.id_kategoria as subor2__id_kategoria,
        kategoria2.id as kategoria2__id,
        kategoria2.nazov as kategoria2__nazov,
        kategoria2.cesta as kategoria2__cesta,
        subor2.datum_modifikacie as subor2__datum_modifikacie,
        vyrobca.aktivny as vyrobca__aktivny,
        vyrobca.farba as vyrobca__farba,
        vyrobca.skratka as vyrobca__skratka
FROM vyrobca
        INNER JOIN subor AS subor ON subor.id = vyrobca.id_logo
        INNER JOIN kategoria AS kategoria ON kategoria.id = subor.id_kategoria
        INNER JOIN subor AS subor2 ON subor2.id = vyrobca.id_pozadie
        INNER JOIN kategoria AS kategoria2 ON kategoria2.id = subor2.id_kategoria)
t
                         WHERE ("vyrobca__id" = 1)

                         ;
update "vyrobca" set "kod"='hankook', "skratka"='HNK' where "id" = 1;

Ukazka pouzitia null callbacku:

Null callback je sikovny pomocnik, ak mate v modeli pole, ktore je priamo zavisle na ostatnych poliach a viete teda jeho plnenie uplne zautomatizovat.

Model:

    protected function setup()
    {
    ...
	$this->subor= new CharField('max_length=80','notnull','unique');
	$this->nahlad= new CharField('max_length=50','null', 'null_callback=nullNahladCallback');
...
    }

    public function nullNahladCallback()
    {
	return preg_match('#(.*)[.]{1}([^.]{2,5})#', $this->subor, $match) ? $match[1].'-thumb.'.$match[2] : null;
    }

Pouzitie vidiet v priklade Populate tyres:

$manufacturer_logo->kategoria= $manufacturer_logo_category;
      $manufacturer_logo->popis= 'logo-Hankook-OK';
      $manufacturer_logo->subor= 'logo-hankook-ok.jpg';

Sql:

insert into "app_subor" ("popis", "subor", "skupina", "typ", "nahlad",
"datum_vytvorenia", "system_nahlad", "id_kategoria", "datum_modifikacie")
VALUES ('logo-Hankook-OK', 'logo-hankook-ok.jpg', 'image', 'jpg',
'logo-hankook-ok-thumb.jpg', '2010-03-05 15:49:39',
'logo-hankook-ok-systhumb.jpg', 1, '2010-03-05 15:49:39');

TODO:

  • podpora pre sqlite (najskor sqlite2)
  • integrovat dalsie vazby medzi modelmi, ManyToManyField a OneToManyField
  • nasadenie do aplikacie a otestovanie v production

Editoval edke (5. 3. 2010 15:54)

edke
Člen | 198
+
0
-

Aktualny changelog (29. marec 2010, 409456f):

* pridana podpora pre pohlady (views), synchronizovane vytvorenie, premenovanie, dropnutie
* refactoring QuerySet, metody load() a get()
* pre QuerySet a jeho DataSource pridana podpora pre where(), select(), orderBy(), applyLimit()
* pridana podpora pre Choices
* pridane primitivne profilovanie SQL prikazov vykonanych cez PerfORM
* generovanie modelu do stdClass
* pridana dedicnost modelov

edke
Člen | 198
+
0
-

Views

Pri aplikovani do realnej aplikacie som narazil na situaciu, kedy som nebol schopny pri danej interakcii s databazou pouzit PerfORM. Slo o zlozitejsi select, kde bola pouzita agregacia, spolu s joinami, dokonca bolo nutne pouzit having atd. Ked som vsak pozrel na aktualnu databazu, uvedomil som si, ze dany select pouzivam vlastne vsade a je to typicky priklad pre view. No a cim by sa taky view lisil od modelu ? No len tym, ze by boli deaktivovana metoda save().

definicia modelu:

abstract class PerfORMPneumatikaWeb extends PerfORM {

    protected $view= true;

    protected $tableName= 'pneumatika_web';

    protected function setup()
    {
	$this->ks= new IntegerField('null');
	$this->cenabezdph= new DecimalField('max_digits=15', 'decimal_places=2');
	$this->pneumatika= new ForeignKeyField(new Pneumatika, 'notnull');
	...
    }

    public function __toString()
    {
	return $this->pneumatika;
    }


    public function getViewSetup()
    {
	return '
		    select
			sum(tb.ks) as ks,
			tb.cenabezdph,
			tb.id_pneumatika,
			s.typ as typ,
			...
		    from
			pneumatika_sklad as tb
		    left join
			...
		    where ...
		    group by
			...
		    having
			...

	';
    }

}

Definicia sa od modelu lisi tym, ze sa nastavi priznak $view na true a zadefinuje sa metoda getViewSetup(), ktora vracia samotnu definiciu pohladu. Definicia stlpcov sa samozrejme ingnoruje, sluzi vlastne len na vygenerovanie PHPDoc tagov @property-read, pre pohodlnu pracu s modelom v IDE.

Na view su rovnako aplikovane vsetky ficury PerfORM, teda joinovanie dalsich modelov, lazyloading atd.

edke
Člen | 198
+
0
-

QuerySets

Nastalo vacsie upratovanie, stale to este nie je z hladiska OOP najcistejsie, ale je to uz pouzitelne.

load()

Sluzi v situacii, kedy potrebujete dany model naplnit udajmi z databazy jedinym zaznamom, teda podmienka je hodnota primarneho kluca, hodnota unique pola atd. Osetrenie uz je na uzivatelovi, ak result bude obsahovat viacero riadkov, aplikuje sa prvy riadok, ostatne su ignorovane.

Metoda vracia false ak nenajde ziaden zaznam, takze sa to da pekne pouzit pri podmienke.

$category= new Kategoria();
if ( !$category->objects()->load('cesta=%s', 'vyrobca-logo' ) )
{
	$category->cesta= 'vyrobca-logo';
	$category->nazov= 'logo pre značku';
}
get()

Metoda vracia result, vhodny do iteracie napriklad cez foreach, alebo rovno pridat do sablony.

$tyres= new PneumatikaWeb();
$tyres->setLazyLoading(
    'pneumatika->dezen->vyrobca->logo',
    'pneumatika->dezen->vyrobca->pozadie',
    'pneumatika->dezen->dokument'
);

$result= $tyres->objects()
    ->where('pneumatika__sirka = %i', 205)
    ->where('pneumatika__vyska = %i', 55)
    ->where('pneumatika__priemer_cislo = %i', 16)
    ->where('ks >= %i', 70)
    ->orderBy('cenasdph', 'asc');
$paginator->setItemCount(count($result));
$result->applyLimit($paginator->getLength(), $paginator->getOffset())
	->get();

foreach($result as $tyre)
{
	echo $tyre->pneumatika->dezen->nazov;
}

Pred samotnym get() mozme aplikovat where(), select(), orderBy() a applyLimit(), kedze objects() generuje vlastny DataSource. Ten je ale zlozitejsi a preto fieldNames v select(), where() a orderBy() musia byt prekladane. Ak sa odkazujeme na fieldName, ktory je priamo v hlavnom modeli, staci pouzit jeho nazov. Ak ale sa odkazujeme na fieldName, ktory sa nachadza v jeho submodule, je potrebujne zadat „cestu“, aby bolo mozne zrealizovat preklad.

Preto
pneumatika__sirka bude prelozene ako pneumatika__sirka,
pneumatika__dezen__nazov ako pneumatika_dezen__nazov,
pneumatika__dezen__subor__id ako app_subor3__id.

Ak to nie je zrozumitelne, pozrite si SQL v predch. prikladoch.

Dalsie pouzitie do subselectov

Ak z daneho pohladu potrebujeme generovat napriklad unikatne hodnoty do selectboxu, kedze to je DataSource, da sa s nim krasne dalej pracovat a s pouzitim fluent zapisu sa to da aj celkom dobre citat :)

$tyres= new PneumatikaWeb();
$tyres->setLazyLoading(
    'pneumatika->dezen->vyrobca->logo',
    'pneumatika->dezen->vyrobca->pozadie',
    'pneumatika->dezen->dokument',
    'pneumatika->dezen->subor'
);
$data= $tyres->objects()
    ->select('pneumatika__vyska', 'id')
    ->getDataSource()
    ->toFluent()
    ->select('id as value')
    ->groupBy('value' )
    ->having('id > 0')
    ->orderBy('value')
    ->execute()
    ->setType('value', dibi::FIELD_FLOAT)
    ->fetchPairs('id','value');
$form->addSelect('sirka', 'Šírka:', array(''=>'všetky') + $data );
edke
Člen | 198
+
0
-

Choices

Nie vzdy je vhodne pouzit novu tabulku a vazbu, hlavne ak vycet hodnot je v podstate staticky a jeho zmena je malo pravdepodobna, az nemozna. Pouzivanie ciselnikov vzdy komplikuje udrzbu, na jednej strane chceme efektivne ulozit data v databaze, na druhej strane je potrebne zobrazit data v citatelnej podobe a este k tomu obcas treba z takehoto pola vygenerovat formular.

Priklad, ak mame tabulku pneumatika a potrebujeme definovat sezonnost, mozne hodnoty su zvacsa 3: letna, zimna, celorocna.

Na pomoc prichadzaju (podla vzoru Django) Choices.

Definicia:

protected function setup()
{
    ...
    $this->sezona= new CharField('max_length=1','notnull', 'choices=setSeasons');
    ...
}

public function setSeasons()
{
    return array(
    'L' => 'letná',
    'Z' => 'zimná',
    'C' => 'celoročná'
     );
}

Choices sa aplikuju ako parameter pre CharField, jeho velkost si definujeme podla potreby. Samotne moznosti su teda ulozene priamo v modeli.

Pouzitie:

$tread= new Dezen();
$tread->objects()->load('nazov=%s', 'AH11');

# vrati kluc, "L"
echo $tread->sezona;

# vrati hodnotu, "letna"
echo $tread->sezona->display();

# ziskanie celeho pola pre potreby formulara
$form->addRadioList('sezona', 'Sezóna:', $tread->sezona->getChoices() + array(''=>'všetky'))
            ->setDefaultValue('');
edke
Člen | 198
+
0
-

Zjednodusenie naplneneho modelu do stdClass

Pri iteracii s resultsetom sa naplna vzdy nova instancia modelu:

$tyres= new PneumatikaWeb();
$result= $tyres->objects()->get();
foreach($result as $tyre)
{
	echo $tyre->pneumatika->dezen->nazov;
}

Co je cielene spravanie, pretoze s prave takto vytvorenym modelom vieme potom vyuzit silu ORM, pouzivat metody modelu, ktore ulahcuju pracu, magicke metody __toString a pod.

Ale samozrejme ORM znamena reziu navyse, ktora sa prejavi vo vyssich narokoch na renderovanie jednotlivej stranky. Preto je potrebne pouzit techniky ako napriklad kesovanie. Ak ale mame resultset, ktory vrati pole napriklad 20-tich modelov, ulozenie tohto kolosu a jeho opatovne nacitanie najskor vobec nebude smerovat k optimalizacii vykonu. Pri zlozitejsom modeli takto serializovane pole o napr. 20-tich polozkach moze hravo prekrocit 0.5–1MB.

Pri sablonovani, ak si vystacime cisto s hodnotami daneho modelu a nebudeme vyuzivat jeho dalsie moznosti, riesenim by bolo vytvorit z instancie modelu naplneneho hodnotami jeho stdClass klon, ktory by dodrzal strukturu modelu. Tak by bolo mozne pouzit napriklad pri prvom zobrazeni povodny model, vyrenderovat sablonu, vytvorit zjednoduseny model, ktory ulozime do cache a pri opakovanom zobrazeni renderovat sablonu pomocou zjednoduseneho modelu. Oproti kesovaniu povodneho modelu sa velkost ulozenej cache znizi dramaticky (v mojom testovacom priklade z 1MB na 30kB).

Optimalizacia pomohla, pri opakovanom zobrazeni nie je hitovana databaza, odpada cela rezia s pracou s ORM ale je mozne pouzit nakesovany zjednoduseny obraz v podobe stdClassu pre sablonu.

Samozrejme je mozne ist aj dalej, kesovat rovno cele HTML zo sablony, toto riesenie ale nie je mozne pouzit, ak pouzivame vramci tohto renderovacieho bloku snippety, formulare, ine komponenty.

Queryset->getSimplified()

Z vytvoreneho resultsetu vrati rovno pole stdClassov:

$tyres= $tyre->objects()
	->where(...)
	->applyLimit(...)
	->getSimplified();

$this->template->tyres= $tyres;
PerfORM->simplify()

Vytvori z modelu jeho zjednodusenu verziu:

$tyre->objects()
	->load('id = %i', 45);

$this->template->tyre= $tyre->simplify();
edke
Člen | 198
+
0
-

Dedicnost modelov

Na zaver ceresnicka (aspon pre mna), dedicnost modelov. Casto som sa stretol so situaciou, kedy som potreboval ulozit data do tabuliek, a tie sa mierne lisili, alebo dokonca nelisili vobec, len bolo potrebne ulozit data s rovnakou strukturou ale ktore sa lisili tematicky. Nasledne sa teda integrovalo nejake diferencovanie, pridal sa stlpec typ, atd.

Dolezitou vlastnostou OOP je prave dedicnost, preto logicky pri ORM je integrovanie dedicnosti logicky krok.

Moznosti PerfORM a implementacie dedicnosti ukazem na priklade. Na stranke potrebujeme prezentovat dokumenty, ktore chceme ukladat pre moznosti dynamickej spravy v databaze. Dokumenty budu mat ale rozdielne pouzitie, dokonca sa moze lisit vycet stlpcov potrebnych.

Vytvorim teda model Document:

abstract class PerfORMDocument extends PerfORM
{

    protected function setup()
    {
	$this->title= new CharField('notnull','max_length=200');
	$this->slug= new SlugField('null', 'auto_source=title', 'max_length=200');
	$this->modified= new DateTimeField('auto_now', 'notnull');
	$this->created= new DateTimeField('auto_now_add', 'notnull');
	$this->content= new TextField('notnull');
    }

    public function __toString()
    {
	return $this->title;
    }


    public function nullSlug()
    {
	return $this->title;
    }
}

Vyuzijem dedicnost a vytvorim dva node movely PressRelease a TyreTests, ktore budu dedit moj vychodzi model Document. PressRelease nebude nijako rozsirovat povodny model, bude to len jeho tematicky potomok, TyreTests doplni 2 nove vlastnosti:

abstract class PerfORMPressRelease extends PerfORM
{

    protected function setup()
    {
	$this->extends(new Document);
    }

    public function __toString()
    {
	return $this->title;
    }


    public function nullSlug()
    {
	return $this->title;
    }
}

abstract class PerfORMTyreTest extends PerfORM
{

    protected function setup()
    {
	$this->extends(new Document);
	$this->rating= new SmallIntegerField('null');
	$this->title= new CharField('null','max_length=200');
    }

    public function __toString()
    {
	return $this->title;
    }


    public function nullSlug()
    {
	return $this->title;
    }
}
Vytvorenie noveho zaznamu:
$pressRelease= new PressRelease();
$pressRelease->title= 'Hankook Tire Announces 2010 Motorsports Sponsorships';
$pressRelease->content= 'LAS VEGAS, Nov. 5, 2009 - After celebrating its most successful motorsports ...';
$pressRelease->save();


$test= new TyreTest();
$test->title= 'Nokian H “very recommendable” in ADAC tyre test';
$test->content= '“Very recommendable” is the Nokian H according to the “ADAC judgement” in the latest summer tyre test ...';
$test->rating= 95;
$test->save();

SQL:

insert into "document" ("title", "slug", "modified", "created", "content")
VALUES ('Hankook Tire Announces 2010 Motorsports Sponsorships',
'hankook-tire-announces-2010-motorsports-sponsorships', '2010-03-29
13:16:45', '2010-03-29 13:16:45', 'LAS VEGAS, Nov. 5, 2009 - After
celebrating its most successful motorsports ...');

insert into "pressrelease" ("id") VALUES (66);

insert into "document" ("title", "slug", "modified", "created", "content")
VALUES ('Nokian H “very recommendable” in ADAC tyre test',
'nokian-h-very-recommendable-in-adac-tyre-test', '2010-03-29 13:16:45',
'2010-03-29 13:16:45', '“Very recommendable” is the Nokian H according
to the “ADAC judgement” in the latest summer tyre test ...');

insert into "tyretest" ("id", "rating", "title") VALUES (67, 95, 'Nokian H
“very recommendable” in ADAC tyre test');
Nacitanie a modifikacia:

PHP:

	$pressRelease= new PressRelease();
	$pressRelease->objects()->load('id=%i', 66);
	echo $pressRelease->title;

	$pressRelease->title= 'Hankook Tire Announces 2010 Motorsports Sponsorships (modified)';
	$pressRelease->save();



	$test= new TyreTest();
	$result=$test->objects()
	    ->where('rating > %i', 90)
	    ->get();

	foreach($result as $document)
	{
	    echo $document->id.'-'.$document->title;

	    $document->title= "Nokian H “very recommendable” in ADAC tyre test";
	    $document->rating= 97;
	    $document->save();
	}

SQL selecty:

SELECT *
FROM (
SELECT
        document.id as document__id,
        document.title as document__title,
        document.slug as document__slug,
        document.modified as document__modified,
        document.created as document__created,
        document.content as document__content,
        pressrelease.id as pressrelease__id
FROM pressrelease
        INNER JOIN document AS document ON document.id = pressrelease.id) t
                         WHERE (pressrelease__id =66);



SELECT *
FROM (
SELECT
        document.id as document__id,
        document.title as document__title,
        document.slug as document__slug,
        document.modified as document__modified,
        document.created as document__created,
        document.content as document__content,
        tyretest.id as tyretest__id,
        tyretest.rating as tyretest__rating,
        tyretest.title as tyretest__title
FROM tyretest
        INNER JOIN document AS document ON document.id = tyretest.id) t
                         WHERE (tyretest__rating > 90);

A SQL vygenerovane pri modifikacii modelov:

-- pressrelease
update "document" set "title"='Hankook Tire Announces 2010 Motorsports
Sponsorships (modified)',
"slug"='hankook-tire-announces-2010-motorsports-sponsorships-modified',
"modified"='2010-03-29 13:28:05' where "id" = 66;

-- tyretest
update "document" set "title"='Nokian H “very recommendable” in ADAC
tyre test (modified)',
"slug"='nokian-h-very-recommendable-in-adac-tyre-test-modified',
"modified"='2010-03-29 13:29:32' where "id" = 67;
update "tyretest" set "rating"=97, "title"='Nokian H “very
recommendable” in ADAC tyre test (modified)' where "id" = 67;

Pre dnesok tolko, tak a teraz som zvedavy na vase reakcie (ak vobec) :)

Honza Marek
Člen | 1664
+
0
-

Nechceš to radši nějak sepsat do addons než tu ládovat tuny textu do fóra?

edke
Člen | 198
+
0
-

Honza Marek wrote:

Nechceš to radši nějak sepsat do addons než tu ládovat tuny textu do fóra?

Skor toto beriem ako nejaky changelog, v addons si jednak neviem prestavit, ako by som to tam dostal a hlavne do addons sa umiestnuje nieco, co je realne dostupne. A ja release este len chystam. Ale ak to vadi, ze to tu davam, tak nemusim, nic sa nedeje :) Neskor ak to bude releasnute a bude zaujem, kludne to umiestnim aj do addons.

wotaen
Člen | 82
+
0
-

edke napsal(a):

Honza Marek wrote:

Nechceš to radši nějak sepsat do addons než tu ládovat tuny textu do fóra?

Skor toto beriem ako nejaky changelog, v addons si jednak neviem prestavit, ako by som to tam dostal a hlavne do addons sa umiestnuje nieco, co je realne dostupne. A ja release este len chystam. Ale ak to vadi, ze to tu davam, tak nemusim, nic sa nedeje :) Neskor ak to bude releasnute a bude zaujem, kludne to umiestnim aj do addons.

Rozhodně se zájmem čekám na nějaký release…vypadá to výborně

Honza Kuchař
Člen | 1662
+
0
-

Myslím, že wiki by byla lepší, ale klidně to piš sem. Na release jsem velmi zvědavý. Zatím to vypadá suprově. :)

wotaen
Člen | 82
+
0
-

Měl bych dotaz
Asociace to umí, umí to i reprezentaci stromu v tabulce? (id, parent), tak ze kdyz dam $strom->load(id) nacte to do správné objektové struktury celý strom?

edke
Člen | 198
+
0
-

wotaen wrote:

Měl bych dotaz
Asociace to umí, umí to i reprezentaci stromu v tabulce? (id, parent), tak ze kdyz dam $strom->load(id) nacte to do správné objektové struktury celý strom?

Nie, takymto niecim som sa este nezaoberal.

veena
Člen | 98
+
0
-

Ahoj,
jak to vypadá s vývojem? Uvolníš v dohledné době open source verzi?

edke
Člen | 198
+
0
-

veena wrote:

Ahoj,
jak to vypadá s vývojem? Uvolníš v dohledné době open source verzi?

No ked uz som do toho investoval tolko casu, tak by sa patrilo. Len mi to stale cele pripada prasacke, ale co uz, som pripraveny na kritiku a aspon sa to takto niekam posunie.

Dalsi problem vidim v tom, ze to mam tak nejako upravene na moje potreby a instalacia/pouzivanie asi nebudu kazdemu vyhovovat. Navyse som chcel to pustit von, ked to bude spolupracovat aj s inymi databazami ako postgresql. Ale nato teraz nie je cas.

Napriek vsetkemu, co som tu popisal, je zaujem ? Prasknut to na github je najmenej.