GANCHIKU.com

Services_YouTubeを使ったサンプルをまた作った。

2006年9月5日

つーか、サンプル作らずに、サービス作れってな。

先日、Webを徘徊していたら、PHP Proなるページを発見した。と言いつつ、よくよく考えれば、会員だったような気がしないでもない。で、「YouTube APIを用いたシンプルなPHPスクリプトのサンプル」というのを発見した。Services_YouTubeを作った身としては読んでおくか、と思って見てみたところ、これってトンデモな記事じゃねーか。と気づく。面倒なので突っ込まないが、これを選んだ判断基準はなんなんだろうなぁ。で、Services_YouTubeを使って、もう少しマシな感じで書いてみる。つか、最近力業でプログラムを書いていないので、こういうページを作るのにどうしようか、悩む。。。

ところで、記事にはYouTubeのAPIの「優れたpagenation機能」とあるが、私はこのページャの機能にウンザリだ。
YouTubeのAPIにはタグの検索のときだけ(youtube.videos.list_by_tag)ページングをすることができる。

ここ参照

パラメータにpageとper_pageってのをあたえると、よろしくper_page分のpage目のタグを付けられた映像のXMLを取得してきてくれるのである。で、私はこの機能にウンザリと書いたが、ページャの機能があるくせに、全部で何件あるか知る方法がないのだ。おかげで、nextリンクをつけたらいいのか、どうなのかよくわからん。勝手に4ページ目って飛んだところでそんなものあるかどうかもわからんし。でも、確かにせっかくページャの機能があるのだから、next,prevくらいは出したいなー、と思い、やってみた。

list_by_tagで、一件多くyoutubeに問い合わせ、ちゃんと返ってきたら、一件引いて、表示してnextリンクを付けるってな感じにした。そうじゃなきゃ、nextがあるかどうかわからんから。

これで大体同じ機能じゃね?つーか、タグをスペースで区切っているところとか、キャッシュを使えるところがあるので、多機能か。でも、自分のソース汚いな。。なんにも考えずに、もう少しキレイに書けたらいいなー。おそらく、videoの内容を表示しているところは別クラスにした方がいいような気もするし。まぁ、サンプルだし。。。つか、デザパタ勉強しよ。きっとデザパタ知ってたら、もっとマシなコード書けそう。



<?php
require_once 'Services/YouTube.php';

class YouTubeTutorial
{
    protected 
$page     1;
    protected 
$perPage  25;
    protected 
$tag      'guitar';

    private $devId      null;
    private 
$script     null;
    private 
$useDetails null;

    // {{{public
    
public function __construct($devId$useDetails false)
    {
        if (
array_key_exists('page'$_REQUEST) and is_numeric($_REQUEST['page'])) {
            
$this->page = (int)$_REQUEST['page'];
        }
        if (
array_key_exists('per_page'$_REQUEST) and is_numeric($_REQUEST['per_page'])) {
            
$this->perPage = (int)$_REQUEST['per_page'];
        }
        if (
array_key_exists('tag'$_REQUEST)) {
            
$this->tag strip_tags($_REQUEST['tag']);
        }

        $this->devId      $devId;
        
$this->useDetails $useDetails;
        
$this->script     basename($_SERVER['SCRIPT_NAME']);
    }

