kikukawa's diary

都内で活動するシステムエンジニアが書いてます。 興味を持った技術やハマったポイント、自分用メモをつけてます。 最近はweb中心

phpmdでFileCacheDriverのエラー

自分用メモ。
vagrant環境で、phpmdを走らせててエラーが発生しました。
原因は、vagrantのhdの容量不足でした。

エラー内容

下記のようなエラーが大量に流れてて、
原因が分からずしばらく悩みました。
細かいエラーメッセージはちょっとずつ違いますが、
共通しているのはFileCacheDriverでエラーを起こしていることです

PHP Warning:  fopen(/home/vagrant/.pdepend/4q/4q542z177ea.5.6.cache): failed to open stream: No such file or directory in /path/to/vendor/pdepend/pdepend/src/main/php/PDepend/Util/Cache/Driver/FileCacheDriver.php on line 163
PHP Stack trace:
PHP   1. {main}() /path/to/vendor/phpmd/phpmd/src/bin/phpmd:0
PHP   2. PHPMD\TextUI\Command::main() /path/to/vendor/phpmd/phpmd/src/bin/phpmd:122
PHP   3. PHPMD\TextUI\Command->run() /path/to/vendor/phpmd/phpmd/src/main/php/PHPMD/TextUI/Command.php:173
PHP   4. PHPMD\PHPMD->processFiles() /path/to/vendor/phpmd/phpmd/src/main/php/PHPMD/TextUI/Command.php:133
PHP   5. PHPMD\Parser->parse() /path/to/vendor/phpmd/phpmd/src/main/php/PHPMD/PHPMD.php:222
PHP   6. PDepend\Engine->analyze() /path/to/vendor/phpmd/phpmd/src/main/php/PHPMD/Parser.php:123
PHP   7. PDepend\Engine->performParseProcess() /path/to/vendor/pdepend/pdepend/src/main/php/PDepend/Engine.php:323
PHP   8. PDepend\Source\Language\PHP\AbstractPHPParser->parse() /path/to/vendor/pdepend/pdepend/src/main/php/PDepend/Engine.php:575
PHP   9. PDepend\Util\Cache\Driver\FileCacheDriver->store() /path/to/vendor/pdepend/pdepend/src/main/php/PDepend/Source/Language/PHP/AbstractPHPParser.php:403
PHP  10. PDepend\Util\Cache\Driver\FileCacheDriver->write() /path/to/vendor/pdepend/pdepend/src/main/php/PDepend/Util/Cache/Driver/FileCacheDriver.php:151
PHP  11. fopen() /path/to/vendor/pdepend/pdepend/src/main/php/PDepend/Util/Cache/Driver/FileCacheDriver.php:163

容量を喰う理由

phpmdを走らせると、依存ライブラリのpdepend がhome配下に、.pdepend というディレクトリを作り、
そこにファイルのキャッシュを溜め込みます。
このディレクトリの容量が増え続けるので、容量が足りないと正常に実行できません。

検証ファイル数が膨大というわけではないですが、 最終的に500M程度の容量が必要でした。

PHPMDのルールセットの作り方

phpmdのrulesetはxmlファイルとしてまとめておくことができます。
このファイルの内容を毎回調べてるので自分用メモ
公式ドキュメントに書いてあることがほとんどです。

https://phpmd.org/documentation/creating-a-ruleset.html

初期状態

雛形です。
nameとdescriptionは、それぞれ自分のプロジェクトにあったものにします。
descriptionはなくても大丈夫です。
ファイルの配置場所と名前はなんでも大丈夫ですが、
私は、大抵の場合、phpcsと一緒に使うので、phpmd.xmlというファイル名にすることが多いです。
配置場所は、プロジェクトのルートディレクトリにおいてしまうことが多いです。

<?xml version="1.0"?>
<ruleset name="My first PHPMD rule set"
         xmlns="http://pmd.sf.net/ruleset/1.0.0"
         xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
         xsi:schemaLocation="http://pmd.sf.net/ruleset/1.0.0
                     http://pmd.sf.net/ruleset_xml_schema.xsd"
         xsi:noNamespaceSchemaLocation="
                     http://pmd.sf.net/ruleset_xml_schema.xsd">
    <description>
        My custom rule set that checks my code...
    </description>
