SHOYAN BLOG

I am a pragmatic programmer.

レガシーコード向けに修正した部分だけPHP構文チェックをする仕組みを作った

修正した部分だけPHPの構文チェックをする仕組みを作ってみました。
構文に問題があればGithubのPull Requestにコメントされます。

2016-06-23_php-syntax-check

この方法のよいところは既存のソースを変えることなくPHPの構文チェックの仕組みを導入できることです。
規約に沿っていないコードが大量にあり、かつテストコードがないような環境(レガシー環境)にも導入することができます。

使用したツールは以下です。

また、packsaddleのツールはRuby製ですので、Rubyが動作する環境が必要です。

サンプルとしてGithubにphp-syntax-checkというリポジトリを作成しているので参考にしてください。

以下のスクリプトをCircleCIで実行しています。

check_syntax.sh

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
#!/bin/bash

echo "Start"
LIST=`git diff --name-only origin/master | grep -e '.php$'`

if [ -z "$LIST" ]; then
    echo "PHP file not changed."
    exit 0
fi

if [ -n "$CI_PULL_REQUEST" ]; then
    git diff --name-only origin/master \
        | grep -e '.php$' \
        | xargs vendor/bin/phpcs -n --standard=PSR2 --report=checkstyle \
        | bundle exec checkstyle_filter-git diff origin/master \
        | bundle exec saddler report \
        --require saddler/reporter/github \
        --reporter Saddler::Reporter::Github::PullRequestReviewComment
fi

仕組み

以下の3つのセクションに分類されます。

  • 対象のファイルを抽出
  • 構文チェック
  • 結果をレポート

1. 対象のファイルを抽出

以下のコマンドでmasterと差分のあるファイル名を取得します。

1
git diff --name-only origin/master

さらに grepで拡張子が .phpのファイルのみ対象にします。

1
git diff --name-only origin/master | grep -e ‘.php$'

2. 構文チェック

構文チェックツールは好きなものを使えます(CheckStyleフォーマットで出力できるものに限りますが)。
今回はPHP_CodeSnifferを使いました。

1
xargs vendor/bin/phpcs -n --standard=PSR2 --report=checkstyle

xargsは前のコマンドを引数でとるために必要です。

以下のコマンドでmasterとブランチの差分をチェックして、差分があるところのエラーを検知対象のエラーとしています。

1
bundle exec checkstyle_filter-git diff origin/master

エラーの差分の抽出にcheckstyle_filter-gitというツールを使っています。
これは、入力として渡したCheckStyle formatの文字列から、変更した内容の部分のエラーを抽出するツールです。
例えばファイルの10行目〜15行目を変更した場合、その行で発生したエラーのみを抽出します。

3. 結果をレポート

saddlerを使って結果をGithubに通知します。

1
2
3
bundle exec saddler report \
        --require saddler/reporter/github \
        --reporter Saddler::Reporter::Github::PullRequestReviewComment

注意点としては、PRを作っていないと通知でエラーとなります。

ですので、Pull Requestがあるかどうかをチェックするif文をいれています。
$CI_PULL_REQUEST はCIrcleCIの変数で、Pull Requestが作られていればこの変数にURLが格納されています。

1
2
3
if [ -n "$CI_PULL_REQUESTS" ]; then

fi

また、通知にはGITHUB_ACCESS_TOKENを発行して環境変数に登録しておく必要があります。

CircleCIの環境変数の設定については、以下を参照ください。

また、通知ではなく単に結果を出力する場合は、saddler/reporter/textを指定します。

1
2
3
bundle exec saddler report \
  --require saddler/reporter/text \
  --reporter Saddler::Reporter::Text

以下は、出力があった場合はエラーにする例です。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
RESULT=`git diff --name-only origin/master \
    | grep -e '.php$' \
    | xargs vendor/bin/phpcs -n --standard=custom_ruleset.xml --report=checkstyle \
    | bundle exec checkstyle_filter-git diff origin/master \
    | bundle exec saddler report \
      --require saddler/reporter/text \
      --reporter Saddler::Reporter::Text`

if [ -n "$RESULT" ]; then
    echo ""
    echo "An error has been detected,this following:"
    echo "$RESULT"
    exit 1
fi

その他

ファイルのエンコードがEUC-JPのソースコードに適用したところエラーとなりました。 リポジトリをフォークして対応しました。
1.1.0より使えるようになりました!

以下のように設定します。

Gemfile

1
gem "checkstyle_filter-git", git: "https://github.com/shoyan/ruby-checkstyle_filter-git.git", branch: "implement-exec"

