# RFC: Add str_starts_with and str_ends_with to PHP

- Status: Implemented (PHP 8.0)
- Authors: Will Hudgins, will@wkhudgins.info
- Date: 2020-03-25 (Updated: 2020-05-05)
- Version: 0.5
- Original: https://wiki.php.net/rfc/add_str_starts_with_and_ends_with_functions
- Page: https://externals.io/rfc/add_str_starts_with_and_ends_with_functions
- More for agents: search with https://externals.io/search.md?q={query} · RFCs at https://externals.io/rfc.md · JSON API at https://externals.io/api · documentation at https://externals.io/llms.txt

## Votes

### Add str_starts_with and str_ends_with as described (closed)

yes: 51 · no: 4

Voters: alcaeus (yes), alec (no), as (yes), asgrim (yes), ashnazg (yes), beberlei (yes), brzuchal (no), bwoebi (yes), carusogabriel (yes), cmb (yes), daverandom (yes), derick (yes), duncan3dc (yes), duodraco (yes), ekin (yes), galvao (yes), geekcom (yes), girgias (yes), jasny (yes), jbnahan (yes), jhdxr (yes), kalle (yes), kelunik (yes), kguest (yes), klaussilveira (yes), kocsismate (yes), kriscraig (yes), lcobucci (yes), malukenho (yes), marandall (yes), mariano (yes), mcmic (yes), mfonda (yes), mike (no), narf (yes), nicolasgrekas (yes), nikic (yes), ocramius (yes), pajoye (yes), petk (yes), pmmaga (yes), pollita (no), ramsey (yes), reywob (yes), ruudboon (yes), salathe (yes), sergey (yes), stas (yes), svpernova09 (yes), tandre (yes), thorstenr (yes), tiffany (yes), trowski (yes), yunosh (yes), zimt (yes)

## Discussions on the mailing list

