ホームページ > バックエンド開発 > PHPチュートリアル > PHP のネイティブ型メソッド

PHP のネイティブ型メソッド

WBOY
リリース: 2016-08-08 09:22:02
オリジナル
1481 人が閲覧しました

はじめに

他人の記事を翻訳するのは初めてで、CET-4英語レベルを使用して翻訳しています~~囧、不適切な箇所がたくさんあるかもしれません、お気軽にコメントしてください(適切とは思えない箇所もいくつかあります)翻訳は、原文と私自身の非常に低めの翻訳を同時に投稿します。

この記事を翻訳するのに3晩と1昼かかりました〜。最初に技術的な側面を理解してから、何を言いたいのかを理解してから翻訳しました〜

この目的は、一方では私の英語を練習することです、一方で、比較的新しい技術的なアイデアを学ぶこともできます。

この記事では主に、PHP のネイティブ型に対するオブジェクト指向操作の実装について説明します。これは、PHP における不規則な関数名、不規則なパラメーター順序、可読性の低さの問題を解決するための拡張機能を通じて実装されます。

この拡張機能は、ZEND エンジンでオブジェクト指向のメソッドを呼び出すときに、対応する処理関数を変更することで実装されます。IS_STRING の場合はカスタム処理を続行し、それ以外の場合は関数を登録します。 ZEND のデフォルトの処理関数。以下の説明をご覧ください

HOOKPHP コード

PHP はインタープリタ言語であり、コードは中間バイトコードに変換され、ZEND エンジンによって解析されて実行されます。 PHP は中間バイトコード OPCODE を呼び出します。各 OPCODE は ZEND の下部の処理関数に対応し、最終的にこの処理関数が実行されます。 HOOK 関数を実装するには、HOOK OPCODE に対応する処理関数を変更するだけです。

PHP のネイティブ型へのオブジェクト指向アプローチ

数日前、Anthony Ferrara が PHP の将来についての考えを書き留めました。私は彼の主張のほとんどに同意しますが、すべてではありません。この記事では、文字列や配列などのネイティブ型を、メソッド呼び出しを実行できる「疑似オブジェクト」として偽装するという特定の側面に焦点を当てます。

「疑似オブジェクト」が必要な理由を確認するために、いくつかの例から始めましょう:

<code>$str = "test foo bar";
$str->length();      // == strlen($str)        == 12
$str->indexOf("foo") // == strpos($str, "foo") == 5
$str->split(" ")     // == explode(" ", $str)  == ["test", "foo", "bar"]
$str->slice(4, 3)    // == substr($str, 4, 3)  == "foo"

$array = ["test", "foo", "bar"];
$array->length()       // == count($array)             == 3
$array->join(" ")      // == implode(" ", $array)      == "test foo bar"
$array->slice(1, 2)    // == array_slice($array, 1, 2) == ["foo", "bar"]
$array->flip()         // == array_flip($array)        == ["test" => 0, "foo" => 1, "bar" => 2]</code>
ログイン後にコピー

ここで、$str は単なる通常の文字列であり、$array は単なる文字列です。は通常の配列です。オブジェクトではありません。私たちがやったのは、メソッドを呼び出せるように、もう少しオブジェクトのようなプロパティを与えただけです。 $str仅仅是一个普通的字符串,$array仅仅是一个普通的数组——他们不是对象。我们所做的仅仅是给他们了一点儿比较像对象的特性,让他们可以调用方法。

注意上面的特性可不是遥不可及,而是现在已经存在了。PHP扩展scalar_objects允许你在原生PHP类型上面定义面向对象的方法。

原生类型引入对象式的调用方法也带来了许多好处,我会在下面列出来:

一个让API进一步让简洁的机会

听到最多的最常见的关于PHP的抱怨很可能是标准库中函数的前后矛盾、命名不清楚,还有前后不一致、顺序混乱的参数。一些典型的例子:

<code>//不同的函数命名约定
strpos
str_replace

//很不清晰的命名
strcspn
strpbrk

