QuictyによるPHP Webアプリの超高速開発-(6)画像ファイルのアップロード
5月 16th, 2008
前回の「(5)tableのJOINとプルダウンメニュー」では、複数テーブルのJOINを扱った。
今回は、画像イメージをアップロードし、別テーブルで管理し、表示する際にJOINする方法を紹介する。
この例では、新にQuictyの以下の機能の使い方を紹介する。
- ファイルのアップロード
- ImageMagickによる画像のリサイズ
今回は、以下のページとデータセットを作成する。
| データセット | マスタメンテページクラス | ページクラスモジュール |
| restaurant | restaurant | index.class.php |
| image | image | image.class.php |
各データセットは以下のように設定する
restaurantデータセット
| Name(カラム名) | タイトル | DB型 | Quicty型 |
| id | - | int | id |
| name | 店名 | text | text |
| image_file | 画像ファイル | - | file(Virtual) |
| image_file | 画像の説明 | - | text (Virtual) |
※image_file、image_captionはVirtual指定なので、フォームに表示されるが、テーブルにカラムは作成されない。
imageデータセット
| Name(カラム名) | タイトル | DB型 | Quicty型 |
| id | - | int | id |
| image_file | 画像ファイル | - | file(Virtual) |
| image_caption | 料理ID | int | int |
| image_type | 画像タイプ | text | text |
| image_dir | 画像のディレクトリ | text | text |
| image_filename | 画像のファイル名 | text | text |
| image_tag | 画像タグ | text | textarea |
| thumbnail_tag | サムネイルタグ | text | textarea |
| restaurant_id | レストランID | int | int |
※image_fileはVirtual指定なので、フォームに表示されるが、テーブルにカラムは作成されない。
サンプルプログラムコード「restaurant05_3.zip」(5月17日更新)
※使用法
(1) 展開したフォルダ「restaurant05」を「QT」フォルダに入れる
(2) Qtビルダーの「アプリケーションの読み込み」で
サイト名「レストラン05」
Name「restaurant05」
で、登録ボタンを押す。
(3) ソースコードが読み込まれて、Qtビルダーに登録される。
(4) 「restaurant05」の「全データセットの初期化」を実行。
以上で、使用可能になります。
ご注意:このブログのサンプルコードは、シングルクオートやダブルクオートなどがスマートクオートに変換されているため、コピペしてもそのままではエラーになることが多いです(エディタによる)。
なるべく、サンプルコードをダウンロードしてご利用ください。
ImageMagickのインストール
画像処理ソフトのImageMagickをインストールしておく。
以下はMac OS XでMacPortsを使ってインストールする場合。
————————————————
$ sudo port install imagemagick
————————————————
imagemagickの「convert」コマンドが入っているディレクトリをしらべ、Quictyアプリの設定ファイルに書き込んでおく。
設定ファイル「etc/conf/conf_values.conf」をテキストエディタで修正。
————————————————
$this->C['IMGMGK_BIN'] = ‘/opt/local/bin’;
————————————————
の1行を加える。

WindowsにおけるImageMagickのインストール
以下を参考にしてください。
Apache設定ファイルの変更
Apacheの設定ファイルに「Options FollowSymLinks」を設定し、シンボリックファイルを辿れるようにする。
(「MacでWeb開発-(10) XAMPPにQuictyフレームワークを設置」では
/Application/xampp/etc/extra/httpd-vhost.cof内に設定。)
———————————————————
<Directory “/Users/tomoyun/QT”>
AllowOverride None
Options None
Order deny,allow
Deny from all
Allow from localhost
Allow from quicty.local
</Directory>
——————————————————–
↓(修正)
——————————————————–
<Directory “/Users/tomoyun/QT”>
AllowOverride None
Options FollowSymLinks
Order deny,allow
Deny from all
Allow from localhost
Allow from quicty.local
</Directory>
——————————————————–
アプリの登録
サイト名「レストラン05」
Name「restaurant05」
でアプリケーションを登録。

画像をアップロードしたフォルダをWebブラウザで参照できるように、シンボリックリンクを作成。
————————————————————————————————
$ cd QT/restaurant05/htdocs/img
$ ln -s ../../var/upload/ upload
$ ls -la
$ mkdir upload/image
$ chmod a+w upload/image
————————————————————————————————

