PHPの落とし穴

PHPで忘れがちな罠に引っかからない様に、躓いた所を思い出して並べておく。 語彙にも微妙なものがある。

構文

関数っぽく見えてしまう言語機能

次の識別子は関数ではない。 echo, empty, eval, exit, isset, list, print, array そのうちecho, eval, printは関数に分類して良いのにって思う。

switch文の比較

switch文での同値性は==演算子と同等の緩い比較になる。(型変換して一致したらOK) JavaScriptは厳格な比較(===)を行う。

 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
26
27
<?php
switch ("a") {
case "0":
  var_dump("0");
  break;
case 0:
  var_dump(0);
  break;
default:
  echo "@default";
}
//int(0)

switch ("1") {
case "01":
  var_dump("01");
  break;
case "1":
  var_dump("1"); // 前で一致するので出力されない
  break;
case 1:
  var_dump(1);   // 前で一致するので出力されない
  break;
default:
  echo "@default";
}
// string(2) "01"

switch文のcontinue

switch文はcontinue対象になるためループ中でswitchを使いcontinueを使ってもswitch文から抜けるだけになる。

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
<?php
$list = array("apple", "orange", "banana");
foreach ($list as $_val) {
  switch ($_val) {
  case "banana":
    continue;
  default:
    break;
  }
  // break, continue共にここに飛んでくる
  echo $_val. ',';
}
// apple,orange,banana

言語機能

PHPはテンプレートエンジン的なスタートから始まっているので高度なプログラミング概念を備えていない。

  • DEFINEは配列が使えない
  • ジェネレータは5.5以降
  • 末尾最適化はない
  • コルーチンはない

マルチスレッドは望めない

やるならexec('php SCRIPT\_NAME.php &'); みたいにコマンドライン経由で外部プロセスを利用することになる。ただ最近は、こんなモジュールある。

アクターとかチャンネルは勿論の事、平行処理・並列処理関係は期待しないのが基本。

配列

配列インデックスは正規化される

配列のインデックスは基本的の文字列ただし'1'の様な10進数表現は強制的に数値になる。

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
<?php
$test = array(
  '03' => 'apple',
  '1'  => 'orange',
  2    => 'banana'
);
var_dump($test);
//array(3) {
//  ["03"]=>
//  string(5) "apple"
//  [1]=>
//  string(6) "orange"
//  [2]=>
//  string(6) "banana"
//}

関数の返り値の配列は直接使えない

5.4以降は解決している。だけど5.3以前は駄目。

1
2
3
4
5
6
<?php
function test() {
  return array("apple" => 100);
}
test()["apple"];
// => PHP Parse error if php -v < 5.4.0

list表現による部分代入の評価は右から束縛される

 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
26
27
28
29
30
31
32
33
34
35
<?php
function test_list() {
  return array(
    100,
    80,
    60
  );
}
list ($a, $b) = test_list();
var_dump($a);
var_dump($b);
//int(100)
//int(80)

$ar = array();
list($ar[0], $ar[1]) = test_list();
var_dump($ar);
//array(2) {
//  [1] =>
//  int(80)
//  [0] =>
//  int(100)
//}

$ar2 = array();
list($ar2[], $ar2[], $ar2[]) = test_list();
var_dump($ar2);
//array(3) {
//  [0] =>
//  int(60)
//  [1] =>
//  int(80)
//  [2] =>
//  int(100)
//}

名前空間

例を挙げるのも面倒な小さい事は、ここに書く。

  • PHP5.2以前は名前空間が使えない

クラス名・関数名は大文字小文字を区別しない

1
2
3
4
5
6
<?php
try {
  throw new ViewException();
} catch (viewException $e) {
  echo "catch される";
}

require, include された時のスコープは呼び出し個所と一致

  • 基本的に呼び出し箇所と一致

ver 5.2.17以上ではprivateプロパティでも問題がない事を確認したが、 だいぶ古い奴では駄目だった気がする(ver. 忘れてます)。

 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
26
<?php
// cat callie.php
var_dump($local_var);
var_dump($this->public_var);
var_dump($this->private_var);

// cat caller.php

class RequireTestClass
{
  public $public_var;
  private $private_var;

  public function require()
  {
    $local_var = 100;
    $this->public_var = 200;
    $this->private_var = 300;
    require "./callie.php";
  }
}
$ob = new RequireTestClass();
$ob->require();
// int(100)
// int(200)
// int(300)

関数

関数の引数のデフォルト値は定数式

ちなみに変数やプロパティが可能だと、評価のタイミングが問題になる。 Pythonは定義時にRubyは呼び出し毎にでフォルト値が評価される。

1
2
3
4
5
6
7
<?php
function test($val = array(12, 13))
{
  return $val[1];
}
var_dump(test());
// int(12);

可変引数な関数も可能

ただビルトインの関数を使う必要があるので要注意というか少し面倒くさい。 Luaみたいに ... とかで扱えると簡単なのにって思ったり。

 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
<?php
function variable_length($name, $volume)
{
  var_dump($name);
  var_dump($volume);

  var_dump(func_num_args());
  var_dump(func_get_args());
}
variable_length("test",100,40,53,3);
//string(4) "test"
//int(100)
//int(5)
//array(5) {
//  [0] =>
//  string(4) "test"
//  [1] =>
//  int(100)
//  [2] =>
//  int(40)
//  [3] =>
//  int(53)
//  [4] =>
//  int(3)
//}