//颠倒的参数顺序
strpos($haystack, $needle)
array_search($needle, $haystack)</code>
ログイン後にコピー

然而这些问题通常被过分强调了(我们有集成开发环境),很难否定状况还是相当好的。还应当指出的是,许多函数表现出的问题远远超过了诡异的名字这个问题。通常边缘情况的行为是没有被完全考虑的,因而有了在调用代码中对它们进行特殊处理的需要。(对字符串函数来说,边缘情况通常包括空字符串或者超出字符串范围的偏移量。)

一个一般的建议是在PHP6中实现巨大数量的别名,用来统一函数名称和参数顺序。因此我们将会有stringpos(),stringreoplace(),stringcomplement_span()或者类似的。个人而言(而且这看起来像是对许多php-src开发者的意见)这些对我的意义很小。现在的这些函数名已经深深的根植在PHP程序员的记忆中了,对它们实现一些不重要的装饰性的改变看起来好像不值得。

原生类型面向对象API的引入另一方面也提供了一个API重新设计的契机(原文:The introduction of an OO API for primitive types on the other hand offers an opportunity of an API redesign as a side effect of switching to a new paradigm)。这也是真正让我们从零开始,不需要考虑旧的API的期望输出。两个例子:

  • 我非常希望有$string->split($delimiter)$array->join($delimiter)这样的方法,这些是函数功能是普遍接受的名字(与explodeimplode不同)。另一方面如果有一个stringsplit($delimiter)方法有这样的行为我会感到非常反感,因为已经存在的str_split
    上記の機能は手の届かないものではなく、すでに存在していることに注意してください。 PHP 拡張機能 scalar_objects を使用すると、ネイティブ PHP 型でオブジェクト指向メソッドを定義できます。
  • ネイティブ型でのオブジェクト スタイルの呼び出しメソッドの導入は、以下にリストする多くの利点ももたらします:
API をさらに簡素化する機会

PHP に関して私がよく聞く最も一般的な不満は、おそらく矛盾があることです。標準ライブラリ内の関数の名前が不明瞭で、パラメーターに一貫性がなく、順序が正しくありません。いくつかの典型的な例: 🎜
<code>$output = array_map(function($value) {
    return $value * 42;
}, array_filter($input, function($value) {
    return $value > 10;
});</code>
ログイン後にコピー
ログイン後にコピー
🎜 これらの問題はしばしば強調されすぎますが (当社には統合開発環境があります)、状況がかなり良好であることを否定するのは困難です。多くの関数には、奇妙な名前をはるかに超えた問題が存在することにも注意してください。多くの場合、エッジ ケースの動作は十分に考慮されていないため、呼び出し側のコードで特別な処理が必要になります。 (文字列関数の場合、エッジ ケースには通常、空の文字列や文字列の範囲外のオフセットが含まれます。) 🎜🎜 一般的な提案は、関数名とパラメーターの順序を統一するために、PHP6 に膨大な数のエイリアスを実装することです。したがって、stringpos()stringreoplace()stringcomplement_span() などを使用します。個人的には (これは多くの php-src 開発者の意見のようですが)、これらは私にとってほとんど意味がありません。これらの関数名は現在、PHP プログラマーの記憶に深く根付いているため、重要ではない表面上の変更を実装する価値がないように思えるかもしれません。 🎜🎜一方で、プリミティブ型の OO API の導入は、新しいパラダイムへの切り替えの副作用として API を再設計する機会を提供します)。これにより、古い API の予想される出力を考慮することなく、最初から開始できるようになります。 2 つの例: 🎜
    🎜 $string->split($delimiter) メソッドと $array->join($delimiter) メソッドがあればいいのですが、これらは、関数の機能として一般に受け入れられている名前です (explodeimplode とは異なります)。一方、このように動作する stringsplit($delimiter) メソッドがあったとしたら、私は非常に嫌になるでしょう。なぜなら、既存の str_split 関数はまったく異なること (グループ化) を行うからです。 。 🎜🎜🎜 エラー報告に例外を使用する新しい API は当然好きです。これはオブジェクト指向 API であるため、当然、名前変更された API でも例外が使用されます。ただし、これは現在の慣習に反します。プログラム関数がエラー処理に警告を使用するかどうかは決まっていませんが、確かにそれは避けたい論点です :) 🎜🎜🎜 これがネイティブ タイプのオブジェクト指向 API を実装する主な動機でした。ゼロから、合理的な一連の API 設計を実装できるようになります。もちろん、この変更によるメリットをすべて知っているわけではありません。オブジェクト指向構文には、以下で説明する多くの深い利点があります。 🎜

    提高了可读性

    程序式的调用一般没有链式调用好。考虑下面的例子:

