概要
Line exceeds 120 characters; contains 192 characters
Code language: plaintext (plaintext)
1行が長すぎる(横に長い)、という警告ですね。
PHPに限らず、かつては1行80文字まででしたが、ディスプレイが大きくなり、1行120文字まで増えました。
しかし、クラス名もメソッド名も変数名も長くなり、プロパティを参照すると $this-> で7文字使います。1行で表現できる内容は昔と変わっていないか、少なくなっているかもしれません。
引数並びが長い
メソッドや関数の引数並びが長い場合は、1行1引数に改行します。
関数やメソッドを定義している側
function foo($aaaaaaaaa1, $aaaaaaaaa2, $aaaaaaaaa3, $aaaaaaaaa4, $aaaaaaaaa5, $aaaaaaaaa6, $aaaaaaaaa7, $aaaaaaaaa8, $aaaaaaaaa9)
{
}
Code language: PHP (php)
1行1引数に改行します。
function foo(
$aaaaaaaaa1,
$aaaaaaaaa2,
$aaaaaaaaa3,
$aaaaaaaaa4,
$aaaaaaaaa5,
$aaaaaaaaa6,
$aaaaaaaaa7,
$aaaaaaaaa8,
$aaaaaaaaa9
) {
}
Code language: PHP (php)
関数やメソッドを呼んでいる側
$str = sprintf("長いフォーマット文字列", $aaaaaaa1, $aaaaaaa2, $aaaaaaa3);
Code language: PHP (php)
1行1引数に改行します。
$str = sprintf(
"長いフォーマット文字列",
$aaaaaaa1,
$aaaaaaa2,
$aaaaaaa3
);
Code language: PHP (php)
関数やメソッドに分けてしまうのもいい方法です。
$str = myFormat($aaaaaaa1, $aaaaaaa2, $aaaaaaa3);
function myFormat($a1, $a2, $a3);
$fmt = "長いフォーマット文字列";
$str = sprintf($fmt, $a1, $a2, $a3);
return $str;
}
Code language: PHP (php)
計算式が長い
計算式に、メソッド呼び出しを含んでいて長い場合は、
$x = $y * anotherFunc1($aaaaaaa1, $aaaaaaa2, $aaaaaaa3) + anotherFunc2($bbbbbbb1, $bbbbbbb2, $bbbbbbb3);
Code language: PHP (php)
メソッドの結果を変数に代入してから、計算します。
$a1 = anotherFunc1($aaaaaaa1, $aaaaaaa2, $aaaaaaa3);
$a2 = anotherFunc2($bbbbbbb1, $bbbbbbb2, $bbbbbbb3);
$x = $y * $a1 + $a2;
Code language: PHP (php)
ifの条件式が長い
ifの条件式が長い場合は、
// リファクタ前
if ($a1->a1 * $b1->b1 < $c1->c1 * $d1->d1 + $e1->e1() || $a2->a2 * $b2->b2 < $c2->c2 * $e2->e2()) {
Code language: PHP (php)
|| や && を区切りに改行したり、
if ($a1->a1 * $b1->b1 < $c1->c1 * $d1->d1 + $e1->e1() ||
$a2->a2 * $b2->b2 < $c2->c2 * $e2->e2()) {
Code language: PHP (php)
条件部の計算や比較をメソッドに分けます。
if ($this->isSomething1($a1->a1, $b1->b1, $c1->c1, $d1->d1, $e1->e1()) ||
$this->isSomething2($a2->a2, $b2->b2, $c2->c2, $e2->e2())) {
}
function isSomething1($a, $b, $c, $d, $e)
{
return $a * $b < $c * $d + $e;
}
function isSomething2($a, $b, $c, $e)
{
return $a * $b < $c * $e;
}
Code language: PHP (php)
さらに、次のように書くとデバッガでトレースしやすいですが、さきほどの例とは意味が違うので、注意が必要です。
$f1 = $this->isSomething1($a1->a1, $b1->b1, $c1->c1, $d1->d1, $e1->e1());
$f2 = $this->isSomething2($a2->a2, $b2->b2, $c2->c2, $e2->e2());
if ($f1 || $f2) {
}
Code language: PHP (php)
何が違うのかな?
$e2->e2()が呼ばれるたびに、$e2内部のカウンタをインクリメントしているとするわね
1つめの例で、isSomething1()がtrueとします。isSomething2()は呼ばれません。$e2内部のカウンタはいくつかしら?
初期値0とするね。呼ばれないんだから、0のままだね
ピンポーン
2つめの例も、isSomething1()がtrueとします。常にisSomething2()は呼ばれます。$e2内部のカウンタはいくつかしら?
1回呼ばれるから、1だね。さっきと違うね!
ピンポーン。&&は左側がfalseなら、右側の判定はしないの。
じゃあメソッドを呼ばず、プロパティだけを使っていれば、1つめの例も2つめの例も同じかな?
通常は同じね。でもgetterのマジックメソッドを実装しているかもしれないから、断言はできないわ。
HTMLタグが長い
HTMLを表示するphpの場合です。
タグを入れ子で改行して、1行1タグにしておくだけでいいと思います。
<input type="text" id="xxxx" name="yyyy" value="<?php echo $xxxx; ?>" />
Code language: PHP (php)
長いタグを途中で改行したり、
<input type="text" id="xxxx" name="yyyy"
value="<?php echo $xxxx; ?>" />
Code language: PHP (php)
属性ごとに改行したり。ここは好みにおまかせします。
<input
type="text"
id="xxxx"
name="yyyy"
value="<?php echo $xxxx; ?>"
/>
Code language: PHP (php)
AタグのURLクエリーが長い
出力する直前でurlエンコード、htmlエンコードしたほうがいいのですが、それを守ると、クエリー変数2つ並べると、120文字をオーバーしてしまいます。
<a href="xxxx.php?category=<?php echo htmlspecialchars(urlencode($category));?>&from_date=<?php echo htmlspecialchars(urlencode($from_date));?>">
Code language: PHP (php)
方法1:urlエンコード、htmlエンコード済みの変数を用意します。
Aタグは少し短くなります。HTMLタグに混じってPHPコードがあるので、ゴチャゴチャした感じになります。Aタグごとに似たようなエンコード処理を書くことになります。
<?php
$hu_category = htmlspecialchars(urlencode($category));
$hu_from_date = htmlspecialchars(urlencode($from_date));
?>
<a href="xxxx.php?category=<?php echo $hu_category;?>&from_date=<?php echo $hu_from_date;?>">
Code language: PHP (php)
方法2:短い名前のヘルパー関数をphp側のグローバルに用意しておきます。
<?php
function ehu($str)
{
echo htmlspecialchars(urlencode($str));
}
?>
Code language: PHP (php)
短くなりました。名前が短すぎて何をしているか連想しやすいかというと...
<a href="xxxx.php?category=<?php ehu($category);?>&from_date=<?php ehu($from_date);?>">
Code language: PHP (php)
方法3:build_http_query関数を使って、URLクエリーを作成します。
<?php
$vars = [
'category' => $category,
'from_date' => $from_date,
'to_date' => $to_date,
];
$query = build_http_query($vars);
?>
<a href="xxxx.php?<?php echo htmlspecialchars($query);?>">
Code language: PHP (php)
外部ファイルに関数定義しておき、
<?php
function xxxx_build_query($category, $from_date, $to_date)
{
$vars = [
'category' => $category,
'from_date' => $from_date,
'to_date' => $to_date,
];
$query = build_http_query($vars);
return htmlspecialchars($query);
}
Code language: PHP (php)
ファイル先頭でrequire_onceします。
<?php
require_once __DIR__ . '/xxxx_helper.php';
?>
<a href="xxxx.php?<?php echo xxxx_build_query($category, $from_date, $to_date);?>">
Code language: PHP (php)
SQL文が長い
生のSQLで、SELECTするカラムを "*" ではなく、一つ一つ指定すると、すぐに1行が長くなり、phpcsのLineLengthに引っかかります。
public function selectXxx()
{
....
$sql = "SELECT id, name, category, aaa, bbb, ccc, ddd FROM ... WHERE ... ORDER BY ...;";
....
}
Code language: PHP (php)
まず、元々の箇所からコピペして、SQL文だけを返すメソッドに分けて、phpcs:ignoreしておきます。
public function selectXxx()
{
....
$sql = $this->sqlToSelectXxx();
....
}
public function sqlToSelectXxx()
{
// phpcs:ignore Generic.Files.LineLength
$sql = "SELECT id, name, category, aaa, bbb, ccc, ddd FROM ... WHERE ... ORDER BY ...;";
return $sql;
}
Code language: PHP (php)
元々の場所で、SQL文字列内で変数を参照している場合は、
public function selectXxx()
{
....
$sql = "SELECT id, name, category FROM ... WHERE date >= '$date_from' ORDER BY ...;";
....
}
Code language: PHP (php)
参照している変数をメソッドの引数で渡します。
public function selectXxx()
{
....
$sql = $this->sqlToSelectXxx($date_from);
....
}
public function sqlToSelectXxx($date_from)
{
// phpcs:ignore Generic.Files.LineLength
$sql = "SELECT id, name, category FROM ... WHERE date >= '$date_from' ORDER BY ...;";
return $sql;
}
Code language: PHP (php)
phpMyAdminのSQLフォーマット風に改行してあると行数が長くなり、phpmdの1メソッド100行にひっかかります。
元のメソッドを @SupressWarnings(PHPMD.ExssesiveMethodLength)で抑制するよりは、
/**
*
* @SupressWarnings(PHPMD.ExssesiveMethodLength)
*/
public function selectXxx()
{
....
$sql = "
SELECT
id,
name,
category
FROM
...
WHERE
date >= '$date_from'
ORDER BY
...;
";
....
}
Code language: PHP (php)
SQL文だけをメソッドに分けて、分けたメソッドを@SupressWarningsで抑制したほうがいいでしょう。元々のメソッドの本来の行数の長さでphpmdをかけることができます。
public function selectXxx()
{
....
$sql = $this->sqlToSelectXxx($date_from);
....
}
/**
*
* @SupressWarnings(PHPMD.ExssesiveMethodLength)
*/
public function sqlToSelectXxx($date_from)
{
$sql = "
SELECT
id,
name,
category
FROM
...
WHERE
date >= '$date_from'
ORDER BY
...;
";
return $sql;
}
Code language: PHP (php)
コピペしたSQL文は、リファクタせずに、そのままにしておきます。この手のSQL文を編集する機会はとても少ないので、そのままでも問題ないと思います。
SQL文をリファクタする場合は、テスト用にインポートするデータを用意して、selectXxxのユニットテストを書いてからにします。
SQL文のここは間違いかも?と思っても、修正も慎重になりましょう。
ユニットテストがない状態で、修正やリファクタする場合は、SQL文を表示して、それをコピペして、phpMyAdminやmysqlコマンドで動作確認します。
何回も動作確認するのが面倒になって、ユニットテストを書くようになるのよ
文字列が長い
巨大文字列は、別ファイルにしましょう。
public function foo()
{
....
$text = "
とにかく長い $name
とにかく長い $category
";
....
}
Code language: PHP (php)
まず、文字列だけを返すメソッドに切り分けます。
public function foo()
{
....
$text = $this->getTextFoo($name, $category);
....
}
/**
*
* @SupressWarnings(PHPMD.ExssesiveMethodLength)
*/
public function getTextFoo($name, $category)
{
$text = "
とにかく長い $name とにかく長い
とにかく長い $category とにかく長い
";
return $text;
}
Code language: PHP (php)
文字列を別のphpファイル、foo_text.phpにします。
$name
や、$category
などの変数を参照している箇所は、<?php echo $name; ?>
、<?php echo $category; ?>
にします。
とにかく長い <?php echo $name; ?> とにかく長い
とにかく長い <?php echo $category; ?> とにかく長い
Code language: PHP (php)
つまり、HTMLを表示するphpファイルと同じ使い方です。
バックスラッシュエスケープは必要ありません。ダブルクォート、シングルクォートも書きたいように書くことができます。
気をつけることは、行末に <?php echo $name; ?>
を書くと、その行は改行されません。そこで、改行するための空行を一つ入れます。
とにかく長い <?php echo $name; ?> とにかく長い <?php echo $category; ?>
元のphpファイルに戻ります。
一時的に新メソッドgetTextFoo_v2を作ります。foo_text.phpを(require_onceではなく)requireします。このままではfoo_text.phpの内容が表示されてしまうので、ob_start()とob_get_clean()を使って変数にキャプチャします。
public function foo()
{
...
$text = $this->getTextFoo($name, $category);
...
}
public function getTextFoo($name, $category)
...
}
public function getTextFoo_v2($name, $category)
{
ob_start();
require __DIR__ . '/foo_text.php';
$text = ob_get_clean();
return $text;
}
Code language: PHP (php)
簡易的な動作確認として、foo()メソッド内で比較するコードを書いておきます。デバッガのステップ実行で$fの値がtrueかどうか確認したり、$fの値をvar_dumpします。
(最後にユニットテストの例があります)
public function foo()
{
....
$text = $this->getTextFoo($name, $category);
$text2 = $this->getTextFoo_v2($name, $category);
$f = $text === $text2;
var_dump($f);
....
}
public function getTextFoo($name, $category)
}
public function getTextFoo_v2($name, $category)
{
ob_start();
require __DIR__ . '/foo_text.php';
$text = ob_get_clean();
return $text;
}
Code language: PHP (php)
古いメソッドを削除し、getTextFoo_v2メソッドをgetTextFooにリネームして、リファクタ完了です。
public function foo()
{
....
$text = $this->getTextFoo($name, $category);
....
}
public function getTextFoo($name, $category)
{
ob_start();
require __DIR__ . '/foo_text.php';
$text = ob_get_clean();
return $text;
}
Code language: PHP (php)
この例はデータベースを使わないので、ユニットテストを書いて、テスト実行しましょう。
元の巨大文字列をユニットテスト側にコピペしておきます。そのメソッドの結果を$expected、新しいメソッドの結果を$actualとして、$this->assertEqualsで比較します。
/**
* @test
*/
public function getTextFoo()
{
$name = "太郎";
$category = "カテA";
$target = new Bar();
$actual = $target->getTextFoo($name, $category);
$expected = $this->getTextFoo_old($name, $category);
$this->assertSame($expected, $actual);
}
private function getTextFoo_old($name, $category)
{
$text = "
とにかく長い $name とにかく長い
とにかく長い $category とにかく長い
";
return $text;
}
Code language: PHP (php)