    public function execute()
    {
        try {
            
$youtube = new Services_YouTube($this->devId, array('useCache' => true));
            
$xml     $youtube->listByTag($this->tag$this->page$this->perPage 
1);
            
$videos  $xml->xpath('//video');

            $pager   $this->createPager($videos);

            echo $this->createHeader($pager);
            echo 
$this->createBody($videos);
            echo 
$this->createFooter($pager);

        } catch (Services_YouTube_Exception $e) {
            
// internal server error. service is not available.
            
echo $e;
        }
    }
    
// }}}
    // {{{protected
    
protected function createPager(&$videos)
    {
        
$page    = (int)$this->page;
        
$perPage = (int)$this->perPage;
        
$tag     htmlspecialchars(urlencode($this->tag));
        
$script  htmlspecialchars($this->script);

        $prev    '';
        
$next    '';

        if ($page 1) {
            
$prev sprintf('<a href="%s?page=%s&per_page=%s&tag=%s">prev page</a>|',
                
$script$page -1$perPage$tag);
        }

        // XXX youtube does not return the result number! it is hard to implement pagenator
        
if (count($videos) == $perPage 1) {
            
// delete the last array data. it is used only for checking existance of the next page.
            
array_pop($videos);
            
$next sprintf('<a href="%s?page=%s&per_page=%s&tag=%s">next page</a>',
                
$script$page  ="color: #0000BB">1$perPage$tag);
        }

        return '<div style="float:right">' $prev.$next '</div>';
    }

    protected function createHeader($pager)
    {
        
$page     = (int)$this->page;
        
$perPage  = (int)$this->perPage;
        
$tag      htmlspecialchars(urlencode($this->tag));
        
$script   htmlspecialchars($this->script);

        $lastNum  $page $perPage;
        
$firstNum $lastNum $perPage 1;

        // mendokusa
        
$form     sprintf('
            <form method="get" action="%s">
            Results per page:
            <select name="per_page">
            <option value="5">5</option>
            <option value="10">10</option>
            <option value="15">15</option>
            <option value="20">20</option>
            <option value="25">25</option>
            </select>
            <input type="text" name="tag" value="%s">
            <input type="submit" value="Search">
            </form>'
$script$tag);

        $search  sprintf('Searching Tags: "%s"'$tag);
        
$current sprintf('<div style="float:right"><h4>Displaying Videos %d - %d</h4></div>'$firstNum$lastNum);

        return $form.$pager.$search.$current;
    }

    protected function createFooter($pager)
    {
        
// mendokusa
        
return $pager;
    }

    protected function createBody($videos)
    {
        
$html '';
        foreach (
$videos as $video) {
            
e="color: #0000BB">$html .= $this->getTitle($video);
            
$html .= $this->getThumbnail($video);
            
$html .= $this->getUploadTime($video);
            
$html .= $this->getAuthor($video);
            if (
$this->useDetails) {
                
$html .= $this->getChannel($video);
            }
            
$html .= $this->getLength($video);
            
$html .= $this->getDescription($video);
            
$html .= $this->getCommentCount($video);
            
$html .= $this->getTags($video);
            
$html .= $this->getViewCount($video);
            
$html .= $this->getRating($video);
        }
        return 
$html;
    }

    // {{{ UI, override them if you want to. or maybe different class is better.
    
protected function getThumbnail($video)
    {
        
$title         htmlspecialchars($video->title);
        
$url           htmlspecialchars($video->url);
        
$thumbnail_url htmlspecialchars($video->thumbnail_url);

        return '<a href="'.$url.'"><img src="'.$thumbnail_url.'" alt="'.$title.'"></a>';
    }
    protected function 
getTitle($video)
    {
        
$title htmlspecialchars($video->
title
);

        return '<h2>'.$title.'</h2>';
    }
    protected function 
getUploadTime($video)
    {
        
$uploadTime date("F j, Y, g:i a", (int)$video->upload_time);

        return '<b>added:</b> '.$uploadTime;
    }
    protected function 
getAuthor($video)
    {
        
$author htmlspecialchars($video->author);

        // xxx youtube does not return author data...
        
return '<b>by:</b> '.$author;
    }

