14
4

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?

More than 3 years have passed since last update.

【PHP8.0】StartsWith/EndsWithがPHP本体に実装される

Posted at

先日PHP8でstr_containsが導入されることが決まったばかりですが、さらにもっと直接的な『〇〇で始まる』『〇〇で終わる』関数までも導入されることになりました。

Add str_starts_with() and str_ends_with() functionsというRFCが投票中です。
2020/05/04時点では賛成50反対4で、ほぼ導入確定です。

PHP RFC: Add str_starts_with() and str_ends_with() functions

Introduction

str_starts_withは、文字列が指定の文字列で始まるか否かをチェックし、結果をbool値で返します。
str_ends_withは、文字列が指定の文字列で終わるか否かをチェックし、結果をbool値で返します。

これらの機能は既存の文字列関数、たとえばsubstrstrpos/strrpos、そしてstrncmpsubstr_compare、あまつさえstrlenなどを駆使して実装されてきました。
これらユーザランドの実装には、様々な問題点があります。

str_starts_withとstr_ends_withの需要は高く、SymfonyLaravelYiiFuelPHP、そしてPhalconと、あらゆるフレームワークによってサポートされています。

文字列の始めと終わりをチェックすることは非常に一般的なタスクであり、簡単に行えるべきです。
多くのフレームワークがこのタスクを実装しているということは、このタスクを実行することが簡単ではないことを意味しています。
JavaScript/Java/Haskell/Matlabといった多くの高水準言語が標準でこの機能を実装している理由でもあります。
文字列の開始と終了をチェックすることは、これだけのためにフレームワークを導入したり、ユーザランドで最適ではない(どころかバグが入るかもしれない)実装を行ったりする必要のあるべき作業ではありません。

Downsides of Common Userland Approaches

この機能のアドホックな実装は、専用関数に比べると直感的ではありません。
PHPのニュービーや、他言語と同時開発する開発者にとっては特にそうです。
また、特に===を含む場合、実装を簡単に間違えます。
さらに多くのユーザランド実装はパフォーマンス上の問題があります。

注意:以下の実装には、E_WARNINGを防ぐために$needle === "" || strlen($needle) <= strlen($haystack) && のようなガードを入れましょう。

str_starts_with

substr($haystack, 0, strlen($needle)) === $needle

$haystackの無駄なコピーが発生するため、メモリ効率が良くありません。

strpos($haystack, $needle) === 0

$needleが見つからなかった場合に$haystackを最後まで調べてしまうため、CPU効率が悪くなります。

strncmp($haystack, $needle, strlen($needle)) === 0 // generic
strncmp($subject, "prefix", 6) === 0 // ad hoc

これは効率的ですが、$needleの文字数を別に渡す必要があり冗長です。

str_ends_with

substr($haystack, -strlen($needle)) === $needle

str_starts_with同様、メモリ効率がよくありません。

strpos(strrev($haystack), strrev($needle)) === 0

str_starts_with同様CPU効率が悪いだけでなく、文字列反転処理まで入るので、さらに非効率です。

strrpos($haystack, $needle) === strlen($haystack) - strlen($needle)

冗長であり、CPURL効率が悪くなることがあります。

substr_compare($haystack, $needle, -strlen($needle)) === 0 // generic
substr_compare($subject, "suffix", -6) === 0 // ad hoc

効率的ですが、冗長です。

Proposal

2つの関数、str_starts_with()str_ends_with()を導入します。

str_starts_with ( string $haystack , string $needle ) : bool
str_ends_with ( string $haystack , string $needle ) : bool

str_starts_with()は、$haystack$needleで始まるかどうかを調べます。
strlen($needle) > strlen($haystack)であれば即座にfalseを返し、そうでなければ両文字列を比較し、先頭一致すればtrueを、一致しなければfalseを返します。

str_ends_with()も同じですが、後方一致です。

以下に例を示します。

$str = "beginningMiddleEnd";
if (str_starts_with($str, "beg")) echo "printed\n"; // true
if (str_starts_with($str, "Beg")) echo "not printed\n"; // false
if (str_ends_with($str, "End")) echo "printed\n"; // true
if (str_ends_with($str, "end")) echo "not printed\n"; // false
 
// 空文字
if (str_starts_with("a", "")) echo "printed\n"; // true
if (str_starts_with("", "")) echo "printed\n"; // true
if (str_starts_with("", "a")) echo "not printed\n"; // false
if (str_ends_with("a", "")) echo "printed\n"; // true
if (str_ends_with("", "")) echo "printed\n"; // true
if (str_ends_with("", "a")) echo "not printed\n"; // false

空文字に関しては、受理済のstr_containsのRFCの挙動に従います。
これはJavaやPythonなどと共通の動作です。

Backward Incompatible Changes

ユーザランドに同名の関数がある場合は競合します。

Proposed PHP Version(s)

PHP8

RFC Impact

・SAPI:全てのPHP環境に関数が追加されます
・エクステンション:無し
:Opcache:無し
・New Constants:無し
・php.ini Defaults:無し

Votes

投票は2020/05/04まで。
投票者の2/3+1の賛成で受理されます。

Patches and Tests

https://github.com/php/php-src/pull/5300

References

他言語の類似機能
・JavaScript: String#startsWith() / String#endsWith()
・Python: str#startswith() / str#endswith()
・Java: String#startsWith() / String#endsWith()
・Ruby: String#start_with?() / String#end_with?()
・Go: strings.HasPrefix() / strings.HasSuffix()
・Haskell: Data.String.Utils.startswith / Data.String.Utils.endswith
・MATLAB: startsWith()) / endsWith()

bugs.php.net
bug #50434 / bug #60630 / bug #67035 / bug #74449

過去のRFC
PHP RFC: rfc:add_str_begin_and_end_functions

Rejected Features

大文字小文字を区別しない版とマルチバイト版は、以前のRFCには含まれていましたが、このRFCでは廃止されました。
理由はstr_containsを参照してください。

感想

PHPの文字列関数ってやたら大量に用意されてるわりに意外と基本的なところが抜けていたのですが、PHP8でstr_continsとこの関数が追加されたことによって、テキスト処理に必要なものは出揃うことになったのではないでしょうか。

他に必要なのって何かありますかね。デフォルト関数の命名規則とか?

14
4
1

Register as a new user and use Qiita more conveniently

  1. You get articles that match your needs
  2. You can efficiently read back useful information
  3. You can use dark theme
What you can do with signing up
14
4

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?