Help us understand the problem. What is going on with this article?

シリアライズ可能なcURLのラッパークラス

More than 5 years have passed since last update.

Cookie がシリアライズ可能なcURLのラッパークラスがあんまり無いような気がしたので作ってみました。

Source

BSD2条項ライセンス でお願いします。

<?php

/**
 * cURL
 * 
 * @author CertaiN
 * @github https://github.com/Certainist/cURL
 * @license BSD 2-Clause
 */
class cURL implements Serializable {

    /**
     * Default User-Agent.
     * 
     * @static
     * @access public
     */
    public static $defaultUserAgent = 'Chrome';

    private $ch;
    private $fp;
    private $userAgent;
    private $cookie;

    /**
     * You have to call parent::__construct() on your extended method.
     * 
     * @magic
     * @access public
     * @param string [$user_agent = null]
     */
    public function __construct($user_agent = null) {
        $list = static::getUserAgents();
        if (!func_num_args()) {
            $user_agent = static::$defaultUserAgent;
        }
        if (!is_string($user_agent)) {
            throw new InvalidArgumentException('User-Agent value type must be string.');
        }
        if (!is_array($list)) {
            throw new DomainException('static::getUserAgents() must return 1 dimentional assoc.');
        }
        if (!array_key_exists($user_agent, $list)) {
            throw new InvalidArgumentException('Unknown User-Agent.');
        }
        if (!is_string($list[$user_agent])) {
            throw new DomainException('static::getUserAgents() return array must contain string values.');
        }
        $this->userAgent = $list[$user_agent];
        $this->init();
    }

    /**
     * For GET requests.
     * 
     * @access public
     * @param string $url
     * @param mixed [&$info = null] Set result of curl_getinfo().
     * @return string Response body.
     */
    public function get($url, &$info = null) {
        if (!is_string($url)) {
            throw new InvalidArgumentException('URL value type must be string.');
        }
        if (!is_resource($this->ch)) {
            throw new BadMethodCallException('cURL resource is not initialized');
        }
        curl_setopt_array($this->ch, array(
            CURLOPT_URL => $url,
            CURLOPT_HTTPGET => true,
        ));
        return $this->exec($info);
    }

    /**
     * For POST requests.
     * 
     * @access public
     * @param string $url
     * @param mixed $params Query string or associative array.
     * @param mixed [&$info = null] Set result of curl_getinfo().
     * @return string Response body.
     */
    public function post($url, $params, &$info = null) {
        if (!is_string($url)) {
            throw new InvalidArgumentException('URL value type must be string.');
        }
        if (!is_resource($this->ch)) {
            throw new BadMethodCallException('cURL resource is not initialized');
        }
        curl_setopt_array($this->ch, array(
            CURLOPT_URL => $url,
            CURLOPT_POST => true,
            CURLOPT_POSTFIELDS => $params,
        ));
        return $this->exec($info);
    }

    /**
     * Clear cookies.
     * 
     * @access public
     */
    public function clearCookies() {
        if (!is_resource($this->ch)) {
            throw new BadMethodCallException('cURL resource is not initialized');
        }
        ftruncate($this->fp, 0);
    }

    /**
     * Return the list of User-Agents.
     * You can extend this method.
     * 
     * @static
     * @access protected
     * @return array Associative array.
     */
    protected static function getUserAgents() {
        return array(
            'Chrome' =>
                'Mozilla/5.0 (Windows NT 6.1) ' .
                'AppleWebKit/537.36 (KHTML, like Gecko) ' .
                'Chrome/28.0.1500.63 Safari/537.36'
            ,
            'Firefox' =>
                'Mozilla/5.0 (Windows NT 6.1; WOW64; rv:9.0.1) ' .
                'Gecko/20100101 Firefox/9.0.1'
            ,
            'Android' =>
                'Mozilla/5.0 (Linux; Android 4.1.1; Nexus 7 Build/JRO03S) ' .
                'AppleWebKit/535.19 (KHTML, like Gecko) ' .
                'Chrome/18.0.1025.166 Safari/535.19'
            ,
            'iOS' =>
                'Mozilla/5.0 (iPhone; CPU iPhone OS 6_0 like Mac OS X) ' .
                'AppleWebKit/536.26 (KHTML, like Gecko) ' .
                'Version/6.0 Mobile/10A403 Safari/8536.25'
            ,
            'Windows Phone' =>
                'Mozilla/5.0 (compatible; MSIE 9.0; Windows Phone OS 7.5; ' .
                'Trident/5.0; IEMobile/9.0; ' .
                'FujitsuToshibaMobileCommun; IS12T; KDDI)'
            ,
            'Internet Explorer' =>
                'Mozilla/5.0 (Windows NT 6.3; WOW64; ' . 
                'Trident/7.0; Touch; rv:11.0) like Gecko'
            ,
        );
    }