</ruleset>

ルールセットを追加してみる

phpmdが元々持っているunused code のrulesetを追加してみます。
使ってない変数とかを警告してくれるものなので、どんなプロジェクトでも入れるでしょう。
下記の書き方だと、 unused codeのすべてのruleを追加します。

unusedcodeの部分はドキュメントを見ても、
具体的にどんなものを指定すればよいか書いてないのですが、

https://phpmd.org/rules/index.html

のCurrent Rulesetsに書いてあるリストを、全て小文字にして、Rulesを消したもので大丈夫です。
具体的には、 Unused Code Rules: だったら、unusedcodeになります。

関係ないですが、下記ではdesctiprionを消してます。

<?xml version="1.0"?>
<ruleset name="PHPMD rule set for foo"
         xmlns="http://pmd.sf.net/ruleset/1.0.0"
         xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
         xsi:schemaLocation="http://pmd.sf.net/ruleset/1.0.0
                     http://pmd.sf.net/ruleset_xml_schema.xsd"
         xsi:noNamespaceSchemaLocation="
                     http://pmd.sf.net/ruleset_xml_schema.xsd">
    <!-- unused code の rule set を追加 -->
    <rule ref="rulesets/unusedcode.xml"/>
</ruleset>

ルールセットの中から一つだけルール追加してみる

さっきは、phpmdが用意してくれているルールセットの中から一つを選んで、
そのルールセットが持っているすべてのルールを追加しました。
今度は、ルールを一つだけ追加してみます。

Controversial Rulesというルールセットだと、
メソッド名などにキャメルケースを強制させるルールがあります。
しかし、プロジェクトによっては、スネークケースにしているところもあるでしょう。
その場合でも、Superglobals のルールだけは適用したいという場合の書き方です。

<?xml version="1.0"?>
<ruleset name="PHPMD rule set for foo"
         xmlns="http://pmd.sf.net/ruleset/1.0.0"
         xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
         xsi:schemaLocation="http://pmd.sf.net/ruleset/1.0.0
                     http://pmd.sf.net/ruleset_xml_schema.xsd"
         xsi:noNamespaceSchemaLocation="
                     http://pmd.sf.net/ruleset_xml_schema.xsd">
    <!-- unused code の rule set を追加 -->
    <rule ref="rulesets/unusedcode.xml"/>
    <!-- スネークケースのプロジェクトなので、Superglobalsだけ適用 -->
    <rule ref="rulesets/controversial.xml/Superglobals"/>
</ruleset>

ちなみに、1つのルールだけを除外したい場合は、excludeを使います。
例えば、Controversial Rulesというルールセットから、
CamelCaseClassName だけを除外したい場合は
下記のように書きます。

<rule ref="rulesets/controversial.xml">
    <exclude name="CamelCaseClassName"/>
</rule>

なので、前述のSuperglobalsだけ適用する場合は、

<rule ref="rulesets/controversial.xml/Superglobals"/>

このように書き換えても同様です。
適用ルールが多いか、除外ルールが多いかで決めればいいと思います。

<rule ref="rulesets/controversial.xml">
    <exclude name="CamelCaseMethodName"/>
    <exclude name="CamelCaseParameterName"/>
    <exclude name="CamelCaseVariableName"/>
    <exclude name="CamelCasePropertyName"/>
    <exclude name="CamelCaseClassName"/>
</rule>

ルールのプロパティを変えたい場合

Naming Rules を適用すると、$id というよく使う変数名まで警告されてしまいます。
ShortVariable ルールのデフォルトのminimumが3だからです。
2文字以下の変数名は警告されます。
なので、$id だけは、警告から除外されるようにします。

ポイントは、一旦ShortVariableを除外することです。
除外しないと、元々の値が適用されてしまいます。
プロパティ名やどんな値を指定するかは、ドキュメントに書いてあります。

