Archive

Archive for the ‘Zend Framework’ Category

Serwer do konwersji plików multimedialnych

January 25th, 2009 4 comments

Chciałbym zaprosić do nowego projektu mojego kolegi Łukasza Krajewskiego służącego do konwersji formatów multimedialnych i nie tylko…

Budując strony Internetowe, często chcemy wzbogacić je o możliwość wgrywania filmów przez uczestników. Wgrywanie filmów (tak samo jak wgrywanie zdjęć) wymaga wykonania pewnych czynności po stronie serwera związanych z konwersją filmu do formatu internetowego, na przykład do FLV lub H264. Niestety, konwersja filmów jest skomplikowana. Po pierwsze, jest czasochłonna – czas jej trwania jest zależny od długości filmu i może trwać od kilku sekund do wielu godziny. Po drugie, wymaga zainstalowania na serwerze specjalnego oprogramowania które potrafi odczytać jak najwięcej formatów wideo i które potrafi zapisywać do formatów internetowych. Oprogramowanie to instaluje się dość ciężko i przeważnie wymaga dedykowanego serwera lub przynajmniej VPS. Właśnie dlatego pisanie aplikacji umożliwiających użytkownikom wgrywanie multimediów jest trudne. Projekt Multimedia Conversion ma to uprościć.

Projekt Multimedia Conversion (MC) jest darmowy i dostępny na licencji GPL. Wykorzystuje technologie PHP, Zend Framework i FFmpeg. Multimedia Conversion to usługa sieci web. W skrócie działa to tak: aplikacja która chce konwertować filmy, musi wykonać metodę na serwerze MC. Serwer MC poinformuje aplikację o statusie konwersji. Odsyłam do Wiki po bardziej szczegółowy opis.

Na chwile obecną dostępne są konwersje filmów do FLV, H264 i 3GP oraz konwersja muzyki do formatu MP3. Dodatkowo, podczas konwersji do formatów wideo wykonywana jest miniaturka – czyli klatka ze środka filmu. Nowe formaty dodawać łatwo jest (jak by to powiedział Yoda).

Gol projektu to wyizolowanie skomplikowanego procesu konwersji filmów i zamknięcie go na osobnym serwerze. Taki serwer będzie łatwiejszy w administracji. Jest to szczególnie ważne, gdy w firmie powstaje wiele aplikacji i potrzebują one automatycznej konwersji materiałów video. Dzięki serwerowi MC ich wdrożenie będzie tańsze. Aplikacje multimedialne nie będą wymagały instalowania skomplikowanego oprogramowania do konwersji filmów. Skomplikowana konwersja filmów zostanie wykonana na wydzielonym serwerze – MC. Aplikacje multimedialne które używają MC będzie można uruchamiać na zwyczajnym – tanim hostingu.

Zainteresowanym osobom mogę udostępnić serwer MC do testowania.

Tak się fajnie składa, że wiem co nieco na temat planów rozwoju aplikacji MC. Plany są takie.: aplikacja MC nie będzie służyła wyłącznie do konwersji formatów wideo, ale ma działać jako aplikacja do montażu filmów video. Czy na przykład mamy kilka filmów, wycinamy z nich fragmenty i montujemy w nowy film. Trzymam kciuki Łukasz!!!

 

 

 

 

Categories: PHP, Programowanie, Zend Framework Tags:

Zend Framework i keszowanie zapytań do bazy danych

