あーる学習帳

自分が勉強したことや気になることなど、99%自分用です。コードを書いてるのでPCから閲覧を推奨。

PHP実習:ファイルへの書き込み、ファイルからの読み込み

昼にITに詳しい方々のつぶやきを目にし、モチベーションが高まっております。こんなのを作ってみたいなぁ、という方向性も漠然としているものの見えてきた感じ。

 

※第10章では「メール送信とファイル操作」というタイトルでPHPによるメール送信とファイル操作について扱っているが、書籍で想定している環境と自分が使っている仮想環境が違いすぎるのでメールの設定が一筋縄ではいかないことが判明。今回はメールの部分は飛ばし、ファイルの読み書きについてのみ扱う。

第10章-03

テイストファイルなどへの書き込みをする場合、いきなりファイルに文字列を記録するのではなく、いくつかの手順を踏む必要がある。

  1. ファイルを開く
  2. ファイルをロックする
  3. ファイルに書き込む
  4. ファイルのロックを解除する
  5. ファイルを閉じる 

 ファイルを開く際、書き込み対象となるファイルがない場合は自動で作成するなどの設定も可能。次にファイルに複数人からの同時書き込みが起こらないように回避する対策を施す。これを「ファイルのロック」と呼んでいる。これによって、先に書き込み処理を行ったプログラムの終了を待ってから次の書き込みが行われるようになる。もしこの対策をしなかった場合、ファイルが壊れてしまう可能性もあるので注意する。

今回はアクセスログを作ってみる。

 <?php
 $time=date('H:i:s');
 $ip=$_SERVER['REMOTE_ADDR'];
 $data="{$time}\t{$ip}\r\n";
 $file=@fopen('access.log','a') or die(',ファイルを開けませんでした。');
 flock($file,LOCK_EX);
 fwrite($file,$data);
 flock($file,LOCK_UN);
 fclose($file);
 echo "アクセスログを記録しました。";

IPアドレスは$_SERVER['REMOTE_ADDR']で取得できる。

ファイルを開く際にはfopen()という関数の頭に@をつける。これは「エラー制御演算子」といって、エラーが発生する可能性のあるものに付けた場合、エラーが発生しても警告を発生しなくなる。今回のコードではエラーが発生した場合、自前のメッセージを表示して処理を中止させている。「or」は演算子の一種で、左辺のfopen()が問題なく動作した場合は実行されない。fopen()の第2引数に'a'とつけておけば、第1引数で指定したファイルが存在しない場合は新たにファイルを作成する。ここで$fileに代入されるのは「resource(リソース)」と呼ばれる型のもので、これを取得することによってファイルを操作することが可能になる。

実行するとこのような画面になる。

f:id:R_de_aru:20180705202640p:plain

この画面になった後、Cyberduckを見るとaccess.logができていることがわかる。

f:id:R_de_aru:20180705202714p:plain

このaccess.logを開くと、先ほどの画面を出した時間とどのIPから開かれたのかがログとして残っているのが分かる。

f:id:R_de_aru:20180705202756p:plain

flock()は第1引数にファイルのリソース、第2引数にオペレーションを設定する。

flock( ファイルのリソース , オペレーション )

オペレーション一覧

LOCK_EX...書き手が行う排他的ロック(他者の読み書きを禁止) 

LOCK_SH...読み手が行う共有ロック(他者の書き込みを禁止)

LOCK_UN...ロック解除

今回は自分が書き込む間は他者が書き込みできないようにするためのロックをかけるので、LOCK_EXを使用する。

fwrite()は第1引数にリソース、第2引数に文字列を設定する。「"{$time}\t{$ip}\r\n"」のように、変数や改行を展開させるためにダブルクォーテーションで囲むこと。

最後にLOCK_UNを設定してロックを解除する。これにより、もし書き込みを待っている別のユーザーがいれば新たな書き込みが開始される。fclose()でファイルを閉じることを明記する。この一連の流れを必ず行うこと。

 

第10章-04

今度はファイルの読み込みを行う。その前に開き方についてまとめるが、先ほどは@fopen('access.log','a') というように「a」で追加書き込み専用の設定を行った。これをオープンモードという。主要なモードについては以下の通りとなる。

r...読み込み専用

r+...rに加えて書き込みも可能

w...ファイルをクリアして上書き書き込み

w+...wに加えて読み込みも可能

a...既存の内容に追記書き込み

a+...aに加えて読み込みも可能 

x...ファイルが存在する場合にエラーを出す

今回はファイルを読み込むだけなので「r(=read)」でよい。 実際にコードを書いていく。

$file=@fopen('access.log','r') or die(',ファイルを開けませんでした。');
flock($file,LOCK_SH); // 共有ロック
while (!feof($file)){
 $line=fgets($file);
 echo '<p>'.$line.'</p>';
}
flock($file,LOCK_UN);
fclose($file); 

 

f:id:R_de_aru:20180705204629p:plain

このように出力される。