<?xml version="1.0"?>
<ruleset name="PHPMD rule set for foo"
         xmlns="http://pmd.sf.net/ruleset/1.0.0"
         xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
         xsi:schemaLocation="http://pmd.sf.net/ruleset/1.0.0
                     http://pmd.sf.net/ruleset_xml_schema.xsd"
         xsi:noNamespaceSchemaLocation="
                     http://pmd.sf.net/ruleset_xml_schema.xsd">
    <!-- unused code の rule set を追加 -->
    <rule ref="rulesets/unusedcode.xml"/>
    <!-- スネークケースのプロジェクトなので、Superglobalsだけ適用 -->
    <rule ref="rulesets/controversial.xml/Superglobals"/>
    <!-- id という変数名だけは許可 -->
    <rule ref="rulesets/naming.xml">
        <exclude name="ShortVariable"/>
    </rule>
    <rule ref="rulesets/naming.xml/ShortVariable">
        <properties>
            <property name="exceptions" value="id"/>
        </properties>
    </rule>
</ruleset>

汎用サンプル

コードスタイルにPSRを適用していて、
phpunitで、テストコード書いているようなプロジェクトなら、
全部盛りで下記のような感じになると思います。

TooManyMethods,TooManyPublicMethodsで、
メソッド名がtestから始まる場合は、カウントから除外するようにしています。
テストクラスは、メソッドが増えること多いので。

<?xml version="1.0"?>
<ruleset name="PHPMD rule set for foo"
         xmlns="http://pmd.sf.net/ruleset/1.0.0"
         xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
         xsi:schemaLocation="http://pmd.sf.net/ruleset/1.0.0
                     http://pmd.sf.net/ruleset_xml_schema.xsd"
         xsi:noNamespaceSchemaLocation="
                     http://pmd.sf.net/ruleset_xml_schema.xsd">
    <rule ref="rulesets/unusedcode.xml"/>
    <rule ref="rulesets/design.xml"/>
    <rule ref="rulesets/controversial.xml"/>
    <!-- id という変数名だけは許可 -->
    <rule ref="rulesets/naming.xml">
        <exclude name="ShortVariable"/>
    </rule>
    <rule ref="rulesets/naming.xml/ShortVariable">
        <properties>
            <property name="exceptions" value="id"/>
        </properties>
    </rule>
    <!-- テストのメソッドはカウントから除外 -->
    <rule ref="rulesets/codesize.xml">
        <exclude name="TooManyMethods"/>
        <exclude name="TooManyPublicMethods"/>
    </rule>
    <rule ref="rulesets/codesize.xml/TooManyMethods">
        <properties>
            <property name="ignorepattern" value="(^(set|get|test))i"/>
        </properties>
    </rule>
    <rule ref="rulesets/codesize.xml/TooManyPublicMethods">
        <properties>
            <property name="ignorepattern" value="(^(set|get|test))i"/>
        </properties>
    </rule>
</ruleset>

composer scriptでphpのci周りを登録する

毎回調べているのでメモ

phpでciを回すのによく使うものに

があります。
php7ならphanもですね。

これを、composerのrun-script 経由で叩けるようにしておくと、
毎回オプションなどを指定しなくて済むので楽です。
書き方を毎回調べているのでメモ的に残します。

composer.json(抜粋)

    "scripts": {
        "l": "php -l ./src",
        "test": "./vendor/bin/phpunit -c ./phpunit.xml",
        "cs": "./vendor/bin/phpcs --standard=./codesniffer/ruleset.xml ./src ./tests",
        "cbf": "./vendor/bin/phpcbf --standard=./codesniffer/ruleset.xml ./src ./tests",
        "md": "./vendor/bin/phpmd ./src,./tests text ./messdetector/ruleset.xml",
        "ci": [
            "@l",
            "@cs",
            "@md",
            "@test"
        ]
    }

解説

各コマンドのキーになっているところが、 composerのサブコマンドになります。
サブコマンドは、@ つきでエイリアスとして使用できるので、2重定義しなくて済みます。
複数のコマンドを実行したい時は、配列で指定します。

実行方法

composer run-script ci

定義した ci を実行します。
または、単に

composer ci

としても動きます。 サブコマンドにオプションを渡したいときは、 -- で区切ります。

composer run-script test -- --group=Foo

上記は、 test に--group=Foo を渡して実行してくれます。

./vendor/bin/phpunit -c ./phpunit.xml --group=Foo

と同義です。

Facebook APIのバージョンアップ対応 phpのgraph-sdkのバージョンアップ