    // i don't think it is a very good idea to use youtube.videos.get_details call.
    // too many requests...
    
protected function getChannel($video)
    {
        static 
$youtube null;

        $devId $this->devId;
        
$id    htmlspecialchars($video->id);

        $youtube       = new Services_YouTube($devId, array('useCache' => true));
        
$detailsXml    $youtube->getDetails($id);

        $details       array_pop($detailsXml->xpath('//video_details'));
        
$channelsArray array_map(array('self''callbackChannels'), $details->xpath('//channel'));
        
$channels      implode("\n"$channelsArray);

        return $channels.'<br />';
    }
    protected function 
getLength($video)
    {
        
$lengthSeconds gmdate("i:s", (int)$video->length_seconds);

        return '<b>Length:</b>'.$lengthSeconds.'<br />';
    }
    protected function  pan>getDescription($video)
    {
        
$description htmlspecialchars($video->description);

        return '<i>'.$description.'</i><br />';
    }
    protected function 
getCommentCount($video)
    {
        
$commentCount = (int)$video->comment_count;

        return '<b>Comments:</b>'.$commentCount;
    }
    protected function 
getTags($video)
    {
        
$tags      htmlspecialchars($video->tags);

        $tagsArray explode(" "$tags);
        
$tagsArray =  array_map(array('self''callbackTags'), $tagsArray);

        $tags      implode("\n"$tagsArray);

        return '<b>tags:</b> '.$tags.'<br />';
    }
    protected function 
getViewCount($video)
    {
        
$viewCount = (int)$video->view_count;

        return '<b>Views:</b>'.$viewCount.' | ';
    }

    protected function getRating($video)
    {
        
$ratingAvg   $video->rating_avg;
        
$ratingCount $video->rating_count;

        return '<b>rating:</b>'.$ratingAvg.' with '.$ratingCount.' total votes';
    }

    // {{{ callback functions
    
protected function callbackTags($tag)
    {
        
$tag     htmlspecialchars($tag);
        
$script  htmlspecialchars($this->script);
        
$perPage = (int)$this->perPage;

        return '<a href="'.$script.'?per_page='.$perPage.'&page=1&tag='.$tag.'">'.$tag.'</a>&nbsp;';
    }

    protected function callbackChannels($channel)
    {
        
$channel    htmlspecialchars((string)$channel);
        
$youtubeUrl "http://youtube.com/result?search_type=search_videos";

        return '<a href="'.$youtubeUrl.'&search_query='.$channel.'">'.$channel.'</a>';
    }
    
// }}}
    // }}}
    // }}}

}

$t = new YouTubeTutorial('YOUR_DEV_ID');
$t->execute();

?>

createHeader, createFooter, createBodyあたりをもう少しキレイな見ためにするため、オーバーライドしたらいいんじゃない?
ソースコードを晒すと、セキュリティホールがどうだとか突っ込まれるような気もするが、こっちとしてはその方が勉強になるので、ウェルカムです。もちろん、随時直していく予定だし。

つーか、youtube落ち杉。あと、simplexml_load_stringって失敗したら、falseを返すのはいいんだけど、Warningが。。。falseでExceptionを投げようと思うのだけど、simplexml_load_stringで失敗した時点でWarningが出てしまうので、なんか嫌だ。simplexml_load_stringする前にかますXML文字列が妥当かどうかチェックしないといけないのなあ。。よくわからん。

というか、つい最近phpの-sのオプションを知りました。キレイにハイライトしてくれていいですねー。


array_mapでcallbackを指定しているところなんだけど、本当は、create_functionで指定してあげたいのだが、なんか読みにくそうなので、分けた。 selfを渡して、自分のインスタンスメソッドを指定してみた。

はい。当分の間Services_YouTubeでは遊びません。Dapperの調査が途中なので、そちらをしないと。。あと、Reflection_AnnotationとPHP_Annotationの機能の違いを調べるんだった。。。うーん。

コメントをどうぞ

メールアドレスが公開されることはありません。 * が付いている欄は必須項目です

*

次のHTML タグと属性が使えます: <a href="" title=""> <abbr title=""> <acronym title=""> <b> <blockquote cite=""> <cite> <code> <del datetime=""> <em> <i> <q cite=""> <strike> <strong>

Shin Ohno 2003-2012