    /**
     * Serialize your own properties.
     * You can extend this method.
     * 
     * @access protected
     * @return mixed
     */
    protected function userSerialize() { return null; }

    /**
     * Unserialize your own properties.
     * You can extend this method.
     * 
     * @param anything $data
     * @access protected
     * @return mixed
     */
    protected function userUnserialize($data) { }

    /**
     * You have to call parent::__destruct() on your extended method.
     * 
     * @magic
     * @access public
     */
    public function __destruct() {
        if (is_resource($this->ch)) {
            curl_close($this->ch);
        }
        if (is_resource($this->fp)) {
            $this->cookie = stream_get_contents($this->fp);
            fclose($this->fp);
        }
    }

    final public function serialize() {
        $this->__destruct();
        $this->init($this->cookie);
        return serialize(array(
            $this->userAgent,
            $this->cookie,
            $this->userSerialize(),
        ));
    }

    final public function unserialize($data) {
        if (
            !$data = @unserialize($data) or
            !array_key_exists(0, $data) or
            !array_key_exists(1, $data) or
            !array_key_exists(2, $data) or
            !in_array($data[0], static::getUserAgents(), true) or
            !is_string($data[1])
        ) {
            throw new UnexpectedValueException('Invalid serial');
        }
        $this->userAgent = $data[0];
        $this->init($data[1]);
        $this->userUnserialize($data[2]);
    }

    private function exec(&$info) {
        $ret = curl_exec($this->ch);
        $info = curl_getinfo($this->ch);
        return $ret;
    }

    private function init($data = '') {
        $this->fp = tmpfile();
        if ($data !== '') {
            fwrite($this->fp, $data);
            rewind($this->fp);
        }
        $info = stream_get_meta_data($this->fp);
        $cookie_uri = $info['uri'];
        $this->ch = curl_init();
        curl_setopt_array($this->ch, array(
            CURLINFO_HEADER_OUT => true,
            CURLOPT_AUTOREFERER => true,
            CURLOPT_FOLLOWLOCATION => true,
            CURLOPT_RETURNTRANSFER => true,
            CURLOPT_SSL_VERIFYPEER => false,
            CURLOPT_CONNECTTIMEOUT => 10,
            CURLOPT_TIMEOUT => 15,
            CURLOPT_MAXREDIRS => 5,
            CURLOPT_COOKIEFILE => $cookie_uri,
            CURLOPT_COOKIEJAR => $cookie_uri,
            CURLOPT_ENCODING => 'gzip, deflate',
            CURLOPT_USERAGENT => $this->userAgent,
            CURLOPT_HTTPHEADER => array(
                'Accept: ' .
                    'text/html,' . 
                    'application/xhtml+xml,' .
                    'application/xml' .
                    ';q=0.9,*/*;q=0.8'
                ,
                'Accept-Language: ' .
                    'ja,en-us;q=0.7,en;q=0.3'
                ,
            ),
        ));
    }

}

Example

ニコニコ動画にログインして履歴を取得する例

class NicoNico extends cURL {