Facebookが提供しているPHPSDKのバージョンアップについてです。
3系から5系にバージョンアップしたので少しだけサンプル残したいと思います。

3系

$params = [
  'message' => '投稿メッセージ',
  'link' => 'http:example.com',
  'access_token' => '{access_token}'
];
$facebook = new Facebook(['appId' => '{app-id}','secret' => '{app-secret}'];
$ret = $facebook->api('/v2.4/me/feed/', 'POST', $params);

5系

$params = [
  'message' => '投稿メッセージ',
  'link' => 'http:example.com',
];
$facebook = new FacebookSDK([
    'app_id' => '{app-id}',
    'app_secret' => '{app-secret}',
    'default_graph_version' => 'v2.9',
]);
//postメソッドの場合、第5引数にバージョンを指定することもできます。
//getメソッドは第4引数にバージョンを指定できます。
$facebook->post('/me/feed', $params, '{access_token}');

5系の戻り値について

5系の場合、post、getメソッドの戻り値は、FacebookResponseのオブジェクトです。
ここからエンドポイントにあった方法で値を取得することができます。
どのエンドポイントでどのオブジェクトを使用できるかは、
分かってないんですが、合わないメソッドを呼ぶとエラーが出るので
開発時には気づくと思います。 下記の他にもいくつかるのですが、よく使いそうなものを紹介します。

getGraphNode

getGraphObjectに替わる新しいメソッドです。 多分、単一のオブジェクトを取得する用なんだと思います。

$res = $facebook->post('/me/feed', $params, '{access_token}');
$node = $res->getGraphNode();
$id = $node->getField('id'); //これでポストしたメッセージのidがとれます。

ちなみにgetGraphObject は、sdk のv6で削除予定です。

* @deprecated 5.0.0 getGraphObject() has been renamed to getGraphNode()
* @todo v6: Remove this method

getGraphEdge

多分、リスト形式のオブジェクトを取り扱う用です。 配列として扱えます。

$res = $facebook->get('/me/permissions', '{access_token}');
$edge = $res->getGraphEdge();
if(count($edge) > 0){
  foreach($edge as $item){
    //somthing
  }
}

getGraphUser

ユーザー情報に特価したオブジェクト用です。

$res = $facebook->get('/me?fields=id,name', '{access-token}');
$user = $res->getGraphUser();
$name = $user['name'];

Facebook APIのバージョンアップ対応 影響箇所の調査

前回の記事の続きです

実際に自分がやらなければならない対応を知るためには
アップグレードツール、APIの変更履歴を確認します。

アップグレードツール

https://developers.facebook.com/docs/graph-api/advanced/api-upgrade-tool/

facebookが提供しているツールです
自分がアプリの管理者(もしくは開発者)で、
このツールを開くと、
影響を受けるGraph API(エンドポイント)を叩いている場合、それを警告してくれます。

ただし、このツールで分かるのは、
過去7日間に影響のあるエンドポイントを叩かれた場合だけです。
ツールのページで Items to fix to upgrade XXXXX from v2.4 to v2.9: と書かれている表の
Call Volumeは 過去7日間に叩かれた回数だそうです。

頻繁に叩かれないようなものは、ここには出てこないので
分かりません。
その場合は、Graph APIの変更履歴と
照らし合わせながら、自分で調査する必要があります。

変更履歴

https://developers.facebook.com/docs/apps/changelog?locale=ja_JP

Graph APIマーケティングAPIの変更履歴が分かります。
詳細な変更は、このページから各詳細を参照することで分かります。

このページで気をつけないといけないのが、
90日後に変更(削除) となっている箇所です。
この箇所に書かれていることは、どのバージョンを使っていたとしても
そのバージョンがリリースされてから
90日後に影響が出ることになります。

例えば、現在、v2.4を使っていたとしても、
v2.9の90日後に削除のところに書かれている

エッジやダイアログでリンクを投稿にアタッチできる次のフィールドは廃止されます。

は影響を受けます。
このことは、 例えば 'feed'のエンドポイントのページも書いてあったりします。

As of April 18, 2017, the following parameters are no longer supported by Graph API versions 2.9 and higher. For versions 2.8 and lower, the parameters will continue working until July 17, 2017.

Facebook APIのバージョンアップ対応 バージョンとは

Graph APIのバージョンアップをすることがあったので
対応内容とか周辺知識をまとめておきます。
マーケティングAPIは対象外です。

バージョンについて

まず、バージョンアップといっても何をバージョンアップすればいいか
分からなかったので調べました。

見るべきところは、アプリ自体のバージョンと
Graph APIを実際にコールしているところのバージョンです。
私の場合は、Graph APIを直接叩くことはしていなく
phpやjsなどのSDKを用いていたので、そこを見ました。

アプリのバージョン

facebook for developers で自分が管理しているアプリのバージョンです。
アプリのダッシュボードの画面で、今どのバージョンを使っているか確認できます。

ここのバージョンがいったい何に影響しているのかは
よく分かりませんでした。
多分、Graph APIでバージョンを指定しないで叩いたときのデフォルトの
バージョンなんだと思います。

Graph APIを叩くときに指定するバージョン

facebookが出しているsdkで指定するバージョンです。
SDKの初期化時に指定する方法と、
APIを叩くときに指定する方法があります。

php

手元にあった sdk5.4.4では、
default_graph_version を指定しなくても大丈夫ですが、
Facebook\Facebook の中を除くと

// @todo v6: Throw an InvalidArgumentException if "default_graph_version" is not set

というコメントがあったので、将来的に必須になるようです。

初期化時に指定するデフォルトバージョン

$fb = new Facebook\Facebook([
  'app_id' => '{app-id}',
  'app_secret' => '{app-secret}',
  'default_graph_version' => 'v2.2',
  ]);

叩くときに指定する方法

初期化時にバージョンを指定しなかった場合には
ここでバージョンを指定する必要があります。

$fb->get('/v2.9/me/feed', '{access-token}');

or

$fb->get('/me/feed', '{access-token}', null, 'v2.9');

js

いいね!ボタン構成ツールで取得できるコードを見ると
バージョンを指定している箇所があります。
私の場合は、いいねボタンくらいしか使ってなかったので
ここだけしかありませんでした。

生成されたもの

<script>(function(d, s, id) {
  var js, fjs = d.getElementsByTagName(s)[0];
  if (d.getElementById(id)) return;
  js = d.createElement(s); js.id = id;
  js.src = "//connect.facebook.net/ja_JP/sdk.js#xfbml=1&version=v2.9&appId={app-id}";
  fjs.parentNode.insertBefore(js, fjs);
}(document, 'script', 'facebook-jssdk'));</script>

自前で初期化している場合

<script>
window.fbAsyncInit = function() {
    FB.init({appId  : '{app-id}',
             status : true,
             cookie : true,
             xfbml  : true,
             version: 'v2.9'
    });
};
(function(d, s, id) {
        var js, fjs = d.getElementsByTagName(s)[0];
        if (d.getElementById(id)) return;
        js = d.createElement(s); js.id = id;
        js.src = "//connect.facebook.net/ja_JP/sdk.js";
        fjs.parentNode.insertBefore(js, fjs);
}(document, 'script', 'facebook-jssdk'));
</script>

Gitのリベース途中でエラーメッセージ - VCS_INFO_get_data_git:223

現象

Gitのリベース途中でコンフリクトの修正中に起こった問題です。
git checkout --oursとか打つと、下記のメッセージが出てきて困りました。

VCS_INFO_get_data_git:223: no such file or directory: .git/rebase-apply/msg-clean

解決方法

oh-my-zshのissueで話題に上がってました。

zshのバージョンを上げると解決するって書いてあるんですが、
私の場合は、意図したバージョンのzshが使えてなかったために起きてました。

環境によっては、zshのバージョンの確認方法に気をつけなればなりません。
バージョンの確認方法は2つあって、

  • zsh –version
  • echo $ZSH_VERSION

です。

❯ zsh --version
zsh 5.3.1 (x86_64-apple-darwin15.6.0)
❯ echo $ZSH_VERSION
5.0.8

私の場合、上記のような結果となってした。

何をしたか

/etc/shells に /usr/local/bin/zshを追記して、 改めて chsh -s /usr/local/bin/zsh

無事に解決しました。