iconvでgit diffの出力結果の文字コードをEUC-JP->UTF-8に変換して渡せるようにしました。

1
2
3
4
git diff --name-only origin/master \
  | grep -e '.php$' \
  | xargs phpcs -n --standard=custom_ruleset.xml --report=checkstyle \
  | bundle exec checkstyle_filter-git exec "git diff origin/master | iconv -f EUCJP -t UTF8"

PRもしているので直ることに期待です。
Mergeいただきました。

サンプルとしてGithubにphp-syntax-checkというリポジトリを作成しているので参考にしてください。

関連記事

参考リンク

ShellのTipsてきなやつ

ShellスクリプトのTipsです。

シェル変数の初期化をする

変数に値が設定されていない場合は、指定された値を返します。

1
: ${VAR:=初期化する値}

パラメーターを取得する

$@ にパラメーターが格納されています。

例えば以下の記述をした for.sh ファイルを作成します。

1
2
3
for role in "$@"; do
  echo $role
done

以下のように出力されます。

1
2
3
$ ./for.sh hoge moge
=> hoge
moge

評価結果を変数に格納する

"$()" で囲めば、評価結果を変数に格納することができます。

1
2
3
4
5
6
7
8
9
$ name="$(hostname)"
$ echo $name
=> shoyan-pc

# 以下も同じです。

$ name=`hostname`
$ echo $name
=> shoyan-pc

if と test コマンドの合わせ技

ifとtestコマンドを使う場合はtestコマンドを省略した[]がよく使われます。

[ 文字列1 = 文字列2 ]
[ 数値1 オプション 数値2 ]
[ オプション 評価対象 ]

[ の直後と ] の直前には必ず半角スペースが必要です (無いと正常に動作しません)。

ちなみに比較は == でなくて =です。

シェルの比較は文字列の比較と数値の比較があり、それぞれに対応するオペレーターがあります。
そのせいで複雑になっている印象があります。

数値の比較

数値1と数値2が等しいかどうか

-eq、もしくは =を使います。

1
2
3
4
$  [ 1 -eq 1 ]; echo $?
0
$  [ 1 = 1 ]; echo $?
0

数値1と数値2が等しくないかどうか

-ne を使います。

1
2
$  [ 1 -ne 2 ]; echo $?
0

!= でも動作しますが、慣習的にあまり使わないようです。

文字列の比較

文字列1と文字列2が等しいかどうか

同じ場合にtrueを返す=と その否定である != を使います。

1
2
$  [ "hey" = "hey" ]; echo $?
0

ちなみに数値の比較に使う -eq で比較したところエラーとなりました。