June 23rd, 2007 3 comments
Witam, Od pewnego czasu programiści PHP naśladują programistów Javy, sam zresztą to robię. Efektem tego jest przenoszenie wzorców projektowych z Javy do PHP. Na przykład Zend Framework a w szczególności jego kod odpowiedzialny za bazy danych – Zend_Db (działający prawie jak ORM) przypomina w pewien sposób to, za co świat Javy pokochał Hibernate. Konsekwencją użycia ORM w projekcie jest to, że spada nam złożoność zapytań do bazy danych, natomiast zwiększa się ich ilość. Hibernate potrafi keszować obiekty. Zend Framework niestety nie. Jednak wczoraj tego bardzo potrzebowałem, renderowanie prostej strony WWW wymagało 500 zapytań do bazy, grrr, z czego 450 było powtórzeniem poprzednich zapytań. O ile keszowanie obiektów było by bardzo skomplikowane, to w miarę prosto można było keszować wyniki zapytań SQL w obrębie jednego żądania HTTP. Jak się do tego zabrać? Zauważyłem, że wszystkie zapytania do bazy danych produkowane przez Zend Framework w pewnym momencie trafią do metody query obiektu Zend_Db_Adapter_*, fajnie:). Naszą klasę keszującą trzeba odziedziczyć po tej klasie. Oczywiście można to zrobić na wiele innych sposobów, i bardzo ładnie wpasować w to wzorzec projektowy dekorator, ale ja nie miałem czasu. Modyfikujemy metodę query, tak by zapamiętywała swoje wyniki i trzymała w keszu. U mnie działa coś takiego (wstydził bym się tego kodu, gdyby nie to że robi coś na prawdę przydatnego):
class JakubiakDbAdapterPdoPgsql extends Zend_Db_Adapter_Pdo_Pgsql{
  public function query($sql, $bind = array()){
    $sql = $sql . ""; // a sprobuj no bez tego
    $hash = $this->_calculateHash($sql,$bind);
    // czy uda nam sie pobrac z kesza
    if($this->_isCacheEnabled) {
      if($this->_allowCache($sql,$bind)) {
        if(isset($this->_cachedResults[$hash])) {
          return $this->_cachedResults[$hash];  
        } 
      }
    }
    $res = parent::query($sql, $bind);
    // dopisanie do kesza
    if($this->_isCacheEnabled) {
      $this->_cachedResults[$hash] = $res;
    }
    return $res;
  }
  // pozwalam na keszowanie tylko zapytan rozpoczynajacych sie od select
  private function _allowCache($sql,$bind) {
    return preg_match("/^select/i",$sql));
  }
  private function _calculateHash($sql,$bind) {
    return md5($sql.var_export($bind,true));
  }
  [...]
}
Ten kod omal nie zadziała. Jednak trzeba zrobić jeszcze parę myków. Przeciążyć konstruktor naszego adaptera.
  public function __construct($config) {
    parent::__construct($config);
    $this->getConnection()->setAttribute(PDO::ATTR_STATEMENT_CLASS, array('JakubiakDbStatement', array($this)));
  }
I dopisać taką oto koszmarną klasę:
class JakubiakDbStatement extends PDOStatement {
  $_cache = null;
  public function fetchAll($how, $class_name=null, $ctor_args=null) {
    if(!empty($this->_cache)) {
      return $this->_cache;
    }
    $this->_cache = parent::fetchAll($how);
    return $this->_cache;
  }
}
Ten kod, ma prawo nie działać i zawiera błędy. Jednak u mnie działa. I to dobrze działa. Zamiast 500 zapytań do bazy mam ich 50. Serwer jest mi za to wdzięczny, nie trzeba już oliwić wentylatorka od chłodzenia;). PS. do testów użyłem starej wersji Zend_Framework – 0.9.1.
Categories: ORM, PHP, PostgreSQL, Zend Framework Tags:

Zend Framework 1.0.0 RC1 && Zend_Db_Statement

June 8th, 2007 No comments
Zainteresowała mnie dziś nowa wersja Zend Framework 1.0.0RC1 – i to był błąd. Ktoś wpadł na pomysł przepisania klasy Zend_DB_Statement, stworzono nową klasę: Zend_Db_Statement_Pdo i z nią właśnie jest problem. Jednak nie do końca z nią a bardziej z PDO. Wcześniej klasa Statement implementowała metodę bindParam jako wyrażenie regularne podmieniające stringi – o dziwo, to mi działało. Nowa implementacja bindParam deleguje metodę do obiektu PDO. Tymczasem ta metoda klasa PDO (w moim PHP 5.2.1) nie działa: SQLSTATE[HY093]: Invalid parameter number: no parameters were bound. Wniosek, na razie będę dalej korzystam z starej wersji biblioteki.
Categories: PHP, Zend Framework Tags:

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: