PhpStormでisset()を使う時の注意点

ちょっとした凡ミスを防ぐためのメモ書きです。

PhpStormは(多分)他のどのPHPエディタ・IDEより賢いので、未定義な変数があるとちゃんとエラーをハイライトして「ここは使ってないよ」って教えてくれます。


ただ、PHPには元々 isset()っていう未定義かどうか判断できるメソッドがあり、
if条件文にこれを使ってしまうとPhpStormのエラーハイライトが効かない場合があります。

例えばこんな場合、
f:id:tan_go238:20140810141643p:plain

13行目のように直接 isset() を使わずラップしたメソッドを使用すればエラーはハイライト表示されるのですが、

9行目のように癖で直接 isset() を使ってしまうと当たり前ですがエラーはハイライトしません。
※ここを && ではなく || にするとエラーはハイライトされます。


結論

PhpStorm使う場合は、無意識のうちにエラーチェックをIDEに依存してしまっているので、
できるだけissetなどの未定義チェックはラップしたメソッドで行なうようにしましょう。

CakePHP2でFixtureのデータをCSVから任意のタイミングで変更する

CakePHPのFixtureは個人的に使い辛いと常々感じていて、理由としては

  • 1Fixtureクラスに対して1データしか用意できない
  • 状況によりデータを変えたい場合1Modelに対して複数のFixtureクラスを用意する必要がある

といったことなどから基本CakePHPではできるだけFixtureを使わずモックで済ませてきました。

ですが列数が多いDBのデータをモックで返すことや、DBも参照してテストしたい場面があったので、CSVからFixtureのデータを読み込むのをさくっと作ってみました。

この方法以外にも、PHPUnit_Extensions_Database_DataSet_CsvDataSet使うとか Fabricate 使うとか状況によって色々あるかと覆いますが参考までにコード載せときます。

app/Test/AppTestCase.php

<?php
App::uses('FileUtil', 'Utility');
App::uses('CakeTestFixture', 'TestSuite/Fixture');

class AppTestCase extends CakeTestCase
{

	/**
	 * CSVファイルを読込んでファイル内容を返します。<br>
	 * CSVファイルは実行するテストクラスと同じディレクトリに配置します。<br>
	 * ※1.CSVファイルのファイルサイズは1MBまで<br>
	 * ※2.CSVファイルの文字コーディングはShift_JIS<br>
	 * ※3.ヘッダー行には列名が必要<br>
	 *
	 * @param string ファイル名
	 * @return array 配列の配列を返します
	 */
	protected function readCsv($filename)
	{
		$class = new ReflectionClass($this);
		$testPath = pathinfo($class->getFileName());
		$csv = $testPath['dirname'] . DS . $filename;

		if (!file_exists($csv)) {
			throw new CakeException("Could not find CSV file. [$csv]");
		}

		$size = filesize($csv);
		if ($size > 1048576) {
			// 最大1MBまで
			throw new CakeException("CSV File is over maximum size. [$csv]-[$size]");
		}

		$data = array();
		$fp = fopen($csv, "r");
		// ヘッダー行取得
		$header = fgetcsv($fp);
		while ($row = fgetcsv($fp)) {
			mb_convert_variables('UTF-8', 'SJIS-win', $row);
			$array = array();
			foreach ($header as $index => $column) {
				if (array_key_exists($index, $row)) {
					$array[$column] = $row[$index] != 'NULL' ? $row[$index] : null;
				} else {
					$array[$column] = null;
				}
			}
			$data[] = $array;
		}
		return $data;
	}

	protected function loadCsvFixture($fixtureName, $filename)
	{
		$fixture = substr($fixtureName, strlen('app.'));
		$fixturePath = TESTS . 'Fixture';

		$className = Inflector::camelize($fixture);
		if (is_readable($fixturePath . DS . $className . 'Fixture.php')) {
			$fixtureFile = $fixturePath . DS . $className . 'Fixture.php';
			require_once $fixtureFile;
			$fixtureClass = $className . 'Fixture';
		} else {
			throw new CakeException('Could not read fixture class. ' . $fixtureName);
		}

		$fixture = new $fixtureClass();
		$data = $this->readCsv($filename);
		$fixture->records = $data;

		/** @var $fixtureManager AppFixtureManager */
		require_once TESTS . 'AppFixtureManager.php';
		$fixtureManager = new AppFixtureManager();
		$fixtureManager->loadSingle($fixture);
	}