<code>$output = array_map(function($value) {
    return $value * 42;
}, array_filter($input, function($value) {
    return $value > 10;
});</code>
ログイン後にコピー
ログイン後にコピー

咋一看,哪个是arraay_maparray_filter各自的使用?(原文:what are array_map and array_filter applied to? )他们调用的顺序是什么?变量$input隐藏在两个闭包之间,函数的书写顺序也和他们实际调用的顺序相反。现在同样的例子使用面向对象的语法:

<code>$output = $input->filter(function($value){
	return $value > 10;
})->map(function($value){
	return $value * 42;
});</code>
ログイン後にコピー

我敢说使用这种方式,操作的顺序(先filtermap)和初始输入数组$input更加明显。

这个例子明显有人为拼凑的感觉,因为array_maparray_filter是函数参数顺序颠倒的另外一个例子(这就是为什么输入数组在中间)。再看另外一个输入参数在同一个位置的例子(来自实际的代码):

<code>substr(strstr(rtrim($className, '-'), '\\', '_'), 15);</code>
ログイン後にコピー

在这个例子中,最后面是一连串的额外的参数'_'), '\\', '_'), 15,,很难把这些参数和应用的函数对应起来。把这个和使用面向对象方法的版本做个比较:

<code>$className->trimRight('_')->replace('\\', '_')->slice(15);</code>
ログイン後にコピー

这次函数运算和他们的参数紧密的联系在了一起,而且方法的调用和他们的执行顺序相匹配。

另一个来自这种语法的可读性的好处是needle/haystack不明确问题。别名通过引入统一的参数顺序规范让我们解决了这个问题,使用面向对象的API这个问题基本不存在了。

<code>$string->contains($otherString);
$string->contains($someValue);

$string->indexOf($otherString);
$string->indexOf($someValue);</code>
ログイン後にコピー

这里哪个部分应用了哪个规则的困惑不复存在了。

多态

目前PHP有提供Contable接口,这个接口可以通过类实现自定义的输出函数count($obj)。为什么需要这个?因为我们PHP的函数没有多态。然而我们方法中确实需要多态:

如果数组实现$array->count()作为一个方法,实际上代码是不会在意$array是不是一个数组的,他可以是其他任何类型的实现count()方法的对象,这基本上给了我们Countable的所有行为,~(原文:This basically gives us the same behavior as Countable, just without the engine hackery it requires.)

这也是一个很一般的解决方案。举个例子,你可以实现一个实现所有字符串类型方法的UnicodeString类,然后可以随便的使用正常的字符串和UnicodeStrings。好吧,至少这还是理论。这很明显局限于那些字符串方法的使用,而且调用级联操作的时候会返回错误(原文:This would obviously only work as long as the usage is limited to just the string methods, and would fail once the concatenation operator is employed)(运算符重载目前只支持内核中的类)。

我仍然有强大的信念希望这个清晰起来,同样应用在数组等上面。通过继承相同的接口,你可有一个和数组行为方式相同的SplFixedArray。(原文:you could have an SplFixedArray behave the same way as an array, by implementing the same interface.)

既然我们已经总结了这个方法的一些好处,让我们也来看看它的问题:

松散的类型

摘抄自Anthony发表的博客:

标量不是对象,但更重要的是他们不是任何类型。PHP依赖一个类型系统,字符串和数字是同一个。系统中许多的灵活性基于任何标量可以很容易的转换为其他标量。

