第50回PHP勉強会に参加してきた。

2月22日、第50回PHP勉強会(http://events.php.gr.jp/events/show/90)に参加してきました。オープンソーシャル関連の勉強会ということで、
50名以上の参加者がいる勉強会になりました。gusagiさん、お疲れ様でした。
http://d.hatena.ne.jp/gusagi/20100223/1266859247 gusagiさんの日記はこちら

で、もともとPHPを使ってMIXIアプリを作りたい(作ってる)んだけど、なかなか情報がないんで、勉強会とかやってくれません?
とgusagiさんに打診したんですが、、、なぜか俺が話すことに。。。^^;

当日のスピーカーは、
webooさん
kunitsuji
個々一番さん
の3名。

勉強会参加者の中には、、、PHPの神といわれているあんな人や、CentOSの書籍を少し前に出したあんな人や、
そりゃもう、、、恐れ多い方がたくさんいらっしゃっててですね。。。
そんなところで俺がなにをはなせばいいんだ!?みたいな状況に追い込まれてましてw

一応、
CodeIgniterを使ってWEBアプリを作るようにしてMIXIアプリを作る
という概要で話をさせてもらいました。

今実際にさぎょうしているので。

しかし、PHPでいくらMIXIアプリ作れるといっても、、、サーバインフラ周りのほうが実は大変なんじゃないか?とおもってましたが、
個々いちさんの話をきいてたら、納得。
そっちのほうが大事でしょうな。。。

webooさん、Twitterでフォローしてるんですが、勉強会で初めてお会いしました^^;
よろしくお願いします。

CodeIgniterをつかってMIXIアプリを作る(0)

久しぶりのブログ投稿です。
ずっとしこしことCodeIgniterを使ったフレームワークの拡張を行っておりましたが、現在は次のような感じ

1)MatchBoxライブラリを辞めて、HMVCを使ってモジュール化を実現。
これに関しては、速度の問題、CORE本体に手を入れなくて済むという問題をクリアしてくれました。
※メモリの利用率はMatchBoxより気持ち使うようです。
※移植で一番問題がでたのは、コントローラがHMVCの独自になってますが、ここにバグが。(最新バージョンでは
直っているかも知れません。作者に変な英語でバグの部分と再現方法、パッチを送りましたので)
簡単にいうと、Controllerの_outpu()メソッドが使えなかったんです。また、CIのオブジェクトを利用するとき、
独自のアプリケーション用共通コントローラをつかっていたのですが、そのメソッドやプロパティを参照しませんでした。
こちらに原因があるのかと思って調べたところ、そもそものCIオブジェクトの保持の仕方が違ってました。
_outputを使わなかったり、モジュールのコントローラからアプリのコントローラのメソッドを利用する場合はきがつかないのですが、
codeigniter.phpが最終出力を行う際、outpu.php、アウトプットクラスを見てそこから出力をおこなうので、
CIオブジェクトに_output()があれば、というのがCIのつくりです。つまり、コントローラはCIのオブジェクトとして動くんですが、
HMVCの場合、コントローラを独自にHMVCが用意しており、そこでのCIオブジェクトの保持方法に不具合があったということですね。

とりあえずアウトプットクラスをアプリにおいてそこで処理するようにしました。。

2)MyNETS2という名称をSNSに限定
現在触っているものはSNS、ということではなくCodeIgniterを使ったプリケーション開発フレームワークです。
ですので、あくまでも開発ツールという位置づけ。
そこから作り出されたアプリと名称を分けることにしました。

MoonBASEという名称に決定。(どこかで聞いたことありますが)。
で、Usagi Project、USAGIから連想する月、の基地、ベースということでMoonBASE。
でそこから作り出される、現状のMyNETSの新バージョンを「KAGUYA」とすると思います。

3)本題に入りますがw
そのMoonBASEで簡単にMIXIアプリを構築する事ができるようにしています。
MIXIアプリはフラッシュをつかったゲーム、が多いのですが、MoonBASEでは既存のWEBサイトを構築するようにして
オープンソーシャルアプリ、MIXIアプリを構築することが可能です。
OpenSocial RESTfull API を使ってowner_idなどを取得し、それを元にアプリ側のDBと照合します。
認証はOAuthを使いますが、これもライブラリを入れてコンシューマーキーやアプリIDを設定保存するだけで動きます。
フラッシュで作る場合はデザインなど高いスキルが要求され、自分には太刀打ちできませんが、
WEBサイト構築を行うようにつくれば、そのままMIXIアプリとしてうごかせるので、今までの経験が無駄になりません。

