20200619のPHPに関する記事は14件です。

かつて見たびっくりクソコード

概要

@rana_kualu さんの記事に触発されて、
自分が今まで見かけたなかでもヤバめのクソ コードについて書いてみる。

(元記事)
・これまで見た中で最も汚いソースコード - Qiita
https://qiita.com/rana_kualu/items/2f3c40642836c11f5ddc

クソ コード

① typedef

再現コード.c
typedef void coid;
typedef void boid;

キーボードを見ると [c] [v] [b] が並んでいる。
即ちこれは、 タイプミスを型名の再定義でカバー しているというもの。
流石にコレは プログラムではない、と思った。

② マイナス1万

変数名はとりあえず適当。

再現コード.php
$date0 = '20201122'; //画面からYYYYMMDDで文字列が来る
$date1 = $date0 -10000;
$year = substr($date1, 0, 4);
$month = substr($date1, 4, 2);
$day   = substr($date1, 6, 2);

なんだこの1万を引いている処理は・・・。
(当時の)同僚が保守中の仕組みからこのコード見つけ、一緒に悩んだ。
日付系処理では1万はキリが悪い。。。
幸いにも書いた輩が隣に居たので直接聞いてみた。

1年前を計算している

:thinking::thinking::thinking::thinking::thinking:

:astonished:なるほど!
:rage:書き直せ!:punch_tone2:

幸いにも書いた輩が高校から友人(同級生)だったので、とりあえず殴った。

日付操作は日付操作用の関数がちゃんとあるので、それを使いましょう。
(と言うか、私は静的型畑の人間なので、動的型変換の使い方にも膝が震えた。)

③ たこいかぱにっく

再現コード.java
boolean checkNumber (str) {
  boolean isNumber = !NumberUtils.isNumber(str);
  if(isNumber) {
    //エラーメッセージ処理処理
  }
  return !isNumber;
}

ツッコミどころ

  • 変数名が明らかに嘘をついている
  • 結局この関数は、また反転して戻している
  • じゃぁそもそも反転していた理由は何???

論理値(真偽値)がめちゃくちゃである。
さらに、この見つけたコードをコミットした人間に話を聞くと、

元のコード見た?

どうやら理由があって別の処理から流用したコードらしい。
しかし、指摘内容自体は把握していた様で、でもそのまま流用したとか。
なお、流用したほうもなかなかのキャリアの持ち主で、
単純処理すら直さないで放置したことにもヤバイと思った。

論理値は非常に混乱しやすいので、扱いには本当に注意が必要。
私は、ポジティブな方を真、ネガティブな方を偽になる様に心がけている。

以上

クソコードを書かない様に日々精進したいものです。

  • このエントリーをはてなブックマークに追加
  • Qiitaで続きを読む

かつて見たびっくりクソ コード

概要

@rana_kualu さんの記事に触発されて、
自分が今まで見かけたなかでもヤバめのクソ コードについて書いてみる。

(元記事)
・これまで見た中で最も汚いソースコード - Qiita
https://qiita.com/rana_kualu/items/2f3c40642836c11f5ddc

クソ コード

① typedef

再現コード.c
typedef void coid;
typedef void boid;

キーボードを見ると [c] [v] [b] が並んでいる。
即ちこれは、 タイプミスを型名の再定義でカバー しているというもの。
流石にコレは プログラムではない、と思った。

② マイナス1万

変数名はとりあえず適当。

再現コード.php
$date0 = '20201122'; //画面からYYYYMMDDで文字列が来る
$date1 = $date0 -10000;
$year = substr($date1, 0, 4);
$month = substr($date1, 4, 2);
$day   = substr($date1, 6, 2);

なんだこの1万を引いている処理は・・・。
(当時の)同僚が保守中の仕組みからこのコード見つけ、一緒に悩んだ。
日付系処理では1万はキリが悪い。。。
幸いにも書いた輩が隣に居たので直接聞いてみた。

1年前を計算している

:thinking::thinking::thinking::thinking::thinking:

:astonished:なるほど!
:rage:書き直せ!:punch_tone2:

幸いにも書いた輩が高校から友人(同級生)だったので、とりあえず殴った。

日付操作は日付操作用の関数がちゃんとあるので、それを使いましょう。
(と言うか、私は静的型畑の人間なので、動的型変換の使い方にも膝が震えた。)

③ たこいかぱにっく

再現コード.java
boolean checkNumber (str) {
  boolean isNumber = !NumberUtils.isNumber(str);
  if(isNumber) {
    //エラーメッセージ処理処理
  }
  return !isNumber;
}

ツッコミどころ

  • 変数名が明らかに嘘をついている
  • 結局この関数は、また反転して戻している
  • じゃぁそもそも反転していた理由は何???

論理値(真偽値)がめちゃくちゃである。
さらに、この見つけたコードをコミットした人間に話を聞くと、

元のコード見た?

