PHPインタビューの準備:Callable Pseudotype

彼らがインタビューで難しい質問をするのが好きなのは秘密ではありません。 常に適切であるとは限らず、現実と常に関連しているとは限りませんが、事実は残っています。 もちろん、質問は議論の余地があり、時には一見すると馬鹿げているように見える質問は、実際にあなたが書いている言語をどれだけ知っているかをチェックすることを目的としています。
画像
一連の記事の第2部は、現代のPHPに関する最も複雑で膨大な質問の1つに当てられています-「呼び出し可能」とは何ですか? 私は、この問題に関する最低限の知識を単一のテキストに取り入れようとしました。


callableとは何ですか?


Callableは、PHPの特別なデータ擬似型で、「関数として呼び出すことができるもの」を意味します。 以下に示すように、この疑似型の値は実際の型とは非常に異なる場合がありますが、それらを結合するものは常に存在します。これは関数として使用できる機能です。

例がありますか?


はい、簡単です。 現代言語で最も一般的に使用される呼び出し可能オプションは、匿名関数です。
$x = function ($a) { return $a * 2; }; assert( true === is_callable($x) ); assert( 4 == $x(2) ); 


is_callable()関数は、渡された値が呼び出し可能な疑似型に属するかどうかを確認するだけです。 もちろん、匿名関数はこの疑似型に属し、is_callable()はtrueを返します。

匿名関数を変数に割り当ててから、これらの変数を使用して呼び出すことができます(例で示しています)。 もちろん、匿名関数は別の関数に引数として渡すことも、return演算子を使用して関数から返すこともできます。これは、array_mapやarray_reduceなどの関数ファミリと一緒に、関数プログラミングの道を開きます(狭いトラック、私は言わなければなりません関数型言語ではありません)。

PHPには、クロージャーという特別なシステムクラスがあります。 新しい匿名関数を作成すると、本質的にこのクラスのオブジェクトが暗黙的に作成されます。 詳細については、マニュアルをご覧くださいphp.net/manual/ru/class.closure.php

ちょっとした混乱。 匿名関数の最新の構文が最初に登場したバージョン5.3の作成者は、実際の匿名関数(ラムダ関数)とクロージャー(この匿名関数のコンテキストへの変数のクロージャー)という2つの概念を混同しました。 そのため、たとえば、Lambdaなどではなく、Closureシステムクラスを使用する言語で匿名関数が実装されます。 インタビュー中にこの事実に留意してください-多くのインタビュアー自身が「ラムダ関数」と「クロージャ」の概念を混同しています。 ただし、「閉鎖」とは何かについての詳細な説明は、この記事の範囲外です。


呼び出し可能な、歴史的な参照としての文字列


PHPの文字列は呼び出し可能です! この場合、インタープリターは、指定された文字列に一致する名前を持つ通常の非匿名関数を探し、成功した場合、そのような関数を呼び出します。
 function foo($bar) { return $bar * 2; } $x = 'foo'; assert( true === is_callable($x) ); assert( 4 == $x(2) ); 


したがって、関数とライブラリ関数の両方を呼び出すことができます。 多くの制限があります-isset()、empty()、および実際に言語構造である他の関数を呼び出すことはできません。

呼び出し可能な文字列には、 'ClassName :: method'という形式の構造が含まれることがあります。これは禁止されていません。そのような文字列も呼び出し可能になります。 特異性に注意してください-この場合、引数リストの括弧は書かれていません!
 class Foo { public static function bar() { return 42; } } $x = 'Foo::bar'; assert( true === is_callable($x) ); assert( 42 == call_user_func($x) ); 

このような呼び出し可能な文字列の2番目の機能は、$ x()を使用して直接呼び出すことができないことです。「致命的なエラー:未定義関数Foo :: bar()の呼び出し」という形式のエラーが発生し、ここに特別な関数call_user_func( )、通常の構文では不可能な場合でも、「シャープコーナー」をバイパスして、呼び出し可能な疑似型の値を呼び出すことができます。