「トップページに機能を追加」をクリック。
タイトル「レストラン」
Name「restaurant」
ページの種類「マスタメンテ/マルチページ」
で登録。

text型フィールドを1つ追加する。
タイトル「店名」
Name「name」

部品テンプレート「input_form.inc」と「table_view.inc」を保存。

「ページを追加」で新しいページを追加。
タイトル「画像」
Name「image」
ページの種類「マスタメンテ/マルチページ」
で登録。

file型のフィールドを追加。
タイトル「画像ファイル」
Name「image_file」
Virtual Field「チェック」
で登録。

以後、下の画像のように必要なフィールドを追加する。

プログラムの修正
lib/Pages/image_classes/_base.class.php
————————————————————————————————
function display_record($condition=”) {
require_once ‘form_options.class.php’;
$this->form = $this->new_data_set(‘image’);
$options = new image_form_options($this);
$options->set_form_options();
$this->add_form_options();
$this->form->validate_and_freeze();
$this->form->bind_button_control();
$result = $this->form->automatic_form_handler($condition);
$this->image = $result['image'] ? $result['image'] : $result['values'];
return $result;
}
————————————————————————————————
↓(修正)
————————————————————————————————
function display_record($condition=”) {
require_once ‘form_options.class.php’;
$this->form = $this->new_data_set(‘image’);
$options = new image_form_options($this);
$options->set_form_options();
$this->add_form_options();
$this->form->validate_and_freeze();
$this->form->bind_button_control();
$result = $this->form->automatic_form_handler($condition);
$this->image = $result['image'] ? $result['image'] : $result['values'];
if($this->QuictyStatus==’INSERT’ or $this->QuictyStatus==’UPDATE’) {
umask(0);
$basename = ‘image’.$this->image['id'];
$image_dir = ‘../var/upload/image’;
$ht_img_dir = quicty_base_path().’/img/upload/image’;
$dir1 = substr(sprintf(“%08d”,$this->image['id']),0,2);
if(!file_exists(“$image_dir/$dir1″)) mkdir(“$image_dir/$dir1″,0777);
$dir2 = substr(sprintf(“%08d”,$this->image['id']),2,2);
if(!file_exists(“$image_dir/$dir1/$dir2″)) mkdir(“$image_dir/$dir1/$dir2″,0777);
$dir3 = substr(sprintf(“%08d”,$this->image['id']),4,2);
if(!file_exists(“$image_dir/$dir1/$dir2/$dir3″)) mkdir(“$image_dir/$dir1/$dir2/$dir3″,0777);
$subdir = “$dir1/$dir2/$dir3″;
$file = $this->form->move_upload_file2dir(“$image_dir/$subdir”,$basename,$element=’image_file’);
if($file['result']) {
$this->resize_image(“$image_dir/$subdir”,”$ht_img_dir/$subdir”,$file,$basename);
unlink(“$image_dir/$subdir/”.$file['name']);
}
}
return $result;
}
function resize_image($image_dir,$ht_img_dir,$file,$basename) {
$file = $this->form->resize_image($file,64,64,$image_dir,$basename.’_t.’.$file['type'],’thumbnail’);
chmod($file['thumbnail_path'],0666);
$thumbnail_image = ““;
$file = $this->form->resize_image($file,160,160,$image_dir,$basename.’_n.’.$file['type'],’normal’);
chmod($file['normal_path'],0666);
$normal_image = ““;
$this->form->update_table(array(‘image_dir’=>$file['dir'],’image_filename’=>$file['name'],’image_tag’=>$normal_image,’thumbnail_tag’=>$thumbnail_image),”where id=”.$this->image['id']);
}
————————————————————————————————
(修正例)


画像登録のテスト
imageクラスだけで画像登録の動作を確認する。
任意の画像ファイルを選択し、
画像の説明「画像テスト」
レストランID「1」
と入力して投稿。

投稿後、一覧に画像が表示される。