現在、セッション処理をMIXIアプリのowner_idを使うことで行うように手を入れている状況です。

出来上がれば、これでいくつかMIXIモバイルアプリを作る予定。
PCもいけますね。

CodeIgniterの_remapメソッドでパラメータを指定する方法

デフォルトのCodeIgniterでの _remap() メソッドでは、メソッド名の指定のみ有効で、パラメータ付きのコントローラのメソッドを再マッピングできませんでした。

調べたところ、Codeigniter.php のクラス内で再マッピングしている場所はこちら

if (method_exists($CI, '_remap'))
	{
		$CI->_remap($method);
	}
	else
	{
		// is_callable() returns TRUE on some versions of PHP 5 for private and protected
		// methods, so we'll use this workaround for consistent behavior
		if ( ! in_array(strtolower($method), array_map('strtolower', get_class_methods($CI))))
		{
			show_404("{$class}/{$method}");
		}

		// Call the requested method.
		// Any URI segments present (besides the class/function) will be passed to the method for convenience
		call_user_func_array(array(&$CI, $method), array_slice($URI->rsegments, 2));
	}

マッピングしない場合は、call_user_func_arrayをつかってパラメータを配列で渡していますが、
_remapを使った場合、メソッドしか指定していません。

なので、これを使えるようにカスタマイズしてみました。

if (method_exists($CI, '_remap'))
    {
        $CI->_remap($method, array_slice($URI->rsegments, 2));
    }
    else
    {
        // is_callable() returns TRUE on some versions of PHP 5 for private and protected
        // methods, so we'll use this workaround for consistent behavior
        if ( ! in_array(strtolower($method), array_map('strtolower', get_class_methods($CI))))
        {
            show_404("{$class}/{$method}");
        }

        // Call the requested method.
        // Any URI segments present (besides the class/function) will be passed to the method for convenience
        call_user_func_array(array(&$CI, $method), array_slice($URI->rsegments, 2));
    }

これだけではだめで、
コントローラで_remap()を使う場合に
function _remap($method, $params = array())
とします。

で、条件によって振り分ける際に、
$paramsに値がある場合は
call_user_func_array(array($this, $method), $params);
無い場合は
$this->$method();
とすることで、パラメータ付きのものが渡せるようになりました。

これ、本家にパッチを贈ろうかと思いますが、、なぜ_remapの場合methodのみにしてるんでしょうかね?
理由があるのかもしれませんが。。。

[PHP][CodeIgniter]CodeIgniterのanchor()関数でimgタグを使う

CIでanchor()タグの第二引数に <img タグを入れると、おかしな <a タグが生成されます。
これは、自動的に title に第二引数が渡されるためです。

<img タグを入れて画像のリンクを生成する場合は

anchor('hogehoge/fugafuga', '<img src="hoge" />', array('title'=>'ほげげ'));

として、第三引数にタイトルを入れればOKです。
ちょっとした方法ですね。

PHPカンファレンス2009にて

2009年9月5日(土)、PHPカンファレンス2009にてCodeIgniterを活用した業務案件の効率化うんぬん。。を発表してきました。
時間は20分。

資料はこちら。
http://handsout.jp/slide/1673

時間も短かったので、具体的なソースを開設できませんでしたが、それなりにいい感じでした。
また、エロい人たちからも概ね好評だったようですが。。。ここが一番怖いw

まあ、仕事に使うために汎用性を上げると、当然カスタマイズしていくわけですが、
そういう部分でCIは触りやすい、ということですね。

基本的に何でもかんでもサポートされているわけではないので、たとえば日本の携帯需要向けの開発になると、
まず作らなければなりません。
そんな時に組み込み易い、なおかつコアの部分には手をつけないで、フレームワーク機能を損なわずカスタマイズする、
ということにCodeIgniterは向いていると思います。

CodeIgniterを使ったアプリケーションBASEとして「MyNETS2」をぜひw

CodeIgniterでのファイルアップロードのラッパークラスを作った

CodeIgniterでサイト上で画像をアップする時に、通常は1ファイルのみの扱いですが、これを以前MYNETS_Uploadクラスというもので
複数ファイルの同時アップロードを行えるようにしました。

が、実際にファイルを操作するとなると、次のことが必要となります。
1)ファイルをリサイズする
2)複数サイズの画像をあらかじめ作成しておく
等が必要となります。

この場合、毎回同じような設定情報を受け渡したりして結構記述が面倒になります。
これを解消するため、MYNETS_Image_libクラスを作成しました。
基本的な動作はCI_Image_libをextendsし、あくまでもめんどくさい部分を解消するためのラッパークラスとして使います。