feof()はファイルポインタ(何行目がフォーカスを持っているか示す)が終端にくるとTrueを返す。fgets()は1行分の文字列を取得した後にファイルポインタを1行進める。

eofは「EndOfFile」の略。今のatomの設定では見ることはできないが、テキストファイルの終端には必ず存在している。

次に、今のコードを改造して行数をカウントして出力する。

$file=@fopen('access.log','r') or die(',ファイルを開けませんでした。');
flock($file,LOCK_SH); // 共有ロック
$count=0; //カウント用変数の初期化
while (!feof($file)){
 $line=fgets($file);
 echo '<p>'.$line.'</p>';
 $count++; // カウント追加
}
flock($file,LOCK_UN);
fclose($file);
echo ($count-1).'回の訪問がありました。';//最後の行数の1を引いて出力

f:id:R_de_aru:20180705205224p:plain

行数のカウントは$countを初期化し、fgets()するごとに1を足して実現する。書き込み時に改行コードを入れているため、「eof」の制御コードは文字列の1つ下の行にある。この行もカウントされてしまうので、$countから1引いて出力すればログと行数が一致する。

また、ファイルの読み込み方法はfgets()だけではない。

<?php

 $file=file('access.log');

 foreach($file as $line){

  echo '<p>'.$line.'</p>';

 }

 file()を使うと、行ごとに格納した配列として取得する。しかし、便利である反面、巨大なテキストを相手にした際のメモリの消費量が大きくなるという問題もある、

今回の実習はメール送信が入っているため割愛。お問い合わせフォームを作るという実習だったのでやりたかった…。

PHP実習:正規表現と文字列の置き換え、違反ワードをチェックする機能を作ろう

今夜も正規表現について学んでいこう。

 

第9章-04

今回は文字列の操作について学ぶ。クライアントからの入力値には空白や全角英数字などプログラムにとって不都合な文字が含まれている場合がある。そのようなときは文字列を操作しなければならない。

文字列の前後に「スペース」や「タブ」が入ってしまっている場合、それを取り除くための関数が用意されている。

trim()...文字列の両端の空白を削除

rtrim()...文字列の右端の空白を削除する

ltrim()...文字列の左端の空白を削除する

chop()...ltrim()と同じ意味 

chop()はltrim()のエイリアスである。 エイリアスとは名前だけを変えて昨日は同じものという意味。

trim()で取り除ける空白文字とは何を意味するのか、実際のコードで確認。

<?php
 $str1=' AB C '; // 文字列の前後に半角スペース
 $str2=' こんにちは '; // 文字列前にタブ2つ、後ろに全角スペース
 $result1=trim($str1);
 $result2=trim($str2);
 var_dump($result1);
 var_dump($result2); 

 出力結果は、「string(4) "AB C" string(18) "こんにちは "」となる。

前後に削除対象の文字列があればすべて取り除くことが分かるが、全角スペースは取り除けない。trim()の削除対象となるのは、半角空白、タブ、リターン、改行、Nullバイトなどである。

 

PHPにおける「mb」とはマルチバイトのことである。半角英数が1文字1バイトなのに対し、文字コードUTF-8における日本語は基本的に1文字に付き3バイトほどあるのでそのように呼ばれる。

mb系組み込み関数の代表的なものを以下に挙げる。

mb_strlen()...文字列の長さを得る

mb_strtolower()...文字列を小文字にする

mb_convert_encoding()...文字エンコーディングを変換する

mb_language()...現在の言語を設定あるいは取得する

mb_substr()...文字列の一部を得る

mb_convert_kana()...カナを変換する(全角と半角の変換)

mb_strpos()...指定した文字列が最初に現れる位置を探す 

この中で、mb_convert_kana()についてサンプルコードで学ぶ。

<?php
 $str1='KV:プログラミング';
 $str2='as:私はMOVIEが 好きです。';
 $result1=mb_convert_kana($str1,'KVas','UTF-8');
 $result2=mb_convert_kana($str2,'KVas','UTF-8');
 var_dump($result1);
 var_dump($result2);

それぞれオプションに「KV」「as」を設定して効果を確認する。

戻りとして「string(24) "KV:プログラミング" string(33) "as:私はMOVIEが 好きです。"」と画面に表示される。

mb_convert_kana()の第2引数には複数の変換オプションを指定することが可能である。「K」は「半角カタカナを全角カタカナに変換」「V」は「濁点付きの文字を1文字に変換(プ→プ)」を表す。もしVを指定しなかった場合、「プログラミング」は「フ゜ロク゛ラミンク゛」と表示されてしまうので、KとVはセットで運用するのがよい。「a」は「全角英数字を半角英数字に変換」「s」は「全角スペースを半角に変換」を意味する。また、他にも「n(全角数字を半角に変換)」「H(全角ひらがなを全角カタカナに変換)」などのオプションがある。

このように、データベースなどの登録前にクライアントからの入力値を最適な状態に整えることはメンテナンス上非常に大事なことである。

 

