Edited at

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

More than 1 year has passed since last update.

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

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



お前誰よ


  • うさみけんた / GitHub: zonuexe


  • ピクシブ株式会社 (東京)


    • 福岡オフィスできました!

    • CTOが赴任して週明けから稼動するよ



  • PhpStorm普及業の傍らEmacsのphp-mode開発



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

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



さて



PHPの世界観



PHPの世界観


  • 関数
     

  • 言語構造(構文)



PHPの世界観


  • 関数


    • PHPの世界から定義できる



  • 言語構造(構文)


    • (PHPの世界からは)定義できない

    • (C拡張で介入できるけど今回は触れない)





よくある質問


  • Q. issetis_null()ってどう違うの?

  • Q. issetarray_key_exists()ってどう違うの?



よくある質問


  • Q. issetis_null()ってどう違うの?

  • Q. issetarray_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)


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

$f = [$obj, 'hoge']; // $obj->hoge()

$f = ['Ns\Klass', 'foo']; // Ns\Klass::foo()

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



こんなこともできる

// Before

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

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

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



こんなこともできる

$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)」の話



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



言語構造(構文)


  • 英語では language constructs


  • if ($a == $b) みたいなやつ


    • PHPの経験があればifが関数ではない
      ({}がくっついたりする)ことは明らかだが、言語仕様の知識がなければ、これは自明なことではない



  • 実際、if が言語構造(構文)ではない言語はある





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

諸説あるが

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

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


WEB+DB PRESS Vol.94に掲載した「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
値/変数が「空」または「偽」かの検査
if (empty($v) || empty($a['i']))

eval
文字列をPHPスクリプトとして評価
eval($s)

exit
PHPスクリプトの終了
exit(0);

include
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
require_once
PHPスクリプトの読み込み
require 'lib.php';

unset
変数/インデックスの破棄

unset($v);
unset($a['i']);



いっぱいある!!!!



出力するやつ



echo, print

echo "fizz";

echo("buzz");

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



echo, print


  • デフォルトで標準出力に出力できる


  • echo は値を返さない


  • print は値を返す (関数っぽい言語構造)

  • 出力バッファリングでキャプチャできる


  • fwrite(STDOUT, ...) とは微妙に異なる ←重要



echo, print

// これは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

exit;

exit(1);

foo() or die(1);



exit, die


  • 引数で渡した値は終了ステータスになる



    • 0が正常、1が異常

    • Webサーバーだと200とか500とかにマッピングされる





配列リテラル

$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

declare(strict_types=1);
declare(ticks=1);
declare(encoding='ISO-8859-1');


  • ファイルローカルに適用される特殊な宣言

  • 関数っぽいけど declare(name=val) みたいな変な構文

  • ファイルのコンパイル時に評価される


    • 変数・定数などは利用できない





値のチェック



isset, empty

isset($v);

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

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



  • issetは複数の値を評価できる


  • emptyはPHP 5.5までは変数しか評価できなかった



ファイルを読み込む



include, require

include("foo.php");

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



  • () は省略可能

  • 絶対パスではないファイル指定をすると、include_path から探索する


    • なので最近はオートローディングが主流



  • PHPをテンプレートエンジンとして利用するときは部分テンプレートを取り込める



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

eval("foo();");

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



変数を消す

unset($v);

unset($v["key"]);



表明



assert

assert(foo() === 1);

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


  • PHP 5では関数

  • PHP 7では関数でもあり言語構造でもある
    謎の存在に昇華した


    • is_callable('assert') // => true

    • おそらく互換性のため



  • 本番運用時に実行されないようにしたりできる



最後に



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

// C言語の sizeof は演算子

// PHPでは、ただの関数 (countのエイリアス)
$i = sizeof($a);

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



三三三 ヾ(〃><)ノ゙