Archive

Archive for April, 2007

Google Calendar i Zend Framework

April 28th, 2007 No comments
Witam, Dziś pokażę, jak można używać Google Calendar i Zend Framework do monitorowania czasu pracy nad projektem. W kalendarzu od Googla mogę zapisywać co robię i ile czasu mi to zajmuje. Używając Frameworka od Zenda mogę te dane odczytać. A więc:
$client = new Zend_Http_Client();
$gdataCal = new Zend_Gdata_Calendar($client);
$gdataCal->setUser('user@gmail.com');
$gdataCal->setVisibility('private-XXXXX'); // to znajdziesz w ustawieniach kalendarza
$gdataCal->setProjection('full-noattendees');
$gdataCal->setMaxResults(100); // na szybko,
$eventFeed = $gdataCal->getCalendarFeed();
$total = 0;
foreach ($eventFeed as $event) {
  $title = $event->title();
  $when = $event->{'gd:when'};
  if(!preg_match('/^AAA/',$title)) continue; // literał, identyfikujący mój projekt
  $start = strtotime($when['startTime']);
  $end = strtotime($when['endTime']);
  $interval = $end - $start;
  $total += $interval;
  echo date("Y-m-d",$start)." ".date("H:i",$interval)." - $title";
}
$totalHours = date('z',$total)*24 + date('H',$total);
echo "Total: $totalHours" . date(":i", $total);
Proste, co nie? Na wyniku jest odpowiedź, zawierająca łączną liczbę godzin spędzoną w pracy nad AAA.
Categories: PHP, Zend Framework Tags:

Zend Framework, zachciało mi się HQL’a

April 22nd, 2007 1 comment
Zend Framework – framework, o którym ostatnio sporo pisałem. HQL język używany w Hibernate, będący połączeniem ciemności świata relacji i światłości obiektów. HQL było inspiracją dla JPQL , dzięki czemu stanie się standardem przemysłowym. HQL jest fajny, bo można pisać na przykład tak:
from Movie m where m.media.user.name='Antek';
zamiast dłubać w SQL:
select m.* from movie v 
join media m on m.mediaid=v.mediaid 
join user u on u.userid=m.userid 
where user.name='Antek';
Niestety, Zend Framework nie posiada czegoś takiego i nie zanosi się na to. Ja, jednak klepnąłem świniaka, dzięki któremu mogę pisać podobnie. Zmodyfikowałem klasę: Zend_Db_Table ^ JakubiakDbTable. Nowy kod to:
protected function _fetch($where = null, $order = null, 
$count = null, $offset = null) {
  $select = $this->_db->select();
  $select->from($this->_name, $this->_cols);
  $where = (array) $where;
  foreach ($where as $key => $val) {
    if (is_int($key)) {
      $select->where($val);
    } else {
      // drobna modyfikacja rodzica
      $key = $this->refJoin($select,$key);
      $select->where($key, $val);
    }
  }
  // dalej tak jak u rodzica[...]
}
public function refJoin($select, $where) {
  foreach($this->_referenceMap as $key => $ref) {
    $regExp = "#^$key\.#";
    if (preg_match($regExp,$where)) {
      $refDao = new $ref['refTableClass'];
      $on = "";
      foreach($ref['columns'] as $i => $colName){
        if($i>0) $on .= ' and ';
        $on .= ' ';
        $on .= $this->getTableName() . '.' . $colName . '=';
        $on .= $refDao->getTableName() . '.' .$ref['refColumns'][$i] . ' ';
      }
      $select->join($refDao->getTableName(), $on,array());
      $where = preg_replace($regExp,"",$where);
      return $refDao->refJoin($select,$where); 
    }
  }
  return $this->getTableName().'.'.$where;
}
public function getTableName() {
  return $this->_name;
}
Kod powyżej działa, ale jest zły. Nie używaj go. Ja mogę go używać tak:
$movieTable = new MovieTable();
$movies = $movieTable->fetchAll(array("media.user.name=?"=>$_REQUEST['username']));
Czyli, prawie, prawie jak HQL.
Categories: ORM, PHP, Zend Framework Tags:

