Pokus o dibi ORM
- Honza Marek
- Člen | 1664
Předně bych chtěl zmínit, že jsem ORM nikdy nepoužíval, ale inspiroval jsem se tím, co jsem o něm slyšel. A taky třídou DibiTable, která je teď zavržená a nevyvíjená.
Co umí:
$page = new Page;
$page->findByUrl("kniha-hostu");
echo $page->name;
$page->visits = $page->visits + 1;
$page->save();
$comments = $page->getComments();
foreach ($comments as $comment) {
echo $comment->text;
}
// přidat komentář
$page->addComment(array("text" => "Blablabla"))->save();
// přidat stránku
$page2 = new Page;
$page2->name = "Nová";
$page2->setData(array("url" => "nova", "text" => "Nová stránka"));
$page2->save();
// přesměrovat po založení stránky
$presenter->redirect("Page:", $page2->getPk());
což by předpokládalo dvě poděděné třídy Page a Comments:
class Page extends BaseModel {
/** @var string */
protected $table = "pages";
public function __construct($data = null) {
parent::__construct($data);
$this
->setType("visits", dibi::INTEGER)
->setType("allowed", dibi::BOOL);
}
public function addComment($data = null) {
$comment = new Comment;
$comment->page = $this->getPk();
if ($data !== null) {
$comment->setData($data);
}
return $comment;
}
public function getComments() {
$comment = new Comment;
return $comment->fetchAllByPage($this->getPk());
}
}
class Comment extends BaseModel {
/** @var string */
protected $table = "comments";
}
a samozřejmě třídu BaseModel:
<?php
/**
* Base model
*
* @author Jan Marek, David Grudl
*/
abstract class BaseModel extends Object {
/** @var DibiConnection */
protected $db;
/** @var array */
private $data = array();
/** @var array */
protected $types = array();
/** @var string */
protected $table;
/** @var string */
protected $pk = "id";
/** @var string */
protected $pkType = dibi::INTEGER;
/** @var int */
protected $pkValue;
public function __construct($data = null) {
$this->db = dibi::getConnection();
$this->setType($this->pk, $this->pkType);
if ($data !== null) {
$this->setData($data);
}
}
/**
* Set column type
* @param string $name
* @param string $type
*/
protected function setType($name, $type, $format = null) {
$this->types[$name] = array("type" => $type, "format" => $format);
if ($name === $this->pk) {
$this->pkType = $type;
}
return $this;
}
/**
* Get column type
* @param string $name
* @return string
*/
protected function getType($name) {
if (isset($this->types[$name])) {
return $this->types[$name]["type"];
} else {
return dibi::TEXT;
}
}
/**
* Remove column type
* @param string $name
*/
protected function removeType($name) {
unset($this->types[$name]);
}
/**
* Get primary key value
* @return int
*/
public function getPk() {
return $this->pkValue;
}
/**
* Get field value
* @param string $name
* @return mixed
*/
public function & getValue($name) {
return $this->data[$name];
}
/**
* Magic getter for field value
* @param string $name
* @return mixed
*/
public function & __get($name) {
return $this->getValue($name);
}
/**
* Magic setter for field value
* @param string $name
* @param mixed $value
*/
public function __set($name, $value) {
$this->setValue($name, $value);
}
/**
* Set field value
* @param string $name
* @param mixed $value
*/
public function setValue($name, $value) {
if ($name === $this->pk) {
$this->pkValue = $value;
}
$this->data[$name] = $value;
}
/**
* Find row by primary key
* @param int|string $id
* @return Page
*/
public function find($id) {
$q = $this->db
->select("*")
->from($this->table)
->where("%n = %" . $this->pkType, $this->pk, $id)
->limit(1);
$data = $q->execute()->setTypes($this->types)->fetch();
$this->setData($data);
return $this;
}
/**
*
* @param array $data
* @return Page
*/
public function setData($data) {
foreach ($data as $key => $value) {
$this->setValue($key, $value);
}
return $this;
}
/**
* Get data
* @return array
*/
public function getData() {
return $this->data;
}
/**
* Get data with type modifiers
* @return array
*/
protected function getPreparedData() {
$data = array();
foreach ($this->data as $key => $value) {
$data[$key . "%" . $this->getType($key)] = $value;
}
return $data;
}
/**
* Insert row
*/
protected function insertAsNew() {
$this->db
->insert($this->table, $this->getPreparedData())
->execute();
$id = $this->db->getInsertId();
$this->pkValue = $this->pkType === dibi::INTEGER ? (int) $id : $id;
}
/**
* Update row
*/
protected function update() {
$this->db
->update($this->table, $this->getPreparedData())
->where("%n = %" . $this->pkType, $this->pk, $this->pkValue)
->execute();
}
/**
* Insert or update row
* @return bool
*/
public function save() {
try {
if (isset($this->pkValue)) {
$this->update();
} else {
$this->insertAsNew();
}
} catch (DibiDriverException $e) {
return false;
}
return true;
}
/**
* Delete row from table
*/
public function remove() {
$this->db
->delete($this->table)
->where("%n = %" . $this->pkType, $this->pk, $this->pkValue)
->execute();
$this->pkValue = null;
}
/**
* Magic fetch.
* - $row = $model->fetchByUrl('about-us');
* - $arr = $model->fetchAllByCategoryIdAndVisibility(5, TRUE);
*
* @param string
* @param array
* @return BaseModel|array
*/
public function __call($name, $args) {
if (strncmp($name, 'fetchBy', 7) === 0) { // single row
$single = true;
$name = substr($name, 7);
} elseif (strncmp($name, 'fetchAllBy', 10) === 0) { // multi row
$single = false;
$name = substr($name, 10);
} else {
parent::__call($name, $args);
}
// ProductIdAndTitle -> array('product', 'title')
$parts = explode('_and_', strtolower(preg_replace('#(.)(?=[A-Z])#', '$1_', $name)));
if (count($parts) !== count($args)) {
throw new InvalidArgumentException("Magic fetch expects " . count($parts) . " parameters, but " . count($args) . " was given.");
}
$class = $this->getClass();
if ($single) {
$data = $this->db->query(
'SELECT * FROM %n', $this->table,
'WHERE %and', array_combine($parts, $args),
'LIMIT 1'
)->setTypes($this->types)->fetch();
return $data ? new $class($data) : false;
} else {
$data = $this->db->query(
'SELECT * FROM %n', $this->table,
'WHERE %and', array_combine($parts, $args)
)->setTypes($this->types)->fetchAll();
if (!$data) return array();
foreach ($data as &$row) {
$row = new $class($row);
}
return $data;
}
}
}
Uvítám nějaké nápady. Sám jsem zvědav, co mi to přinese v praxi. Pokud máte někdo pocit, že je to k ničemu, tak to taky klidně řekněte, třeba se pak naučim nějaké normální ORM a budu taky spokojen :-D
- ViliamKopecky
- Nette hipster | 230
Vypadá to docela pěkně. Pár poznámek:
- Co takhle místo
__construct
udělat metoduinit
prosetType
a podobné? - Pozor na to, že
DibiConnection::getInsertId()
vyhazujeDibiException
pokudPK
neníAUTO_INCREMENT
možná by bylo fajn takto:
/** @var bool */
protected $pkAutoIncrement = TRUE;
protected function insertAsNew() {
$preparedData = $this->getPreparedData();
$this->db
->insert($this->table, $preparedData)
->execute();
if ($this->pkAutoIncrement)
$id = $this->db->getInsertId();
else
$id = $preparedData[$this->pk];
$this->pkValue = $this->pkType === dibi::INTEGER ? (int) $id : $id;
}
// Anebo bez použití $autoIncrement zachytávat Exception při getInsertId()
Co jsem zatím stihnul, tak posílám, ještě se k tomu vrátím.
- Dále metoda
save
také počítá jen sAUTO_INCREMENT
PK
Editoval enoice (25. 10. 2009 15:05)
- deric
- Člen | 93
použil bych název BaseRow
a BaseTable
, je to
z toho lépe patrné jestli třída pracuje nad celou tabulkou nebo nad jedním
řádkem.
co třeba implementovat lazy-load?
$page = new Page(1);
//nebo $page->setId(1);
echo $page->title;
ad primary key:
optimalizovaná databáze by měla mít jednoznačný identifikátor řádku,
ať už je to normální id nebo surrogate key (v oracle tuším existuje vždy
rowid). v praxi se to občas používá, ovšem implementace je trochu
krkolomná
- _Martin_
- Generous Backer | 679
deric napsal(a):
ad primary key:
optimalizovaná databáze by měla mít jednoznačný identifikátor řádku, ať už je to normální id nebo surrogate key (v oracle tuším existuje vždy rowid). v praxi se to občas používá, ovšem implementace je trochu krkolomná
Á, dobrovolník na flame=) Osobně si myslím, že složený primární klíč není známka neoptimalizované databáze. Řekněme, že používám tabulku s texty stránek a chci je mít multijazyčné. V takovém případě je vhodné použít jako primární klíč kombinaci ID stránky + ID jazyka (s tím, že jazyky mám v jiné tabulce a i stránky – sitemapu – mám jinde). Jakékoliv další ID je v té překladové stránce nadbytečný údaj.
- Honza Marek
- Člen | 1664
Moc se nám tu nehádejte. Všechno bude. Dibi je mocné. Dokonce tolik, že příští verze dibi orm se bude podobat tomuto zdrojovému kódu:
/**
* Super databázový layer
*
* @author Jan Marek
* @license MIT
*/
class DibiOrm extends Dibi
{
}
- LuKo
- Člen | 116
O nějaké formě ORM jsem už dlouho přemýšlel. Docela se mi líbí přístup zvolený v Symfony: http://www.symfony-project.org/…-Model-Layer – je to skoro stejné, jako Honzův přístup s drobným rozdílem, kdy tabulku mají reprezentovanou zvlášť třídou, kdy se záznamy získávají přes statické metody této třídy.
Místo:
<?php
$page = new Page;
$page->findByUrl("kniha-hostu");
echo $page->name;
?>
mají:
<?php
$page = PagePeer::retrieveByUrl("kniha-hostu");
echo $page->name;
?>
Osobně mi to připadá přehlednější (nad objektem konkrétního záznamu se nehledají další stejné záznamy) a když jsem kdysi se Symfony experimentoval, velmi rychle jsem si na to zvykl.
BTW: Jak to vůbec dopadlo s DibiDataSource?
- Honza Marek
- Člen | 1664
Souhlasím, že ty statické metody jsou pěkné. Ale blbě se s nima dělá v php < 5.3.
- _Martin_
- Generous Backer | 679
Prošel jsem si stránku o Symphony a jsem nadšen – tak nějak bych si představoval objektový model databáze. Ono je asi jedno, zda to jsou statické metody (byť by byly vhodnější), či zda to je instance – důležitá je ona myšlenka, že jsou zde zvlášť konkrétní záznamy (=modely) a zvlášť pomocné objekty na jejich získávání.
Jak je myšleno, že je práce se statickými metodami v PHP < 5.3 obtížná (v souvislosti s těmito „továrničkami na modely“)?
Editoval _Martin_ (7. 11. 2009 11:36)
- Jan Tvrdík
- Nette guru | 2595
_Martin_ napsal(a):
Jak je myšleno, že je práce se statickými metodami v PHP < 5.3 obtížná?
Předpokládám, že je to kvůli chybějící podpoře __callStatic + (možná) Late static bindings
- Honza Marek
- Člen | 1664
Tak to by šlo poměrně snadno obelstít přes getBy($array)
,
horší je ta absence late static bindingu. Pomocí self
nikdy
nedostaneš vlastnosti poděděné třídy, které bys potřeboval.
Odkážu na vrtákův blog: http://www.vrtak-cz.net/…keho-objektu
- Honza Marek
- Člen | 1664
Pokud se bavíme o statických třídách, tak přesně to by nešlo :) Protože pomocí klíčového slova self by se nenačetla ta předefinovaná proměnná, ale ta původní. A viz odkaz výše, ani s tím static to není tak slavné.
Editoval Honza M. (7. 11. 2009 13:09)
- LuKo
- Člen | 116
Minimum requirements
php-activerecord only works with PHP 5.3!
Jinými slovy, nemá to stejný problém, jako to, co zde řešíme?
V Symfony si s tím uměli poradit již před lety. Nebyla by cesta od
YML
struktury přes propel
generátor po
předgenerované třídy, do kterých se jen podle potřeby doplní metody
public static function getKdesiCosi()
? I když uznávám, je to
asi spíš řešení hrubou silou, než elegancí přes magické metody.
- Honza Marek
- Člen | 1664
LuKo napsal(a):
Minimum requirements
php-activerecord only works with PHP 5.3!
Jinými slovy, nemá to stejný problém, jako to, co zde řešíme?
Už ne. Vyřešili ho tím, že dali minimum PHP 5.3 :-D Když jsem koukal, co to umí, tak to ani jinak udělat nejde.
- Honza Marek
- Člen | 1664
Vývojovou verzi si můžete prohlédnout na githubu: https://github.com/…arek/dibiorm
Mám dilema ohledně metody save. Buď bude vracet boolean, jestli uložení bylo úspěšné. Anebo bude vracet $this a vyhazovat výjimky, což by možná mohlo být praktičtější.
- Ondřej Brejla
- Člen | 746
Save
se provede, výjimka se nevyhodí…save
se
neprovede, výjimka se vyhodí…je to mnohem více sexy, než trapný boolean
;-) A navíc můžeš vracet krásně $this
, jak říkáš.
Editoval Warden (8. 11. 2009 16:09)
- _Martin_
- Generous Backer | 679
Honza M. napsal(a):
Vývojovou verzi si můžete prohlédnout na githubu: https://github.com/…arek/dibiorm
Koukám na to a měl bych pár poznámek:
- Metody
insert
aupdate
? Máme přeci metodusave
(čili určitě ne public) - Metoda
findAll
,create
, atd…? Mrkni na to Symphony, myslím, že jít cestou „továrniček na modely“ a „samotné modely“ je lepší, navíc tím nebude třída modelu plněna funkcemi, které s ní nesouvisí. - Hlasuji pro výjimky, všude a vždy – nejlépe tedy nějaké vlastní, ne čistě dibi.
- Fluent interface je fajn, ač u metody
save
mám pocit, že je zbytečný (save
je takový konec, co potom s tím objektem dělat dál?) - Připojení k DB může být víc, je nešikovné celé ORM napevno svázat s jedním konkrétním připojením z configu.
Editoval _Martin_ (8. 11. 2009 18:05)
- Patrik Votoček
- Člen | 2221
Honza M. napsal(a):
Mám dilema ohledně metody save. Buď bude vracet boolean, jestli uložení bylo úspěšné. Anebo bude vracet $this a vyhazovat výjimky, což by možná mohlo být praktičtější.
- vyjímkám
- Honza Marek
- Člen | 1664
_Martin_ napsal(a):
Honza M. napsal(a):
Vývojovou verzi si můžete prohlédnout na githubu: https://github.com/…arek/dibiorm
Koukám na to a měl bych pár poznámek:
- Metody
insert
aupdate
? Máme přeci metodusave
(čili určitě ne public)
Já to zvážim. Chtěl jsem dát na výběr.
- Metoda
findAll
,create
, atd…? Mrkni na to Symphony, myslím, že jít cestou „továrniček na modely“ a „samotné modely“ je lepší, navíc tím nebude třída modelu plněna funkcemi, které s ní nesouvisí.
To rozdělení má nějaké nevýhody, přes které bych se musel přenést. Třeba nastavení typů sloupečků je věc, která je potřeba u obou. A autodetekce selhává na sloupcích boolean, protože na to v mysql není typ (bool je alias pro tinyint(1)). Takže by to vedlo částečně třeba k duplikaci kódu.
Navíc pro třídy reprezentující tabulku bych určitě zavedl statický přístup, takže by se musela podporovaná verze php omezit na 5.3.
- Hlasuji pro výjimky, všude a vždy – nejlépe tedy nějaké vlastní, ne čistě dibi.
Jasný.
- Fluent interface je fajn, ač u metody
save
mám pocit, že je zbytečný (save
je takový konec, co potom s tím objektem dělat dál?)
Třeba můžeš po uložení získat id, když je auto increment.
- Připojení k DB může být víc, je nešikovné celé ORM napevno svázat s jedním konkrétním připojením z configu.
Však jo… je tu udělané tak, aby sis metodu getDb mohl snadno přepsat.
- paranoiq
- Člen | 392
Honza M. napsal(a):
Mám dilema ohledně metody save. Buď bude vracet boolean, jestli uložení bylo úspěšné. Anebo bude vracet $this a vyhazovat výjimky, což by možná mohlo být praktičtější.
při chybě je vždy lepší výjimka, než nějaké záhadné návratové hodnoty (i když jde o boolean..)
- Ondřej Brejla
- Člen | 746
Tak si tak programuju a najednou zjišťuju, že by se mi ORM opravdu velice hodilo. Nicméně sahat po Doctrine apod. se mi opravdu nechce…takže si počkám na Honzův výtvor ;-)
Jinak rozhodně fluent + výjimky.
- romansklenar
- Člen | 655
Honza Marek napsal(a):
_Martin_ napsal(a):
- Fluent interface je fajn, ač u metody
save
mám pocit, že je zbytečný (save
je takový konec, co potom s tím objektem dělat dál?)Třeba můžeš po uložení získat id, když je auto increment.
IMHO není třeba, pokud to bude fungovat takhle:
$page = new Page;
$page->name = '...';
$page->content = '...';
echo $page->id; // vede k výjimce
$page->save();
echo $page->id; // zobrazí správně id => není důvod získávat ze save()
- _Martin_
- Generous Backer | 679
Honza Marek napsal(a):
Já to zvážim. Chtěl jsem dát na výběr.
Zvaž. Protože bych ovšem byl velmi rád za kvalitní ORM, doporučuji
ponechat jen metodu save
a nedělat z toho obal na dibi.
To rozdělení má nějaké nevýhody, přes které bych se musel přenést. Třeba nastavení typů sloupečků je věc, která je potřeba u obou. A autodetekce selhává na sloupcích boolean, protože na to v mysql není typ (bool je alias pro tinyint(1)). Takže by to vedlo částečně třeba k duplikaci kódu.
Navíc pro třídy reprezentující tabulku bych určitě zavedl statický přístup, takže by se musela podporovaná verze php omezit na 5.3.
Můj názor je, že návrh by měl být implementaci podřizován co nejméně – nejlépe vůbec. Ono se vždy nějaké řešení najde.
Třeba můžeš po uložení získat id, když je auto increment.
Jo, ale logické to moc není. Hezky to napsal Roman.
Však jo… je tu udělané tak, aby sis metodu getDb mohl snadno přepsat.
OK, to je pravda. A možná by se časem dal vymyslet nějaký elegantnější způsob.
romansklenar napsal(a):
$page = new Page; $page->name = '...'; $page->content = '...'; echo $page->id; // vede k výjimce $page->save(); echo $page->id; // zobrazí správně id => není důvod získávat ze save()
Otázka, zda výjimka či NULL. Každopádně se mi to líbí=)
- Honza Marek
- Člen | 1664
Možná by se daly udělat výjimky pro neexistující sloupce. Ale null jako nenastaveno mi přijde naprosto v pořádku.
- Ondřej Brejla
- Člen | 746
A co třeba takto:
Pokud přistupuju k hodnotě primárního klíče, která ještě nebyla
nastavena, pak je to chyba a vyhazoval bych třeba
UndefinedValueException
. Pokud přistupuju k hodnotě sloupce,
který je definován jako NOT NULL
a také nemá nastavenou hodnotu
(v podstatě stejný příklad jako s PK), zase je to chyba a opět bych
vyhazoval výjimku UnknownValueException
, pokud přistupuju
k nenastavené hodnotě sloupce, který je definován jako NULL
,
pak je vše v pořádku a pak vracím NULL
.
- LuKo
- Člen | 116
Ještě to trochu zkomplikuji. $page->keywords;
může být
prázdný, ale v DB ho mám kvůli indexům nastaven na NOT NULL
.
Výjimku bych asi vyhodil jen u PK, byla by to jen pojistka. Výjimku (asi
také?) bude vyhazovat save()
v případě, že se po provedeném
insertu nepodaří získat hodnotu PK. Při vkládání PK do FK v jiné
tabulce musí být kontrola na správnost vkládané hodnoty. Nějak se nám to
komplikuje ;-)
- Ondřej Brejla
- Člen | 746
„Prázdnost“ sloupce a NOT NULL
omezení spolu
nesouvisí…pokud máš sloupec prázdný, pak je v něm hodnota (protože
není NULL
)…pokud do VARCHAR
u uložim
''
, pak je v něm hodnota, prázdná a přitom sloupec dodržel
NOT NULL
omezení.
Já popisuji stav, kdy v NOT NULL
sloupci ještě není
uložená ani ta „prázdná“ (DEFAULT
?) hodnota…pak by
přístup k této „nedefinované“ hodnotě měl vyhazovat
UnknownValueException
. Snad si rozumíme ;-)
- LuKo
- Člen | 116
Omlouvám se, sypu si popel na hlavu, špatně jsem používal odporné
termity ;-) Měl jsem na mysli nedefinovanou hodnotu = NULL
.
Například:
<?php
$page = new Page;
echo $page->keywords; // v DB nastaveno NOT NULL
?>
by i bez uložení či načtení prázdného řetězce z DB nemělo vyhodit výjimku. Jednoduše by to do stránky nemělo nic vypsat. Jde to sice řešit přes default values
<?php
class Page
{
private $keywords = '';
// ...
?>
ale to mi kdysi jeden zkušenější programátor z kódu důsledně mazal, že je to chyba :-/
- Ondřej Brejla
- Člen | 746
Naopak si myslím, že vracení jakési DEFAULT
hodnoty,
v případě, že je proměnná sloupce ještě nenastavena a přitom je
definována jako NOT NULL
, je ta správná cesta (ať už je ono
DEFAULT implementováno jakkoliv).
$page = new Page;
echo $page->keywords; // v DB nastaveno NOT NULL, ale v ORM mám definovanou defalut hodnotu: vrátí se DEFAULT hodnota
echo $page->id; // v DB je PK: vyhodí UnknownValueException, protože PK ještě nebyl nastaven
echo $page->login // v DB nastaveno NOT NULL: vyhodí UnknownValueException, protože ještě nebyl nastaven
echo $page->name // v DB nastaveno NULL: vrátí NULL
Editoval Ondřej Brejla (9. 11. 2009 13:04)
- paranoiq
- Člen | 392
Ondřej Brejla napsal(a):
Pokud přistupuju k hodnotě primárního klíče, která ještě nebyla nastavena, pak je to chyba a vyhazoval bych třeba
UndefinedValueException
. Pokud přistupuju k hodnotě sloupce, který je definován jakoNOT NULL
a také nemá nastavenou hodnotu (v podstatě stejný příklad jako s PK), zase je to chyba a opět bych vyhazoval výjimkuUnknownValueException
, pokud přistupuju k nenastavené hodnotě sloupce, který je definován jakoNULL
, pak je vše v pořádku a pak vracímNULL
.
a jak rozliším, zda bylo NULL načteno ze sloupce, který může obsahovat
NULL, nebo zda jde o doposud nenačtený záznam z databáze? i když může
sloupec obsahovat NULL, není jisté zda ho opravdu obsahuje dokud a) nenaplním
nový záznam daty nebo b) nenačtu záznam z DB. takže bych výjimku
vyhazoval vždy. u záznamu, který dosud nebyl nějak
inicializován InvalidStateException
vlastně ne. existenci neinicializovaného záznamu bych raději vůbec nepřipustil. nový záznam by měla daty naplnit továrnička
- Ondřej Brejla
- Člen | 746
Teď nevím, jestli ti přesně rozumím. Můžeš to pro jistotu ukázat na nějakém příkladu?
EDIT:
Dosud nenačtený záznam z DB může buď vyhodit výjimku, nebo vrátit
NULL
. Který sloupec je definován jako NULL
, nebo
NOT NULL
by mělo být dané z nějaké definice tabulky, se
kterou pracuji (tedy ve třídě reprezentující tabulku, se kterou
pracuji).
Příklad s prázdným objektem:
$page = new Page(); // $page je prázdný objekt...žádná data z db
echo $page->name; // v Page je $name definováno jako NOT NULL, a proto vyhodím vyjímku, protože $page je prázdné
$page->name = 'John Doe';
echo $page->name; // $name je NOT NULL a už MÁ hodnotu, vracím tedy 'John Doe'
echo $page->age; // v Page je $age definováno jako NULL, a proto vrátím NULL, protože $page je prázdné
$page->age = 30;
echo $page->age; // přestože je v Page $age definováno jako NULL, vracím 30, protože už má atribut hodnotu
Příklad s naplněným objektem:
$page = new Page($id); // $page je plný objekt, podle $id načtu hodnoty z db (nebo ho naplním jinak, to je jedno)
echo $page->name; // v Page je $name definováno jako NOT NULL: vrátím načtené jméno, pokud bylo nalezeno, nebo vyhodím vyjímku, pokud DB vrátila NULL (což by neměla, pokud je i sloupec v DB definován jako NOT NULL)
echo $page->age; // v Page je $age definováno jako NULL: vrátím to, co bylo v DB nalezeno...buď věk, a pokud nalezen nebyl, vrátím NULL
Je to trochu srozumitelné?
Editoval Ondřej Brejla (9. 11. 2009 14:41)
- _Martin_
- Generous Backer | 679
paranoiq napsal(a):
vlastně ne. existenci neinicializovaného záznamu bych raději vůbec nepřipustil. nový záznam by měla daty naplnit továrnička
Nezůstaneme rovnou u dibi::insert
?
Tady přece není nic jako „neinicializovaný“ záznam. Mám objekt. Ten nějak nastavím. A pokud uznám za vhodné, tak jej uložím. Uložené objekty mohu znovu vyvolat.
Je to jak s listem papíru. Vemu prázdnej list a něco na něj napíšu. Napsal jsem blbost – ani ho neuložím do zásuvky a rovnou ho vyhodím. A nebo ho uložím. A později ho vyndám.
Jasné?
ID jako NULL mi přijde v pořádku – prakticky znamená, že záznam nebyl uložen.
Editoval _Martin_ (9. 11. 2009 14:37)
- paranoiq
- Člen | 392
@ondřej et @martin: máte pravdu. pokud může být pole NULL, tak není nutné vyhazovat výjimku (u nepovinného pole nelze určit zda jde o chybu). chybu programátora tak bohužel ošetřit nejde. může si objekt nakrásně uložit a nepovinná data dodat později
@martin:
ID jako NULL mi nepřijde v pořádku – svědčí to o tom, že čtu něco
o čem netuším, jestli je to nastaveno – nemám vlastně pod kontrolou tok
programu. pokud jsem ještě objekt neuložil, měl bych to vědět, nebo bych
to měl zjisti nějakým vhodným způsobem – např. metodou
isSaved()
. pokud musím při každém načtení hodnoty
kontrolovat, jestli není návratová hodnota nějaká magická konstanta
(v tomhle případě NULL), určitě to není dobré API a určitě na tu
kontrolu někde zapomenu (=BUG)
o tom zahazován rozdělaných objektů si myslím tohle: primárním
účelem ORM je ukládání (a načítání) dat. pokud jsem nový objekt za
nějakým účelem rozdělal nebo pozměnil a záměrně neuložil je
to v pořádku, ale měl bych to říci explicitně (např.
$nejakyObjekt->discard()
). jinak může dojít k tomu, že ho
mohu neuložit omylem. šlo by to možná ošetřit vyhazováním warningu
v destruktoru (při neuložení). stejně tak bych od databáze očekával, že
když mám nepotvrzenou transakci, tak mi při ukončení spojení pošle
nějaký warning. tiché zahození nepotvrzených dat (bez
ROLLBACK
) může být chyba. to by šlo ošetřit warningem
v destruktoru DibiConnection nebo při ručním odpojení.
Editoval paranoiq (9. 11. 2009 17:57)
- _Martin_
- Generous Backer | 679
paranoiq napsal(a):
@martin:
ID jako NULL mi nepřijde v pořádku – svědčí to o tom, že čtu něco o čem netuším, jestli je to nastaveno – nemám vlastně pod kontrolou tok programu. pokud jsem ještě objekt neuložil, měl bych to vědět, nebo bych to měl zjisti nějakým vhodným způsobem – např. metodouisSaved()
. pokud musím při každém načtení hodnoty kontrolovat, jestli není návratová hodnota nějaká magická konstanta (v tomhle případě NULL), určitě to není dobré API a určitě na tu kontrolu někde zapomenu (=BUG)
Naopak, NULL znamená, že to není nastaveno. Co na tom, že jde o hodnotu,
která se nastavuje až při uložení? Kdybych měl políčko
lastSavedDate
, tak by před uložením taky nemělo házet
výjimku. Proč taky? Přeci každé čtení dat neošetřím try – catch
blokem.
A asi si nerozumíme v jedné věci: to, že z ID = NULL lze odvodit, že
záznam nebyl uložen, neznamená, že z ID = NULL mám zjišťovat
(ne)uložení záznamu. O žádných magických konstantách nemůže být
řeč. Jinak metoda isSaved
je dobrej nápad.
o tom zahazován rozdělaných objektů si myslím tohle: primárním účelem ORM je ukládání (a načítání) dat. pokud jsem nový objekt za nějakým účelem rozdělal nebo pozměnil a záměrně neuložil je to v pořádku, ale měl bych to říci explicitně (např.
$nejakyObjekt->discard()
). jinak může dojít k tomu, že ho mohu neuložit omylem. šlo by to možná ošetřit vyhazováním warningu v destruktoru (při neuložení). stejně tak bych od databáze očekával, že když mám nepotvrzenou transakci, tak mi při ukončení spojení pošle nějaký warning. tiché zahození nepotvrzených dat (bezROLLBACK
) může být chyba. to by šlo ošetřit warningem v destruktoru DibiConnection nebo při ručním odpojení.
Tak to se neshodneme, já si zase myslím, že explicitně bych měl potvrzovat změnu a ne čekat, že se bude objekt automaticky ukládat. V případě nepotvrzené transakce, na kterou nebyl zavolán rollback, na tom asi něco je – ovšem transkace je způsob uložení, takže to nelze srovnávat se samotným uložením.
Jinými slovy: samotná změna property není akce.
Z nás by měl Martin Malý radost…
- Honza Marek
- Člen | 1664
paranoiq napsal(a):
měl zjistit nějakým vhodným způsobem – např. metodouisSaved()
.
$object->getState() === BaseModel::STATE_LOADED
- Honza Marek
- Člen | 1664
_Martin_ napsal(a):
Z nás by měl Martin Malý radost…
Já z vás mám taky radost :-D Nakonec to zřejmě udělám všechno podle sebe, tedy podle zásady, že nejjednodušší řešení je to nejlepší.