次に、文字を置き換えるコードであるpreg_replace()とstr_replace()について確認していく。preg_replace()は正規表現が使える分、str_replace()の精密版ともいえる。

<?php
 $str=' プログラミングを、習いたい。';
 $result1=preg_replace('/\s|、|。/','',$str);
 var_dump($result1); 

 これは戻りとして「string(36) "プログラミングを習いたい"」が画面に表示される。

preg_replace()は第1引数に正規表現、第2引数に置き換える文字列、第3引数に対象の文字列を入れる。今回は第1引数を「'/\s|、|。/'」とすることで、半角スペースか読点か句読点ならば「''(空の文字列)」に置き換えるという指定をしている。preg_replace()は対象の文字を置き換えた後、置き換え後の文字列を返してくる。また、str_replace()では第1引数に置き換えたい文字列を単純に指定する。

preg_replace( 正規表現 , 置き換える文字 , 対象の文字列)

str_replace( 検索する文字 , 置き換える文字 , 対象の文字列) 

 

第9章-05 実習

違反ワードをチェックする機能を作る。

制作の流れは、

  1. 検索対象の文字を整える
  2. 許可ワードを検索対象から外す
  3. 禁止ワードをチェックする

許可ワードは「*」に置き換える仕組みとする、禁止ワードの有無、置き換えた許可ワードを結果として表示する。

例:「コーヒー」を禁止ワードにした場合

禁止ワードが含まれています。ミルクコーヒー->ミルクコーヒー 

例:「パン」を禁止、「フライパン」を許可ワードにした場合

禁止ワードは含まれていません。フライパン->*

 要件定義は、

  • チェック機能を日本語に対応させる
  • 1つのワードに対して複数の禁止ワードが設定できるようにする
  • 許可ワードも設定できるようにする

まず、チェックしたいワードを検索しやすいように整えていく。

<?php
 $str='ミルクコーヒー';
 // 検索しやすいように文字列を整える
 $target=mb_strtolower($str,'UTF-8');
 $target=mb_convert_kana($target,'KVas','UTF-8');
 $target=preg_replace('/\s|、|。/','',$target);

 $flag=0;

 // 許可ワードを検索対象から外す

 // 禁止ワードをチェックする

 if ($flag>0) {
  echo "禁止ワードは含まれています。";
 } else{
  echo "問題のない文字列です。";
 }
 echo '「'.$str.'」';

英字はmb_strtolower()を使い、大文字をすべて小文字に直す。元の文字列はデータベースに登録したりするときに必要なので、$targetという変数を定義して代入する。mb_convert_kana()でオプションをKVasとすることで半角カナや全角スペースなどを取り除くことができる。さらにpreg_replaceでは正規表現を使い、半角スペースやタブなどを取り除いている。今回はフラグとして$flagを定義し、禁止ワードを見つけた場合は1を代入する。

次は許可ワードを検索対象から外す処理を考える。例えば「コーヒー」をNGにしたいが「コーヒーゼリー」は許可したい場合を考えていく。「コーヒーゼリー」という文字列は「*」に変換しておき、禁止ワード検索の時にチェックを回避するようにしておくのがよさそうである。