どうやら理由があって別の処理から流用したコードらしい。
しかし、指摘内容自体は把握していた様で、でもそのまま流用したとか。
なお、流用したほうもなかなかのキャリアの持ち主で、
単純処理すら直さないで放置したことにもヤバイと思った。

論理値は非常に混乱しやすいので、扱いには本当に注意が必要。
私は、ポジティブな方を真、ネガティブな方を偽になる様に心がけている。

以上

クソコードを書かない様に日々精進したいものです。

  • このエントリーをはてなブックマークに追加
  • Qiitaで続きを読む

index.php

<?php
require_once('data.php');
require_once('menu.php');
?>

<!DOCTYPE html>



Café Progate





Café Progate


メニュー<?php echo Menu::getCount() ?>品




<?php foreach ($menus as $menu): ?>

<?php echo $menu->getName() ?>
<?php if ($menu instanceof Drink): ?>

<?php echo $menu->getType() ?>


<?php else: ?>
<!-- for文を用いて、spicinessプロパティの数だけ繰り返し処理を行ってください -->
<?php for($i = 0; $i< $menu->getSpiciness();$i++): ?>

<?php endfor ?>
        <?php endif ?>
        <p class="price">¥<?php echo $menu->getTaxIncludedPrice() ?>(税込)</p>
        <input type="text" value="0" name="<?php echo $menu->getName() ?>">
        <span>個</span>
      </div>
    <?php endforeach ?>
  </div>
  <input type="submit" value="注文する">
</form>




//唐辛子を表示する
  • このエントリーをはてなブックマークに追加
  • Qiitaで続きを読む

【PHP 】sqlをPHPにてブラウザ上にて表示させる

本日は、sqlをphpを使ってブラウザ上に表示していく。

始めに、sql.phpにて

$pdo=new PDO('mysql:host=localhost;dbname=mydb;charset=utf8','root','root');

のようにPDOの中に記載していく。

$pdo=new PDO('使用するsql:host=ホスト名;dbname=データベース名;charset=utf8','ユーザー名','パスワード');

PHP とデータベースサーバーの間の接続を表します。
データベースを代入すると

$sql='SELECT * FROM dbPractice';

のようにデータベースのテーブル名を選択する。
そして、

$statement=$pdo->prepare($sql);

とsqlを使用する準備をする役割を持つparepare()関数を使用する。

最後に

$statement->execute();

executeメソッドによってデータを取得する。

これでーデータの取得が完了する。
そして、これらのデータを取り出す作業に移行する。

$results=[];
while($row=$statement->fetch(PDO::FETCH_ASSOC)){
    $results[]=$row;
}

このように記述する。
while構文の条件式は、
fetch関数により、配列を一行ずつ取り出す。
そして、
引数のPDO::FETCH_ASSOCは、列名を記述し配列で取り出す設定をしている。配列の最後まで下記を実行し続ける。
そして、予め作成しておいた$results変数の空配列に$rowを代入する。

$statement=null;
$pdo=null;
require_once 'view/sql.tpl.php';
//テンプレートの呼び出し

最後にsql.tpl.phpに

  <?php foreach($results as $value){?>
       <p><?php print_r($value);?></p>
    <?php }?>

これで以下のようにデータベースの取得結果が出力される。
スクリーンショット 2020-06-19 16.44.12.png

引き続きアウトプットとインプットを続けていく。
間違っている部分、フィードバックなどございましたら教えていただけると嬉しいです。
よろしくお願い致します。

  • このエントリーをはてなブックマークに追加
  • Qiitaで続きを読む

51歳(現52)からのプログラミング 備忘 formから配列を送ってクッキーに登録してリダイレクト

実用的なコードは、コメント欄にご投稿頂きました rfc828様 の素敵なコードをご参照ください(←おすすめ)

構成
sam1.php
sam2.php

sam1.php
<form action="sam2.php" method="post">
<input type="text" name="sam[]" value="<?= $_COOKIE['sam'][0] ?? 'no';?>">
<input type="text" name="sam[]" value="<?= $_COOKIE['sam'][1] ?? 'no';?>">
<button>send</button>
</form>

<?= $_COOKIE['sam'][int] ?? 'no';?>
条件演算子のNULL合体演算子を使ってます。
条件A ?? 条件B // 条件Aがnullでなければ条件Aを実行、nullなら条件Bを実行

これで、クッキーにsam配列が登録されていれば、input欄に該当する値をセットだね。

sam2.php
<?php
$i=0;
foreach($_POST['sam'] as $v){
  setcookie("sam[$i]",$v);
  $i++;
}
header('Location:sam1.php');

POSTされた配列を、foreachで取り出して、クッキーにsam[]として登録ですね。

  • このエントリーをはてなブックマークに追加
  • Qiitaで続きを読む

PHP でリダイレクト

次のページを参考にしました。
PHPの「正しい」リダイレクト方法と、HTTPステータスコード

RaspberryPi で走る Apache2.4 で確認しました。

index.php
<?php
    $url = 'https://ekzemplaro.org/';
    header('Location: ' . $url, true, 301);
    exit;
