GoogleカレンダーAPIのPHP実装

Webサービスを使うのは大変なので、色んな言語用にAPIが用意されている。
Zendさんが作ったphpの実装を使ってみようと思ったのだけど、
プロキシを使ってWebにアクセスしていると、ZendのGoogle Calendar API が上手く動かなかった。
色々調べたら「ライブラリを修正しろ」と書いていたので、
言われる通りに修正したんだけど、途中ではまって死にそうになったのでメモに残す。

http://groups.google.com/group/google-calendar-help-dataapi/browse_thread/thread/8f13fb00cb518535/cd859d49f3d2f3b4
全ての答えは↑のページにまとまってるのだけど、私は日本人なのでイマイチ内容が理解できなかった。


Zend/Http/Client/Adapter/ のフォルダにある「Proxy.php」をコピーして「ProxyWithCurl.php」を作る。
connectメソッド、writeメソッドを修正して、readメソッドを追加。

ソースの全文「ProxyWithCurl.php

<?php

/**
 * Zend Framework
 *
 * LICENSE
 *
 * This source file is subject to the new BSD license that is bundled
 * with this package in the file LICENSE.txt.
 * It is also available through the world-wide-web at this URL:
 * http://framework.zend.com/license/new-bsd
 * If you did not receive a copy of the license and are unable to
 * obtain it through the world-wide-web, please send an email
 * to license@zend.com so we can send you a copy immediately.
 *
 * @category   Zend
 * @package    Zend_Http
 * @subpackage Client_Adapter
 * @version    $Id: Proxy.php 5771 2007-07-18 22:06:24Z thomas $
 * @copyright  Copyright (c) 2005-2007 Zend Technologies USA Inc. (http://www.zend.com)
 * @license    http://framework.zend.com/license/new-bsd     New BSD License
 */

require_once 'Zend/Uri/Http.php';
require_once 'Zend/Http/Client.php';
require_once 'Zend/Http/Client/Adapter/Socket.php';
require_once 'Zend/Http/Client/Adapter/Exception.php';

/**
 * HTTP Proxy-supporting Zend_Http_Client adapter class, based on the default
 * socket based adapter.
 *
 * Should be used if proxy HTTP access is required. If no proxy is set, will
 * fall back to Zend_Http_Client_Adapter_Socket behavior. Just like the
 * default Socket adapter, this adapter does not require any special extensions
 * installed.
 *
 * @category   Zend
 * @package    Zend_Http
 * @subpackage Client_Adapter
 * @copyright  Copyright (c) 2005-2007 Zend Technologies USA Inc. (http://www.zend.com)
 * @license    http://framework.zend.com/license/new-bsd     New BSD License
 */
class Zend_Http_Client_Adapter_ProxyWithCurl extends Zend_Http_Client_Adapter_Socket
{
    /**
     * Parameters array
     *
     * @var array
     */
    protected $config = array(
        'ssltransport'  => 'ssl',
        'proxy_host'    => '',
        'proxy_port'    => 8080,
        'proxy_user'    => '',
        'proxy_pass'    => '',
        'proxy_auth'    => Zend_Http_Client::AUTH_BASIC
    );

    /**
     * Connect to the remote server
     *
     * Will try to connect to the proxy server. If no proxy was set, will
     * fall back to the target server (behave like regular Socket adapter)
     *
     * @param string  $host
     * @param int     $port
     * @param boolean $secure
     * @param int     $timeout
     */
    public function connect($host, $port = 80, $secure = false)
    {
        // If no proxy is set, fall back to Socket adapter
        if (! $this->config['proxy_host']) {
            return parent::connect($host, $port, $secure);
        }
    }

