PHP「関数っぽいもの」列伝

これは2017年6月10日の[PHPカンファレンス福岡2017](http://phpcon.fukuoka.jp/2017/)懇親会LTで発表したものです。初出は2017年5月19日に社内勉強会で発表したものを大幅に加筆したものです。

これを5分…？ 無理ですな。

----

# お前誰よ

![](https://qiita-image-store.s3.amazonaws.com/0/4121/profile-images/1473683797)

* うさみけんた / GitHub: [zonuexe](https://github.com/zonuexe)
* [ピクシブ株式会社](http://www.pixiv.co.jp/) (東京)
    * 福岡オフィスできました！
    * CTOが赴任して週明けから稼動するよ
* PhpStorm普及業の傍らEmacsのphp-mode開発

----

##### このスライドはQiitaで公開済みなので、PHPについて知りたい型はゆっくり読んでね

せっかくなので自薦のPHP記事を置いておきますね

* [FizzBuzzから始めるコードの再利用性を高めるトレーニング](http://qiita.com/tadsan/items/b9521f149d4964b506d0)
* [インスパイヤされて掲示板を作りたくなった(1)](http://qiita.com/tadsan/items/cdbbb5b08591af2b110d)
* [シンプルなルーティングがしたかった](http://qiita.com/tadsan/items/bcaa14504d0ecdd9e096)
* [array_mapと部分適用](http://qiita.com/tadsan/items/475e8dc4657d771eb304)→[array_mapにありがとう、さよなら](http://qiita.com/tadsan/items/bfd1acb7c35a9a6fe452)
* [モダンPHPアンチパターン](http://qiita.com/tadsan/items/157969b338fd8b782b21)

----

# さて

----

# PHPの世界観

----

## PHPの世界観

* 関数<br>　
* 言語構造(構文)

----

## PHPの世界観

* 関数
    * PHPの世界から定義できる
* 言語構造(構文)
    * (PHPの世界からは)定義できない
    * *(C拡張で介入できるけど今回は触れない)*

----

# よくある質問

* Q. `isset`と`is_null()`ってどう違うの？
* Q. `isset`と`array_key_exists()`ってどう違うの？

----

# よくある質問

* Q. `isset`と`is_null()`ってどう違うの？
* Q. `isset`と`array_key_exists()`ってどう違うの？

## A. 違うよ、全然違うよ

----

# 関数

* 算数で習ったのは $f(x)=x\times2$ みたいなの
* PHP的には`$f = function($x) { return $x * 2; };`
    * ↑引数が同じなら絶対に同じ値を`return`する

----

# 関数

* 算数で習ったのは $f(x)=x\times2$ みたいなの
* PHP的には`$f = function($x) { return $x * 2; };`
    * ↑引数が同じなら絶対に同じ値を`return`する

### 余談

* PHP(とか他言語)の関数は、そうとは限らない
* **例)** `mt_rand(0, 9)` は、その前提だと困る

----

## 関数のなかまたち (callable)

- ユーザー定義関数 (文)：
    - `function f($a){ ... }`
- クロージャ (関数式)：
    - `$f = function($a) { ... };`
- メソッド：
    - クラス内 `public function f($a){ ... }`
- 静的メソッド：
    - クラス内 `public static function f($a){ ... }`
- オブジェクト + `__invoke` (マジックメソッド)
    - クラス内 `public function __invoke($a){ ... }`

----

## 関数のなかまたち (callable)

* メソッドとかクロージャとかいろいろあるけど、こいつらは関数のようなもの

```php
$f = [$obj, 'hoge']; // $obj->hoge()
$f = ['Ns\Klass', 'foo']; // Ns\Klass::foo()

$f($v);
is_callable($f); // => true
```

----

## こんなこともできる

```php
// Before
if ($v === 1) {
    $obj->hoge($v)
} else {
    Ns\Klass::foo($v)
}

// After
$f = ($v === 1) ? [$obj, 'hoge'] : ['Ns\Klass', 'foo'];
$f($v);
```

※ どちらが良いコードかは*場面による*

----

## こんなこともできる

```php
$ary = ["apple", "orange", "banana"];

// Before
$bry = [];
foreach ($ary as $i => $a) {
    $bry[$i] => ucfirst($a);
}

// After
$cry = array_map('ucfirst', $ary);
// => ["Apple", "Orange", "Banana"]
```

----

## ここまでは「関数」と「関数っぽいフレンズ(callable)」の話


----

## 「関数っぽいけど関数ではない」ものがある

----

# 言語構造(構文)

* 英語では <q>language constructs</q>
* `if ($a == $b)` みたいなやつ
    * PHPの経験があれば`if`が関数ではない<br>(`{}`がくっついたりする)ことは明らかだが、言語仕様の知識がなければ、これは自明なことではない
* 実際、`if` が言語構造(構文)ではない言語はある
    * みんな大好きなExcelとかね！
    * [PHPでif - Qiita](http://qiita.com/tadsan/items/afafceaa70dacf1c3b93)

----

# 関数っぽく見える言語構造

諸説あるが

`array`, `assert`, `declare`, `die`, `echo`, `empty`, `eval`, `exit`, `include`, `include_once`, `isset`, `list`, `print`, `require`, `require_once`, `unset`, ……

一見すると見分けがつかない (主観)

----

[WEB+DB PRESS Vol.94](http://gihyo.jp/magazine/wdpress/archive/2016/vol94)に掲載した「PHP大規模開発入門 【第15回】PHP初心者がハマりがちな落とし穴 ……型のキャスト，変数とリファレンス，引数による挙動の違い」に掲載したものを改変した。

| 構文                   | 意味                            | 例                                     |
|------------------------|---------------------------------|----------------------------------------|
| array                  | 配列リテラル                    | `array(1, 2)`                          |
| assert                | 表明 | `assert(foo() === "foo")` |
| declare | PHP処理系に特殊な命令 | `declare(strict_types=1)` |
| die                    | PHPスクリプトの終了             | `foo() or dir(1);`                     |
| echo                   | 文字列を標準出力                | `echo $message, PHP_EOL;`              |
| empty                  | 値／変数が「空」または「偽」かの検査 | <code>if (empty($v) &#124;&#124; empty($a['i']))</code> |
| eval                   | 文字列をPHPスクリプトとして評価 | `eval($s)`                             |
| exit                   | PHPスクリプトの終了             | `exit(0);`                             |
| include<br>include_once | PHPスクリプトの読み込み         | `include_once __DIR__ . '/Foo.php';`   |
| isset                  | 値／変数が存在するかの検査         | `$v = isset($a['i']) ? $a['i'] : 's';` |
| list                   | 複数の値（配列）を変数に代入      | `list($a, $b, $c) = [1, 2, 3];`        |
| print                  | 文字列を標準出力                | `($c === 1) ? print $t : print $f;`    |
| require<br>require_once  | PHPスクリプトの読み込み         | `require 'lib.php';`                  |
| unset                  | 変数／インデックスの破棄     | `unset($v);` <br> `unset($a['i']);`           |

----

# いっぱいある！！！！

----

# 出力するやつ

----

## echo, print

```php
echo "fizz";
echo("buzz");

print "foo";
print("bar");
```

----

## echo, print

* デフォルトで標準出力に出力できる
* `echo` は値を返さない
* `print` は値を返す (関数っぽい言語構造)
* 出力バッファリングでキャプチャできる
* `fwrite(STDOUT, ...)` とは微妙に異なる ←**重要**

----

## echo, print

```php
// これはok
$v === "x" and print "foo";

// これはだめ
$v === "x" and echo "foo";
// PHP Parse error: Syntax error, unexpected T_ECHO on line 1
```

----

# PHPを終了するやつ

## exit, die

```php
exit;
exit(1);

foo() or die(1);
```

----

## exit, die

* 引数で渡した値は終了ステータスになる
    * **0**が正常、**1**が異常
    * Webサーバーだと200とか500とかにマッピングされる

----

# 配列リテラル

```php
$a = array()
$b = array("apple", "banana", "orange");
$c = array("name" => "Miku", "age" => 16);

list($a, $b) = foo();
list($a) = array("a");

// PHP 7.1から
list("foo" => $f) = ["foo" => "F"];
```

* PHP 5.3までは `["apple", "banana", "orange"]` って書けなかった
* `list()` は関数っぽいものが代入の左辺にあって他の言語の常識があると死ぬ

----

# ディレクティブ宣言

----

## declare

```php
<?php
declare(strict_types=1);
declare(ticks=1);
declare(encoding='ISO-8859-1');
```

* ファイルローカルに適用される特殊な宣言
* 関数っぽいけど `declare(name=val)` みたいな変な構文
* ファイルのコンパイル時に評価される
  * 変数・定数などは利用できない

----

# 値のチェック

----

## isset, empty

```php
isset($v);
isset($v[0]);
isset($v, $w);

empty($v);
empty($v[0]);
```

* `isset`は複数の値を評価できる
* `empty`はPHP 5.5までは変数しか評価できなかった

----

# ファイルを読み込む

----

## include, require

```php
include("foo.php");
require("foo.php");
include_once("foo.php");
require_once("foo.php");
```

* `()` は省略可能
* 絶対パスではないファイル指定をすると、`include_path` から探索する
    * なので最近はオートローディングが主流
* PHPをテンプレートエンジンとして利用するときは部分テンプレートを取り込める

----

# コード辺を実行したりする

```php
eval("foo();");
eval("\$v = foo();");
```

----

# 変数を消す

```php
unset($v);
unset($v["key"]);
```

----

# 表明

----

## assert

```php
assert(foo() === 1);
assert("foo() === 1");
```

* PHP 5では関数
* PHP 7では関数でもあり*言語構造でもある*<br>**謎の存在**に昇華した
    * `is_callable('assert') // => true`
    * おそらく互換性のため
* 本番運用時に実行されないようにしたりできる

----

# 最後に

----

## 言語構造ではないもの(ただの関数)

```php
// C言語の sizeof は演算子
// PHPでは、ただの関数 (countのエイリアス)
$i = sizeof($a);

$i = sizeof $a; // ← これはだめ！
```

----

## 三三三 ヾ(〃＞＜)ﾉﾞ