レストランの一覧に表示
imageテーブルの画像をレストランの一覧に表示できるようにプログラムを修正。
「lib/Pages/index_classes/list_options.class.php」内のmake_where_condition()
————————————————————————————————
function make_where_condition($add_condition=”,$cond=’AND’) {
$search_system_file = $this->page->home_dir.’/etc/search_view/restaurant.sys’;
if(file_exists($search_system_file)) {
$search_system = read_data_array($search_system_file);
} else {
$search_system = array(
‘keyword’=>array(‘title’=>’restaurant.name’,'url’=>’restaurant.url’), // keyword search target column
‘condition’=>array(‘id’=>’restaurant.id’,'application_id’=>’restaurant.application_id’), // id search target column
//’period’=>array(‘start_date’=>array(‘column’=>’date’,'end’=>’end_date’))
);
}
return $this->list->make_condition($search_system,$add_condition,$cond);
}
————————————————————————————————
↓(修正)
————————————————————————————————
unction make_where_condition($add_condition=”,$cond=’AND’) {
$search_system = array(
‘keyword’=>array(‘title’=>’restaurant.name’,),
);
return $this->list->make_condition($search_system,$add_condition,$cond);
}
————————————————————————————————
「lib/Pages/index_classes/list_options.class.php」内のmake_join_condition()
————————————————————————————————
function make_join_condition() {
//$this->list->real_count = true; // set this flag if join other table
//$joins[] = “LEFT JOIN {table_name} ON restaurant.{table_name}_id={table_name}.id”;
//$join_condition = implode(‘ ‘,$joins);
return $join_condition;
}
————————————————————————————————
↓(修正)
————————————————————————————————
function make_join_condition() {
//$this->list->real_count = true; // set this flag if join other table
$joins[] = “LEFT JOIN image ON restaurant.id=image.restaurant_id”;
$join_condition = implode(‘ ‘,$joins);
return $join_condition;
}
————————————————————————————————
「lib/Pages/index_classes/list_options.class.php」内のmake_select_fields()
————————————————————————————————
function make_select_fields() {
$this->select_system = array(
‘restaurant’=>’*', // specify all column
//’restaurant’=>array(‘id’,'name’,'url’), // specify each column
//’restaurant’=>array(‘id’=>’restaurant_id’), //specify another column name
);
return $this->list->make_select_fields($this->select_system);
}
————————————————————————————————
↓(修正)
————————————————————————————————
function make_select_fields() {
$this->select_system = array(
‘restaurant’=>’*', // specify all column
‘image’=>array(‘image_caption’,'image_tag’,'thumbnail_tag’),
);
return $this->list->make_select_fields($this->select_system);
}
————————————————————————————————
「lib/Pages/index_classes/list_options.class.php」内のmake_sort_condition()
————————————————————————————————
function make_sort_condition() {
$sort_system = array(
‘default’=>’ order by id’,
// sort keys
‘columns’=>’*', // specify all column
//’columns’=>array(‘id’,'name’,'url’), // specify each column
);
if($this->page->sort_option) {
$this->list->set_sort_option($this->page->sort_option,$this->page->order_option);
}
return $this->list->make_order($sort_system);
}
————————————————————————————————
↓(修正)
————————————————————————————————
function make_sort_condition() {
$sort_system = array(
‘default’=>’ order by restaurant.id’,
‘columns’=>’*', // specify all column
);
if($this->page->sort_option) {
$this->list->set_sort_option($this->page->sort_option,$this->page->order_option);
}
return $this->list->make_order($sort_system);
}
————————————————————————————————
「view/index.html」
————————————————————————————————
<!– Main Contents–>
<div id=”contents-main”>
{include file=’includes/common/auto_search_form.inc’}
{*include file=’includes/restaurant/search_form.inc’*}
{include file=’includes/common/messages.inc’}
{include file=’includes/common/auto_table_view.inc’}
{*include file=’includes/restaurant/table_view.inc’*}
{include file=’includes/common/auto_pager.inc’}
</div>
<!– End of id=”contents-main” –>
————————————————————————————————
↓(修正)
————————————————————————————————
<!– Main Contents–>
<div id=”contents-main”>
{include file=’includes/common/auto_search_form.inc’}
{*include file=’includes/restaurant/search_form.inc’*}
{include file=’includes/common/messages.inc’}
{include file=’includes/restaurant/table_view.inc’}
{include file=’includes/common/auto_pager.inc’}
</div>
<!– End of id=”contents-main” –>
————————————————————————————————
「view/includes/restaurant/table_view.inc」
————————————————————————————————
{if $smarty.foreach.restaurant_view.first}
<tr>
<th> </th>
<th class=”col-name”>{$row.name.label}<span id=”asc-name”><a href=”./index.html?sort=name&order=asc”>{$W.asc_mark}</a></span><span id=”desc-name”><a href=”./index.html?sort=name&order=desc”>{$W.desc_mark}</a></span></th>
</tr>
{/if}
<tr>
<td><a href=”{$display_cmd}?id={$row.id.body}”>{$row.sequence.value}</a></td>
<td>{$row.name.html}</td>
</tr>
————————————————————————————————
↓(修正)
————————————————————————————————
{if $smarty.foreach.restaurant_view.first}
<tr>
<th> </th>
<th class=”col-name”>{$row.name.label}<span id=”asc-name”><a href=”./index.html?sort=name&order=asc”>{$W.asc_mark}</a></span><span id=”desc-name”><a href=”./index.html?sort=name&order=desc”>{$W.desc_mark}</a></span></th>
<th>Image</th>
</tr>
{/if}
<tr>
<td><a href=”{$display_cmd}?id={$row.id.body}”>{$row.sequence.value}</a></td>
<td>{$row.name.html}</td>
<td>{$row.thumbnail_tag.value}</td>
</tr>
————————————————————————————————
レストラン一覧の動作確認
「レストラン 追加」でお店を1軒目のお店を登録する。

先ほど登録した画像は、レストランID=1としておいたので、ここでJOINされてサムネイルが表示される。

レストランの詳細ページに画像を表示する
「view/info.html」を複製し、「view/edit.html」を作成しておく。

各プログラムとテンプレートを修正する。
「lib/Pages/index.class.php」
————————————————————————————————
function dispatch_info() {
$this->assign_search_form();
$result = $this->display_record();
$this->assign_form(‘input_form’,$this->form);
$this->assign_page_and_pathlist($this->W['restaurant'].’ ‘.$this->W['display']);
return $this->display($this->current_template);
}
————————————————————————————————
↓(修正)
————————————————————————————————
function dispatch_info() {
$this->assign_search_form();
$result = $this->display_record();
$list_table = $this->list_records(”);
$this->assign(‘form_view’,$this->list->display_table_value($list_table));
$this->assign_form(‘input_form’,$this->form);
$this->assign_page_and_pathlist($this->W['restaurant'].’ ‘.$this->W['display']);
return $this->display($this->current_template);
}
————————————————————————————————
「view/info.html」
————————————————————————————————
<!– Main Contents–>
<div id=”contents-main”>
{include file=’includes/common/auto_search_form.inc’}
{*include file=’includes/restaurant/search_form.inc’*}
{include file=’includes/common/messages.inc’}
{include file=’includes/common/auto_input_form.inc’}
{*include file=’includes/restaurant/input_form.inc’*}
</div>
<!– End of id=”contents-main” –>
————————————————————————————————
↓(修正)
————————————————————————————————
<!– Main Contents–>
<div id=”contents-main”>
{include file=’includes/common/auto_search_form.inc’}
{*include file=’includes/restaurant/search_form.inc’*}
{include file=’includes/common/messages.inc’}
{include file=’includes/restaurant/form_view.inc’}
</div>
<!– End of id=”contents-main” –>
————————————————————————————————
「view/includes/restaurant/form_view.inc」
————————————————————————————————
<table cellspacing=”0″ summary=”form view”>
<tr>
<th><label for=”name”>{$restaurant_form.name.label}</label></th>
<td>{$restaurant_form.name.html}</td>
</tr>
</table>
————————————————————————————————
↓(修正)
————————————————————————————————
<table cellspacing=”0″ summary=”form view”>
<tr>
<th><label for=”name”>{$restaurant_form.name.label}</label></th>
<td>{$restaurant_form.name.html}</td>
</tr>
<tr>
<th><label for=”image_tag”>{$restaurant_form.image_tag.label}</label></th>
<td>{$restaurant_form.image_tag.html}</td>
</tr>
</table>
————————————————————————————————
動作確認
レストラン一覧でお店の番号をクリックすると、画像付きで表示される。
(サンプル画像なし)
restaurantデータセットを拡張し、お店と画像を同時投稿を可能にする
restaurantデータセットにfile型フィールドを追加。
タイトル「画像ファイル」
Name「image_file」
Virtual Field「チェック」

続けてtext型フィールドを追加。
タイトル「画像の説明」
Name「image_caption」
Virtual Field「チェック」

下の画面と同じようになっていることを確認。

部品テンプレート「input_form.inc」を保存。

プログラムとテンプレートの修正
「lib/Pages/index_classes/_base.class.php」
————————————————————————————————
function display_record($condition=”) {
require_once ‘form_options.class.php’;
$this->form = $this->new_data_set(‘restaurant’);
$options = new index_form_options($this);
$options->set_form_options();
$this->add_form_options();
$this->form->validate_and_freeze();
$this->form->bind_button_control();
$result = $this->form->automatic_form_handler($condition);
$this->restaurant = $result['restaurant'] ? $result['restaurant'] : $result['values'];
return $result;
}
————————————————————————————————
↓(修正)
————————————————————————————————
function display_record($condition=”) {
require_once ‘form_options.class.php’;
$this->form = $this->new_data_set(‘restaurant’);
$options = new index_form_options($this);
$options->set_form_options();
$this->add_form_options();
$this->form->validate_and_freeze();
$this->form->bind_button_control();
$result = $this->form->automatic_form_handler($condition);
$this->restaurant = $result['restaurant'] ? $result['restaurant'] : $result['values'];
require_once ‘Pages/image.class.php’;
$image = new image($this->home_dir);
$image->QuictyStatus = $this->QuictyStatus;
if($this->QuictyStatus==’INSERT’ ) {
$_POST['restaurant_id'] = $this->restaurant['id'];
$image->display_record();
} elseif ($this->QuictyStatus==’UPDATE’ OR $this->QuictyStatus==’DELETE’) {
$_POST['restaurant_id'] = $_POST['id'];
$condition = ‘restaurant_id=’.$_POST['id'];
$image->display_record($condition);
}
return $result;
}
————————————————————————————————
「view/edit.html」
————————————————————————————————
<!– Main Contents–>
<div id=”contents-main”>
{include file=’includes/common/auto_search_form.inc’}
{*include file=’includes/restaurant/search_form.inc’*}
{include file=’includes/common/messages.inc’}
{include file=’includes/common/auto_input_form.inc’}
{*include file=’includes/restaurant/input_form.inc’*}
</div>
<!– End of id=”contents-main” –>
————————————————————————————————
↓(修正)
————————————————————————————————
<!– Main Contents–>
<div id=”contents-main”>
{include file=’includes/common/auto_search_form.inc’}
{*include file=’includes/restaurant/search_form.inc’*}
{include file=’includes/common/messages.inc’}
{include file=’includes/restaurant/input_form.inc’}
</div>
<!– End of id=”contents-main” –>
————————————————————————————————
「view/includes/restaurant/form_view.inc」
————————————————————————————————
<tr>
<th><label for=”image_file”>{$restaurant_form.image_file.label}</label></th>
<td>{$restaurant_form.image_file.html}</td>
</tr>
————————————————————————————————
↓(修正)
————————————————————————————————
<tr>
<th><label for=”image_file”>{$W.image}</label></th>
<td>{$restaurant_form.image_tag.html}</td>
</tr>
————————————————————————————————
動作確認
「レストラン 追加」で店名、画像ファイル、画像の説明を投稿。

レストラン一覧に画像付きで表示される。

レストランの番号をクリックすれば大きめな画像付きで表示。

以上。
ヒント
ファイルがアップロードできない場合は、PHPの設定ファイル「php.ini」の「upload_tmp_dir =」の項目に明示的にテンポラリディレクトリを記述する。
————————————————————————————————
;;;;;;;;;;;;;;;;
; File Uploads ;
;;;;;;;;;;;;;;;;
; Whether to allow HTTP file uploads.
file_uploads = On
; Temporary directory for HTTP uploaded files (will use system default if not
; specified).
;upload_tmp_dir =
; Maximum allowed size for uploaded files.
upload_max_filesize = 2M
————————————————————————————————
↓(修正)
————————————————————————————————
;;;;;;;;;;;;;;;;
; File Uploads ;
;;;;;;;;;;;;;;;;
; Whether to allow HTTP file uploads.
file_uploads = On
; Temporary directory for HTTP uploaded files (will use system default if not
; specified).
upload_tmp_dir = “/tmp”
; Maximum allowed size for uploaded files.
upload_max_filesize = 2M
————————————————————————————————