ソースはこちら

<?php  if ( ! defined('BASEPATH')) exit('No direct script access allowed');
/**
 * LICENSE
 *
 * This source file is subject to the new BSD license that is bundled
 * with this package in the file LICENSE.txt.
 *
 * @category
 * @package    MYNETS_Image_lib Class
 * @author     KUNIHARU Tsujioka <kunitsuji@gmail.com>
 * @copyright  Copyright (c) 2008 KUNIHARU Tsujioka <kunitsuji@gmail.com>
 * @copyright  Copyright (c) 2006-2008 Usagi Project (URL:http://usagi-project.org)
 * @license    New BSD License
 */

// ------------------------------------------------------------------------

class MYNETS_Image_lib extends CI_Image_lib {

    private $CI;

    private $upload_base   = 'uploads/';
    private $max_size      = '500';        //500KByteまで
    private $max_width     = '1024';      //1024px
    private $max_height    = '1024';      //1024px
    private $overwrite     = TRUE;        //上書きするか
    private $allowed_types = 'gif|jpg|png';

    public  $upload_error_message = ''; //エラーがあった場合、エラーメッセージを格納する

    private $old_filename = '';       //オリジナルのファイル名
    private $new_filename = '';       //生成された新しいファイル名
    
    public  $upload_filename = array();  //アップロードされたファイル名を格納する配列
    /**
     * Constructor
     *
     * @access  public
     * @param   string
     * @return  void
     */
    public function __construct($params = array())
    {
        parent::__construct();

        if (count($params) > 0)
        {
            $this->initialize($params);
        }

        //file upload classをロード
        $this->CI =& get_instance();

        //log_message('debug', "Image Lib Class Initialized");
    }

    // --------------------------------------------------------------------

    /**
     * ファイルアップロードクラスをロードする
     * 必須はupload_path モジュール名以下、命名規則に沿って指定
     * 例)日記の場合は、'diary/XXXXX/XXXXX'など
     *
     */
    public function execute($config = array())
    {
        if (isset($config['upload_path']))
        {
            $config['upload_path'] = PUBPATH.$this->upload_base.$config['upload_path'];
            if ( ! $this->_path($config['upload_path']))
            {
                return FALSE;
            }
        }
        else
        {
            //パスの指定がないものはエラーとする
            $this->upload_error_message = 'ファイルを格納する場所が未定義です。';
            return FALSE;
        }
        
        if ( ! isset($config['allowed_types']))
        {
            $config['allowed_types'] = $this->allowed_types;
        }

        if ( ! isset($config['max_size']))
        {
            $config['max_size']      = $this->max_size;
        }

        if ( ! isset($config['max_width']))
        {
            $config['max_width']     = $this->max_width;
        }

        if ( ! isset($config['max_height']))
        {
            $config['max_height']    = $this->max_height;
        }

        if ( ! isset($config['overwrite']))
        {
            $config['overwrite']     = $this->overwrite;
        }

        $this->CI->load->library('upload', $config);
    }
    
    /**
     * 指定のフィールド名のファイルがアップロードされているかどうかを返却
     * @params  string  field name
     * @params  bool    TRUE or FALSE
     *
     */
    public function is_upload($name)
    {
        if ($this->CI->upload->is_uploaded($name))
        {
            return TRUE;
        }
        else
        {
            return FALSE;
        }
    }

    /**
     * 保存ディレクトリが存在しているかを調査する
     *
     *
     */
    private function _path($path)
    {
        if (is_dir($path))
        {
            if (is_writable($path) !== TRUE)
            {
                $this->upload_error_message = 'ファイルを書き込む権限がありません。';
                return FALSE;
            }
            else
            {
                return TRUE;
            }
        }
        else
        {
            //存在していない場合は、その場所を作成する
            if (mkdir($path,0777,TRUE))
            {
                return TRUE;
            }
            $this->upload_error_message = 'ファイルを格納する場所が作成できません。';
            return FALSE;
        }
    }

    /**
     * 画像を複数枚にコンバートする。変換する画像のサイズを配列で受け取る。
     * array('120', 180', '76',50)の場合、それぞれのサイズにリサイズする
     * オリジナル画像を保存する場合は、リネームして新しく保存する
     * @params  string field name
     * @params  array  convert size
     * @params  bool   original image deleted default FALSE
     * @params  bool   original image renamed default TRUE
     *
     */
    public function convert_image($column, $option, $deleted = FALSE, $rename = TRUE)
    {
        if ( ! $column)
        {
            return FALSE;
        }
        if ( ! $option)
        {
            return '';      //何もしない
        }

        if ( ! is_array($option))
        {
            $option = (array)$option;
        }

        $this->new_filename = '';
        $this->old_filename = '';
        
        if ( ! $this->CI->upload->do_upload($column))
        {
            $this->upload_error_message = $this->CI->upload->display_errors();
            return FALSE;
        }
        else
        {
            //ファイル名を生成
            $this->_filename();

            $image_data = $this->CI->upload->data($column);
            
            //元のサイズを保存する場合(縮小なしで保存)
            if ($rename)
            {
                $option[] = '';
            }

            foreach ($option as $val)
            {
                if ( ! $this->_image_risize($image_data, $val))
                {
                    return FALSE;
                }
                $this->upload_filename[$column]['new_filename'] = $this->new_filename;
                $this->upload_filename[$column]['old_filename'] = $this->old_filename;
            }

            if ($deleted)
            {
                unlink($image_data['full_path']);
            }
            
            return TRUE;
        }
    }

    private function _filename()
    {
        $u_id = $this->CI->session->userdata('user_id');

        $filename_prefix = date('Y').date('m').$u_id;
        $file_token      = md5(uniqid($filename_prefix));

        $this->file_token = $file_token;
    }

    private function _image_risize($img_data, $size = '')
    {
        //サイズが指定されていない場合は、ファイルそのものをコピーする
        if ( ! $size)
        {
            if ( ! copy($img_data['full_path'], $img_data['file_path'].$this->file_token.$img_data['file_ext']))
            {
                $this->upload_error_message = 'ファイルのコピーに失敗しました。';
                return FALSE;
            }
            else
            {
                return TRUE;
            }
        }
        else
        {
            //画像操作
            $img_conf['image_library']  = 'gd2';
            $img_conf['source_image']   = $img_data['full_path'];
            $img_conf['create_thumb']   = FALSE;
            $img_conf['maintain_ratio'] = TRUE;
            $img_conf['new_image']      = $img_data['file_path'].$this->file_token.$img_data['file_ext'];

            $s_width  = $img_data['image_width'];
            $s_height = $img_data['image_height'];
            $w = $size;
            $h = $size;
            if (!$w) $w = $s_width;
            if (!$h) $h = $s_height;
            $image_resize = $this->_create_image_size($s_width, $s_height, $w, $h);

            //指定サイズで処理
            $img_conf['width']  = $image_resize['width'];
            $img_conf['height'] = $image_resize['height'];
            $img_conf['new_image'] = $img_data['file_path'].$size.'_'.$this->file_token.$img_data['file_ext'];
            $this->initialize($img_conf);
            if ($this->resize())
            {
                $this->new_filename = $this->file_token.$img_data['file_ext'];
                $this->old_filename = $img_data['file_name'];
                return TRUE;
            }
            else
            {
                $this->clear();
                $this->upload_error_message = '画像リサイズ時にエラーが発生しました。';
                return FALSE;
            }
        }
    }

    private function _create_image_size($s_w, $s_h, $w, $h)
    {
        // リサイズの必要がない場合
        if ($s_w <= $w && $s_h <= $h)
        {
            //そのまま
            $img_resize['width']  = $s_w;
            $img_resize['height'] = $s_h;
        }
        else
        {
            // 出力サイズ変更
            $o_width  = $s_w;
            $o_height = $s_h;

            if ($w < $s_w)
            {
                $o_width  = $w;
                $o_height = $s_h * $w / $s_w;
                if ( $o_height < 1 )
                {
                    $o_height = 1;
                }
            }
            if ($h < $o_height && $h < $s_h)
            {
                $o_width  = $s_w * $h / $s_h;
                $o_height = $h;
                if ( $o_width < 1 )
                {
                    $o_width = 1;
                }
            }

            $img_resize['width']  = $o_width;
            $img_resize['height'] = $o_height;
        }

        return $img_resize;
    }
}
// END MYNETS_Image_lib Class

/* End of file MYNETS_Image_lib.php */
/* Location: ./system/mynets/libraries/MYNETS_Image_lib.php */

