1689

How can I write two functions that would take a string and return if it starts with the specified character/string or ends with it?

For example:

$str = '|apples}';

echo startsWith($str, '|'); //Returns true
echo endsWith($str, '}'); //Returns true
Ivar
  • 6,138
  • 12
  • 49
  • 61
Ali
  • 261,656
  • 265
  • 575
  • 769
  • 26
    See Laravel's [Str class](https://github.com/laravel/framework/blob/4.2/src/Illuminate/Support/Str.php) startsWith() and endsWith() for [well-tested](https://github.com/laravel/framework/blob/4.2/tests/Support/SupportStrTest.php) methods. [Edge cases](https://github.com/laravel/framework/commit/a70c74d68c9ba922207246f225cccc64d5407911) had been encountered, so the widespread use of this code is an advantage. – Gras Double Dec 06 '14 at 23:32
  • 1
    You might find [`s($str)->startsWith('|')`](https://github.com/delight-im/PHP-Str/blob/8fd0c608d5496d43adaa899642c1cce047e076dc/src/Str.php#L69) and [`s($str)->endsWith('}')`](https://github.com/delight-im/PHP-Str/blob/8fd0c608d5496d43adaa899642c1cce047e076dc/src/Str.php#L117) helpful, as found in [this standalone library](https://github.com/delight-im/PHP-Str). – caw Jul 26 '16 at 23:46
  • 5
    Warning: most answers here are unreliable in multi-byte encodings such as UTF-8. – Álvaro González May 15 '17 at 14:07
  • Following up to my above comment, you may make sure to use the latest version (as of today, [5.4](https://github.com/laravel/framework/blob/5.4/src/Illuminate/Support/Str.php)). Notably, startsWith() has been optimized for large haystack strings. – Gras Double May 22 '17 at 18:51
  • 10
    PHP 8.0 introduces new methods for this job `str_starts_with` and `str_end_with`: https://stackoverflow.com/a/64160081/7082164 – Jsowa Oct 01 '20 at 17:06

34 Answers34

1850

PHP 8.0 and higher

Since PHP 8.0 you can use the

str_starts_with Manual and

str_ends_with Manual

Example

echo str_starts_with($str, '|');

PHP before 8.0

function startsWith( $haystack, $needle ) {
     $length = strlen( $needle );
     return substr( $haystack, 0, $length ) === $needle;
}
function endsWith( $haystack, $needle ) {
    $length = strlen( $needle );
    if( !$length ) {
        return true;
    }
    return substr( $haystack, -$length ) === $needle;
}
sh6210
  • 4,190
  • 1
  • 37
  • 27
MrHus
  • 32,888
  • 6
  • 31
  • 31
  • variable names are unclear. eg, char is a string, not a single char. Perhaps consider rename params to conventional php $haystack and $needle. – timoxley Feb 01 '11 at 01:33
  • 5
    Just FYI, the $length parameter in `endsWith()` is redundant, since `substr()` will terminate at the end of the string anyway. – AgentConundrum Feb 07 '11 at 18:11
  • 20
    I'd say endsWith('foo', '') == false is the correct behavior. Because foo doesn't end with nothing. 'Foo' ends with 'o', 'oo' and 'Foo'. – MrHus Apr 13 '12 at 13:34
  • thank you, I found this notation more concise substr($haystack, - strlen($needle)) No need for $start and * -1 – Micha Roon May 02 '12 at 06:18
  • 153
    EndsWith can be written a lot shorter: `return substr($haystack, -strlen($needle))===$needle;` – Rok Kralj Jun 11 '12 at 09:57
  • 1
    startWith doesn't work with arguments ("100", "100"), changing equals to == solve issue – ruX Jul 21 '12 at 17:51
  • 1
    for startWith, strpos is faster than substr. – wdev Aug 06 '12 at 00:40
  • I was searching for a simple manner of testing is a string starts with any of a set of strings, something like strStartsWith(array(allStarts)). I posted what I did below, hoping someone could find it useful. I'm quite surprised PHP developers did not implement these functions, since they are very useful everyday. – Benj Nov 29 '12 at 15:15
  • Testing for `$haystack{0}===$needle{0}` before, speeds things up a bit if $needle and $haystack are not empty (see reply). If you are reusing the same $needle, save its length, no need to strlen() every time, add a $length parameter to startWith(...). Any information you have on the strings helps. – FrancescoMM Jul 28 '13 at 14:44
  • 10
    @RokKralj But only if `$needle` is not empty. – Ja͢ck Feb 21 '14 at 03:01
  • used it in my string class because its not using regex :-) https://github.com/bes89/string – Besnik Oct 11 '14 at 21:17
  • 15
    You can avoid the `if` altogether by passing `$length` as the third parameter to `substr`: `return (substr($haystack, -$length, $length);`. This handles the case of `$length == 0` by returning an empty string and not the whole `$haystack`. – mxxk Jan 23 '15 at 01:21
  • Hey @MrHus, do you mind if I use your functions in this post without attribution? – D.Tate Jun 05 '15 at 18:47
  • 2
    @D.Tate If I didn't want people to use it I would not have posted it to stackoverflow. So no attribution needed. – MrHus Jun 08 '15 at 10:00
  • 2
    Much more readable than the other; this solution is easier to bugfix. – redolent Jul 03 '15 at 22:53
  • 3
    5 lines of code, 5 suggestions of making it better. 500 lines of code, and everyone goes "meh, looks good to me" – Loupax Apr 18 '16 at 08:30
  • 6
    @Loupax that is a good thing. People can comprehend 5 lines of code and reason about it. When it's 500 lines that's too much to comprehend. This is why you should write short functions. – Pieter B Apr 22 '16 at 08:25
  • 22
    @MrHus I would recommend using multi-byte safe functions, e.g. mb_strlen and mb_substr – 19Gerhard85 Apr 25 '16 at 08:49
  • 2
    By the way, what’s wrong with `$startsWith = strpos($str, '|') === 0; $endsWith = substr($str, -1) === '}';` ? – Taufik Nurrohman May 07 '17 at 13:35
  • 1
    @RokKralj I disagree. All strings implicitly both start and end with the empty string. That is, the last 0 characters of the string matches the empty string, just like the first 0 characters does. Why handle n=0 differently than n=1,2,…? – devios1 Sep 05 '17 at 18:22
  • The idea to handle empty strings separately is not a product of my brain. Watch out before you accuse someone. – Rok Kralj Sep 10 '17 at 08:05
  • 3
    @19Gerhard85 you don't need to worry about multi-bytes when comparing bytes to bytes. simple substr() and strlen() functions are ok for that. – Mike Shiyan Dec 19 '17 at 11:26
  • @MrHus why not simply use strpos() in startsWith()? – Mike Shiyan Dec 19 '17 at 11:27
  • @MikeShiyan: Nobody mentioned any bytes until now, it's all characters. When comparing bytes, "ü" != "ü" - one is "LATIN SMALL LETTER U WITH DIAERESIS", the other is "LATIN SMALL LETTER U" followed by "COMBINING DIAERESIS". Completely different bytes, different byte length (!), yet both are the same one *character*. Surely you have read https://www.joelonsoftware.com/2003/10/08/the-absolute-minimum-every-software-developer-absolutely-positively-must-know-about-unicode-and-character-sets-no-excuses/ – Piskvor left the building Mar 23 '18 at 12:32
  • @Piskvor so you're saying that the answer compares characters, not bytes? I think, that's not true - I suppose the `strlen()` and the `substr()` are binary-safe functions. As well as the `strpos()`. So I just don't understand, why the answer uses that code instead of simple `strpos($haystack, $needle) === 0`. P.S.: though I haven't read the mentioned article, I know the difference between characters and bytes, don't worry about that. – Mike Shiyan Mar 23 '18 at 14:52
  • 1
    That's indeed true: the *question* is about characters, but the *answer* compares bytes. – Piskvor left the building Mar 23 '18 at 14:53
  • At least for startWith() I don't get why this sneeky solution is needed. 1 million iterations take 1086ms, strncmp takes 1017ms with same result. – Rudiger W. Jul 17 '18 at 15:47
  • I think that "public static" should be added so that they can be used as a reference function without instantiating like if( SomeClassNAME::startsWith() ) – Rubén Ruíz Oct 05 '18 at 21:22
  • 3
    the fact that there aren't native "starts with" and "ends with" functions maeks php silly. – ahnbizcad Jun 21 '19 at 22:37
  • adding strtoupper on the $haystack and $needle, with another parm for 'ignore case' would be helpful – JohnnyB Feb 13 '20 at 19:36
  • 1
    This `startsWith()` function could be optimized by using [`strncmp()`](https://www.php.net/manual/en/function.strncmp.php): `strncmp($haystack, $needle, strlen($needle)) === 0` (refs [PR 32243 in Laravel](https://github.com/laravel/framework/pull/32243)). See also the "Common Userland Approaches" in this [PHP 8 RFC](https://wiki.php.net/rfc/add_str_starts_with_and_ends_with_functions#downsides_of_common_userland_approaches) (pointed out in [Jon's answer](https://stackoverflow.com/questions/834303/startswith-and-endswith-functions-in-php/62169159#62169159)). – Gras Double Jul 04 '20 at 03:14
  • Just make sure to return `true` if the needle is en empty string. – Gras Double Jul 04 '20 at 04:33
  • I would suggest to check symfony php80 polyfills: https://github.com/symfony/polyfill-php80/blob/main/Php80.php#L96-L104 – mircobabini Aug 26 '21 at 20:01
1104

You can use substr_compare function to check start-with and ends-with:

function startsWith($haystack, $needle) {
    return substr_compare($haystack, $needle, 0, strlen($needle)) === 0;
}
function endsWith($haystack, $needle) {
    return substr_compare($haystack, $needle, -strlen($needle)) === 0;
}

This should be one of the fastest solutions on PHP 7 (benchmark script). Tested against 8KB haystacks, various length needles and full, partial and no match cases. strncmp is a touch faster for starts-with but it cannot check ends-with.

Salman A
  • 262,204
  • 82
  • 430
  • 521
  • Please note that @DavidWallace and @FrancescoMM comments apply to an older version of this answer. The current answer uses `strrpos` which (should) fail immediately if needle does not match the beginning of haystack. – Salman A Apr 21 '16 at 14:00
  • 2
    I don't get it. Based on http://php.net/manual/en/function.strrpos.php: "If the value is negative, search will instead start from that many characters from the end of the string, searching backwards." This seems to indicate that we're starting at character 0 (due to `-strlength($haystack)`) and searching *backward* from there? Doesn't that mean you're not searching anything? I also don't understand the `!== false` parts of this. I'm guessing this is relying on a quirk of PHP where some values are "truthy" and others "falsy" but how does that work in this case? – Welbog Apr 21 '16 at 14:44
  • 3
    @Welbog: for example haystack = `xxxyyy` needle = `yyy` and using `strrpos` the search starts from the first `x`. Now we do not have a successful match here (found x instead of y) and we cannot go backward anymore (we're at start of string) the search fails *immediately*. About using `!== false` -- `strrpos` in the above example will return 0 or false and not other value. Likewise, `strpos` in the above example can return `$temp` (the expected position) or false. I went with `!== false` for consistency but you could use `=== 0` and `=== $temp` in the functions respectively. – Salman A Apr 21 '16 at 15:01
  • So much unnecessary work on this. Why not use strpos === 0 for the startsWith. Down voted for drastically over complicating suboptimal coding response. – Spoo Oct 07 '16 at 19:39
  • 13
    @spoo it has already been established that strpos === 0 is a terrible solution if haystack is large and needle does not exist. – Salman A Oct 08 '16 at 06:22
  • @hanshenrik: _"substr_compare — Binary safe comparison"_, and Unicode supports binary representation, see https://unicode.org/reports/tr15/#Norm_Forms . So both substr_compare() and str_starts_with() are case-sensitive. – hakre Jul 21 '23 at 20:50
260

Updated 23-Aug-2016

Functions

function substr_startswith($haystack, $needle) {
    return substr($haystack, 0, strlen($needle)) === $needle;
}

function preg_match_startswith($haystack, $needle) {
    return preg_match('~' . preg_quote($needle, '~') . '~A', $haystack) > 0;
}

function substr_compare_startswith($haystack, $needle) {
    return substr_compare($haystack, $needle, 0, strlen($needle)) === 0;
}

function strpos_startswith($haystack, $needle) {
    return strpos($haystack, $needle) === 0;
}

function strncmp_startswith($haystack, $needle) {
    return strncmp($haystack, $needle, strlen($needle)) === 0;
}

function strncmp_startswith2($haystack, $needle) {
    return $haystack[0] === $needle[0]
        ? strncmp($haystack, $needle, strlen($needle)) === 0
        : false;
}

Tests

echo 'generating tests';
for($i = 0; $i < 100000; ++$i) {
    if($i % 2500 === 0) echo '.';
    $test_cases[] = [
        random_bytes(random_int(1, 7000)),
        random_bytes(random_int(1, 3000)),
    ];
}
echo "done!\n";


$functions = ['substr_startswith', 'preg_match_startswith', 'substr_compare_startswith', 'strpos_startswith', 'strncmp_startswith', 'strncmp_startswith2'];
$results = [];

foreach($functions as $func) {
    $start = microtime(true);
    foreach($test_cases as $tc) {
        $func(...$tc);
    }
    $results[$func] = (microtime(true) - $start) * 1000;
}

asort($results);

foreach($results as $func => $time) {
    echo "$func: " . number_format($time, 1) . " ms\n";
}

Results (PHP 7.0.9)

(Sorted fastest to slowest)

strncmp_startswith2: 40.2 ms
strncmp_startswith: 42.9 ms
substr_compare_startswith: 44.5 ms
substr_startswith: 48.4 ms
strpos_startswith: 138.7 ms
preg_match_startswith: 13,152.4 ms

Results (PHP 5.3.29)

(Sorted fastest to slowest)

strncmp_startswith2: 477.9 ms
strpos_startswith: 522.1 ms
strncmp_startswith: 617.1 ms
substr_compare_startswith: 706.7 ms
substr_startswith: 756.8 ms
preg_match_startswith: 10,200.0 ms

startswith_benchmark.php

mpen
  • 272,448
  • 266
  • 850
  • 1,236
  • 3
    If the strings are not empty, as in your tests, this is actually somehow (20-30%) faster: `function startswith5b($haystack, $needle) {return ($haystack{0}==$needle{0})?strncmp($haystack, $needle, strlen($needle)) === 0:FALSE;}` I added a reply below. – FrancescoMM Jul 28 '13 at 15:38
  • @FrancescoMM I've incorporated your version into my tests. That does not appear to be true; at least not when the first letter has a 1 in 64 chance of being a match. – mpen Nov 19 '14 at 00:49
  • strange, it may depend on the PHP implementation as I had tested it on your same set, anyway thanks for adding it – FrancescoMM Nov 19 '14 at 07:46
  • why did you say that substr_startswith is the fastest when substr_compare_startswith only has 133? – Jronny Dec 02 '14 at 14:22
  • 3
    @Jronny Because 110 is less than 133...?? – mpen Dec 02 '14 at 16:41
  • 2
    Darn, I don't know what went to my head that time. Prolly the lack of sleep. – Jronny Dec 18 '14 at 03:08
  • This is what I got on the same set on OsX MAMP environment on 10000 loops, to confirm it depends on the setup. `generating tests..........done! substr_startswith: 51.8469810486 ms preg_match_startswith: 1991.78195 ms substr_compare_startswith: 53.2069206238 ms strpos_startswith: 63.8959407806 ms strncmp_startswith: 45.2649593353 ms strncmp_startswith2: 42.7808761597 ms` so.. do some testing on you production machine! – FrancescoMM Jul 28 '15 at 14:35
  • Another repro, on a 2015 Macbook Pro (Core i5) with php 5.6.20. Loops = 20000: generating tests....................done! substr_startswith: 39.290904998779 ms preg_match_startswith: 1911.6959571838 ms substr_compare_startswith: 37.993907928467 ms strpos_startswith: 51.711082458496 ms strncmp_startswith: 42.64497756958 ms strncmp_startswith2: 42.857885360718 ms – rivimey Apr 19 '16 at 11:01
  • Updated for PHP 7. `strncmp_startswith2` moved from 4th place to 1st. Most functions got significantly faster, except preg_match which is now twice as slow. – mpen Aug 23 '16 at 18:13
  • @mpen any chance of showing us your preferred endsWith() alternatives as well ? – AdamJones Sep 19 '16 at 16:13
  • @AdamJones I didn't come up with any alternatives. Here's what I'm using now: https://gist.github.com/mnpenner/1e35f9f20d0982c541c6ea1fd45ff5a8 – mpen Sep 19 '16 at 16:45
  • preg_quote() is a very slow function. with - preg_match_startswith: **2,240.1** ms; without - preg_match_startswith: **258.0** ms – Visman Nov 18 '17 at 04:22
  • Use `return preg_match('~\Q' . $needle . '\E~A', $haystack) > 0;` – Visman Nov 18 '17 at 04:35
  • @Visman You'd still have to double the backslashes in the needle if you used `\Q...\E` no? Edit: Or I guess *just* `\E` you'd have to escape... actually I don't know *how* to escape a literal `\E`. Edit2: I guess you could `str_replace('\\E', '\\E\\\\E\\Q', $needle)` which is even uglier than preg_quote. Not sure if it'd be faster or not. – mpen Nov 18 '17 at 21:26
  • @mpen, http://sandbox.onlinephpfunctions.com/code/5d54ac02d3ccd5317f82677dcf6600c1c27ed78b - working, http://sandbox.onlinephpfunctions.com/code/2e57ac68f2f52ca5af68f27be26aff39df72283f - does not work. Probably a bug. https://regex101.com/r/kFBxWR/1 - the emulator is working properly. – Visman Nov 19 '17 at 12:26
  • @Visman Yeah...but I asked about putting a literal `\E` in the search string, not just a start/end delim. – mpen Nov 20 '17 at 17:23
  • 1
    @mpen, I noticed not the elephant at all :( – Visman Nov 21 '17 at 02:15
  • 1
    These tests are no good in testing the performance. What you are doing is using random string as the needle. In 99.99% cases there will be NO match. Most of the functions will exit after matching first byte. What about cases when a match is found? Which function takes least amount of time to conclude successful match? What about cases where 99% of needle match but not the last few bytes? Which function takes least amount of time to conclude no match? – Salman A Dec 15 '17 at 15:06
  • 1
    @SalmanA You raise some good points. Not sure I want to redesign the test cases right now though. – mpen Dec 15 '17 at 19:02
  • 3
    `$haystack[0]` will throw a notice error if you don't test it with isset. The same for needles. But if you add tests, it will slow down its performance – Thanh Trung Apr 15 '18 at 18:12
  • What about `endsWith`? – mbomb007 Feb 16 '22 at 18:42
  • @mbomb007 Try these: https://www.php.net/manual/en/function.str-starts-with.php#125913 – mpen Feb 17 '22 at 19:12
147

All answers so far seem to do loads of unnecessary work, strlen calculations, string allocations (substr), etc. The 'strpos' and 'stripos' functions return the index of the first occurrence of $needle in $haystack:

function startsWith($haystack,$needle,$case=true)
{
    if ($case)
        return strpos($haystack, $needle, 0) === 0;

    return stripos($haystack, $needle, 0) === 0;
}

function endsWith($haystack,$needle,$case=true)
{
    $expectedPosition = strlen($haystack) - strlen($needle);

    if ($case)
        return strrpos($haystack, $needle, 0) === $expectedPosition;

    return strripos($haystack, $needle, 0) === $expectedPosition;
}
Ram Sharma
  • 8,676
  • 7
  • 43
  • 56
Sander Rijken
  • 21,376
  • 3
  • 61
  • 85
  • 2
    `endsWith()` function has an error. Its first line should be (without the -1): `$expectedPosition = strlen($haystack) - strlen($needle);` – Enrico Detoma Aug 05 '10 at 17:16
  • 7
    The strlen() thing is not unnecessary. In case the string doesn't start with the given needle then ur code will unnecessarily scan the whole haystack. – AppleGrew Jan 04 '11 at 15:46
  • you think scanning the entire string is cheaper than checking just the beginning? i don't think strpos is any cheaper. – mpen Mar 03 '11 at 21:26
  • 6
    @Mark yea, checking just the beginning is a LOT faster, especially if you're doing something like checking MIME types (or any other place where the string is bound to be large) – chacham15 Sep 26 '11 at 15:39
  • 2
    @mark I did some benchmarks with 1000 char haystack and 10 or 800 char needle and strpos was 30% faster. Do your benchmarks before stating that something is faster or not... – wdev Aug 06 '12 at 00:39
  • 1
    @wdev: did your string always contain the needle? `strpos` would be slowest when the string doesn't even contain the needle (has to scan the whole thing). and what are you comparing against exactly? using substr, or other methods? `substr` has to allocate a new string, there's a substantial cost in that too. – mpen Aug 06 '12 at 00:44
  • @Mark Match, no match and same string comparison: https://gist.github.com/3268655 – wdev Aug 06 '12 at 01:05
  • @wdev: Not a fair comparison. Two of your 3 tests have the needle start at the beginning, which is where strpos excels at. Run it just for "no match" and `strncmp` wins. I don't know why strpos would ever win though. Perhaps because `strncmp` is doing a full comparison, and not just equal/not-equal. And btw, I did do some benches if you had looked further down this thread; but regardless, I'm entitled to speculate. – mpen Aug 06 '12 at 01:55
  • 8
    You should strongly consider quoting the needle like `strpos($haystack, "$needle", 0)` if there's _any_ chance it's not already a string (e.g., if it's coming from `json_decode()`). Otherwise, the [odd] default behavior of `strpos()` may cause unexpected results: "[If needle is not a string, it is converted to an integer and applied as the ordinal value of a character.](http://php.net/strpos)" – quietmint Dec 03 '12 at 03:47
  • Please don't use this example, you risk falling onto large performance problems. – Sylvain Apr 02 '13 at 13:14
  • return strpos($haystack, $needle, $expectedPosition) === $expectedPosition; should be better in that it doesn't scan the whole string when failing. Though then you will need an `if ($expectedPosition < 0) return false;` check in case the needle's length is greater than the haystack's. – Fabrício Matté Nov 22 '13 at 18:17
  • this. I really wonder why this is not the accepted `startsWith()` function. – Félix Adriyel Gagnon-Grenier Oct 14 '15 at 18:50
  • This returns false for `startsWith('abc', '')` You got to check for empty string first. – nawfal Jan 29 '16 at 12:15
  • 3rd arguments to all str*pos() functions are unnecessary. They default to "0". – Mike Shiyan Dec 19 '17 at 11:34
55

PHP 8 update

PHP 8 includes new str_starts_with and str_ends_with functions that finally provide a performant and convenient solution to this problem:

$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";

The RFC for this feature provides more information, and also a discussion of the merits and problems of obvious (and not-so-obvious) userland implementations.

Flimm
  • 136,138
  • 45
  • 251
  • 267
Jon
  • 428,835
  • 81
  • 738
  • 806
53
function startsWith($haystack, $needle, $case = true) {
    if ($case) {
        return (strcmp(substr($haystack, 0, strlen($needle)), $needle) === 0);
    }
    return (strcasecmp(substr($haystack, 0, strlen($needle)), $needle) === 0);
}

function endsWith($haystack, $needle, $case = true) {
    if ($case) {
        return (strcmp(substr($haystack, strlen($haystack) - strlen($needle)), $needle) === 0);
    }
    return (strcasecmp(substr($haystack, strlen($haystack) - strlen($needle)), $needle) === 0);
}

Credit To:

Check if a string ends with another string

Check if a string begins with another string

Rubens Mariuzzo
  • 28,358
  • 27
  • 121
  • 148
KdgDev
  • 14,299
  • 46
  • 120
  • 156
  • 2
    strtolower is not the best way to make case insensitive functions. In some locales casing is more complex than just upper and lower. – Sander Rijken May 13 '09 at 21:25
  • 9
    I see complaining and no solution... If you're gonna say it's bad, then you should give an example of how it should be as well. – KdgDev May 14 '09 at 11:06
  • 2
    @WebDevHobo: that's why I added an answer myself a day before your comment. For your code strcasecmp was indeed the right thing to do. – Sander Rijken Aug 06 '10 at 07:34
47

This question already has many answers, but in some cases you can settle for something simpler than all of them. If the string you're looking for is known (hardcoded), you can use regular expressions without any quoting etc.

Check if a string starts with 'ABC':

preg_match('/^ABC/', $myString); // "^" here means beginning of string

ends with 'ABC':

preg_match('/ABC$/', $myString); // "$" here means end of string

In my simple case, I wanted to check if a string ends with slash:

preg_match('#/$#', $myPath);   // Use "#" as delimiter instead of escaping slash

The advantage: since it's very short and simple, you don't have to define a function (such as endsWith()) as shown above.

But again -- this is not a solution for every case, just this very specific one.

noamtm
  • 12,435
  • 15
  • 71
  • 107
  • you don't need to hard code the string. the regex _can_ be dynamic. – Ryan May 15 '16 at 03:02
  • 2
    @self true, but if the string is not hardcoded, you have to escape it. Currently there are 2 answers on this question that do it. This is easy, but it complicates the code just a bit. So my point was that for very simple cases, where hardcoding is possible, you can keep it simple. – noamtm May 15 '16 at 19:13
39

The regex functions above, but with the other tweaks also suggested above:

 function startsWith($needle, $haystack) {
     return preg_match('/^' . preg_quote($needle, '/') . '/', $haystack);
 }

 function endsWith($needle, $haystack) {
     return preg_match('/' . preg_quote($needle, '/') . '$/', $haystack);
 }
Timo Tijhof
  • 10,032
  • 6
  • 34
  • 48
tridian
  • 631
  • 1
  • 5
  • 10
  • 2
    in php for string operations the ordering of parameters is $haystack, $needle. these functions are backwards and act like array functions where the ordering is actually $needle, $haystack. – Andrew Dec 02 '14 at 01:59
32

Fastest endsWith() solution:

# Checks if a string ends in a string
function endsWith($haystack, $needle) {
    return substr($haystack,-strlen($needle))===$needle;
}

Benchmark:

# This answer
function endsWith($haystack, $needle) {
    return substr($haystack,-strlen($needle))===$needle;
}

# Accepted answer
function endsWith2($haystack, $needle) {
    $length = strlen($needle);

    return $length === 0 ||
    (substr($haystack, -$length) === $needle);
}

# Second most-voted answer
function endsWith3($haystack, $needle) {
    // search forward starting from end minus needle length characters
    if ($needle === '') {
        return true;
    }
    $diff = \strlen($haystack) - \strlen($needle);
    return $diff >= 0 && strpos($haystack, $needle, $diff) !== false;
}

# Regex answer
function endsWith4($haystack, $needle) {
    return preg_match('/' . preg_quote($needle, '/') . '$/', $haystack);
}

function timedebug() {
    $test = 10000000;

    $time1 = microtime(true);
    for ($i=0; $i < $test; $i++) {
        $tmp = endsWith('TestShortcode', 'Shortcode');
    }
    $time2 = microtime(true);
    $result1 = $time2 - $time1;

    for ($i=0; $i < $test; $i++) {
        $tmp = endsWith2('TestShortcode', 'Shortcode');
    }
    $time3 = microtime(true);
    $result2 = $time3 - $time2;

    for ($i=0; $i < $test; $i++) {
        $tmp = endsWith3('TestShortcode', 'Shortcode');
    }
    $time4 = microtime(true);
    $result3 = $time4 - $time3;

    for ($i=0; $i < $test; $i++) {
        $tmp = endsWith4('TestShortcode', 'Shortcode');
    }
    $time5 = microtime(true);
    $result4 = $time5 - $time4;

    echo $test.'x endsWith: '.$result1.' seconds # This answer<br>';
    echo $test.'x endsWith2: '.$result4.' seconds # Accepted answer<br>';
    echo $test.'x endsWith3: '.$result2.' seconds # Second most voted answer<br>';
    echo $test.'x endsWith4: '.$result3.' seconds # Regex answer<br>';
    exit;
}
timedebug();

Benchmark Results:

10000000x endsWith: 1.5760900974274 seconds # This answer
10000000x endsWith2: 3.7102129459381 seconds # Accepted answer
10000000x endsWith3: 1.8731069564819 seconds # Second most voted answer
10000000x endsWith4: 2.1521229743958 seconds # Regex answer
Lucas Bustamante
  • 15,821
  • 7
  • 92
  • 86
  • 5
    +1 for taking time to compare different solutions and actually benchmark them! you should also mention what version of PHP you used, as optimisations are done as the language evolves! I've seen dramatic improvements on string comparison functions from one PHP version to another :) – Christophe Deliens Oct 03 '18 at 13:51
  • 2
    echoing @ChristopheDeliens and his request to provide the PHP version. I ran your test on 7.3.2 and got similar results FWIW. – Jeff Feb 19 '19 at 21:50
29

If speed is important for you, try this.(I believe it is the fastest method)

Works only for strings and if $haystack is only 1 character

function startsWithChar($needle, $haystack)
{
   return ($needle === $haystack[0]);
}

function endsWithChar($needle, $haystack)
{
   return ($needle === $haystack[strlen($haystack) - 1]);
}

$str='|apples}';
echo startsWithChar('|',$str); //Returns true
echo endsWithChar('}',$str); //Returns true
echo startsWithChar('=',$str); //Returns false
echo endsWithChar('#',$str); //Returns false
lepe
  • 24,677
  • 9
  • 99
  • 108
  • 1
    this is probably the most efficient answer because not using any function as extra, just usual string... –  Aug 01 '13 at 10:29
  • It should likely check if string has at least one character and has the two parameter swapped – a1an May 15 '18 at 14:51
  • 2
    Creative. Needles which contain haystacks. BTW there is some ugly waning with: `endsWithChar('','x')`, but the result is correct – Tino Jul 20 '18 at 11:28
  • 1
    I like your answer, but it's quite funny,... the needle and haystack are the other way around :) ... i.e. you would search for a Needle in a Haystack, therefore, it should be: return ($needle === $haystack[0]); , but nice answer, thanks! – Heider Sati Aug 17 '20 at 09:24
  • 1
    @HeiderSati: Great observation! That is what @Tino was talking about `Creative. Needles which contain haystacks.`... I didn't pay enough attention. Thanks! I fixed it. :) – lepe Aug 17 '20 at 10:11
23

Here are two functions that don't introduce a temporary string, which could be useful when needles are substantially big:

function startsWith($haystack, $needle)
{
    return strncmp($haystack, $needle, strlen($needle)) === 0;
}

function endsWith($haystack, $needle)
{
    return $needle === '' || substr_compare($haystack, $needle, -strlen($needle)) === 0;
}
Ja͢ck
  • 170,779
  • 38
  • 263
  • 309
  • 2
    +1 Works since PHP5.1 and IMHO best answer. But `endsWidth` should do `return $needle==='' || substr_compare(`... so it works as expected for `-strlen($needle)===0` which, without the fix, makes `endsWith('a','')` return `false` – Tino Feb 20 '14 at 21:53
  • @Tino Thanks ... I feel that's a bug in `substr_compare()` actually, so I've added a [PR](https://github.com/php/php-src/pull/599) to fix that :) – Ja͢ck Feb 21 '14 at 02:55
  • 3
    The call `endsWith('', 'foo')` triggers a Warning: “substr_compare(): The start position cannot exceed initial string length”. Maybe that's another bug in `substr_compare()`, but to avoid it, you need a pre-check like ...`|| (strlen($needle) <= strlen($haystack) && substr_compare(`...`) === 0);` – gx_ Aug 09 '15 at 19:47
  • @gx_ No need to slowdown with more code. Just use `return $needle === '' || @substr_compare(`.. to suppress this warning. – Tino Jul 20 '18 at 11:35
19

I realize this has been finished, but you may want to look at strncmp as it allows you to put the length of the string to compare against, so:

function startsWith($haystack, $needle, $case=true) {
    if ($case)
        return strncasecmp($haystack, $needle, strlen($needle)) == 0;
    else
        return strncmp($haystack, $needle, strlen($needle)) == 0;
}    
James Black
  • 41,583
  • 10
  • 86
  • 166
  • how would you do endswith with this? – mpen Aug 26 '11 at 15:20
  • @Mark - you can look at the accepted answer, but I prefer to use strncmp mainly because I think it is safer. – James Black Aug 26 '11 at 16:45
  • I mean with strncmp specifically. You can't specify an offset. That would mean your endsWith function would have to use a different method entirely. – mpen Aug 26 '11 at 18:50
  • @Mark - For endsWith I would just use strrpos (http://www.php.net/manual/en/function.strrpos.php), but, generally, anytime you go to use strcmp strncmp is probably a safer option. – James Black Aug 27 '11 at 00:15
15

Here's a multi-byte safe version of the accepted answer, it works fine for UTF-8 strings:

function startsWith($haystack, $needle)
{
    $length = mb_strlen($needle, 'UTF-8');
    return (mb_substr($haystack, 0, $length, 'UTF-8') === $needle);
}

function endsWith($haystack, $needle)
{
    $length = mb_strlen($needle, 'UTF-8');
    return $length === 0 ||
        (mb_substr($haystack, -$length, $length, 'UTF-8') === $needle);
}
Vahid Amiri
  • 10,769
  • 13
  • 68
  • 113
  • 3
    im pretty sure this is just a waste of CPU. all you need to check, for StarstWith and EndsWith, is just checking that the bytes match, and that's exactly what the accepted answer is doing. this 1 wastes time calculating the number of utf8 characters of the needle, and where the position of the n'th utf8 character of the haystack is.. i think, without being 100% certain, this is just a waste of cpu. can you come up with an actual test case where the accepted answer fails, and this doesn't? – hanshenrik May 12 '18 at 00:06
  • 2
    @hanshenrik - it could happen btw, in the very rare case when you look for a string that contains the same bytes as an UTF8 but with half of the last character missing. Like, you have unicode C5 91 (letter "ő") and you look for C5 (letter "Å") it shouldn't give you a match. On the other hand, sure, why would you search an utf haystack for a non-utf needle... But for bulletproof checks, this must be considered a possibility. – dkellner Sep 08 '18 at 10:43
  • In `startsWith` it should be `$length = mb_strlen($needle, 'UTF-8');` – Thomas Kekeisen Apr 16 '19 at 16:02
  • 2
    @ThomasKekeisen Thanks, fixed it. – Vahid Amiri Apr 17 '19 at 17:08
  • The accepted (well, currently accepted) solution is already multibyte-safe. It's actually binary-safe, which is an even stronger guarantee. – Jon Oct 30 '20 at 14:32
12

You can use strpos and strrpos

$bStartsWith = strpos($sHaystack, $sNeedle) == 0;
$bEndsWith = strrpos($sHaystack, $sNeedle) == strlen($sHaystack)-strlen($sNeedle);
Bhavik Shah
  • 2,300
  • 1
  • 17
  • 32
Lex Podgorny
  • 2,598
  • 1
  • 23
  • 40
  • 2
    Should you be using triple equals here `strpos($sHaystack, $sNeedle) == 0` like this `strpos($sHaystack, $sNeedle) === 0`? I see a bug, when `false == 0` evaluates to `true`. – Kalyan Jun 23 '17 at 14:37
10

Focusing on startswith, if you are sure strings are not empty, adding a test on the first char, before the comparison, the strlen, etc., speeds things up a bit:

function startswith5b($haystack, $needle) {
    return ($haystack{0}==$needle{0})?strncmp($haystack, $needle, strlen($needle)) === 0:FALSE;
}

It is somehow (20%-30%) faster. Adding another char test, like $haystack{1}===$needle{1} does not seem to speedup things much, may even slow down.

=== seems faster than == Conditional operator (a)?b:c seems faster than if(a) b; else c;


For those asking "why not use strpos?" calling other solutions "unnecessary work"


strpos is fast, but it is not the right tool for this job.

To understand, here is a little simulation as an example:

Search a12345678c inside bcdefga12345678xbbbbb.....bbbbba12345678c

What the computer does "inside"?

    With strccmp, etc...

    is a===b? NO
    return false



    With strpos

    is a===b? NO -- iterating in haysack
    is a===c? NO
    is a===d? NO
    ....
    is a===g? NO
    is a===g? NO
    is a===a? YES
    is 1===1? YES -- iterating in needle
    is 2===3? YES
    is 4===4? YES
    ....
    is 8===8? YES
    is c===x? NO: oh God,
    is a===1? NO -- iterating in haysack again
    is a===2? NO
    is a===3? NO
    is a===4? NO
    ....
    is a===x? NO
    is a===b? NO
    is a===b? NO
    is a===b? NO
    is a===b? NO
    is a===b? NO
    is a===b? NO
    is a===b? NO
    ...
    ... may many times...
    ...
    is a===b? NO
    is a===a? YES -- iterating in needle again
    is 1===1? YES
    is 2===3? YES
    is 4===4? YES
    is 8===8? YES
    is c===c? YES YES YES I have found the same string! yay!
    was it at position 0? NOPE
    What you mean NO? So the string I found is useless? YEs.
    Damn.
    return false

Assuming strlen does not iterate the whole string (but even in that case) this is not convenient at all.

FrancescoMM
  • 2,845
  • 1
  • 18
  • 29
  • There's only a speed up if the first characters are different. – Ja͢ck Aug 02 '13 at 07:51
  • 3
    @Jack yes, of course, the idea is that statistically that happens, so the speedup is generrally a 20%-30% over the whole test set (including cases where it is not different). You gain a lot when they are different and loose very little when they are not. In the average you gain that 30% (varies depending on set, but mostly you gain speed on large tests) – FrancescoMM Sep 18 '13 at 08:42
  • **"but it is not the right tool for this job"**... Any citation? – Fr0zenFyr Mar 24 '18 at 08:29
  • 2
    WTF. I listed all the process below whom should I cite, more than that? Would you use a function that searches till the end of a string to tell you that the fist character is not an 'a'? Do it who cares? It's not the right tool because it is a tool for searching, not for comparing, there is no need to cite Aristoteles to state the obvious! – FrancescoMM Mar 24 '18 at 08:40
9

Short and easy-to-understand one-liners without regular expressions.

startsWith() is straight forward.

function startsWith($haystack, $needle) {
   return (strpos($haystack, $needle) === 0);
}

endsWith() uses the slightly fancy and slow strrev():

function endsWith($haystack, $needle) {
   return (strpos(strrev($haystack), strrev($needle)) === 0);
}
Dan
  • 115
  • 1
  • 1
  • @FrancescoMM: **strpos is not the "right tool"**... Why? What are the "right tools" then? ***EDIT:*** I read your answer below. I thought programming is like invention using the resources you have.. So there's no right or wrong... only working or not working... performance is secondary. – Fr0zenFyr Mar 24 '18 at 08:22
  • "because it is a tool for searching, not for comparing?" Cit. Aristoteles – FrancescoMM Mar 24 '18 at 08:42
8

in short:

function startsWith($str, $needle){
   return substr($str, 0, strlen($needle)) === $needle;
}

function endsWith($str, $needle){
   $length = strlen($needle);
   return !$length || substr($str, - $length) === $needle;
}
Vincent Pazeller
  • 1,448
  • 18
  • 28
8

I hope that the below answer may be efficient and also simple:

$content = "The main string to search";
$search = "T";
//For compare the begining string with case insensitive. 
if(stripos($content, $search) === 0) echo 'Yes';
else echo 'No';

//For compare the begining string with case sensitive. 
if(strpos($content, $search) === 0) echo 'Yes';
else echo 'No';

//For compare the ending string with case insensitive. 
if(stripos(strrev($content), strrev($search)) === 0) echo 'Yes';
else echo 'No';

//For compare the ending string with case sensitive. 
if(strpos(strrev($content), strrev($search)) === 0) echo 'Yes';
else echo 'No';
Srinivasan.S
  • 3,065
  • 1
  • 24
  • 15
8

I usually end up going with a library like underscore-php these days.

require_once("vendor/autoload.php"); //use if needed
use Underscore\Types\String; 

$str = "there is a string";
echo( String::startsWith($str, 'the') ); // 1
echo( String::endsWith($str, 'ring')); // 1   

The library is full of other handy functions.

yuvilio
  • 3,795
  • 32
  • 35
8

The answer by mpen is incredibly thorough, but, unfortunately, the provided benchmark has a very important and detrimental oversight.

Because every byte in needles and haystacks is completely random, the probability that a needle-haystack pair will differ on the very first byte is 99.609375%, which means that, on average, about 99609 of the 100000 pairs will differ on the very first byte. In other words, the benchmark is heavily biased towards startswith implementations which check the first byte explicitly, as strncmp_startswith2 does.

If the test-generating loop is instead implemented as follows:

echo 'generating tests';
for($i = 0; $i < 100000; ++$i) {
    if($i % 2500 === 0) echo '.';

    $haystack_length = random_int(1, 7000);
    $haystack = random_bytes($haystack_length);

    $needle_length = random_int(1, 3000);
    $overlap_length = min(random_int(0, $needle_length), $haystack_length);
    $needle = ($needle_length > $overlap_length) ?
        substr($haystack, 0, $overlap_length) . random_bytes($needle_length - $overlap_length) :
        substr($haystack, 0, $needle_length);

    $test_cases[] = [$haystack, $needle];
}
echo " done!<br />";

the benchmark results tell a slightly different story:

strncmp_startswith: 223.0 ms
substr_startswith: 228.0 ms
substr_compare_startswith: 238.0 ms
strncmp_startswith2: 253.0 ms
strpos_startswith: 349.0 ms
preg_match_startswith: 20,828.7 ms

Of course, this benchmark may still not be perfectly unbiased, but it tests the efficiency of the algorithms when given partially matching needles as well.

Veeno
  • 113
  • 2
  • 7
7

Do it faster:

function startsWith($haystack,$needle) {
    if($needle==="") return true;
    if($haystack[0]<>$needle[0]) return false; // ------------------------- speed boost!
    return (0===substr_compare($haystack,$needle,0,strlen($needle)));
}

That extra line, comparing the first character of the strings, can make the false case return immediately, therefore making many of your comparisons a lot faster (7x faster when I measured). In the true case you pay virtually no price in performance for that single line so I think it's worth including. (Also, in practice, when you test many strings for a specific starting chunk, most comparisons will fail since in a typical case you're looking for something.)

NOTE: the bug in @Tino's comment below has aleady been fixed

As for strings vs integers

If you want to force string comparison (that is, you expect startsWith("1234",12) to be true), you'll need some typecasting:

function startsWith($haystack,$needle) {
    if($needle==="") return true;
    $haystack = (string)$haystack;
    $needle   = (string)$needle;
    if($haystack[0]<>$needle[0]) return false; // ------------------------- speed boost!
    return (0===substr_compare($haystack,$needle,0,strlen($needle)));
}

I don't think it's necessary but it's an interesting edge case, leading to questions like "does boolean true begin with a t?" - so you decide, but make sure you decide for good.

dkellner
  • 8,726
  • 2
  • 49
  • 47
5

This may work

function startsWith($haystack, $needle) {
     return substr($haystack, 0, strlen($needle)) == $needle;
}

Source: https://stackoverflow.com/a/4419658

Community
  • 1
  • 1
user507410
  • 512
  • 4
  • 12
4

Why not the following?

//How to check if a string begins with another string
$haystack = "valuehaystack";
$needle = "value";
if (strpos($haystack, $needle) === 0){
    echo "Found " . $needle . " at the beginning of " . $haystack . "!";
}

Output:

Found value at the beginning of valuehaystack!

Keep in mind, strpos will return false if the needle was not found in the haystack, and will return 0 if, and only if, needle was found at index 0 (AKA the beginning).

And here's endsWith:

$haystack = "valuehaystack";
$needle = "haystack";

//If index of the needle plus the length of the needle is the same length as the entire haystack.
if (strpos($haystack, $needle) + strlen($needle) === strlen($haystack)){
    echo "Found " . $needle . " at the end of " . $haystack . "!";
}

In this scenario there is no need for a function startsWith() as

(strpos($stringToSearch, $doesItStartWithThis) === 0)

will return true or false accurately.

It seems odd it's this simple with all the wild functions running rampant here.

Peter Mortensen
  • 30,738
  • 21
  • 105
  • 131
Bill Effin Murray
  • 426
  • 1
  • 5
  • 13
  • 3
    Seems odd that if you are searching for "xy" inside string "abcdefghijklmxyz" instead of just comparing "x" to "a" and returning FALSE, you look every character from "a" to "m" then end up finding "xy" inside the string, and at last you return FALSE because the position of it is not zero! This is what you are doing, and it is odd and wilder than any other rampant function here. – FrancescoMM Jan 23 '14 at 15:05
  • The simplicity is in the typing, not the logic. – Bill Effin Murray Jan 23 '14 at 17:34
  • It's not so much the logic, it's the possible optimization that Francsco was pointing out. Using `strpos()` will be slow except when it does match. `strncmp()` would be much better in this case. – Alexis Wilke Jul 02 '14 at 00:33
  • When you're doing such low level functions, you typically want to go for the most speed-optimized solution, no matter how complex, as this will be called millions of times. Every microsecond you gain or lose here will make a very real difference. So better tweak the hell out of it (and then forget about the complexity, now that you have the function), instead of going for the looks and lose horrifying amount of time later when you don't even know what's gone wrong. Imagine checking a 2GB string that doesn't match. – dkellner Sep 03 '18 at 13:09
4

I would do it like this

     function startWith($haystack,$needle){
              if(substr($haystack,0, strlen($needle))===$needle)
              return true;
        }

  function endWith($haystack,$needle){
              if(substr($haystack, -strlen($needle))===$needle)
              return true;
        }
Jelle Keizer
  • 723
  • 5
  • 9
  • Forgetting to return false if it doesn't match. Errgo incorrect as is the return value of a function should not be 'assumed', but I know what you're going after at least compared to other answers. – Spoo Oct 07 '16 at 19:58
4

The substr function can return false in many special cases, so here is my version, which deals with these issues:

function startsWith( $haystack, $needle ){
  return $needle === ''.substr( $haystack, 0, strlen( $needle )); // substr's false => empty string
}

function endsWith( $haystack, $needle ){
  $len = strlen( $needle );
  return $needle === ''.substr( $haystack, -$len, $len ); // ! len=0
}

Tests (true means good):

var_dump( startsWith('',''));
var_dump( startsWith('1',''));
var_dump(!startsWith('','1'));
var_dump( startsWith('1','1'));
var_dump( startsWith('1234','12'));
var_dump(!startsWith('1234','34'));
var_dump(!startsWith('12','1234'));
var_dump(!startsWith('34','1234'));
var_dump('---');
var_dump( endsWith('',''));
var_dump( endsWith('1',''));
var_dump(!endsWith('','1'));
var_dump( endsWith('1','1'));
var_dump(!endsWith('1234','12'));
var_dump( endsWith('1234','34'));
var_dump(!endsWith('12','1234'));
var_dump(!endsWith('34','1234'));

Also, the substr_compare function also worth looking. http://www.php.net/manual/en/function.substr-compare.php

biziclop
  • 14,466
  • 3
  • 49
  • 65
3

Based on James Black's answer, here is its endsWith version:

function startsWith($haystack, $needle, $case=true) {
    if ($case)
        return strncmp($haystack, $needle, strlen($needle)) == 0;
    else
        return strncasecmp($haystack, $needle, strlen($needle)) == 0;
}

function endsWith($haystack, $needle, $case=true) {
     return startsWith(strrev($haystack),strrev($needle),$case);

}

Note: I have swapped the if-else part for James Black's startsWith function, because strncasecmp is actually the case-insensitive version of strncmp.

bobo
  • 8,439
  • 11
  • 57
  • 81
3

Many of the previous answers will work just as well. However, this is possibly as short as you can make it and have it do what you desire. You just state that you'd like it to 'return true'. So I've included solutions that returns boolean true/false and the textual true/false.

// boolean true/false
function startsWith($haystack, $needle)
{
    return strpos($haystack, $needle) === 0 ? 1 : 0;
}

function endsWith($haystack, $needle)
{
    return stripos($haystack, $needle) === 0 ? 1 : 0;
}


// textual true/false
function startsWith($haystack, $needle)
{
    return strpos($haystack, $needle) === 0 ? 'true' : 'false';
}

function endsWith($haystack, $needle)
{
    return stripos($haystack, $needle) === 0 ? 'true' : 'false';
}
womplefrog
  • 769
  • 1
  • 6
  • 18
  • True. However, Peter was asking for a function that would work with character strings. Nonetheless, I've updated my answer to appease you. – womplefrog Oct 23 '14 at 21:02
  • After the edit your solution now is completely obsolete. It returns `'true'` and `'false'` as strings, which are both `true` in a boolean sense. It's a good pattern for something like http://underhanded.xcott.com/ though ;) – Tino Oct 26 '14 at 11:08
  • Well, Peter just stated he wanted it to return 'true'. So I figured I'd return what he asked for. I've added both versions, just in case that isn't what he wanted. – womplefrog Oct 27 '14 at 13:04
3

No-copy and no-intern-loop:

function startsWith(string $string, string $start): bool
{
    return strrpos($string, $start, - strlen($string)) !== false;
}

function endsWith(string $string, string $end): bool
{
    return ($offset = strlen($string) - strlen($end)) >= 0 
    && strpos($string, $end, $offset) !== false;
}
hanshenrik
  • 19,904
  • 4
  • 43
  • 89
mazatwork
  • 1,275
  • 1
  • 13
  • 20
2

Here’s an efficient solution for PHP 4. You could get faster results if on PHP 5 by using substr_compare instead of strcasecmp(substr(...)).

function stringBeginsWith($haystack, $beginning, $caseInsensitivity = false)
{
    if ($caseInsensitivity)
        return strncasecmp($haystack, $beginning, strlen($beginning)) === 0;
    else
        return strncmp($haystack, $beginning, strlen($beginning)) === 0;
}

function stringEndsWith($haystack, $ending, $caseInsensitivity = false)
{
    if ($caseInsensitivity)
        return strcasecmp(substr($haystack, strlen($haystack) - strlen($ending)), $haystack) === 0;
    else
        return strpos($haystack, $ending, strlen($haystack) - strlen($ending)) !== false;
}
Patrick Smith
  • 518
  • 6
  • 9
1

You also can use regular expressions:

function endsWith($haystack, $needle, $case=true) {
  return preg_match("/.*{$needle}$/" . (($case) ? "" : "i"), $haystack);
}
Freeman
  • 5,810
  • 3
  • 47
  • 48
0

You can use the fnmatch function for this.

// Starts with.
fnmatch('prefix*', $haystack);
// Ends with.
fnmatch('*suffix', $haystack);
ya.teck
  • 2,060
  • 28
  • 34
0
function startsWith($haystack, $needle)
{
    $length = mb_strlen($needle);
    return mb_substr($haystack, 0, $length) === $needle;
}

function endsWith($haystack, $needle)
{
    $length = mb_strlen($needle);
    if(!$length)
    {
     return true;
    }
    
    return mb_substr($haystack, -$length) === $needle;
}

String length and character positioning are two examples where multi-byte vs single-byte can play a difference. Standard strings in PHP are char arrays just as in C, which are always single byte. so the third character in a string (str[2]) by normal strlen would actually be the start of the second character if UTF-16. If looking for { at the beginning of a string to see if a string could be JSON this wouldn't be a huge deal since { is a valid single byte character such as in ASCII encoding, but if checking the end of a string for the emogi vs to judge reactions it's a different story since they are multipbyte characters.

JSON
  • 1,819
  • 20
  • 27
-1
$ends_with = strrchr($text, '.'); // Ends with dot
$start_with = (0 === strpos($text, '.')); // Starts with dot
ymakux
  • 3,415
  • 1
  • 34
  • 43
  • 5
    According to the docs, strrchr() will return the string from the last occurrence of '.' until the end, meaning your $ends_with would be true if '.' is __anywhere__ in $text. Therefore ends_with should be: **('.' === strrchr($text, '.'))** – Sam Bull Jul 05 '16 at 13:20
  • 6
    As your answer is wrong and does not do what it claims, and you refuse to accept my edit to fix your answer, I'm downvoting this answer as it is "dangerously incorrect". – Sam Bull Mar 25 '17 at 17:33
-1

Laravel 9.0

If you are using Laravel then you can do the following (if you are not using Laravel, you really should be).

Str::of('a long string')->startsWith('a');
Str::of('a long string')->endsWith('string');

//true
//true
Toby Allen
  • 10,997
  • 11
  • 73
  • 124