Zend Framework i PostgreSQL aka ORM

April 21st, 2007 No comments
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:

Zend Framework – Zend_Db_Table

April 9th, 2007 1 comment
Krytykowałem już Zend Framework za zmienność. Dziś go pochwalę. Bardzo podoba mi się Zend_Db_Table. Jest to proste, ale prawdziwe OR/M. W dodatku, żeby go używać nie trzeba się sporo napisać i nie trzeba się wiele uczyć. Aktualizacja: 2007-04-10. Cholera! Jest sobie funkcja Zend_Db_Table_Abstract::insert() – ale nie działą dla PDO_PGSQL. Kod źródłowy, sprawia wrażenie nigdy nie testowanego. W dodatku jest nie poprawny merytorycznie. Aż zacytuje:
    public function insert(array $data)
    {
        $this->_db->insert($this->_name, $data);
        return $this->_db->lastInsertId();
    }
    public function lastInsertId($tableName = null, $primaryKey = 'id')
    {
        if (!$tableName) {
            throw new Zend_Db_Adapter_Exception("Sequence name must be specified");
        }
Zend Framework jest fajny bo nowatorski. Niestety, jest jeszcze w stadium mocno rozwojowym. Ten błąd na szczęście mogę poprawić w swojej aplikacji bez modyfikacji kodu źródłowego bibliotek Zend. Ale ile takich błędów jeszcze znajdę?
Categories: ORM, PHP, Zend Framework Tags:

Zend Framework – jeszcze nie teraz

April 7th, 2007 No comments
Witam! Do jednego z projektów które realizuje zachciało mi się używać Zend Framework. Pożałowałem tego już nie jeden raz. Ale od początku. Kolega polecił mi Zend Framework. Zend Framework to zbiór bibliotek PHP5, które mają się przydać programiście podczas tworzenia aplikacji WWW. Zend Framework kojarzę po nazwie z firmą, która tworzy PHP – to powinno gwarantować jakość. Niestety tak nie jest. Zend Framework klepany jest bez mocnego przemyślenia. Świadczą o tym zmiany, jakie zachodzą pomiędzy kolejnymi wersjami tego szkieletu aplikacji. Kolejne wersje nie są kompatybilne wstecz, a dokument pomocy pisane dla starszych wersji nie są przydatne w wersji aktualnej. Miarka się przebrała, gdy doszedłem do klasy Zend_Translator. W dokumentacji jest obietnica tworzenia własnych rozszerzeń tej klasy, tymczasem jej błędna implementacja zabrania tego. Wspomnę tylko, że błędna implementacja tłumaczeń opartych na plikach pseudo CSV uniemożliwiła mi ich wykorzystanie. Spróbowałem napisać własną implementację plików CSV i podpiąć ją pod translatora, ale się to nie dało. Tak skończyła się moja cierpliwość. Nie po to mam framework, żeby go patchować. Zend Freamework 0.9.2 Beta jest bardziej Beta niż 0.9. Jestem pewny, że kolejne edycje nie będą kompatybilne wstecz. Uważam że Zend Framework to dobry pomysł, który idzie w złym kierunku. Programiści starają się dostarczyć coraz to nowych funkcjonalności, zamiast zrobić dobrze te, które już są i są podstawą framework. We frameworku nie muszę mieć implementacji np “Audioscrobbler”, zamiast tego chciałbym po prostu działający i dający się rozbudować moduł tłumaczeń. Na chwilę obecną odradzam użycie Zend Framework jako szkieletu aplikacji. A ja wracam do pracy, niestety, bo muszę napisać własny moduł tłumaczeń.
Categories: PHP, Zend Framework Tags: