第10章 プロフィール画像の追加

前章では、ログインしたユーザが、プロフィールの情報を変更するための機能を追加しました。本章ではユーザが自分のプロフィール画像をアップロードして設定できるように変更を加えます。

その前に、本章から読み始める人のために、前回までの成果物の設定方法を以下に示します。ただし0章の環境構築は既に済んでいるものとします。

前回までの成果物:

こちらから3つのファイルをダウンロードしてください。

  • htdocs-chap9.zipを解凍して、xamppのhtdocsのディレクトリに展開してください。
  • 次にhtdocs\protected\config 内にあるmain.phpを開いてください。
  • メール送信機能に関連した以下の部分をご自身のGmailのアカウント情報に変更してください。
		'mailer'=>array(
    			'class' => 'application.extensions.mailer.EMailer',
			    'From' => 'yii.juncheng@gmail.com', // 送信者のアドレス
			    'FromName' => 'yii-juncheng',// 名前
			    'CharSet' => 'iso-2022-jp',  //文字コード
			    'Encoding' => '7bit',
			    'Mailer'=>'smtp',
			    'Port'=>'587',
			    'SMTPSecure'=>'tls',
			    'Host'=> 'smtp.gmail.com',
			    'SMTPAuth' => true,
			    'Username' => 'xxxx@gmail.com', // gmailのメールアドレスがユーザ名となる
			    'Password' => 'xxxxxxxx',       // ここにGmailのアドレスを入力する
		),
  •  またxamppのMySQLのデータベース名やデータベースのrootのパスワードを、本ブログの設定と異なる設定にした方は、以下の部分も変更してください。
		'db'=>array(
    			'connectionString' => 'mysql:host=localhost;dbname=yii',// dbname=yiiに変更する
			    'emulatePrepare' => true,
			    'username' => 'root',
			    'password' => 'unko9314', // ここを0章で設定したmysqlのパスワードに変更する
			    'charset' => 'utf8',
		),

以上でプログラムが実行できるようになります。


xamppの設定変更

まずはじめに、xamppからphp.ini の設定を少し変更していただく必要があります。xamppのコントロール画面でApache→config→php.ini を選んでください。

chap10-2

以上を選択するとメモ帳でphp.iniが開かれますので、以下のような行を探してください。もし先頭にセミコロンがあれば、そのセミコロンを削除して保存してください。

;extension=php_fileinfo.dll
; ↓ 変更
extension=php_fileinfo.dll

その後、apacheを再起動してください。apacheの再起動をStopを押してからStartをもう一度押せばOKです。


画像保存用のフォルダを追加

xamppの htdocs の直下に avatar という名前のフォルダを作成します。ユーザがアップロードした画像ファイルはここに保存されるようにします。なおもしLinuxで本プログラムを動かす場合は、このフォルダが書き込み可能なようにパーミッションを適切な値に設定する必要があるかと思います。

chap10-3

また、ついでにやっておきたいことがあります。プログラム中で、画像保存用のフォルダの位置を指定するのに便利なので、一番最下層のルート・ディレクトリ(htdocsの位置)を返す変数の設定を、設定ファイルに書き込んでおきます。

htdocs/protected/config/main.php を開いてください。そこの一番最後のところに以下のような修正を加えましょう。

        // application-level parameters that can be accessed
        // using Yii::app()->params['paramName']
        'params'=>array(
                // this is used in contact page
                'adminEmail'=>'webmaster@example.com',
                'webRoot'=>dirname(dirname(dirname(__FILE__))), // chap10: ここを追加
        ),

これによりYii::app()->params[‘webRoot’] によりhtdocs の位置が取得できます。


デフォルトのダミー画像の設置

デフォルトのダミー画像(default.png)をこちらに置きましたので、これをダウンロードしてください。ダウンロードをしたら、これをhtdocsの直下に設置してください。これは新規登録したユーザの仮の画像として使用します。


 

画像サイズ編集ライブラリの導入

まずは画像をリサイズしてくれるyii用のライブラリをご自身の環境に組み込みます。まずはこちらからimagemodifier.zipをダウンロードしてください。

※ 本家yiiframeworkのサイト上のリンクはこちらですが、以前それをそのまま使ったらバグがあったので、上記の当サイトの準備したリンクからダウンロードしてください。

解凍したらimagemodifierのディレクトリをprotected/extensions以下にそのままコピーしてください。(図のように)

chap10-1

次に上記の画像処理ライブラリを使えるようにするために設定ファイルを少し変更します。