更重要的是,由于松散的类型系统,你不可能在任何时候知道一个变量的类型是哪个。你可以说出你想要他是什么类型,但你不知道他内在的类型是什么。Even with casting or scalar type hinting it isn’t a perfect situation since there are cases where types can still change.

为了阐明这个问题,考虑下面的例子:

<code>$num = 123456789;
$sumOfDigits = array_sum(str_split($num));</code>
ログイン後にコピー

这里$num被作为一个字符串数字,被str_split切分后使用array_sum求和。现在试试同样效果的面向对象方法调用:

<code>$num = 123456789;
$sumOfDigits = $num->chunk()->sum();</code>
ログイン後にコピー

这里字符串的cheunk()方法被数字来调用。会发生什么??Anthony建议这样解决:

这意味着所有的标量运算将必然需要对应的标量类型。这将导致需要一个标量有所有的数学方法的对象模型,当然包括所有的字符串方法。真是一个噩梦。。。。。

就像引言中所说的那样,这绝不是一个可接受的解决方法。然而我想我们可以绝对侥幸的逃脱仅仅在那种情况的时候抛出一个错误(异常!)。为了解释为什么这种方法是可行的,让我们看看PHP可以拥有哪些类型。

PHP中的原生类型

除了对象之外,PHP有下面的变量类型:

<code>null
bool
int
float
string
array
resource</code>
ログイン後にコピー

现在,我们考虑下上面这些里面的哪些会需要面向对象的方法:我们首先去掉resource,然后在剩下的里面看。nullbool明显不需要面向对象的方法,除非你想进行像$bool->invert()这样无聊的转换。

绝大多数的数学函数使用面向对象的方法也不是很合适。考虑下面几个例子:

<code>log($n)		$n->log()
sqrt($n)	$n->sqrt()
acosh($n)	$n->acosh()</code>
ログイン後にコピー

我想你会同意数学函数可读性比函数符号的形式更好。当然存在少许的面向对象方法你可以适当的应用数字类型,比如说$num->format(10)读起来相当的不错。然而,关于这里,对于一个面向对象的数字API不是真正的需要,只有少量的函数你可能需要。(再者来说目前的数学API在命名方面没有太多的问题,而且数学操作相关的命名相当的标准。)

现在剩给我们的只有字符串和数组了,我们已经看到这两种类型有许多很棒的API。但关于松散类型的问题我们所有的必须要做的有哪些?下面是重要的几点:

我们经常性的把字符串视为数字的时候(例如来自HTTP或者DB),这样反过来是不对的:直接将数字作为字符串非常少见。举个栗子,下面的代码将让我们感到困惑:

<code>strpos(54321, 32, 1);</code>
ログイン後にコピー

这样将数字视为字符串是一个很怪异的操作,这种情况也ok啊,只需要强制转换一次就好了。使用原来的求和数字的例子:

<code>$num = 123456789;
$sumOfDigits = ((string) $num)->chunk()->sum();</code>
ログイン後にコピー

现在我们弄明白了,是的,我们确实想将数字视为字符串。对我来说,这样来处理想这样使用这种技术的地方是可以接受的。

对于数组情况就更简单了:他不会出现讲一个数组操作视为一个其他不是数组类型的操作。

另一方面可以通过标量类型提示改善这个问题(我完全认为在PHP所有的版本都存在——最令人尴尬的问题是现在仍然没有(原文:which I totally assume to be present in any PHP version this gets in - really embarrassing that we still don’t have them))。如果内类型提示string,你获取输入的字符串将会是一个字符串(即使传递给函数的不是——这取决于类型提示实现的具体内容)。

当然了,我并不是暗示这里没有一点问题。由于错误的函数设计,有时候可能会发生未知的类型潜入代码中,例如substr($str, strlen($str))将自作聪明的返回bool(false)而不是string(0) ""。(不过,这个问题仅有substr存在。面向对象的API不存在那个问题,所以你碰不到那个问题。)

对象传递语义

