Proponuje kilka moich poprawek do
Zend Framework 0.9.2. Dzięki temu praca z tą biblioteką jest jeszcze przyjemniejsza. Wersja 0.9.2 ma parę niedociągnięć, jednak można je poprawić poprzez dziedziczenie.
Zacznę od klasy
Zend_Db_Adapter_Pdo_Pgsql PostgreSQL.
Pisałem już o niej. Proponuje następujące ulepszenia:
class JakubiakDbAdapterPdoPgsql extends Zend_Db_Adapter_Pdo_Pgsql{
public function query($sql, $bind = array()){
// poprawienie błędu dla typu Boolean
if(is_array($bind)){
foreach($bind as $k => $v) {
if(is_bool($v)) {
$bind[$k] = $v ? 't' : 'f';
}
}
}
return parent::query($sql, $bind);
}
public function lastInsertId($tableName = null) {
// Jeżeli sekwencja nazywasz inaczej niż domyślnie, to prawdopodobnie trzeba
// będzie nadpisać też tą funkcję, u mnie wygląda ona tak
// sekwencje dla kluczy głównych nazywam {nazwa_tabelki}_seq
if (!$tableName) {
throw new Zend_Db_Adapter_Exception("Sequence name must be specified");
}
$this->_connect();
$sequenceName = "{$tableName}_seq";
$sequenceName = preg_replace('/_+/','_',$sequenceName);
return $this->_connection->lastInsertId($sequenceName);
}
Teraz zabawię się z klasą która reprezentuje tabelę
Zend_Db_Table. Jest ona podwaliną
ORM, dlatego mi się podoba:
class JakubiakDbTable extends Zend_Db_Table {
// chcę, aby wszystkie wiersze w tabelkach były reprezentowane przez
// obiekty tej klasy
protected $_rowClass = 'JakubiakDbTableRow';
// następnie poprawiam błąd - lastInsertedId
public function insert(array $data) {
$this->_db->insert($this->_name, $data);
return $this->_db->lastInsertId(empty($this->_seqName)
? $this->_name : $this->_seqName);
}
// przydatna funkcja do zliczania wierszy w tabeli
public function count(array $where = array()) {
$select = $this->_db->select();
$select->from($this->_name, array('count'=>'count(*)'));
$where = (array) $where;
foreach ($where as $key => $val) {
if (is_int($key)) {
$select->where($val);
} else {
$select->where($key, $val);
}
}
$select->limit(1, 0);
$stmt = $this->_db->query($select);
$data = $stmt->fetch();
return empty($data['count']) ? 0 : intval($data['count']);
}
// pobieranie nowej encji
public function fetchNew() {
$newRow = parent::fetchNew();
$newRow->loadDefaults();
return $newRow;
}
// to się jeszcze przyda, aczkolwiek wydaje mi się, że ta funkcja trafi prędzej czy
// później do core
public function getReferenceMap($key){
if(empty($this->_referenceMap[$key])){
return null;
}
return $this->_referenceMap[$key];
}
}
I jeszcze klasa która reprezentuje encje
Zend_Db_Table_Row – lub jak kto woli – wiersz w tabeli:
class JakubiakDbTableRow extends Zend_Db_Table_Row {
// czy jesteśmy nową encją?
private $_isNew = false;
// wczytanie domyślnych wartości, na podstawie metadanych zapisanych w tabeli
public function loadDefaults() {
$db = $this->_getTable()->getAdapter();
$info = $this->_getTable()->info();
foreach($info['metadata'] as $col => $meta){
if(empty($meta['DEFAULT'])) continue;
$default = $meta['DEFAULT'];
$one = $db->query("select $default as def")->fetch();
$this->_data[$col] = $one['def'];
}
$this->_isNew = true;
}
// zapisywanie rekordu do bazy, kod prawie taki sam jak w klasie nadrzędnej,
// ale wykorzystuje prywatną zmienną isNew
public function save() {
$keys = $this->_getPrimaryKey();
$values = array_filter($keys);
if ($this->_isNew) {
$this->_insert();
$result = $this->_getTable()->insert($this->_data);
if (is_numeric($result)) {
$this->_data[key($keys)] = $result;
$this->_refresh();
}
} else {
$where = $this->_getWhereQuery(false);
$this->_update();
$depTables = $this->_getTable()->getDependentTables();
if (!empty($depTables)) {
$db = $this->_getTable()->getAdapter();
$pkNew = $this->_getPrimaryKey(true);
$pkOld = $this->_getPrimaryKey(false);
$thisClass = get_class($this);
foreach ($depTables as $tableClass) {
Zend_Loader::loadClass($tableClass);
$t = new $tableClass(array('db' => $db));
$t->_cascadeUpdate($this->getTableClass(), $pkOld, $pkNew);
}
}
$result = $this->_getTable()->update($this->_data, $where);
if (is_int($result)) {
// update worked, refresh with data from the table
$this->_refresh();
}
}
return $result;
}
// pobieranie many to one w bardziej intuicyjny sposób
public function __get($key) {
$r = $this->_getTable()->getReferenceMap($key);
if (!empty($r)) {
// tu można dopisać loader dla klasy
return $this->findParentRow($r['refTableClass'],$key);
}
return parent::__get($key);
}
// domyślnie setter zabrania zmieniać wartości klucza głównego,
// ale czasami chcę to robić, kod prawie taki sam jak rodzica
public function __set($key, $value) {
if (!$this->_isNew && in_array($key, $this->_primary)) {
require_once 'Zend/Db/Table/Row/Exception.php';
throw new Zend_Db_Table_Row_Exception("Changing the primary key value(s) is not allowed");
}
if (!array_key_exists($key, $this->_data)) {
require_once 'Zend/Db/Table/Row/Exception.php';
throw new Zend_Db_Table_Row_Exception("Specified column \"$key\" is not in the row");
}
$this->_data[$key] = $value;
}
Po tej zabawie, mogę używać bibliotek na przykład tak:
$fileTable = new FileTable();
$fileRow = $fileTable->fetchNew($_FILES[$filename]);
$fileRow->save();
$mediaTable = new MediaTable();
$mediaRow = $mediaTable->fetchNew();
$mediaRow->mediatitle = $request->getParam('mediatitle');
$mediaRow->mediadescription = $request->getParam('mediadescription');
$mediaRow->fileid = $fileRow->fileid;
$mediaRow->userid = UserTable::getFromAuth()->userid;
$mediaRow->save();
$photoTable = new PhotoTable();
$photoRow = $photoTable->fetchNew();
$photoRow->mediaid = $mediaRow->mediaid;
$photoRow->photowidth = $width;
$photoRow->photoheight = $height;
$photoRow->save();
echo $photoRow->media->file->fileid;
Miodzio? Nie chcę wracać do czasów, gdy nie znałem ORM. Czuję się prawie jak w
JPA.
Acha, nie zapomnij o przeczytaniu
dokumentacji Zenda bo jest bardzo dobra.