Home > ORM, PHP, PostgreSQL, Zend Framework > Zend Framework i PostgreSQL aka ORM

Zend Framework i PostgreSQL aka ORM

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.
Categories: ORM, PHP, PostgreSQL, Zend Framework Tags:
  1. No comments yet.
  1. No trackbacks yet.

Subscribe without commenting