1
2
3
$  [ "hey" -eq "hey" ]; echo $?
[: integer expression expected: hey
2

文字列1と文字列2が等しくないかどうか

文字列比較のNOT条件
!=を使用します。

1
[ "$hoge" != "fuga" ]

数値比較のNOT条件

数値の比較の場合は -ne オプションを使うのが慣習のようです。

1
[ 1 -ne 2 ]

変数が空かどうか

1
[ -z "$hoge" ]

空でないかを判定する場合は -n を使います。

1
[ -n "$hoge" ]

findで抽出したファイルをreadコマンドで1行ずつ処理する

1
find application -type f -name "*.ini" | while read LINE; do wc -l ${LINE}; done;

実行ファイルのあるパスを取得する

1
echo "$(cd $(dirname $0);pwd)"

1つ上のディレクトリは以下のように取得する

1
echo "$(cd $(dirname $0);cd ..;pwd)"

参考リンク

Treant.jsでツリーダイアグラムを描画する

Treant.jsとはツリーダイアグラムを描画するためのJavaScriptライブラリです。
Treant.jsでツリーダイアグラムを描画してみました。

http://codepen.io/shoyan/details/MeaqzN/

簡単なサンプルを作ってみる

まずは簡単なサンプルを作ってみましょう。

http://codepen.io/shoyan/pen/qNOMoN

codepen.ioを使うと簡単にサンプルが作成できるので便利です。

Treant.jsを利用するには、いくつか必要なモジュールがあります。

JS

CSS

BaseとなるHTMLのテンプレート

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
<!doctype html>
<html lang="ja">
<head>
    <meta charset="utf-8"/>
    <title></title>

    <!-- stylesheets -->
    <link rel="stylesheet" href="http://fperucic.github.io/treant-js/Treant.js" type="text/css"/>
     <style>
         body,div,dl,dt,dd,ul,ol,li,h1,h2,h3,h4,h5,h6,pre,form,fieldset,input,textarea,p,blockquote,th,td { margin:0; padding:0; }
         body { background: #fff; }
         /* optional Container STYLES */
         .chart { height: 159px; width: 332px; margin: 5px; margin: 5px auto; border: 3px solid #DDD; border-radius: 3px; }
         .node { color: #9CB5ED; border: 2px solid #C8C8C8; border-radius: 3px; }
         .node p { font-size: 20px; line-height: 20px; height: 20px; font-weight: bold; padding: 3px; margin: 0; }
    </style>
</head>
<body>
    <div id="tree-simple" style="width:335px; height: 160px" </div>

    <!-- javascript -->
    <script src="http://fperucic.github.io/treant-js/vendor/raphael.js"></script>
    <script src="http://fperucic.github.io/treant-js/Treant.js"></script>
</body>
</html>

JS

JavaScriptファイルです。設定をJSONで定義して、コンストラクタに渡しています。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
simple_chart_config = {
    # chartの設定をします
    chart: {
        container: "#tree-simple"
    },
    # ノードを定義します
    nodeStructure: {
        text: { name: "Parent node" },
        children: [
            {
                text: { name: "First child" }
            },
            {
                text: { name: "Second child" }
            }
        ]
    }
};
# コンストラクタ
var my_chart = new Treant(simple_chart_config);

基本的には、nodeStructureに必要なノードを定義して、CSSで見た目を調整するという感じです。
アニメーションにも対応していて、その場合はjQueryが必要だったりするようです。
サンプルも色々あるので参考にしてみるとよいと思います。
http://fperucic.github.io/treant-js/

[Ruby]CSVファイルのエンコードをsjisに指定する

日本語の場合、CSVのファイルエンコードをShift JISにする要件がけっこうあると思います。
RubyのCSVライブラリは、encodingというオプションが用意されており、encoding: ’sjis’ のようにファイルエンコーディングを指定できます。

1
2
3
4
require 'csv'
CSV.open("hoge.csv", "wb", encoding: 'sjis') do |csv|
  csv << ["ID", "担当者", "メールアドレス"]
end

nkfコマンドを使ってファイルエンコーディングを確認します。

1
2
  nkf -g hoge.csv
Shift_JIS

Shift_JISで作成されていることが確認できました。

FTPについて調べてみた

FTPはすでにご存知のかたも多いと思います。
自分はFTPについて、「ファイル転送に使われるプロトコルであり、セキュリティが脆弱である」というくらいしか理解していなかったので詳細を把握するために調べました。

FTPとは

FTPとはインターネットの初期の頃から存在するプロトコルで、今でもインターネットでよく使われています。
用途としては、以下に使われます。

  • ウェブページの各種ファイル(HTMLや画像)をクライアントのPCからサーバーへアップロードする
  • ソフトウェアの配布サイトやFTPファイルサーバーからクライアントPCへダウンロードする

FTPを利用するにはユーザー名とパスワードが必要です。
ソフトを配布するための目的で使う匿名でアクセスできるAnonymous(匿名)FTPサーバーもあります。
しかし、形式上ユーザー名とパスワードは必要なので、ユーザー名にanonymousやftpを使います。パスワードは何でもよいですが、慣習としてメールアドレスを入力するようです。

プロトコルの概要

FTPのプロトコルはRFC959に定義してあります。
RFC959が書かれたのが1985年なのでおよそ30年前に作られたプロトコルです。

FTPは大きくわけると、コネクションの確立とデータ転送にわけることができます。
そして、使うポートも2つが用意してあります。

  • 20: データ転送ポート
  • 21: コントロールポート

20番がデータを転送するときに使うポートで、21番は認証などの接続に使うためのポートです。

コネクションの確立

FTPは アクティブモードパッシブモード のいずれかで動作し、サーバーとの接続をする際にどちらかを選択します。

アクティブモードの場合

アクティブモードの場合、クライアントはデータ転送用のポートを用意しPORTコマンドを使って待ち受けポートをサーバーに伝えます。
サーバーはPORTコマンドを受け取ると、そのポートに対して接続を行います。
しかし、Firewallがある環境の場合、サーバーからの接続が拒否されうまくいかない場合があります。
その際はパッシブモードを使います。

パッシブモード

パッシブモードでは、まず最初にクライアントがPASVというコマンドを使いサーバーに送信します。
そのコマンドを受け取ったサーバーは自身のIPアドレスとポート番号をクライアントに送信します。
クライアントは受け取ったIPアドレスとポート番号へコネクションを確立しにいきます。

アクティブモードはサーバーより接続を行う、パッシブモードはクライアントより接続を行うという違いがあります。
ちなみに、FTPはよく使われるプロトコルのため、PORTコマンドがFirewallを通過する際にPORTコマンドに書かれたPORTは通過できるようにしてくれるFirewallもあるとのことです。

データ転送

接続ができたら、次はデータ転送を行います。
FTPにはデータタイプという考え方があって、現在は2つのモードが使われています。

アスキーモード

アスキーモードは、必要であればデータを変換します。
例えば異なるOS間でファイルを送ると改行コードが違ったりする場合があると思いますが、アスキーモードはファイルを受け取るホストに適した改行コードにファイルを変換します。

Imageモード(バイナリモード)

Imageモードは一般的にバイナリモードと呼ばれます。
バイト単位でデータを転送します。
アスキーモードのようにデータの変換は行われません。

データの転送モードには、以下の3つのモードがあります。

  • ストリームモード
  • ブロックモード
  • 圧縮モード

ストリームモード

データをそのまま転送するモードです。

ブロックモード

データをブロックに分割して転送するモードです。

圧縮モード

ランレングスエンコーディングを使ってデータを圧縮して転送するモードです。

セキュリティ上の問題

FTPはセキュアなプロトコルとして設計されていません。
ユーザー名やパスワードを暗号化せずに送信する問題のほかにも数多くのセキュリティ脆弱性があげられています。

  • 総当たり攻撃
  • en:FTP bounce attack
  • パケットキャプチャ (sniffing)
  • Port stealing
  • en:Spoofing attack
  • ユーザ名保護

また、通信内容を暗号化できないので通信経路上でパケットキャプチャすることで盗聴することができてしまいます。
セキュアにする一般的な方法として、SSL/TLSセッション上で通信を行うようにします。
これをFTPSと言います。
また、SSHを介してファイル転送を行うSFTP、SCPを使います。

ちなみにFTPSとSFTPの違いは以下のようになります。

  • FTPS : FTPの通信をSSL/TLSで暗号化 → FTPの拡張
  • SFTP : SSHの通信を使って、FTPを行う → SSHで動作するアプリケーション

理解を深めるためにtelnetを使って実際にFTPサーバーと通信してみます。

実際にFTPを試してみる

実際にFTPを試してみる場合、FTPサーバーが必要です。
ロリポップ!レンタルサーバーを使えば無料でFTPサーバーが利用できます。

ちなみにtelnetでファイルの送受信はできません。
というのも、telnetでは1つのポートを使った通信しかサポートしていないからです。
FTPはデータ転送用のポートと制御用のポートの2つを利用するため、telnetでは認証しか行えません。

まずは、telnetで認証をやってみます(localhostにftpサーバーが起動しているという前提です)。

1
2
3
4
5
$ telnet localhost 21
Trying 127.0.0.1...
Connected to localhost.
Escape character is '^]'.
220 FTP server ready.

まず、ユーザ名を入力します。

1
2
USER ftp
331 Password required for example.com

次に、パスワードを入力します。

1
2
PASS 123
230 User example.com logged in.

telnetでは他に何もできませんのでQUITします。

1
2
3
QUIT
221 Goodbye.
Connection closed by foreign host.

FTPでファイルの送受信を行う

FTPでファイルの送受信を行うには、ftpコマンドを使います。
-d オプションはデバッグモードです。

1
ftp -d localhost 21

ユーザー名とパスワードを聞かれるので入力します。

lsでファイルの一覧をみることができます。

1
2
3
4
5
6
7
8
9
10
ftp> ls
---> EPSV
229 Entering Extended Passive Mode (|||65086|)
229 Entering Extended Passive Mode (|||65086|)
---> LIST
150 Opening ASCII mode data connection for file list
drwx---r-x   6 shoyan shoyan      4096 Jun 17 11:31 .
drwx---r-x   6 shoyan shoyan     4096 Jun 17 11:31 ..
-rw-r--r--   1 shoyan shoyan     3096 Jun 17 11:30 index.html
226 Transfer complete

getコマンドでファイルをダウンロードできます。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
ftp> get index.html
local: index.html remote: index.html
---> SIZE index.html
213 3096
---> EPSV
229 Entering Extended Passive Mode (|||65011|)
229 Entering Extended Passive Mode (|||65011|)
---> RETR index.html
150 Opening BINARY mode data connection for index.html (3096 bytes)
100% |************************************************************************************************************************************************************************************************|  3096        4.61 MiB/s    00:00 ETA
226 Transfer complete
3096 bytes received in 00:00 (163.72 KiB/s)
---> MDTM index.html
213 20160617023028
parsed date `20160617023028' as 1466130628, Fri, 17 Jun 2016 11:30:28 +0900

参考リンク