    /**
     * Send request to the proxy server
     *
     * @param string        $method
     * @param Zend_Uri_Http $uri
     * @param string        $http_ver
     * @param array         $headers
     * @param string        $body
     * @return string Request as string
     */
    public function write($method, $uri, $http_ver = '1.1', $headers = array(), $body = '')
    {
        // If no proxy is set, fall back to default Socket adapter
        if (! $this->config['proxy_host']) {
            return parent::write($method, $uri, $http_ver, $headers, $body);
        }

        $host = $this->config['proxy_host'];
        $port = $this->config['proxy_port'];

        $ch = curl_init();
        curl_setopt($ch, CURLOPT_VERBOSE, 0);
        curl_setopt($ch, CURLOPT_RETURNTRANSFER, true);
        curl_setopt($ch, CURLOPT_HEADER, true);
        curl_setopt($ch, CURLOPT_PROXYTYPE, CURLPROXY_HTTP);
        curl_setopt($ch, CURLOPT_PROXY,"http://" . $host . ':' . $port);
        curl_setopt($ch, CURLOPT_SSL_VERIFYPEER, FALSE);
        curl_setopt($ch, CURLOPT_URL, $uri->__toString());
        curl_setopt($ch, CURLOPT_TIMEOUT, 120);

        // Save request method for later
        $this->method = $method;

        curl_setopt($ch, CURLOPT_CUSTOMREQUEST, $method);

        // Add Proxy-Authorization header
        if ($this->config['proxy_user'] && ! isset($headers['proxy-authorization'])) {
            $headers['proxy-authorization'] = Zend_Http_Client::encodeAuthHeader(
                $this->config['proxy_user'], $this->config['proxy_pass'], $this->config['proxy_auth']
            );
        }

        $curlHeaders = array();

        // Add all headers to the curl header array
        foreach ($headers as $k => $v) {
            if (is_string($k)) $v = ucfirst($k) . ": $v";
            $curlHeaders[] = $v;
        }
        curl_setopt($ch, CURLOPT_HTTPHEADER, $curlHeaders);

        if ($body != null) {
            curl_setopt($ch, CURLOPT_POSTFIELDS, $body);
        }

        $this->result = curl_exec($ch);
        // HACK- The server returns chunked transfer encoding attimes, and it's indicated in the header
        // However, curl and ZF both attempt to decode, leading toexceptions being thrown
        // There has to be a way to avoid the following line (orsomething like it), but haven't figured it out yet
        $this->result = str_ireplace('Transfer-encoding:', 'Transfer-encoding-old:', $this->result);
        return $this->result;
    }

    public function read()
    {
        return $this->result;
    }

    /**
     * Destructor: make sure the socket is disconnected
     *
     */
    public function __destruct()
    {
        if ($this->socket) $this->close();
    }
}

Zend/GData/ のフォルダにある「ClientLogin.php」を開いて、130行目付近にある以下の行をコメントにする。

//            $client = new Zend_Http_Client();

これで終わり。


動作確認コード

<?php

require_once 'Zend/Loader.php';
Zend_Loader::loadClass('Zend_Gdata');
Zend_Loader::loadClass('Zend_Gdata_AuthSub');
Zend_Loader::loadClass('Zend_Gdata_ClientLogin');
Zend_Loader::loadClass('Zend_Gdata_Calendar');
Zend_Loader::loadClass('Zend_Http_Client');

$service = Zend_Gdata_Calendar::AUTH_SERVICE_NAME; // predefined service name for calendar
$user = '[GMailのアドレス]';
$pass = '[パスワード]';

// HTTPクライアントを作成
$client = new Zend_Http_Client();
$client->setConfig(array(
    'adapter'      => 'Zend_Http_Client_Adapter_ProxyWithCurl', // Proxy.phpをコピーして作ったクラス
    'ssltransport' => 'tls',
    'proxy_host'   => '[プロキシのホスト名]',
    'proxy_port'   => [プロキシのポート]
  )
);

// Googleにログイン
Zend_Gdata_ClientLogin::getHttpClient($user, $pass, $service, $client);

// カレンダーのタイトルを出力
$gdataCal = new Zend_Gdata_Calendar($client);
$calFeed = $gdataCal->getCalendarListFeed();
foreach ($calFeed as $calendar) {
  echo $calendar->title->text . "\n";
}


?>