コントローラでの使い方はこちら。

    function test_2()
    {
        $this->load->library('image_lib');
        $config['upload_path'] = 'test/';
        $this->image_lib->execute($config);
        $option = array('180','120','60');
        
        $error_msg = array();
        
        if ($this->image_lib->is_upload('image_filename_1'))
        {
            if ( ! $this->image_lib->convert_image('image_filename_1', $option, TRUE))
            {
                $error_msg['image_filename_1'] = $this->image_lib->upload_error_message;
            }
        }
        if ($this->image_lib->is_upload('image_filename_2'))
        {
            if ( ! $this->image_lib->convert_image('image_filename_2', $option, TRUE))
            {
                $error_msg['image_filename_2'] = $this->image_lib->upload_error_message;
            }
        }
        if ($this->image_lib->is_upload('image_filename_3'))
        {
            if ( ! $this->image_lib->convert_image('image_filename_3', $option, TRUE))
            {
                $error_msg['image_filename_3'] = $this->image_lib->upload_error_message;
            }
        }
        if ($this->image_lib->is_upload('image_filename_4'))
        {
            if ( ! $this->image_lib->convert_image('image_filename_4', $option, TRUE))
            {
                $error_msg['image_filename_4'] = $this->image_lib->upload_error_message;
            }
        }
        //print_r($this->image_lib->upload_filename);
    }

簡単に解説すると、コントローラでは、image_libライブラリをロードします。
次に、そこで取り扱うファイルの保存ディレクトリを指定します。
※基本的に、CIのROOTの下にあるuploads/ディレクトリというのを画像保存の既定場所としています。
private $upload_base = 'uploads/';
private $max_size = '500'; //500KByteまで
private $max_width = '1024'; //1024px
private $max_height = '1024'; //1024px
private $overwrite = TRUE; //上書きするか
private $allowed_types = 'gif|jpg|png';

コントローラで指定するのは、そのディレクトリ以下に保存する場所を指す、ということになります。
※このあたりはアプリによって変更したり、仕組みを変えることもありでしょうね。
現在未指定の場合はエラーを返すようにしています。
ここで、たとえば、日記の画像を扱うコントローラの場合、
$config['upload_path'] = 'diary/';
や、さらに会員IDをつけたディレクトリを設定することでそのディレクトリでの保存となります。
※DBでのオブジェクトの保存は現在作成していませんが、今後作成する、かもしれません。
それを、
$this->image_lib->execute($config);
executeメソッドで引き渡し、次に
$option = array('180','120','60');
$this->image_lib->convert_image('image_filename_1', $option, TRUE)
としています。
$optionに渡すのは、縮小するための画像サイズを指定します。
仮に$optionに何も渡さない場合、特に何もしません。
※サイズ変更なしでその画像のみを扱う場合は、リネームのみしたほうがいいかもしれません。またはアップロード処理だけを行うとか。
あくまでも現在は投稿された画像をリネームし、サイズを調整して格納するという時に使います。
convert_imageメソッドの引数は、
投稿されるときに使う<input type="file" name="image_filename_1">のフィールド名を渡します。
これにより、そのフィールドに画像がアップされた場合、処理をすることになります。
二つ目は縮小するときのサイズを配列で。
3つめは元画像を削除するかどうか、
そして4つ目の引数として、オリジナルサイズの画像も、ハッシュ化した画像ファイル名に置き換えるかどうか。

これを使うこととで、
複数ファイルをアップした場合、
指定したディレクトリに、指定したサイズの画像をリサイズして複数同時に作成します。

処理を行った場合、プロパティで元の画像ファイル名、内部で生成したハッシュ化した画像ファイル名トークン部分が取り出せます。
リサイズしたファイルは、それぞれ120_XXXXX等として格納されています。

これで少しコントローラの記述がらくになるかな。

CodeIgniterのVer1.7.1でのファイルアップロードクラスに対応

MYNETS_Upload クラスを、CodeIgniterの1.7.1の対応(セキュリティ対策)に合わせて書き換えました。
変更箇所は次のメソッド

    function is_allowed_filetype()
    {
        if (count($this->allowed_types) == 0 OR ! is_array($this->allowed_types))
        {
            $this->set_error('upload_no_file_types');
            return FALSE;
        }

        $image_types = array('gif', 'jpg', 'jpeg', 'png', 'jpe');
        
        foreach ($this->allowed_types as $val)
        {
            $mime = $this->mimes_types(strtolower($val));

			// Images get some additional checks
            // 20090716 CI Ver1.7.1に対応
			if (in_array($val, $image_types))
			{
				if (getimagesize($this->file_temp[$this->field]) === FALSE)
				{
					return FALSE;
				}
			}
            //////////////////////////////KUNIHARU Tsujioka
            
            if (is_array($mime))
            {
                if (in_array($this->file_type[$this->field], $mime, TRUE))
                {
                    return TRUE;
                }
                //echo $this->file_type[$this->field];
                //exit;
            }
            else
            {
                if ($mime == $this->file_type[$this->field])
                {
                    return TRUE;
                }
            }
        }
        return FALSE;
    }