PHP5.3より前は無名関数が無い

5.2系では使えない。本当に辛い。

無名関数で、親スコープから引き継ぎたい変数は明示する use

LuaとかSchemeとかRubyとかPythonとか静的スコープで自動的に変数が見える。 PHPだと見えない。だから use キーワードを使う。

<?php
function out($val)
{
  return function($operand) use($val) {
    return $operand + $val;
  };
}
$f = out(100);
var_dump($f(200));
// int(300);

無名関数のuse構文は関数の引数と同様に値渡し

無名関数で他の言語と似た挙動をしたかったら明示的に参照渡しにする必要がある

 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
26
27
28
<?php
// 素直なuse節
function out($val)
{
  return function($operand) use($val) {
    $val = $operand + $val;
    return $val;
  };
}
$f = out(100);
var_dump($f(200));
// int(300);
var_dump($f(200));
// int(300);

// 他の言語の無名関数的な使い方をしたいuse節
function out($val)
{
  return function($operand) use(&$val) {
    $val = $operand + $val;
    return $val;
  };
}
$f = out(100);
var_dump($f(200));
// int(300);
var_dump($f(200));
// int(500);

タイプヒンティングで型チェックを行える(ただし動的型検査)

関数の引き数でクラス・インターフェース名を指定して置く事で関数内に入るオブジェクトに制約を加えられる。 だけど動的チェックなのですよ。

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
<?php
class A {};
class B {};
function test_type(A $e) {
  return true;
}
var_dump(test_type(new A()));
//bool(true)
var_dump(test_type(new B()));
//PHP Catchable fatal error:  Argument 1 passed to test_type() must be an instance of A, instance of B given, called in ...

呼び出し可能オブジェクトは色々ある

call_user_func の $callable は色々あって雑。

無名関数

1
2
3
4
5
6
<?php
$hoge = function () {
  echo "lambda";
}
call_user_func($hoge);
//lambda

関数名

<?php
function function_name()
{
  echo "function_name";
}
call_user_func("function_name");
//function_name

配列

オブジェクト・クラス名とメソッド名を格納した配列

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
<?php
class CLASS_NAME
{
  public static function static_method()
  {
    echo "in static_method\n";
  }
  public function method()
  {
    echo "in method\n";
  }
}
call_user_func(array("CLASS_NAME", "static_method"));
//in static_method
$obj = new CLASS_NAME();
call_user_func(array($obj, "static_method"));
//in static_method
call_user_func(array($obj, "method"));
//in method

クラス

クラスのフィールド定義で式が使えない

クラス定義内で許可されるのはリテラルのみ。

1
2
3
4
5
6
<?php
class ClassName
{
  public $name = "First" + "," + "Second";
}
// PHP Parse error: ...

new 演算子でオブジェクト生成した直後はメソッドを直接呼べない。

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
<?php
class DirectCallie
  {
    public function test()
    {
      echo "in method";
    }
  }
$di = new DirectCallie()->test();
// PHP Parse error: ...

静的遅延束縛はstaticキーワードを使う

静的遅延束縛は 5.3 以降じゃないと使えない。

ArrayObjectは配列ではない

配列風オブジェクトなのに配列として判別はしてくれない.

1
2
3
<?php
$aob = new ArrayObject();
var_dump(is_array($aob)); // false

ReflectionMethodクラスを使うとアクセス制御を破れる

5.3.2以降で使えます、それ以前はテストでプライベートメソッドを扱うのは大変ですね。

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
<?php
class Foo
{
  private function _func()
  {
    echo 'test';
  }
}
$foo = new Foo();
$method = new Reflectionmethod(get_class($foo), '_func');
$method->setAccessible(true);
$method->invoke($foo);
// test

5.4からはTraitが使えるよ

RubyでモジュールをMixinする様にクラスに組み込める操作集合を作れる。 Traitのメソッド・プロパティの中身が特定クラス内にインライン展開される感じ。 なので多重継承の継承パスの問題とかは引き起こさない。

  • Traint内の識別子の衝突は静的に解析され、明示的な解決が必要
  • プロパティの上書き定義は不可能
  • 同名のプロパティは共有される
 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
<?php
trait Hello {
  private $name = "apple";

  public function echoName() { echo $this->name; }
}
trait World {
  public function getName() { return $this->name; }
}
class First
{
  use Hello, World;
  public function testMethod($input)
  {
    $this->name = (string)$input;
  }
}
$obj = new First();
$obj->echoName();          // apple
$obj->testMethod("orange");
$obj->echoName();          // orange
var_dump($obj->getName()); // string(6)"orange"

ライブラリ類

なんか面倒になってきたので概要ふれるだけで、詳しく書きたくなったら今度書く。

バッファリングの操作

  • 出力バッファリングはスタック管理されている
  • ob_start のネストレベルが管理されている
  • ob_flush はスタックレベル = 1でのみ実行可能

HTTP, HTML, URL

parse_url(), parse_str() などのパース系関数は文字エンコードの変換もやってくれちゃうので注意が必要。

Iterator

Iteratorなオブジェクトを入れ子foreachすると各ループが独立にならない。挙動に注意 入れ子ループに対応したIteratorを作りたければIteratorAggregateを使いループ毎にインスタンス生成させる必要がある

comments powered by Disqus