// 許可ワードを検索対象から外す
$ok_words= array('フライパン','コーヒーゼリー');
foreach ($ok_words as $ok_word) {
 if (mb_strpos($target,$ok_word)!==false) {
  $target=str_replace($ok_word,'*',$target);
 }

 mb_strpos()で一度許可ワードが含まれていることを確認してから、str_replaceで許可ワードを「*」に変換している。str_replace()はマルチバイトに対応していないため、一度mb_strpos()で許可ワードが含まれていることを確認しないと日本語環境では意図しない文字を変換してしまう可能性がある。mb_strpos()は対象文字が見つかった場合、最初に現れる位置を数字で返す。見つからなかった場合は「false」を返すので、これを判定に使っている。許可ワードが間違いなくあることを確認したうえで、str_replace()によって許可ワードを「*」に変換している。

では、実際に禁止ワードのチェックを行っていく。今回は禁止ワードを「パン」「コーヒー」として、1つでも見つかり次第$flagに1を代入して検索を終了させる。

// 禁止ワードをチェックする
$ng_words=array('パン','コーヒー');
foreach ($ng_words as $ng_word) {
 if (mb_strpos($target,$ng_word)!==false) {
  $flag=1;
  break;
 }

 先ほどと同じくmb_strpos()を使い、今度は禁止ワードが含まれるかをチェックしていく。見つかったらbreakでforeachの処理から抜けるようにしておく。

 

コードはこれで完成、今夜はここまで。明日余裕があればPOSTで受け取った文字列の判定を行うようにコードを組みたい。

PHP実習:正規表現の構文と練習

今夜は正規表現の基本構文について。だいぶ複雑だけど、書きながら慣れていこう。

 

第9章-02

正規表現において文字を表す構文は以下の通り。

.(ドット)...任意の一文字 例:/b..k/→bookがマッチ

[ ]...文字クラス。[a-z]で「A~Zまで」を示す 例:/p[a-g]n/→penがマッチ 

\d...数字[0-9]と同じ 例:/product\d/→product7などがマッチ

\s...タブ、スペース、改行などの空白文字 例:/R\sL/→「R L」にマッチ

\S...すべての文字(\s以外) 例:/\S/→R、=、?などにマッチ

\w...大文字小文字のアルファベット、数字、アンダーバー

\W...非単語文字(\w以外) 例:/\W/→「-」、「&」などにマッチ

この中では特に[ ]\dを覚えておく。 [abc]とすると「abcのうちどれか1文字」[a-g]とすれば「アルファベット順にaからgまでのどれか1文字」を表す。大文字と小文字は区別されるので、大文字を含めたい場合は「a-gA-G」と書く。^」を使えば否定を表現することも可能。「^a-g」と書けば「aからg以外」を表す。また、「もしくは」を意味する「|を使用することもできる。(090|080)と書けば、「090か080のどちらか」という意味になる。

 

繰り返しを表現する構文は以下の通り。

?...0回か1回 例:/r[a-z]?c/→「rec」「rc」などにマッチ

*...0回以上の繰り返し 例:/po*l/→「pool」「pl」などにマッチ

+...1回以上の繰り返し 例:/co+l/→「cool」などにマッチ、「cl」はダメ

{n}...n回の繰り返し 例:/w[a-z]{2}e/→「wake」「wipe」などにマッチ

{m,n}...m回以上、n回以下の繰り返し 例/str[a-z]{2,4}/→「street」「straight」 

「文字を表す構文」に続けて記述することで繰り返しを表現する。{}で回数を指定することも可能。{m,n}について、{m,}と書いた場合は単純にm回以上となり、上限がなくなる。

 

パターン修飾子(/の後ろで指定するオプション)について、実際のコードで学ぶ。

<?php
 $str='Hi, I am Taro.';
 $result=preg_match('/taro/i',$str);
 var_dump($result); 

 /taro/iの部分が正規表現となる。「i」は「アルファベットの大文字小文字を区別しない」ことを指定するパターン修飾子。このコードではvar_dump($result)で1を返すが、iを外すと0を返すようになる。

パターン修飾子は以下の通り。

i...アルファベットの大文字小文字を区別しない

例:/abc/i→「ABC」「aBc」などにマッチする

m...行単位でマッチングを行う

例:/abc/m→複数行のすべての行頭を調べる

s...パターン構文の「.(ドット)」を改行文字にもマッチさせる

例:/abc/s→任意の1文字が改行文字であってもマッチする

u...パターン文字列の文字コードを「UTF-8」として扱う

例:/テニス/u→日本語環境でpreg_match()をする場合は必須

x...パターン中の空白文字を無視する

例:/ab c/x→「abc」にマッチする。「ab c」にはマッチしない。

正規構文の終わりに指定するのがパターン修飾子。

mについてはイメージしづらいので実際のコードで確認。

<?php
$sentences=<<<EOD
はじめまして。
私の名前は田中です。
休日はジョギングをしています。
EOD;
 $result=preg_match('/^休日/um',$sentences);
 var_dump($result); 

この場合、var_dump($result)は1を返す。mを削除すると、var_dump($result)は0を返す。

 

第9章-02 練習問題

  1. 数字4文字の文字列にマッチする正規表現を作り、サンプルの文字列を調べよ。
  2. 行の終端が「でしょう。」になっている文を、例示したコードを使って探せ。

<?php

//1.解答

 $str1='0120';
 $str2='090';
 $result1=preg_match('/[0-9]{4}/',$str1);
 $result2=preg_match('/[0-9]{4}/',$str2);
 var_dump($result1);
 var_dump($result2);

 

//2.解答

 $str1='今日はくもりです。';
 $str2='明日は快晴でしょう。';
 $result1=preg_match('/\Sでしょう。/u',$str1);
 $result2=preg_match('/\Sでしょう。/u',$str2);
 var_dump($result1);
 var_dump($result2);

 

第9章-03

いくつかの例で正規表現を作っていく。

まずは携帯の番号でバリデーションをかけると想定。

  • ハイフンを入れる/入れないは人それぞれ
  • 最初の3桁は「070」「080」「090」で固定

最初の3桁は0と0の間に7から9までのいずれかの数字が入るので、

0[7-9]0 

となる。次はハイフンが入った後に数字4桁。数字には「\d」という書き方もあるので、

0[7-9]0-\d{4}-\d{4}

携帯番号専用のフォームならば、前後に余分な文字はないはずなので始まりと終わりを表す「^」と「$」を付け加える。また、ハイフンがない場合も想定して「?(0回か1回繰り返す)」をハイフンの後ろに書いておく。 

^0[7-9]0-?\d{4}-?\d{4}$ 

これがしっかり機能するか、実際のコードで確認する。

<?php
 $num1='090-1234-5678';
 $num2='08012345678';
 $num3='070-1234-567';
 $pattern='/^0[7-9]0-?\d{4}-?\d{4}$/u'; // 正規表現の変数に格納
 $result1=preg_match($pattern,$num1);
 $result2=preg_match($pattern,$num2);
 $result3=preg_match($pattern,$num3);
 var_dump($result1);
 var_dump($result2);
 var_dump($result3);

画面では$result3のみ0を返すので、しっかり機能していることが分かる。

次に、西暦の記述を見つけるための正規表現を練習する。

「2018/07/03」のように、「/」など既にパターン上で意味を持ってしまっている特殊文字について扱う。特殊文字は「/」をはじめ「.」「?」「*」「$」「[」「)」など様々あるが、ただの文字として表現したい場合はそのまま書くわけにはいかない。特殊文字エスケープについて学んでいく。

西暦は「数字4つ」「/」「数字2つ」「/」「数字2つ」で表す。また、月日については1桁で表すこともあるので、まずは以下のように書く。

/^\d{4}/\d{1,2}/\d{1,2}$/u 

 コードの最初と最後の/はpreg_matchに必須の文字であり、「デリミタ」という名前がついている。「独立した領域の境界を特定する文字」として使われている。正規表現PHP以外の言語でも使えるので、PHPプログラムの中に正規表現のための独立した領域が作られていることになる。

正規表現の中に出てくる/はただの文字としてのスラッシュなので、バックスラッシュ「\」でエスケープする必要がある。

 /^\d{4}\/\d{1,2}\/\d{1,2}$/u 

 文字としての/の前に\を付け加え、「\/」と書くことによってデリミタとして判断されてしまうことを回避している。正規表現中で構文となっている文字をただの文字として使うにはすべてエスケープしなければならない点に注意する。

見づらくなってしまったので、( )で囲ってグループ化を行う。

 /^(\d{4})\/(\d{1,2})\/(\d{1,2})$/u  

 丸かっこがなくても動作はするが、この方が見やすい。

ここまで作った表現をコードで動かす。

<?php
 $date1='2018/7/2';
 $date2='2018/10/20';
 $date3='2018年7月2日';
 $pattern='/^(\d{4})\/(\d{1,2})\/(\d{1,2})$/u';
 $result1=preg_match($pattern,$date1);
 $result2=preg_match($pattern,$date2);
 $result3=preg_match($pattern,$date3);
 var_dump($result1);
 var_dump($result2);
 var_dump($result3); 

 var_dump($result3)のみ0を返し、他は1を返したので成功。

今夜はここまで。

PHP実習:正規表現によるパターンマッチ

今日から正規表現について学んでいく。

正規表現とは、「文字列の集合を1つの文字列で表現する方法」である。もともとは文法を数学的に研究するための形式言語理論から生まれたものを文字検索ツールとしてプログラミングで使っている。

Webプログラミングの世界ではバリデーション(入力値のチェック)で使われるケースが多い。文字数や順序が特定の型にはまっているかを調べることで、データベースの不具合を防いだりサイトを攻撃から守ったりできるようになる。

 

郵便番号の書き方を文字列パターンとして表現し、チェックしてみる。

<?php
 $zipcode1='115-0002';
 $zipcode2='220-601';

 $result1=preg_match('/\A([0-9]{3})-([0-9]{4})\z/',$zipcode1);
 $result2=preg_match('/\A([0-9]{3})-([0-9]{4})\z/',$zipcode2);

 var_dump($result1);
 var_dump($result2);

preg_match()は正規表現で表された文字列パターンとチェック対象の文字列が一致するかを調べる関数である、マッチしたときに「1」、しなかったときに「2」を返す。

preg_match('/正規表現によって作られたパターン/',検索する文字列) 

第1引数には正規表現による文字列パターンを記述する。正規表現はスラッシュで囲むことが必須になる。 第2引数で検索したい文字列を入れる。

今回のコードにおいて、正規表現は「\A([0-9]{3})-([0-9]{4})\z」の部分になる。

\A ([0-9]{3}) - ([0-9]{4}) \z 

\A...文字列の始まり

([0-9]{3})...0~9の数値3桁

-...ハイフン1文字

([0-9]{4})...0~9の数値4桁

\z...文字列の終わり

正規表現の基本構文も見ようと思ったが、長くなりそうなので今日はここまで。

今日は短い…。 

googleサイトでポータルページを作ってみよう

今日はPHPの勉強から離れ、Googleサイトを使って簡単なホームページを作ってみようと思います。

 

Googleサイトとは、Googleが提供している「専門知識なしでホームページを制作できるサービス」のこと。しかもあのGoogleが提供しているので、Googleカレンダーやドライブとの連携も簡単!デザインもドラッグ&ドロップでできる、すべてのテーマがレスポンシブルデザインで構成されているのでスマホでの閲覧でも問題がないなどなど、いいことづくめなサービスです。

なぜ私がこのサービスに注目したかというと、「特定のGoogleのアカウントでしか閲覧できない」「招待URLを持っている人のみ閲覧できる」という制限をかけられるからです。簡易的なポータルサイトの作成にはピッタリ。そういうのを作りたいなーと思っていた時にTwitterGoogleサイトはいいぞという呟きを見かけ、俄然興味が湧いたのでやってみました。

しかし、デメリットというか作る上での制限として「特定の条件を満たさないと独自ドメインが使えない」「フッターにGoogleサイトで作りました、というような内容のメッセージが固定される」という欠点もあります。これらは課金すれば解除可能となるらしいですが、今回はまったくそのつもりがないので割愛します。

 

前置きはこれくらいで、簡単ですが作ってみました。

※諸事情によりURLは公開しません。スクショのみとなります。

Googleサイトのトップページにアクセスし、画面の「+」を押せば新規ページの作成が始まります。

f:id:R_de_aru:20180701235225p:plain

こちらがGoogleサイトを編集している実際の画面です。

f:id:R_de_aru:20180701231957p:plain

超シンプルですが、「ヘッダー」「テキストボックスによるお知らせ欄」「Googleカレンダーを貼り付け」という3つの要素だけを作りました。これらについて簡単ですが説明していきます。

 

1.ヘッダー

ヘッダーには好きな画像を設定することができます。今回はフリー素材のものを拝借し、Googleサイト側で見やすいようにうっすら白くしてもらっています。タイトルは「ポータルサイト(サンプル)」としてみました。

 

2.お知らせ

これはテキストボックスで作っています。

f:id:R_de_aru:20180701233009p:plain

べた書きしているテキストなので、これを編集する際はいちいちこの編集ページを開いて書き換えないといけません。Googleスプレッドシートを使えば、そのスプレッドを編集するだけでこのページに表示される内容も変更されます。テストとしてスプレッドシートを貼り付けてみたのが以下の画像です。

f:id:R_de_aru:20180701233553p:plain

スプレッドにした方が編集は楽だろうけど、見栄えとしてはうーん…。

スプレッドもテキストも、Googleドキュメントへのリンクを貼ることができます。ドキュメント側で共有ボタンを押し、リンクを発行して使いましょう。

 

03.カレンダー

浅はかですが、僕が考えるポータルサイトとしての一番の肝だと思っています。これでアクセスしてくるメンバーの予定を管理・共有ができるのがいいなあと思ったのが今回Googleサイトに手を出した一因なので…。

これは自分のGoogleカレンダーとリンクしています。先ほどのスプレッドと同じく、Googleカレンダー側で編集した内容がそのまま反映される仕組みです。他にメンバーがいる状態で試していないので何とも言えないところがありますが、ここからたぶん編集もできるはず…。

 

自分が作った要素としては今のところこれだけです。

完成したら画面右上の「公開」ボタンをクリックし、初回だけURLを決めてあげればネットの海に公開されます。検索エンジンに出さないようにする設定もできます。

公開前のプレビューもできます。「公開」ボタンの左にある目のマークのボタンをクリックすることで、「PCで見た時」「タブレットで見た時」「スマホで見た時」をPCのブラウザ上で確認することができます。

PCプレビューモード

f:id:R_de_aru:20180701234751p:plain

タブレットプレビューモード

f:id:R_de_aru:20180701234814p:plain

スマホプレビューモード

f:id:R_de_aru:20180701234835p:plain

スマホプレビューの際、スプレッドシートが見切れて表示されてしまうところがありました。Webサイトでよくあるお知らせ欄にはテキストボックスを使うのが無難かも。

注意点としては、このGoogleサイトのデータはGoogleドライブに保存されます。バックアップなどを取るときは注意してください。

 

今回は自分で詳しい仕様を考えず、これが必要かなー…というノリで作ってみました。今PHPやHTMLを触るための勉強をしているので特に思うのですが、このサービスは革新的すぎる…。難しいコードも触らず、必要な要素を並べるだけでページができてしまう…!

Googleサイト、自分でページを持ちたいけどコードを書くのは難しそう…と悩んでいる方にこそおすすめしたいです。簡単に自分のページが持てるので!ぜひ!

 

今夜は変化球でしたが、以上を今日の勉強とします。明日からもPHP頑張るぞ。

 

参考=====

gsuite.google.jp

imitsu.jp

tonari-it.com

 

PHP実習:GETとMySQLを組み合わせたプロフィールページ

昨日はまたサボってしまった…。成果ゼロで寝るのも気持ち悪かったので、昨日は今回使うためのMySQL環境を整えるだけしました。

 

今回は、今まで作ったMySQLのデータベースと組み合わせて「登録されたプロフィールを表示するページ」を作る。

今回の流れは…

  1. クラブ情報を登録するテーブルを作り、userテーブルにclub_idのカラムを追加
  2. 会員データとその会員の所属するクラブの情報を取得
  3. 会員データが存在した場合は、それをHTMLで表示

となる。要件定義としては、

  • ユーザーIDを基にした会員データの表示システムを作成する
  • 2つのテーブルデータを結び付けて取得
  • 会員データが存在しない場合、「存在しない会員IDです」と表示する

では制作していこう。

まず、MySQLにテーブルを追加する。

create table club(

club_id int not null auto_increment primary key,

club_name varchar(100),

count tinyint,

overview text

); 

f:id:R_de_aru:20180630165337p:plain

いろいろスペルミスが多発して寝る前にイライラしてしまったのは内緒。

次に、既存のuserテーブルにカラムを追加する。

alter table user add club_id int not null default 0;

これでuserテーブルに規定値0の「club_id」というフィールドができた。

これと先ほどのclubテーブルを紐づけて使っていく。

ちなみに、clubテーブルのovervirwのデータ型をtextにしているので、改行が適用される。

f:id:R_de_aru:20180630165432p:plain

表示は崩れるけど…。

 

ここまで準備が出来たので、次はブラウザに表示するためのHTMLとPHPを作っていく。今回は1ページのみ。一気に書いていく。

<?php
 $id='';
 if (isset($_GET['id'])) {
  $id=(int)$_GET['id'];
 }

 $dsn='mysql:dbname=user;host=localhost;charset=utf8';
 $user='root';
 $password='';
 $data[]='';

 try{
  $dbh=new PDO($dsn,$user,$password);
  $dbh->setAttribute(PDO::ATTR_ERRMODE,PDO::ERRMODE_EXCEPTION);
  $sql=<<<SQL
  SELECT x.name,
      x.age,
      y.club_name,
      y.count,
      y.overview
  FROM user as x
  INNER JOIN club as y ON x.club_id=y.club_id
  WHERE x.id= :id
  LIMIT 1
SQL;
  $stmt=$dbh->prepare($sql);
  $stmt->bindvalue(':id',$id,PDO::PARAM_INT);
  $stmt->execute();
  $row=$stmt->fetch(PDO::FETCH_ASSOC);
  //var_dump($row);
 } catch (PDOExeption $e) {
  echo('接続エラー:'.$e->getMessage());
  die();
 }
?>

<html>
 <head>
  <meta charset="utf-8">
  <style type="text/css">
   .search{float:right;}
  </style>
 </head>
 <body>
 <div class="search">
  <p>会員IDを入力してください</p>
  <form action="" method="GET">
   <input type="text" name="id">
   <input type="submit" value="確認する">
  </form>
 </div>
 <h1>会員データ</h1>
 <?php if ($row===false): ?>
  <p>存在しない会員IDです。</p>
 <?php else: ?>
  <table border="1">
   <tr>
    <th>お名前</th>
    <th>年齢</th>
    <th>クラブ</th>
    <th>月の活動回数</th>
    <th>概要</th>
   </tr>
   <tr>
    <td><?php echo $row['name']; ?></td>
    <td><?php echo $row['age']; ?></td>
    <td><?php echo $row['club_name']; ?></td>
    <td><?php echo $row['count']; ?>回</td>
    <td><?php echo nl2br($row['overview']); ?></td>
   </tr>
  </table>
 <?php endif; ?>
 </body>
</html>

f:id:R_de_aru:20180630170054p:plain

 

フォームやURLから取得した値は文字列として扱われるので、整数にキャスト(型変換)して$idに代入する。

$id=(int)$_GET['id']; 

 今回はSQL文が長いので、ヒアドキュメントという手法を使う。代入のタイミングで「<<<」とアルファベットを組み合わせることで、改行コードが反映された形での代入が可能となる。アルファベットは通例で大文字を使う(今回は「SQL」)。ヒアドキュメントの終了を表すコード(今回は「SQL;」)は必ず行頭に書かなければならない。

SQLについてはすでに知っている内容なので割愛。

存在しないIDを入れられた場合の処理も作成する。idに一致するデータがない場合は$rowには真偽値であるFalseが返ってくるので、「$row===false」を条件式として「存在しないIDです」と画面に出すようにする。

今回は練習用だから省略したが、実際に表示する前にはデータの無害化をしなければならない。その際には出力時に以下のように書く。

echo nl2br(htmlspecialchars($row['overview'],RNT_QUOTES,'UTF-8')) ;

これでは長すぎるため、もっと短く書く方法を次回から学んでいく。

第8章はこれで終了なので、この記事もここまで。 

PHP実習:PHPで画像データ送信

ブログの記事タイトルもこんな風にしていきます。

googleサイトが気になるところ。あれ使えば会社で予定管理や案件管理ができるポータルサイトが作れるって読んだので…。

 

第8章-04

画像データをアップロードするためのページを作っていく。

<html>
 <body>
  <h1>画像アップロード</h1>
  <form action="pic_receive.php" method="POST" enctype="multipart/form-data">
   <p><input type="file" name="img"></p>
   <p><input type="submit" value="送信"></p>
  </form>
 </body>
</html>

f:id:R_de_aru:20180628203557p:plain

POSTデータとして画像を送信する。formタグで「enctype="multipart/form-data"」と指定することにより、データを配列で送信することができる。フォーム内に画像データが含まれる場合は必ず指定する。そしてinputタグのtypeにfileを指定することにより、ボタンをクリックすると画像の選択画面が現れる。

f:id:R_de_aru:20180628203817p:plain

画像ファイルは専用のディレクトリを作って管理するので、index.phpがあるフォルダに「img」というフォルダを作っておく。受信ページを作成していく。

 <?php
 $err=array();
 $img=$_FILES['img'];
 var_dump($img);
 move_uploaded_file($img['tmp_name'],'./img/'.$img['name']);
?>
<html>
 <head>
  <meta charset="utf-8">
 </head>
 <body>
  <h1>受信ページ</h1>
  <?php
   if (count($err)>0) {
    foreach ($err as $row) {
    echo '<p>'.$row.',</p>';
   }
   echo '<a href="./index.php">戻る</a>';
   } else {
  ?>
  <div><img src="ttp://192.168.33.11:8000/img/<?php echo $img['name'];?>"></div>
  <?php } ?>
 </body>
</html>

この状態で画像をアップしてみる。今回は大好きな仮面ライダーアマゾンオメガの凛々しいお姿をS.H.フィギュアーツのページからお借りしました(権利的な問題があれば即刻消します)。

※ソース内のURLについて、最初のhを消してます

f:id:R_de_aru:20180628214455p:plain

上手くアップされた。

画像のデータは$_FILESというスーパーグローバル変数で取得する。キー名とデータの内容は以下の通り。

name...ファイル名

type...ファイルのMIMEタイプ

tmp_name...サーバー上で一時的に保管されるテンポラリーファイル名

error...アップロード時のエラーコード

size...ファイルサイズ(バイト) 

送信ボタンを押したとき、画像そのもののデータは一時保管場所に預けられる。$_FILESに格納されるのは画像データそのものではなく、画像に付随するデータである。

$_FILES['img']['name']にはもともとのファイル名が格納される。typejpegpngなど画像の種類tmp_nameはtmpフォルダにできたファイル名となる。errorは0が返ってくれば成功、1はファイルサイズのオーバー、7は書き込み失敗を意味する。

一時的にサーバー上のディレクトリにアップロードされたファイルは公開フォルダに移動する必要がある。作成したimgフォルダへの移動にはmove_uploaded_file()という組み込み関数を使う(第1引数は現在地、第2引数は移動先)。ファイル名も一情報の一部とみなされるので、必ず「ディレクトリ名/ファイル名」まで指定すること。これにより、imgフォルダに画像ファイルが移動する。

これでアップロード機能自体は完成しているが、画像ファイルはデータ量が多いのでサーバーの負担になることがある。そもそも画像ファイルが悪意のあるコードを含んだ偽りのファイルである可能性も否めない。そのため、バリデーション機能は必須と言える。さらに、本来画像ファイルはそのままの名前では保存しない。名前を変換してから保存するようにコードを追加・修正していく。

<?php
 $err=array();
 $img=$_FILES['img'];
 var_dump($img);

 $type=exif_imagetype($img['tmp_name']);
 if ($type!==IMAGETYPE_JPEG&&$type!==IMAGETYPE_PNG) {
  $err['pic']='対象ファイルはPNGまたはJPEGのみです。';
 }elseif ($img['size']>102400) {
  $err['pic']='ファイルサイズは100KB以下にしてください。';
 }else {
  $extension=pathinfo($img['name'],PATHINFO_EXTENSION);
  $new_img=md5(uniqid(mt_rand(),true)).'.'.$extension;
 }
 move_uploaded_file($img['tmp_name'],'./img/'.$new_img);
?>

exif_imagetype()によってファイルの形式を取得する($_FILES['img']['type']で検証しないこと)。exif_imagetype()では整数が返ってくる(2:JPEG、3:png)が、後から読むときにわかりづらいのであらかじめ定義されているIMAGETYPE_JPEGやIMAGETYPE_PNGを活用する。ここでは100KB(=102400バイト)よりファイルサイズが大きいものはエラーとする。

ファイル名は元のものを使わないことが一般的。同じ名前でアップロードされたときに上書きされてしまうことへの対処、「phpinfo.php.png」というような多重拡張子を防ぐなどの理由がある。pathinfo($img['name'],PATHINFO_EXTENSION)でファイル名のドット以降の文字を取得する。md5(uniqid(mt_rand(),true))で乱数を作って限りなく重複する可能性が少ない名前を作り、それに取得した拡張子名をつなげる。

php.iniを変更すれば、アップロードできるファイルのデータサイズやファイル数などを設定できる。

 

この勢いで実習まで進みたかったけど、夜も遅くなってきたので今日はここまで。実は途中で詰まってしまってなかなか進めなかったのです。原因が「move_uploaded_file」と書くべきところを「move_upload_file」と書いていたことで、気付くのに30分くらいかかった…。