?>

RaspberryPi の OS

$ uname -a
Linux violet 4.19.118-v7l+ #1311 SMP Mon Apr 27 14:26:42 BST 2020 armv7l GNU/Linux
  • このエントリーをはてなブックマークに追加
  • Qiitaで続きを読む

PHP 改行コード brタグ

改行コード\n

  • テキスト内で改行するときに使う
  • ダブルクォート(")内で記載し、出力する必要がある
  • 改行コードはOSによって異なる
  • 定義済み定数PHP_EOLを使うとOSを自動判定し改行文字を自動的に設定してくれる
  • HTMLでは連続する空白文字は全てスペース1つに置き換えられる

brタグ

  • HTML内で改行するときに使う
  • ソースでは改行されない。ブラウザがHTMLをレンダリングする際に改行される。

参考

https://pisuke-code.com/php-diff-of-nl-code-and-br-tag/
https://www.flatflag.nir87.com/nl2br-540#ltbrgt-2

  • このエントリーをはてなブックマークに追加
  • Qiitaで続きを読む

【PHP】受動的攻撃

書籍のアウトプットとして。

  • 受動的攻撃という攻撃手法
  • ブラウザの防御戦略であるサンボドックス

について説明していきます。

受動的攻撃と能動的攻撃

能動的攻撃

攻撃者が直接Webサーバに対して攻撃することです。

受動的攻撃

攻撃者がWebサイトの利用者をとうして攻撃することです。
Webサイトに罠を仕掛け、罠を閲覧してたユーザを通して攻撃をします。
以下主な受動的攻撃の例を上げていきます。

単純な受動的攻撃

攻撃者がいわゆる「怪しいサイト」を用意し、ユーザが閲覧してマルウェアに感染する場合です。

正規サイトを悪用する受動的攻撃

これはサイトに罠が仕掛けられている場合です。
以下の順番で行われます。

  1. 攻撃者が正規サイトを攻撃して仕掛けを仕込む
  2. 罠が仕掛けられたサイトを利用者が閲覧する
  3. マルウェア感染、情報漏洩、不正操作などが起こる。

1だけを見ると能動的攻撃ですが、2、3は受動的攻撃であり、1はこれの準備であると言えます。

単純な受動的攻撃よりも手間がかかりますが、攻撃者にとって以下のようなメリットがあります。

  • 罠サイトに誘導する手間がかからない
  • 正規サイトは利用者が多いので被害が拡大する可能性が高い
  • 正規サイトの機能を不正利用することにより攻撃者にメリットが得られる。
  • 利用者側の個人情報を盗むことにより攻撃者にメリットが得られる。

正規サイトに罠を仕込む手法は以下の4つが用いられます。

  • FTPなどのパスワードを不正入手してコンテンツを書き換える。
  • Webサーバの脆弱師をついた攻撃によりコンテンツを書き換える。
  • SQLインジェクション攻撃によりコンテンツを書き換える。
  • SNSなどの利用者が投稿できるサイト機能のクロスサイト・スクリプティング脆弱性を悪用する。

サイトをまたがった受動的攻撃

罠サイトを正規サイトをまたがった攻撃です。

  1. 利用者側が罠サイトを閲覧する
  2. 罠サイトから仕掛けを含むHTMLをダウンロードする
  3. HTMLのし掛けが作動して正規サイトに攻撃のリクエストを送信する。
  4. 正規サイトからJavaScriptなどの仕掛けを含むレスポンスが返る。

ブラウザはどのようにして受動的攻撃を防ぐか

サンドボックス

ブラウザ上ではjavasctipやJavaアプレット等、サイトを閲覧した状態でプログラムを実行する機能が提供されています。javascriptなどは安全性を高めるために以下の考え方に基づいています。

  • 利用者に配布元を確認させた上で利用者が許可した場合ものみ実行する
  • プログラムの「できること」を制限するサンドボックスと言う環境を用意する

前者は一般的なアプリ-ケーションに用いるには利用者の負担が大きく、セキュリティ上の問題が出やすいため、提供元からのサポートが終了しつつあります。

サンドボックスはプログラムができることに制約があり、悪意のプログラムを作ろうとしても利用者に被害が及ばないよう配慮されます。
javascriptのサンボドックスでは以下のように機能が制限されます。

  • ローカルファイルへのアクセスの禁止
  • プリンタなどの資源の使用禁止
  • ネットワークアクセ酢の制限(同一オリジンポリシー)

ネットワークについては厳しい成約があり、この制約を同一オリジンポリシーと呼びます。

同一オリジンポリシー

javascript等のクライアントスクリプトからサイトをまたがったアクセスを禁止するセキュリティ上の制限であり、ブラウザのサンドボックスに用意された制限の1つです。

  • このエントリーをはてなブックマークに追加
  • Qiitaで続きを読む

AWS S3 Laravel 画像ファイルアップロード時にエラーが発生する

目的

  • 画像ファイルのアップロード時にpublic指定してアップロードしたところ

実施環境

  • ハードウェア環境
項目 情報
OS macOS Catalina(10.15.5)
ハードウェア MacBook Pro (13-inch, 2020, Four Thunderbolt 3 ports)
プロセッサ 2 GHz クアッドコアIntel Core i5
メモリ 32 GB 3733 MHz LPDDR4
グラフィックス Intel Iris Plus Graphics 1536 MB
項目 情報 備考
AWS EC2インスタンス AmazonLinux2 こちらの方法を用いてイメージからインスタンスを作成→AWS EC2 をMacで使ってみよう!
PHP 7.4.5 こちらの方法でインストール→AWS EC2 AmazonLinux2 PHPをインストールする
composer 1.10.7 こちらの方法でインストール→AWS EC2 AmazonLinux2 composerをインストールする
MySQL 8.0.20 for Linux on x86_64 こちらの方法でインストール→AWS EC2 AmazonLinux2 MySQLを使えるようにする

問題までの経緯

  1. 下記の方法にてS3に対する画像アップロード処理を実装した。
  2. 画像アップロード時の処理を下記の様に修正してpublic状態でアップロードできる様にした。

    • 修正前

      アプリ名ディレクトリ/app/Http/Controllers/ImageController.php
       Storage::disk('s3')->putFile('/test', $request->file('file'));
      
    • 修正後

      アプリ名ディレクトリ/app/Http/Controllers/ImageController.php
       Storage::disk('s3')->putFile('/test', $request->file('file'), 'public');
      
  3. 処理の動作を確認するためブラウザから画像のアップロードを行った。

問題

  • 下記エラーが発生する。

     Error executing "PutObject" on "https://S3のバケットURL/test/8iPIwmDI2VX6qOZwUq9XCJSLPGxGox2kxpfcOAkT.png"; AWS HTTP error: Client error: `PUT https://S3のバケットURL/test/8iPIwmDI2VX6qOZwUq9XCJSLPGxGox2kxpfcOAkT.png` resulted in a `403 Forbidden` response: <?xml version="1.0" encoding="UTF-8"?> <Error><Code>AccessDenied</Code><Message>Access Denied</Message><RequestId>5D23B7 (truncated...) AccessDenied (client): Access Denied - <?xml version="1.0" encoding="UTF-8"?> <Error><Code>AccessDenied</Code><Message>Access Denied</Message><RequestId>5D23B7FF0F008DAE</RequestId><HostId>PETGLYLAydwTGLlN6wskfKQpjULz1bGIQqWZc12NaJvZfCN++WYmjIFJrVoA2V8LNUK+fQwwoJk=</HostId></Error>
    
  • ブラウザでのエラー画面の表示を下記に記載する。

    ?_Error_executing__PutObject__on__https___image-upload-miriwo_s3_ap-northeast-1_amazonaws_com_test_8iPIwmDI2VX6qOZwUq9XCJSLPGxGox2kxpfcOAkT_png___AWS_HTTP_error__Client_error__`PUT_https___image-upload-miriwo_s3_ap-northeast-1_amazonaws_co.png

問題解決までの経緯

  1. AWSのコンソールにログインしてS3のコンソールを開く。
  2. アップロードを行うバケット名をクリックする。

    S3_Management_Console.png

  3. 「アクセス権限」をクリックする。

    S3_Management_Console.png

  4. 「パブリックアクセス」の「編集」をクリックする。

    S3_Management_Console.png

  5. 各チェックを外し「保存」をクリックする。

    S3_Management_Console.png

  6. ブラウザから画像アップロードを実施したところエラーが解消され画像ファイルがアップロードされた。

  • このエントリーをはてなブックマークに追加
  • Qiitaで続きを読む

Apache2 の PHP で virtual を使う

Apache2 の PHP で virtual を使う方法です。

参考情報
Apache 関数 > virtual

test_virtual.php
<?php
// ------------------------------------------------------------------
//  test_virtual.php
// ------------------------------------------------------------------
echo "<html>";
echo '<meta http-equiv="CONTENT-TYPE" content="text/html; charset=utf-8" />';
echo "<body>";
echo "<h2>test_virtual.php</h2>";
echo "<blockquote>テスト</blockquote>";
virtual('./some_script.pl');
echo "Jun/19/2020<p />";
echo "</body>";
echo "</html>";
?>
some_script.pl
#! /usr/bin/perl
#
#   some_script.pl
#
print "Content-type: text/html; charset=utf-8\n\n";
print "Hello<br />\n";
print "おはようございます。<br />\n";
print "Good Morning<br />\n";
print "<p />\n";
#

ブラウザーで test_virtual.php にアクセスした結果
test_virtual_jun19.png

  • このエントリーをはてなブックマークに追加
  • Qiitaで続きを読む

WordPressのページ出力にETagを追加する

ETagとは

HTTPレスポンスヘッダのひとつで、リソースの更新状況を示す識別子です。
主にブラウザのキャッシュに利用されます。

ETagを利用したキャッシュ動作の概要

クライアント(主にWebブラウザ)が対象URLのキャッシュを持っていない場合

  • クライアントからサーバへリソース要求
  • サーバはリソースの更新状況に応じたETagをレスポンスヘッダに付加してリソースを送信
  • クライアントは受け取ったリソースを表示し、ETagが紐付けられたキャッシュを保存

クライアントがキャッシュを持っている場合

  • クライアントはキャッシュに紐付くETagIf-None-Matchとしてリクエストヘッダに付加した上で、サーバへリソース要求
  • サーバは受け取ったIf-None-Matchの内容と該当リソースのETagを比較し、一致していなければサーバ側の持つETagをレスポンスヘッダに付加してリソースを送信、一致する場合はリソースは送信せず、HTTPステータスコード304Not Modifiedを返す
  • クライアントは304 Not Modifiedを受け取った場合はキャッシュの内容を表示し、リソースであればそれを表示した上で該当するキャッシュも更新する

ブラウザキャッシュに関連するHTTPヘッダはETagの他にもいくつかありますが、ETagを利用したキャッシュは有効期限を指定するタイプのキャッシュと違い必要最小限の通信は毎回行なう反面、サーバ側でコンテンツが更新されたにもかかわらず古いキャッシュがいつまでも表示されてしまうといったトラブルも回避しやすくなります。

WordPressをETag出力に対応させる

wp-blog-header.phpに対して以下の追記を行ないます。

wp-blog-header.php
    // Set up the WordPress query.
    wp();
+    // 出力バッファリングを有効に
+    ob_start();

    // Load the theme template.
    require_once ABSPATH . WPINC . '/template-loader.php';
+    // 出力バッファを取得し、バッファを削除
+    $obBuffer = ob_get_clean();
+
+    // ETagを生成
+    $eTag = md5($obBuffer);
+
+    // クライアントからのIf-None-Matchを取得
+    $ifNoneMatch = filter_input(INPUT_SERVER, 'HTTP_IF_NONE_MATCH');
+    // 生成したETagをIf-None-Matchと比較
+    // 圧縮転送が有効な場合サーバ側でETagに更にプレフィクスが付加され
+    // クライアントから受け取ったタグにもそれが付加されているため
+    // 完全一致ではなく、ここで生成したETagがIf-None-Matchに含まれているかで判別
+    if(strpos($ifNoneMatch, $eTag) !== false) {
+        // 一致していれば 304 Not Modified を返して終了
+        header('HTTP/1.1 304 Not Modified');
+        exit;
+    } else {
+        // クライアントからIf-None-Matchを受け取っていない、
+        // あるいはETagと異なる場合はレスポンスヘッダにETagを含めてページデータを返す
+        header(sprintf('ETag: "%s"', $eTag));
+        echo $obBuffer;
+    }

}

動作としては、送信されるべきデータを出力バッファを介して横取りし、そのデータを元にETagを生成、クライアントからのIf-None-Matchの状況に応じて、通常通りデータ(とETag)を送るか、未更新を意味する304 Not Modifiedのみを送るかを振り分けています。

出力バッファを横取りするのにちょうどよい位置のアクションフックが見つからなかったので、プラグインではなくwp-blog-header.phpを直接書き換える方法をとっています。
WordPressの自動更新で度々上書きされるファイルでもあるので、できる限り少ない追記で対応できるようにしたつもりです。

もし他に出力バッファを扱うようなプラグイン等を利用していた場合はお互い影響してしまい正常に動作しなくなることも考えられますので、その場合はこのETag出力対応の使用は中止して下さい。

ETagが出力されているか確認

大抵のWebブラウザにはデベロッパーツール(F12キーで起動できる場合が多い)がありますので、その中のネットワーク監視機能からそれぞれのデータのヘッダも確認することができます。
サーバから送られてくるレスポンスヘッダ内にETagのフィールドがあれば送信されてきています。

curlコマンドの-Iオプションでもレスポンスヘッダを確認できます。
WordPressはHEADリクエストされた場合は上記で変更を加えた部分に到達する前に処理が終了するようなので、-X GETオプションも付けてみてください。

一例

$ curl -I -X GET https://********.jp
HTTP/2 200
server: nginx
date: Thu, 18 Jun 2020 17:37:16 GMT
content-type: text/html; charset=UTF-8
x-powered-by: PHP/7.3.18
etag: "b3d0cb21d6686756a2f24a9311b2c467"
vary: Accept-Encoding

同じURLでありながらリクエストするたびETagが変化するようであれば、そのページには毎回変化する何かが含まれているということになります。
そのようなページであっても同じETagを出してしまってはキャッシュとして不適切ですので、それは正常な動作です。

ETagが付加されていることが確認できたら、そのETagIf-None-Matchで指定して再度リクエストしてみます。

$ curl -I -X GET -H "If-None-Match: "b3d0cb21d6686756a2f24a9311b2c467"" https://********.jp
HTTP/2 304
server: nginx
date: Thu, 18 Jun 2020 17:41:00 GMT

HTTPステータスコード304が返ってくれば、正常に動作しています。

WordPressをETag出力に対応させたところで、どの程度恩恵があるのか

固定ページなど、長期間にわたって内容が変化しないようなページが多ければその分ネットワークトラフィックが減りますので、それなりに恩恵はあるかと思います。

表示内容に応じたETagを生成する都合上、送信する・しないに関わらずリソース(ページデータ)自体は生成しますので、サーバ側の負荷という点で見ればリソース分のトラフィックが節約できる以外はほとんど変わりはありませんが、ネットワークトラフィックの節約はとくに転送データ量に制限のあるモバイル通信などでは有用なのではないかと思います。

但し、ページ内容自体は変わっていないように見えても、そのページ内に例えばランダムに変わるおすすめページのピックアップや、コメントフォームにリロードのたびに内容の変わる認証コード系の隠しフィールドなどがあったりしてHTMLソースレベルでの変化があればETagも異なるものとなるため、その場合はキャッシュとしての動作は望めません。
ETagを使うことで恩恵のあるページがどの程度あるかを見極めながら利用を検討してください。

参考

HTTPヘッダー | MDN
HTTPキャッシュ | MDN
ETag | MDN

  • このエントリーをはてなブックマークに追加
  • Qiitaで続きを読む

Laravel掲示板に画像投稿機能を追加してみた!

paizaラーニングのLaravel入門講座で作成した掲示板に画像投稿機能を付けてみました。

画像投稿の仕組み

1.新規投稿ページからファイル選択して画像を投稿
2.storeメソッドでpublicフォルダに画像を格納。ファイルパスをデータベースに格納
3.ファイルパスを読み取り、画像を表示

1.新規投稿機能を追加

新規投稿ビューにファイルアップロードのフィールドを追加する

new.blade.php
<h1>MYPICTURE</h1>
<p>{{$message}}</p>
{{Form::open(['route'=>'picture.store','files'=>true])}}
<div class="form-group">
    <!--投稿した画像のユーザーネームをDBに格納させる-->
    {{Form::label('user_name','Name:')}}
    {{Form::text('user_name',null)}}
</div>
<div class='form-group'>
    <!--投稿したコメントをDBに格納するビュー-->
    {{Form::label('content','Content:')}}
    {{Form::text('content',null)}}
</div>
<div class='form-group'>
    <!--ファイルを読み込む-->
    {{Form::file('thefile')}}
</div>
<div class="form-group">
    {{Form::submit('作成する',['class'=>'btn btn-primary'])}}
    <a href='{{ route("picture.list")}}'>一覧に戻る</a>
</div>
{{Form::close()}}

Form::openメソッドの「'files'=>true」はファイルのアップロードを行うことを指定します。
「{{Form::file('thefile')}}」で下図のようなファイルアップロードのフォームが出来上がります。
image.png

シンボリックリンク

ビューにファイルアップロードの処理を記述したので次はコントローラ...の前に、シンボリックリンクを張りましょう。
シンボリックリンクとは、特定のファイルやディレクトリを指し示す別のファイルを作成し、それを通じて本体を参照できるようにする仕組みのことで、あるフォルダから別のフォルダに参照できるリンクを作成します。
Laravelにはこの機能が備わっており、ファイルアップロードはこの仕組みを利用します。
ディレクトリをプロジェクトの直下に移動させ、以下のコマンドをターミナルに打ち込みます。

$ php artisan storage:link

すると、publicの直下に以下のようなフォルダが出来上がります。
image.png
これにより、publicからstorageフォルダにアクセスできるようになりました!

2.storeメソッド

アップロードした画像をフォルダに、画像のファイル名をデータベースにそれぞれ格納するメソッドをstoreメソッドに追加します。

PictureController.php(storeメソッド)
    public function store(Request $request)
    {
        $picture=new picture();
        //投稿した画像とコメントをDBに格納させる
        $picture->user_name=$request->user_name;
        $picture->content=$request->content;
        $filename=$request->file('thefile')->store('public');       //storageフォルダに投稿した画像を保存しファイルパスを格納
        $picture->image=str_replace('public/','',$filename);        //ファイル名から「public/」を取り除く
        $picture->save();
         //tinkerコマンドと同じ
        return redirect()->route('picture.show',['id'=>$picture->id]);
    }

アップロードした画像ファイルはstore('')関数でランダムで名前が付けられ、指定したディレクトリに保存されます。(この場合はpublic以下のフォルダに指定)
この時、デフォルトで「storage/app/」に保存され、ファイル名に「public/」と付いてしまう為、ファイル名からこれを取り除く為に一旦$filenameにファイル名を格納し、str_replace関数で「public/」を指定することで取り除いています。
あとは投稿者名やコメントと共にデータベースにファイル名を格納させます。

3.ファイルパスを読み取り、画像を表示

showメソッドとビューを以下のように記述します。

showメソッド

PictureController.php(showメソッド)
    public function show(Request $request,$id,Picture $picture)
    {
        $message='This is your picture.'.$id;
        $picture=Picture::find($id);
        Storage::disk('local')->exists('public/storage/'.$picture->image);
         //$idに格納された番号と一致したデータを引っ張り出す。
        return view('show',['message'=>$message,'picture'=>$picture]);
    }

今回はローカル環境でこのアプリを作ったため、投稿した画像はローカルディスクに保存されます。
なので、「Storage::disk('local')」でローカルディスクを読み取り、「exists」に第一引数として「public/storage/」を、第二引数としてデータベースのimageカラムに格納されているファイル名をそれぞれ指定させます。

ビュー

詳細ページのビューに以下の記述を追加します。

<p><img src="{{ asset('/storage/'.$picture->image)}}"></p>

これでstorageフォルダ直下に格納された画像ファイルを、ファイル名ごとに表示することができました!

完成形

以上のことを踏まえて作成した画像投稿機能ですが、実際はこんな感じになります。

新規投稿画面

image.png

詳細ページ

image.png

課題

今回は「画像を投稿させ、それを表示させる。」ことを最優先にしたので、以下2つの課題があります。

保存するフォルダについて

今回は「storage/app/public」直下に画像を保存させましたが、これだとアプリを公開した際に外から画像のフォルダが丸見えになってしまいます。
ローカル環境ではまだ良いかもしれませんが、公開するとなるとセキュリティ面で非常に危険な状態なので、投稿した画像のフォルダが外から見えないようにしなければなりません。

ファイルのアップロードについて

「画像をアップロードする」って言ってるくせに、実際に組んだのは「ファイルのアップロード」の機能です。
これだと、画像に限らず、テキストだろうが何だろうが「ファイル」という形の物でしたら基本的に何でもアップロードできます。
これを「画像ファイルのみ」にするには、「バリデーション」というファイルの種類を指定する処理が必要になります。

以上2つ、課題として挙げた機能を今後追加していこうと思います。

まとめ

今回はLaravel掲示板に画像投稿機能を追加してみました。
正直、思い付きで「掲示板に画像を投稿出来るようにした方がよくね?」と考えて実装してみましたが、実際にやってみるとかなり難しく、時間がかかってしまいました...。
途中、teratailで質問を投げましたが、回答やアドバイスをして下さった方々に、この場を借りて御礼を申し上げます。

参考文献

[Laravel] ユーザーのアイコン画像を投稿、表示させる機能の実装したのでメモ(画像の保存場所は?シンボリックリンクって?)
[Laravel]保存した画像が404エラーで表示されない
Laravel シンボリックリンクで少しハマった話
IT用語辞典 シンボリックリンク
Laravel Recipes ファイルアップロードのフィールドを作成する
Laravelで画像をアップロードする方法

  • このエントリーをはてなブックマークに追加
  • Qiitaで続きを読む

【Laravel+MySQL+Docker+nginx】Laravel開発環境をDockerで構築する手順

はじめに

Laravelの勉強するための開発環境作成手順メモです。
自分用なので雑い。

準備

Dockerの設定だったりしていきます。

ディレクトリ

プロジェクトディレクトリ配下に以下2つのディレクトリを作成。

  • Docker
  • server

docker-compose.yml

プロジェクトディレクトリ配下にdocker-compose.ymlファイルを作成。

docker-compose.yml
version: '3'

services:
  php:
    container_name: php
    build: ./docker/php
    volumes:
      - ./server:/var/www

  nginx:
    image: nginx
    container_name: nginx
    ports:
      - 80:80
    volumes:
      - ./server:/var/www
      - ./docker/nginx/default.conf:/etc/nginx/conf.d/default.conf
    depends_on:
      - php

  db:
    image: mysql:5.7
    container_name: db
    environment:
      MYSQL_ROOT_PASSWORD: root
      MYSQL_DATABASE: test_db
      TZ: 'Asia/Tokyo'
    command: mysqld --character-set-server=utf8mb4 --collation-server=utf8mb4_unicode_ci
    volumes:
      - ./docker/db/data:/var/lib/mysql
      - ./docker/db/my.cnf:/etc/mysql/conf.d/my.cnf
      - ./docker/db/sql:/docker-entrypoint-initdb.d
    ports:
      - 4306:3306

Dockerfile

Dockerディレクトリ配下にphpフォルダを作成。
その中にDockerfileを作成。

FROM php:7.3-fpm
COPY php.ini /usr/local/etc/php/

RUN apt-get update \
    && apt-get install -y zlib1g-dev libzip-dev mariadb-client \
    && docker-php-ext-install zip pdo_mysql

#Composer install
COPY --from=composer:latest /usr/bin/composer /usr/bin/composer

ENV COMPOSER_ALLOW_SUPERUSER 1

ENV COMPOSER_HOME /composer

ENV PATH $PATH:/composer/vendor/bin

WORKDIR /var/www

RUN composer global require "laravel/installer"

PHPの設定

Dockerfileと同階層でphp.iniファイルを作成。

php.ini
[Date]
date.timezone = "Asia/Tokyo"
[mbstring]
mbstring.internal_encoding = "UTF-8"
mbstring.language = "Japanese"

nginx

Dockerディレクトリにnginxフォルダを作成。
その中にdefault.confファイルを作成。

default.conf
server {
  listen 80;

  root  /var/www/public;
  index index.php;

  location / {
    try_files $uri $uri/ /index.php$is_args$args;
  }

  location ~ \.php$ {
    fastcgi_split_path_info ^(.+\.php)(/.+)$;
    fastcgi_pass   php:9000;
    fastcgi_index  index.php;

    include        fastcgi_params;
    fastcgi_param  SCRIPT_FILENAME $document_root$fastcgi_script_name;
    fastcgi_param  PATH_INFO $fastcgi_path_info;
  }
}

MySQL

Dockerディレクトリにdbフォルダを作成。
その中にmy.cnfファイルを作成。

my.cnf
[mysqld]
character-set-server=utf8mb4
collation-server=utf8mb4_unicode_ci

[client]
default-character-set=utf8mb4

Laravelプロジェクト

プロジェクトのルートディレクトリに移動。
以下コマンドを実行。

docker-compose up -d
docker-compose exec php bash
laravel new

確認

locallhostで画面が表示されれば成功。
やったね。

DB設定

dockerで開発環境を作った場合、.envファイルとdocker-compose.ymlファイルの情報を合わせとかないとmigrateできなかったりとめんどくさいので今やっとく。

DB_CONNECTION=mysql
DB_HOST=db
DB_PORT=3306
DB_DATABASE=test_db
DB_USERNAME=root
DB_PASSWORD=root

これでDBの登録も問題なし。

まとめ

自分用メモなのでめちゃくちゃざっくりしてますが参考になれば。

  • このエントリーをはてなブックマークに追加
  • Qiitaで続きを読む

【Laravel】Laravel最低限の基礎メモ

経緯

ずっとRailsをしてたのですが、ありがたいことにPHPメインの自社開発企業に就職できたので現在PHPを勉強しています。

ただMVCの仕組みだったりあんなにRailsで頑張って覚えたのに、私の記憶が頼りないせいで忘れてしまいそうなので、考えが似た感じのPHPフレームワークLaravelで定着させようと思い勉強し始めました。

まだめっちゃ基礎の段階ですが、とりあえず復習のために色々メモします。

Laravel

PHPのフレームワーク。
多分今PHPのフレームワーク=Laravelみたいになってる。

まだ初歩なので何とも言えませんが、今のところRailsと似てるので割と理解しやすい印象です。

デバックツール

composer require barryvdh/laravel-debugbar

artisan

Railsコマンド的なやつ。
Serverディレクトリで実行します。

php artisan ~~~

Model

Model名は単数形。

作成

php artisan make:model Models/name

マイグレーションファイルとコントローラーも同時に作成する場合

php artisan make:model Models/name -mc

マイグレーションファイルだけ一緒に作りたい場合

php artisan make:model Models/name -m

Migration

テーブル作成の履歴的な。
Migration名は複数形。

作成

php artisan make:migration create_names_table

Databaseフォルダに作成される。
カラム追加は公式参照。
https://readouble.com/laravel/5.5/ja/migrations.html
追加したらMigrateする。

php artisan migrate

カラムを追加したいとき

php artisan make:migration add_カラム名_to_カラムを追加したいテーブル名_table —table=カラムを追加したいテーブル名->after('後ろに挿入したい既存カラム名');

Controller

いろんな処理するところ。

作成

php artisan make:controller コントローラー名

記述

class内にpublicでメソッドを作成。

***Controller.php
return view(viewのフォルダ名.表示させたいファイル名);

View

ファイル名には必ず「***.blade.php」とつける。

Route

記述

web.php
Route::get(表示したいViewのフォルダ名/表示したいファイル名,コントローラー名@メソッド名);

ルート一覧書き出し

php artisan route:list > ファイル名.text

ファサード

get,select,where,groupbyなどSQLに近い構文。

***Controller.php
DB::table(テーブル名)->get();

クエリビルダ

***Controller.php
use Illuminate\Support\Facades\DB; //Controllerに貼り付け

Laravel UI

jsやsassを入れる。
ログイン・新規登録機能などをつける。

【参考】
https://www.techpit.jp/courses/laravel6-aws/lectures/13324326

メッセージの日本語化

下記ファイルをlangフォルダにコピー。
https://github.com/minoryorg/laravel-resources-lang-ja

config/app.phpのlocationがjaになってるか確認。

まとめ

公式見るのがぶっちゃけ一番早い。
Railsと違ってわかりやすかったのでとりあえず困ったら公式でまず検索かけるのが良いと思いました。

あくまでメモなので随時追加していくと思います。

【公式】
https://readouble.com/laravel/7.x/ja/releases.html

  • このエントリーをはてなブックマークに追加
  • Qiitaで続きを読む