protected/config/main.php を開いて、以下のように変更を加えます。(//chap10: 画像処理ライブラリを追加 のコメント下にある3行の部分を追加します)

	// application components
	'components'=>array(
		'user'=>array(
    			// 自分で定義したクラスに変更する
			    'class' => 'WebUser', // protected/components/WebUser.php
			    // enable cookie-based authentication
			    'allowAutoLogin'=>true,
		),
		// chap7: メール送信ライブラリを追加
		'mailer'=>array(
	    		'class' => 'application.extensions.mailer.EMailer',
    			'From' => 'yii.juncheng@gmail.com', // 送信者のアドレス
			    'FromName' => 'yii-juncheng',// 名前
			    'CharSet' => 'iso-2022-jp',  //文字コード
			    'Encoding' => '7bit',
			    'Mailer'=>'smtp',
			    'Port'=>'587',
			    'SMTPSecure'=>'tls',
			    'Host'=> 'smtp.gmail.com',
			    'SMTPAuth' => true,
			    'Username' => 'yii.juncheng@gmail.com', // gmailのメールアドレスがユーザ名となる
			    'Password' => 'unko9314',                // ここにGmailのアドレスを入力する
		),
		// chap10: 画像処理用ライブラリを追加
		'imagemod' => array(
               'class' => 'application.extensions.imagemodifier.CImageModifier',
        ),

 

以上で画像処理ライブラリを使用するための準備は整いました。


 

 データベースの変更(avatarの追加):

今回プロフィール画像のデータ自体はファイルベースで保存しますが、各ユーザとそのプロフィール画像を紐付けるために、ユーザのデータベースに、ファイルへのパス情報を記録するためのカラム(avatar)を追加します。

まずはhttp://localhost/phpmyadmin からyiiのデータベース上で、以下のSQLを実行し、カラムを追加します。

ALTER TABLE user ADD avatar varchar(128) NOT NULL after password;

次にprotected/models/User.php を開いてrules() メソッド内に以下を追加します。

// chap10: プロフィール画像のパス
array('avatar','length', 'max'=>128),

またattributeLabels() に以下を追加しましょう。

'avatar' => 'Avatar Path', // chap10: プロフィール画像のパス

またユーザを登録した際にavatarの情報に何も入っていないので、SiteController.php を開いてユーザ登録時に、デフォルトの画像のパスを指定します。79行目に以下のような変更を加えます。

				$new = new User();
				$new->email = $model->email;              // E-mail
				$new->name  = '';                         // 名前は空にしておく
				$new->password = sha1($model->password);  // ユーザの入力したパスワードを暗号化
				$new->role  = 0;                          // 一般ユーザ
				$new->vcode = rand(1000000,9999999);      // メール認証用の認証コード(100万~1000万未満)
				$new->valid = false;                      // 認証済みか(未認証)
				$new->avatar = 'default.png';             // chap10: プロフィール画像のデフォルト値

 


Userモデルの変更(imageの追加)

次にファイルのイメージデータを処理するためのプロパティ(image)をUserのモデルに追加します。ただしimage自体はデータベース上には存在しません。

上記と同様にUser.phpを開きます。先頭に以下のように$image の定義を追加します。

class User extends CActiveRecord
{
	// chap9: プロフィール編集用のフォーム追加データ
	public $newPassword;
	
	// chap10: 画像処理用のプロパティ
	public $image;

さらにrules() 内に以下のバリデーションルールを追加します。ファイルの拡張子・形式はjpg, gif, pngのみに限ることとします。mimeTypesの指定があるのは、拡張子だけ偽装した場合に発見できるようにするためです。また必ずしも画像の入力があるとは限らないので(プロフィール画像を設定・更新する場合のみ入力があるため)、allowEmptyをtrueにして、画像のデータが空になることを許可しています。入力データは3MBを上限に設けています。

	// chap10: image
	array('image','file','types'=>'jpg, gif, png', // ファイル拡張子のチェック
			             'mimeTypes'=>'image/gif, image/jpeg, image/png', // mimetypeの制限
			             'maxSize'=>1024*1024*3, // ファイルサイズの制限(3MB以下)
			             'allowEmpty'=>true), // 画像の更新をしない場合もあるので空でもよい

同様に、attributeLabels() 内に以下を追加します。

'image' => 'Profile Image', // chap10: プロフィール画像

 


ビューファイルの変更

次にviewファイルを変更して、ファイルアップロードのフォーム部品を追加します。

前章で作ったviews/user/profile.php を開きます。

先頭でファイルアップロードに対応したフォームにするために以下のように変更します。(‘htmlOptions’から始まる部分を追加します。)

<?php $form=$this->beginWidget('CActiveForm', array(
    	'id'=>'profile-form',
	    'enableAjaxValidation'=>true, // ajaxで処理
	    'clientOptions'=>array(
		    'validateOnSubmit'=>true,
	    ),
	    'htmlOptions'=>array('enctype'=>'multipart/form-data'), // chap10: ファイルアップロード用に変更
)); ?>

さらに45行目あたりに以下を追加します。

	<div class="row">
		<?php echo $form->labelEx($model,'image'); ?>
		<?php echo $form->fileField($model,'image'); ?>
		<?php echo $form->error($model,'image'); ?>
	</div>

 


UserControllerの変更/モデル内でのファイル名生成、画像の保存・リサイズ

次にUserController.php を変更します。actionProfile() のメソッドを参照し、以下のように変更を加えます。

 // chap9: プロフィールの編集機能
 public function actionProfile() {
     // 更新に成功した場合
     $update = false;
 
     // 自身のユーザデータを取得
     $user = User::model()->findByPk(Yii::app()->user->id);
 
     // ajax
     if(isset($_POST['ajax']) && $_POST['ajax']==='profile-form')
     {
         echo CActiveForm::validate($user);
         Yii::app()->end();
     }
 
     // フォームに対して入力があった場合
     if(isset($_POST['User'])) {
         // ユーザからの入力を処理
         if(isset($_POST['User']['email']))
             $user->email = $_POST['User']['email'];
         if(isset($_POST['User']['name']))
             $user->name = $_POST['User']['name'];
         if(isset($_POST['User']['newPassword']))
             $user->newPassword = $_POST['User']['newPassword'];
         // chap10: 画像の入力があれば$user->imageに代入
         if(isset($_POST['User']['image']))
             $user->image = CUploadedFile::getInstanceByName('User[image]');
 
         // データを保存する場合:
         if($user->validate()) {
             $user->save();
             $update = true;
         }
     }
 
     // フォームviewを表示
     $this->render('profile',array('model'=>$user,'update'=>$update));
 }

ここでは以下の2行を追加しただけです。

			// chap10: 画像の入力があれば$user->imageに代入
			if(isset($_POST['User']['image']))
				$user->image = CUploadedFile::getInstanceByName('User[image]');

 

ただしこれだけの変更では、Saveをしても画像が保存されないですし、またavatarの画像のパスも設定されていません。これらの処理はUserのモデルの中で処理することにします。ここで再びUser.php (モデル)を開いてください。

ここでのやり方としては、

  • validation(=入力データのチェック検査) が完了した後に自動で実行されるbeforeSave()のメソッド内で、画像のファイル名を生成し、avatarに代入します。なぜそこで行うかというと、そこでは既にvalidationの検査が完了しているため、ファイル名の拡張子が正しく取得できることが保証されているためです(画像のファイルパスには拡張子まで含まれます。) そのため画像のファイルパスの設定は、beforeSave内で行えば、余計な例外処理などを考えなくて済みます。
  • 次に画像の実際のアップロードの作業は、afterSave() というデータベースへの保存が完了してから実行されるメソッド内で実行します。確率は低いですが、validationが完了しているのにsaveが失敗してしまった場合、saveの前に画像をアップロードしてしまうと、使われない画像がどんどんサーバーのフォルダに溜まっていってしまいます。ですので画像のアップロード作業は、実際にデータベースにデータが反映されたことが保証されてから、行うようにします。また画像のリサイズもその後行います。

User.phpbeforeSave のメソッドを開き、以下のように変更を加えます。

	protected function beforeSave() {
		// newPasswordに入力があれば
		if(strlen($this->newPassword) > 0) $this->password = sha1($this->newPassword);
		
		// chap10: 画像の入力があればファイル名を生成し$this->avatarに設定する
		if (!empty($this->image)) {
				$ext=strtolower($this->image->getExtensionName()); // 拡張子の取得(小文字)
				$randomNum=rand(10000, 99999); // ファイル名用に乱数を取得
				$time=date('Y-m-d'); // 本日の日付
				$fileName="{$time}-{$randomNum}.{$ext}"; // ファイル名
				$fullUrl= 'avatar' . '/' . $fileName; // フルファイルパス
				$this->avatar=$fullUrl; // avatarのURLを生成
		}
		
		return parent::beforeSave();
	}

ここで行っている処理内容は、ユーザが画像のアップロードを行おうとした際に、拡張子を取得し、その拡張子とランダムに生成した数値と日付から、ファイル名を生成しています。ファイル名の生成がされたら、それをavatarに代入しています。

次に実際に画像を保存する処理とリサイズの処理を追加します。同様にUser.php のモデル内にafterSave() というメソッドを追加します。内容は以下の通りです。

	/*
	 * chap10: saveの後処理
	 */
	protected function afterSave() {
    		// 画像がアップロードされた場合は、画像の保存・修正を行う
		    // 画像の更新を行わない場合は$this->imageは空のため発動しない
		    if (!empty($this->image)) {
			        // 最終版ファイル名
			        $path = Yii::app()->params['webRoot']. '/' . $this->avatar;
			        // 保存
			        $this->image->saveAs($path);
			        // 画像サイズの修正
			        $img = Yii::app()->imagemod->load($path);
			        $img->image_resize = true; // サイズ変更を実施する
			        $img->image_x = 320; // x 320に矯正
			        $img->image_y = 320; // y 320に矯正
			        $img->file_auto_rename = false; // 勝手にファイル名を変更しないようにする
			        $img->file_overwrite = true;    // 上書きを許可する
			        $img->process(Yii::app()->params['webRoot'].'/'.'avatar'); // 保存実行
	    	}
	}

 

同様にユーザからの画像のアップロードによる入力があった場合に、ファイルの保存作業を行っています。そして、保存が行われたら、その後で再度、画像ライブラリを呼び出して、画像のリサイズを行っています。ここでは画像の縦横のサイズを強制的に320pxに矯正するような設定をしています。(大きなサイズの画像が溜まっていくことを防ぐためにこのようにしています。)

本ライブラリのより詳細な使用例を知りたい方はこちらのURLを参照していただければと思います。

http://www.verot.net/php_class_upload_samples.htm


 アップロードした画像を表示

最後にアップロードした画像が実際に表示できるように、再度ビュー側を変更します。protected/views/user/profile.php を開いてください。

52行目あたりのimage のフォームを出力した部分の下辺りに以下のソースを挿入します。

	<div class="row">
	    <img src="<?php echo Yii::app()->request->baseUrl . '/' . $model->avatar; ?>">
	</div>

全体のソースは以下のようになるはずです。

<?php
// chap9
$this->pageTitle=Yii::app()->name . ' - プロフィール編集';
$this->breadcrumbs=array(
	'プロフィール編集',
);
?>

<h1>プロフィール編集</h1>

<div class="form">
<?php if($update == true):?>
    <p><font color="red">更新に成功しました</font></p>
<?php endif;?>
<?php $form=$this->beginWidget('CActiveForm', array(
	'id'=>'profile-form',
	'enableAjaxValidation'=>true, // ajaxで処理
	'clientOptions'=>array(
		'validateOnSubmit'=>true,
	),
	'htmlOptions'=>array('enctype'=>'multipart/form-data'), // chap10: ファイルアップロード用に変更
)); ?>

	<p class="note"><span class="required">*</span>の項目は入力が必須となります</p>
	<p class="note">HTMLタグは自動的に変換されます。</p>

	<?php echo $form->errorSummary($model); ?>

	<div class="row">
		<?php echo $form->labelEx($model,'email'); ?>
		<?php echo $form->textField($model,'email'); ?>
		<?php echo $form->error($model,'email'); ?>
	</div>
	
	<div class="row">
		<?php echo $form->labelEx($model,'name'); ?>
		<?php echo $form->textField($model,'name'); ?>
		<?php echo $form->error($model,'name'); ?>
	</div>

	<div class="row">
		<?php echo $form->labelEx($model,'newPassword'); ?>
		<?php echo $form->textField($model,'newPassword'); ?>
		<?php echo $form->error($model,'newPassword'); ?>
	</div>
	
	<div class="row">
		<?php echo $form->labelEx($model,'image'); ?>
		<?php echo $form->fileField($model,'image'); ?>
		<?php echo $form->error($model,'image'); ?>
	</div>
	
	<div class="row">
	    <img src="<?php echo Yii::app()->request->baseUrl . '/' . $model->avatar; ?>">
	</div>

	<div class="row buttons">
		<?php echo CHtml::submitButton('プロフィールを編集する'); ?>
	</div>

<?php $this->endWidget(); ?>

</div><!-- form -->

 

これでプロフィール写真が画面に表示されるようになるでしょう。


 今回の成果物:

以下に今回の成果物をダウンロードできるように置いておきます。データベースなども今回の章の差分を反映したものになっていますので、そのまま使うことができます。

ダウンロードはこちらをクリックしてください (default.pngはzipの中に既に設置されています)

  •  htdocs-chap10.zip : ソースコード
  • user.sql
  • bbs.sql

上記の3つのファイルです。SQLのバックアップファイルについてはMySQL上でyiiという名前のデータベースを作って、その上で展開してください。