除了若类型的问题之外,还有原生类型伪方法的一个语义的问题:PHP中的对象和其他类型相比有不同的传递语义(某种程度上和引用类似)。如果我们允许字符串和数组进行面向对象的方法调用,他们看起来会和对象很像,那样的话有些人可能期望他们有对象作为参数的传递语义。这个问题在在字符串和数组中都存在:

<code>function change($arg) {
	echo $arg->length();	//$arg looks like object
	$arg[0] = 'x';			//但是没有对象的传递语义
}

$str = 'foo';
change($str);	//$str stays the same

$array = ['foo', 'o', 'o'];
change($array);	//$array stays the same</code>
ログイン後にコピー

我们当然将会改变传递语义。首先,在我看来通过值传递来传递像数组这种大的数据结构是一个相当low的想法,我更愿意他们像对象一样传递。然而,那将是一个相当大的突破性的向后兼容,并且那将不易于自动的重构(原文:However, that would be a pretty big backwards-compatibility break and one that’s not easy to refactor automatically)(至少我猜想是这样的。我没有做实验去探索这样一个改变带来的实际影响)。另一方面,对于字符串通过对象方式传递参数将是一个灾难,除非我们让字符串同一时间完全的不可变,放弃目前所有的局部变量的可变性(我个人发现非常的容易——去尝试改变一个Python字符串的一个字节)。

我不知道是否有好的方法去解决这个预期的问题,除了在我们的文档中强调字符串和数组在面向对象的方法中仅仅视作”伪对像“,不是真正的对象。

这个问题可以被扩展到其他的对象相关的特性。例如你可将会问像$string instanceof string这样的是否正确。我还没有确定整个事情的完整走向。也许严格坚持仅仅在面向对象的方法使用,然后强调他们不是真正的对象会好一点。然而也许支持面向对象系统的更深层次的特性也会好点。这个观点应该进一步的思考下。

目前的状态

总而言之,这个方法有许多的问题,但我不认为他们特别重要。同时这个提供了一个很好的机会为我们的基本类型引入简洁明了的APIs,提高代码执行操作时候的可读性(可写性)。

那么这个想法目前的状态是什么呢?从我收集的内容来看,内部的人们不是特别的反对这个做法,但更愿意重命名所有的函数。主要的没有推进这个的原因是API提议~

为了这个目的,我创建了scalar_objects扩展,作为一个PHP扩展实现了这个功能。它允许你注册一个处理各自的原生类型的方法调用的类。看一个例子:

<code>class StringHandler {
	public function length(){
		return strlen($this);
	}

	public function contains($str){
		return false !== strpos($this, $str);
	}
}

register_primitive_type_handler('string', 'StringHandler');

$str = 'foo bar baz';
var_dump($str->legth());			//int(11)
var_dump($str->contains('bar'));	//bool(true)
var_dump($str->contains('hello'));	//bool(false)</code>
ログイン後にコピー

不久前,我开始了一个string handler包括一个API说明的工作,但一直没有真正的完成哪个项目(我希望我在不久找到一些重新开始他的动机)。当然也有许多其他项目在为实现这样的APIs而努力。

嗯,这是我想在PHP6中所看到的其中一个改进。我也许会为我的那个方向的计划写另外一篇文章。

引用

原文链接 : http://nikic.github.io/2014/03/14/Methods-on-primitive-types-in-PHP.html

HOOKPHP : http://netsecurity.51cto.com/art/201407/446430.htm

以上就介绍了PHP中原生类型的方法,包括了方面的内容,希望对PHP教程有兴趣的朋友有所帮助。

関連ラベル:
ソース:php.cn
このウェブサイトの声明
この記事の内容はネチズンが自主的に寄稿したものであり、著作権は原著者に帰属します。このサイトは、それに相当する法的責任を負いません。盗作または侵害の疑いのあるコンテンツを見つけた場合は、admin@php.cn までご連絡ください。
人気のチュートリアル
詳細>
最新のダウンロード
詳細>
ウェブエフェクト
公式サイト
サイト素材
フロントエンドテンプレート