	/**
	 * プライベートメソッドを実行します。
	 *
	 * @param object $obj インスタンス
	 * @param string $methodName メソッド名
	 * @return mixed 返り値
	 * @throws CakeException
	 */
	protected function invoke($obj, $methodName)
	{
		if (!isset($obj) || !is_object($obj)) {
			throw new CakeException('The parameter obj is not an object.');
		}
		$clazz = new ReflectionClass($obj);
		$method = $clazz->getMethod($methodName);
		$method->setAccessible(true);

		$countArgs = func_num_args() - 2;
		if ($countArgs > 0) {
			$array = array();
			$args = func_get_args();
			for ($i = 0; $i < $countArgs; $i++) {
				$index = $i + 2;
				$array[] = $args[$index];
			}
			$result = $method->invokeArgs($obj, $array);
		} else {
			$result = $method->invoke($obj);
		}
		return $result;
	}
} 


app/Test/AppFixtureManager.php

<?php
App::uses('CakeFixtureManager', 'TestSuite/Fixture');
App::uses('CakeTestFixture', 'TestSuite/Fixture');

class AppFixtureManager extends CakeFixtureManager
{
	/**
	 * @see CakeFixtureManager::loadSingle
	 */
	public function loadSingle(CakeTestFixture $fixture, $db = null, $dropTables = true)
	{
		if(isset($fixture)) {
			if (!$db) {
				$db = ConnectionManager::getDataSource($fixture->useDbConfig);
			}
			$this->_setupTable($fixture, $db, $dropTables);
			$fixture->truncate($db);
			$fixture->insert($db);
		} else {
			throw new UnexpectedValueException(__d('cake_dev', 'Referenced fixture class is not found'));
		}
	}
} 


使い方

  • AppTestCase.phpを継承したクラスを作成

class XxxTest extends AppTestCase {}

  • テストメソッド内で呼び出す

$this->loadCsvFixture('app.Xxx', Xxx.csv');

この場合、XxxTest.phpと同じディレクトリにXxx.csvを配置します。

Xxx.csvのファイル内容の1行目には列名、2行目以降にデータを入力しておきます。

loadCsvFixture実行時にCSVからデータを読込み、DBの値を更新します。

SourceTreeのマージツールにPhpStormを使う

去年ぐらいから仕事のリポジトリもGitに移行しています。
Subversionと比べるとGitが小回り効くのがすごく気に入ってるのですが、
たまにマージ時にコンフリクトが起こったときにPhpStormファイルが読みにくくなってちょっと不便でした。

他のグラフィカルにマージできるmergetool使えばいいっちゃいいのですが、
せっかくだからPhpStormでDiffとかマージもしたい。

色々調べたのですが、diffはできてもmergeできたという人がいない。。

Gistでやってそうな人を見つけたので試しにやってみたらできました。

https://gist.github.com/lvl-svasseur/7837523


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

1. SourceTree -> 環境設定
2. Diff を選択
3. 外部Diff / Merge にそれぞれ以下の値を入力

差分表示ツール
 その他
Diffコマンド
 /Applications/PhpStorm.app/Contents/MacOS/phpstorm
引数
 diff $(cd $(dirname "$LOCAL") && pwd)/$(basename "$LOCAL") $(cd $(dirname "$REMOTE") && pwd)/$(basename "$REMOTE")


マージツール
 その他

Mergeコマンド
 /Applications/PhpStorm.app/Contents/MacOS/phpstorm
引数
 merge $(cd $(dirname "$LOCAL") && pwd)/$(basename "$LOCAL") $(cd $(dirname "$REMOTE") && pwd)/$(basename "$REMOTE") $(cd $(dirname "$BASE") && pwd)/$(basename "$BASE") $(cd $(dirname "$MERGED") && pwd)/$(basename "$MERGED")

f:id:tan_go238:20140224142710p:plain


いじょ。

2014年の抱負

明けてましたのでおめでとうございます。

今年は色々と慌ただしくなる感じがするので、抱負でも書いて初心を忘れないようにしておきたいと思います。

あんま色々やろうとしても無理なのは分かっているので心がけ程度ですが。


・仕事を丁寧にする

速度よりも時間がなくても丁寧な仕事をするように心がけたいです。

できるだけ使いやすいように。「仕様通り作った」とかいう言い訳は自他ともになしの方向で。


・万全を期して準備する

準備はしっかりしておく。準備しておかないと勝てるものも勝てないって去年やっと悟った。


・遊ぶ

やりたいと思ったことはやる。楽しむことが大事。



少ない感じですが以上かな。

今まで結構このへんのことが疎かになってたので、今年は気を引き締めていきたい。

空堀の旧ヤム邸にカレーを食べに行ってきました。

これは

カレー Advent Calendar 2013 - Adventar

の22日目のブログです。


今日は 空堀の旧ヤム邸 に行ってきましたので思い出をアップします。

カレーとくつろぎ 旧ヤム邸 | 空堀商店街



昼にちょっと一仕事してから空堀商店街に向かいました。

当初中之島のほうに行くつもりでしたが、@backpaper0 さんが休日だということに気がついて予定を変更しました。

日曜はディナーが18時〜なのでみんな18時に集合。(遅刻してすみません。。)


お店に入るとクローブ、シナモンなどスパイスの良い匂いがすぐ漂ってきました。



匂いに耐え切れなくて、みんなを待たせてたにも関わらずいの一番にオーダーしました。


カレーは3つ用意されてあるうち2つの味が選べて1000円。

ラムのキーマと鶏肉のヤムカレーにしました。


f:id:tan_go238:20131223005347j:plain



あとチャイが飲みたかったので、マサラチャイも頼んじゃいました。


ご飯は玄米かターメリックライスのどちらかを選べたので、迷わず玄米にしました。

個人的に 玄米 + キーマ の組み合わせが大好きなのです。


お店は雰囲気も良くっておしゃれ。お店の人の対応もすごく丁寧で良かったです。


カレーの味は優しい味で、キーマに使っているスパイスは結構粗く削られていたので、

コリアンダーはホールスパイスで軽く煎ってからお店でパウダーにしてる気がしました。

全体的にすごくまとまっている味で、カレー好きだけじゃなく男女問わず楽しめる味じゃないかなと思いました。


そのあと空堀商店街を抜けて松屋町駅近くの エクチュア からほり「蔵」 ってところで、

みんなでチョコレート食べましたが、旧ヤム邸に負けず劣らず雰囲気がすごく良かったです。

f:id:tan_go238:20131222193640j:plain


ということでカレーを楽しんだ一日でした。



明日は anoworl さんです。

よろしくお願いします。

簡単!タンドリーチキンの作り方

これは

カレー Advent Calendar 2013 - Adventar

の19日目です。



なんかカレー料理教室みたいな感じになってきましたが、
僕自身あまり詳しくないので他に色んな作り方知ってる人がいたら教えてほしいです。

それでは、今日はタンドリーチキンの作り方について説明します!

f:id:tan_go238:20131220001738j:plain

作り方は簡単!

鶏肉を切る!→漬け込む!→混ぜる!→寝かせる→焼く!

以上です!

用意するものは以下のものになります。

■用意するもの(4人前ぐらい?)

<下ごしらえ>
 ・鶏モモ肉 500g
 ・ヨーグルト 200ml
 ・ハチミツ 少々

<スパイスなど>
・生姜
・ニンニク
・クミン   大さじ1
コリアンダー 大さじ5
ナツメグ 大さじ1/2
・パプリカ 大さじ3(色具合をみて調節する)
ターメリック 大さじ3(色具合をみて調節する)
・レッドペッパー 大さじ1
・塩 適量



【手順】
1. <下ごしらえ>の鶏肉を切って材料をタッパーにいれて冷蔵庫に3時間ほど寝かせます。
2. さらに<スパイスなど>を加えて混ぜます
3. 3時間ほど漬け込みます
4. 取り出して焼きます

以上です!すごく簡単です。

フライパンで焼くのもいいのですが、インド料理屋さんで食べてるようなものには
ならないのでちょっと悔しかったりします。


スパイス少なめでローズマリー加えたりしてバーベキューとかでやったりしても美味しいですよ!


参考画像

f:id:tan_go238:20131220001821j:plain

f:id:tan_go238:20130505154117j:plain

明日はいよいよカレーエンジニア界の重鎮 @kitaindia さんです!
よろしくお願いします!