彼らは質問であなたを捕まえようとするかもしれません-匿名関数がPHPにどのくらい登場しましたか? 正解は次のとおりです。 「PHP 5.3以降、最新の構文はバージョン5.3以前に登場しました。create_function()関数を使用して匿名関数を作成する方法がありました。もちろん、このメソッドは歴史的な興味しかありません。 そして、自尊心のあるプログラマーがgotoオペレーターおよびeval()関数と同じ感情を呼び起こすべきである-これを決して書きたくないという願望。

なぜこの事件についてここに書いているのですか? 実際、create_function()は現代の意味でラムダ関数を作成しませんでした。実際、この関数は「lambda_1」のような名前の名前付き関数を作成し、その名前を返しました。 そして、文字列が呼び出し可能であれば、すでに馴染みのあるメカニズムが機能していました


呼び出し可能配列


PHPの配列も呼び出し可能です! これが機能する主なケースは2つあります。 それらを表示する最も簡単な方法は、例を使用することです。
 class Foo { public static function bar() { return 42; } public function baz() { return 1.46; } } assert( true === is_callable([Foo::class, 'bar']) ); assert( 42 == call_user_func([Foo::class, 'bar']) ); $x = new Foo; assert( true === is_callable([$x, 'baz']) ); assert( 1.46 == call_user_func([$x, 'baz']) ); 

したがって、null要素がクラスの名前で、最初が静的メソッドの名前である配列は呼び出し可能です。 オブジェクトとその動的メソッドの名前で構成される配列とまったく同じです。

興味深い機能に注目する価値があります(読者はコメントで気づきました)。 __call()または__callStatic()メソッドがFooクラスで定義されている場合、is_callable(Foo $ foo、 'bar')またはis_callable(Foo :: class、 'bar')はそれぞれ常にtrueになります。 一般に、これは非常に論理的です。


呼び出し可能オブジェクト


はい、これはPHPで可能です。 オブジェクトは「関数」である可能性があります。クラスで__invoke()マジックメソッドを定義するだけです。
 class Filter { protected $filter = FILTER_DEFAULT; public function setFilter($filter) { $this->filter = $filter; } public function __invoke($val) { return filter_var($val, $this->filter); } } $filter = new Filter(); $filter->setFilter(FILTER_SANITIZE_EMAIL); assert( true === is_callable($filter) ); assert( 'foo@example.com' == $filter('foo @ example . com') ); $filter->setFilter(FILTER_SANITIZE_URL); assert( 'http://test.com' == $filter('http:// test . com') ); 

__invoke()メソッドは、オブジェクトを関数として使用しようとすると自動的に呼び出されます。

タイプヒント


5.4以降の最新バージョンのPHPでは、関数引数の型引数として呼び出し可能な擬似型を指定できるようになりました。
 function filter($value, callable $filter) { return $filter($value); } 

渡された値が呼び出し可能でない場合、この引数で関数を呼び出そうとすると、致命的なエラーが発生します。「キャッチ可能な致命的なエラー:フィルター()に渡される引数2は呼び出し可能でなければなりません」

結論の代わりに


Callableは、PHPの基礎を学ぶ上で最も複雑で混乱を招くトピックの1つです。 一方では、ラムダ関数とクロージャーのための現代的で厳密かつクリーンな構文、優れた__invoke()機能、そして他方では、明らかに言語から切り離されることのない巨大で既に実質的に不要な歴史的遺産があります。

この遺産を知ることは重要です。 また、作業中にcreate_function()などのさまざまなトリックを使用できる古いコードに遭遇する可能性が非常に高いためだけではありません。 まず、自分自身の自己改善のためにそのようなことを知って、さまざまなアプローチ、設計、パラダイムのすべての長所と短所を理解し、適切なタイミングで適切なものを選択できるようにする必要があります。

そして、もちろん、インタビュー中に顔で汚れに落ちないように:)

続きます!

文学

php.net/manual/ru/language.types.callable.php
php.net/manual/ru/function.call-user-func.php
php.net/manual/ru/functions.anonymous.php
php.net/manual/ru/class.closure.php
php.net/manual/ru/language.oop5.magic.php#object.invoke
habrahabr.ru/post/147620
habrahabr.ru/post/167181
fabien.potencier.org/on-php-5-3-lambda-functions-and-closures.html

Source: https://habr.com/ru/post/J259991/


All Articles