    public function __construct($mail_tel, $password) {
        parent::__construct('Chrome');
        $this->get('http://www.nicovideo.jp/login');
        $this->post(
            'https://secure.nicovideo.jp/secure/login?site=niconico',
            array(
                'next_url' => '',
                'mail_tel' => $mail_tel,
                'password' => $password,
            ),
            $info
        );
        if ($info['url'] !== 'http://www.nicovideo.jp/') {
            throw new RuntimeException('ログインに失敗しました');
        }
    }

    public function getHistory() {
        $regex = implode('.*?', array(
            '<div class="outer" id="outer_sm(\d*+)">',
            '<img src="([^"]*+)" alt="([^"]*+)" class="video" />',
            '<span class="videoTime">([^<]*+)</span>',
            '<p class="posttime">(\d*+年\d*+月\d*+日 \d*+:\d*+)',
            '<span>視聴回数(\d*+)回</span>',
            '<li class="play">再生:([^<]*+)</li>',
            '<li class="comment">コメント:([^<]*+)</li>',
            '<li class="mylist">マイリスト:<a href="[^"]*+">([^<]*+)</a></li>',
            '<li class="posttime">(\d*+年\d*+月\d*+日 \d*+:\d*+) 投稿</li>',
        ));
        $regex = "@{$regex}@s";
        $str = $this->get('http://www.nicovideo.jp/my/history');
        if (!preg_match_all($regex, $str, $matches, PREG_SET_ORDER)) {
            throw new RuntimeException('履歴取得に失敗しました');
        }
        foreach ($matches as $match) {
            $ret[] = array(
                'id' => $match[1],
                'url' => "http://www.nicovideo.jp/watch/sm{$match[1]}",
                'thumb_url' => $match[2],
                'title' => $match[3],
                'duraction' => $match[4],
                'watched_at' => $match[5],
                'watched_count' => $match[6],
                'meta_watched_count' => $match[7],
                'meta_comment_count' => $match[8],
                'meta_mylist_count' => $match[9],
                'meta_created_at' => "20{$match[10]}", 
            );
        }
        return $ret;
    }

}
$nico = new NicoNico('aaaaa@bbbbb.cc.jp', 'xxxxxxx');
var_dump($nico->getHistory());
array(30) {
  [0]=>
  array(11) {
    ["id"]=>
    string(8) "21280725"
    ["url"]=>
    string(40) "http://www.nicovideo.jp/watch/sm21280725"
    ["thumb_url"]=>
    string(45) "http://tn-skr2.smilevideo.jp/smile?i=21280725"
    ["title"]=>
    string(69) "【オツキミリサイタル】歌ってみた @ゆいこんぬ"
    ["duraction"]=>
    string(4) "3:45"
    ["watched_at"]=>
    string(23) "2013年12月05日 14:10"
    ["watched_count"]=>
    string(1) "1"
    ["meta_watched_count"]=>
    string(7) "316,137"
    ["meta_comment_count"]=>
    string(5) "4,322"
    ["meta_mylist_count"]=>
    string(6) "14,802"
    ["meta_created_at"]=>
    string(23) "2013年07月05日 17:57"
  }
  [1]=>
  ....

このままシリアライズしてCookieを維持することも可能

file_put_contents('NicoNico.dat', serialize($nico));

間違った継承のしかたをすると例外をスロー

  • parent::__construct() をコールせずに使おうとすると BadMethodCallException をスロー
  • static::getUserAgents() が正しい配列を返さないと DomainException をスロー
Why do not you register as a user and use Qiita more conveniently?
  1. We will deliver articles that match you
    By following users and tags, you can catch up information on technical fields that you are interested in as a whole
  2. you can read useful information later efficiently
    By "stocking" the articles you like, you can search right away
Comments
Sign up for free and join this conversation.
If you already have a Qiita account
Why do not you register as a user and use Qiita more conveniently?
You need to log in to use this function. Qiita can be used more conveniently after logging in.
You seem to be reading articles frequently this month. Qiita can be used more conveniently after logging in.
  1. We will deliver articles that match you
    By following users and tags, you can catch up information on technical fields that you are interested in as a whole
  2. you can read useful information later efficiently
    By "stocking" the articles you like, you can search right away