- [RFC Raised for str_starts_with and str_ends_with](https://externals.io/message/109318.md) — started on 2020-03-26, 5 messages
- [\[VOTE\] Voting opens for str_starts_with and ends_with function](https://externals.io/message/109710.md) — started on 2020-04-20, 3 messages

---

## Introduction

`str_starts_with` checks if a string begins with another string and returns a boolean value (`true`/`false`) whether it does.
`str_ends_with` checks if a string ends with another string and returns a boolean value (`true`/`false`) whether it does.

Typically this functionality is accomplished by using existing string functions such as `substr`, `strpos`/`strrpos`, `strncmp`, or `substr_compare` (often combined with `strlen`). These bespoke userland implementations have various downsides, discussed further below.

The `str_starts_with` and `str_ends_with` functionality is so commonly needed that many major PHP frameworks support it, including [Symfony](https://symfony.com/doc/5.0/components/string.html#methods-to-search-and-replace), [Laravel](https://laravel.com/docs/7.x/helpers#method-starts-with), [Yii](https://www.yiiframework.com/doc/api/2.0/yii-helpers-basestringhelper#startsWith()-detail), [FuelPHP](https://fuelphp.com/docs/classes/str.html#/method_starts_with), and [Phalcon](https://docs.phalcon.io/3.4/en/api/phalcon_text) [1)](#fn__1).

Checking the start and end of strings is a very common task which should be easy. Accomplishing this task is not easy now and that is why many frameworks have chosen to include it. This is also why other high-level programming languages---as diverse as JavaScript, Java, Haskell, and Matlab---have implemented this functionality. Checking the start and end of a string should not be a task which requires pulling in a PHP framework or developing a potentially suboptimal (or worse, buggy) function in userland.

### Downsides of Common Userland Approaches

Ad hoc userland implementations of this functionality are *less intuitive* than dedicated functions (this is especially true for new PHP developers and developers who frequently switch between PHP and other languages---many of which include this functionality natively).
The implementation is also *easy to get wrong* (especially with the `===` comparison).
Additionally, there are *performance issues* with many userland implementations.

*Note: some implementations add “*`$needle === "" || `*” and/or “*`strlen($needle) <= strlen($haystack) && `*” guards to handle empty needle values and/or avoid warnings.*

#### str\_starts\_with

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

This is memory inefficient because it requires an unnecessary copy of part of the haystack.

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

This is potentially CPU inefficient because it will unnecessarily search along the whole haystack if it doesn't find the needle.

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

This is efficient but requires providing the needle length as a separate argument, which is either verbose (repeat “`$needle`”) or error prone (hard-coded number).

#### str\_ends\_with

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

This is memory inefficient (see above).

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

This is doubly inefficient because it requires reversing both the haystack and the needle as well as applying `strpos` (see above).

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

This is verbose and also potentially CPU inefficient.

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

This is efficient but either verbose or error prone (see `strncmp` above).

## Proposal

Add two new basic functions: `str_starts_with` and `str_ends_with`:

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

`str_starts_with()` checks if `$haystack` begins with `$needle`. If `$needle` is longer than `$haystack`, it returns `false`; else, it compares each character in `$needle` with the corresponding character in `$haystack` (aligning both strings at their start), returning `false` if it encounters a mismatch, and `true` otherwise.
`str_ends_with()` does the same thing but aligning both strings at their end.

Examples below:

```php
$str = "beginningMiddleEnd";
if (str_starts_with($str, "beg")) echo "printed\n";
if (str_starts_with($str, "Beg")) echo "not printed\n";
if (str_ends_with($str, "End")) echo "printed\n";
if (str_ends_with($str, "end")) echo "not printed\n";
 
// empty strings:
if (str_starts_with("a", "")) echo "printed\n";
if (str_starts_with("", "")) echo "printed\n";
if (str_starts_with("", "a")) echo "not printed\n";
if (str_ends_with("a", "")) echo "printed\n";
if (str_ends_with("", "")) echo "printed\n";
if (str_ends_with("", "a")) echo "not printed\n";
```

Note: the behavior concerning empty strings is in accordance with what is described in the accepted [str\_contains RFC](https://externals.io/rfc/str_contains#proposal). This behavior is also the same as is common with other languages, including Java and Python.

## Backward Incompatible Changes

This could break functions existing in userland with the same names. But see [the corresponding section in the str\_contains RFC](https://externals.io/rfc/str_contains#backward_incompatible_changes) for a discussion illustrating how this concern may be mitigated and why it does not justify the rejection of this RFC.

## Proposed PHP Version(s)

PHP 8

## RFC Impact

- **To SAPIs:** Will add the aforementioned functions to all PHP environments.
- **To Existing Extensions:** None.
- **To Opcache:** No effect.
- **New Constants:** No new constants.
- **php.ini Defaults:** No changed php.ini settings.

## Votes

Voting closes 2020-05-04

**Vote: Add str\_starts\_with and str\_ends\_with as described** — yes: 51 · no: 4 (closed)

## Patches and Tests

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

## Implementation

After the project is implemented, this section should contain

1. the version(s) it was merged to
2. a link to the git commit(s)
3. a link to the PHP manual entry for the feature

## References

- Implementation of similar methods/functions in other languages: 
    - JavaScript: [String#startsWith()](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/String/startsWith) and [String#endsWith()](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/String/endsWith)
    - Python: [str#startswith()](https://docs.python.org/3/library/stdtypes.html#str.startswith) and [str#endswith()](https://docs.python.org/3/library/stdtypes.html#str.endswith)
    - Java: [String#startsWith()](https://docs.oracle.com/javase/7/docs/api/java/lang/String.html#startsWith(java.lang.String)) and [String#endsWith()](https://docs.oracle.com/javase/7/docs/api/java/lang/String.html#endsWith(java.lang.String)) (and Apache Commons Lang [StringUtils.startsWith()](https://commons.apache.org/proper/commons-lang/apidocs/org/apache/commons/lang3/StringUtils.html#startsWith-java.lang.CharSequence-java.lang.CharSequence-) and [StringUtils.endsWith()](https://commons.apache.org/proper/commons-lang/apidocs/org/apache/commons/lang3/StringUtils.html#endsWith-java.lang.CharSequence-java.lang.CharSequence-))
    - Ruby: [String#start\_with?()](https://ruby-doc.org/core-2.1.1/String.html#method-i-start_with-3F) and [String#end\_with?()](https://ruby-doc.org/core-2.1.1/String.html#method-i-end_with-3F)
    - Go: [strings.HasPrefix()](https://golang.org/pkg/strings/#HasPrefix) and [strings.HasSuffix()](https://golang.org/pkg/strings/#HasSuffix)
    - Haskell: [Data.String.Utils.startswith](https://hackage.haskell.org/package/MissingH-1.4.0.1/docs/Data-String-Utils.html#v:startswith) and [Data.String.Utils.endswith](https://hackage.haskell.org/package/MissingH-1.4.0.1/docs/Data-String-Utils.html#v:endswith) (aliases of [Data.List.isPrefixOf](https://hackage.haskell.org/package/base-4.12.0.0/docs/Data-List.html#v:isPrefixOf) and [Data.List.isSuffixOf](https://hackage.haskell.org/package/base-4.12.0.0/docs/Data-List.html#v:isSuffixOf))
    - MATLAB: [startsWith()](https://www.mathworks.com/help/matlab/ref/startswith.html) and [endsWith()](https://www.mathworks.com/help/matlab/ref/endswith.html)
- PHP feature request: [bug #50434](http://bugs.php.net/bug.php?id=50434) (and duplicates: [bug #60630](http://bugs.php.net/bug.php?id=60630), [bug #67035](http://bugs.php.net/bug.php?id=67035), [bug #74449](http://bugs.php.net/bug.php?id=74449))
- Accepted RFC for related function: [PHP RFC: str\_contains](https://externals.io/rfc/str_contains)
- Rejected Prior RFC: [PHP RFC: rfc:add\_str\_begin\_and\_end\_functions](https://externals.io/rfc/add_str_begin_and_end_functions)

- Discussion on the php.internals mailing list: <https://externals.io/message/109318>

## Rejected Features

- **Case-insensitive** and **multibyte** variants were included in the previous version of this RFC, which was declined. See also [the related section in the str\_contains RFC](https://externals.io/rfc/str_contains#case-insensitivity_and_multibyte_strings).

[1)](#fnt__1)some of those links are for `str_starts_with` functionality, but the mentioned frameworks also contain `str_ends_with` functionality, often visible on the same web page
