-
-
Save juliyvchirkov/8f325f9ac534fe736b504b93a1a8b2ce to your computer and use it in GitHub Desktop.
<?php declare(strict_types = 1); | |
/** | |
* Provides polyfills of string functions str_starts_with, str_contains and str_ends_with, | |
* core functions since PHP 8, along with their multibyte implementations mb_str_starts_with, | |
* mb_str_contains and mb_str_ends_with | |
* | |
* Covers PHP 4 - PHP 7, safe to utilize with PHP 8 | |
*/ | |
/** | |
* @see https://www.php.net/manual/en/function.str-starts-with | |
*/ | |
if (!function_exists('str_starts_with')) { | |
function str_starts_with(string $haystack, string $needle): bool | |
{ | |
return strlen($needle) === 0 || strpos($haystack, $needle) === 0; | |
} | |
} | |
/** | |
* @see https://www.php.net/manual/en/function.str-contains | |
*/ | |
if (!function_exists('str_contains')) { | |
function str_contains(string $haystack, string $needle): bool | |
{ | |
return strlen($needle) === 0 || strpos($haystack, $needle) !== false; | |
} | |
} | |
/** | |
* @see https://www.php.net/manual/en/function.str-ends-with | |
*/ | |
if (!function_exists('str_ends_with')) { | |
function str_ends_with(string $haystack, string $needle): bool | |
{ | |
return strlen($needle) === 0 || substr($haystack, -strlen($needle)) === $needle; | |
} | |
} | |
if (!function_exists('mb_str_starts_with')) { | |
function mb_str_starts_with(string $haystack, string $needle): bool | |
{ | |
return mb_strlen($needle) === 0 || mb_strpos($haystack, $needle) === 0; | |
} | |
} | |
if (!function_exists('mb_str_contains')) { | |
function mb_str_contains(string $haystack, string $needle): bool | |
{ | |
return mb_strlen($needle) === 0 || mb_strpos($haystack, $needle) !== false; | |
} | |
} | |
if (!function_exists('mb_str_ends_with')) { | |
function mb_str_ends_with(string $haystack, string $needle): bool | |
{ | |
return mb_strlen($needle) === 0 || mb_substr($haystack, -mb_strlen($needle)) === $needle; | |
} | |
} |
Covers PHP 4 - PHP 7
It is actually not true, the code uses string
type hints that are part of PHP 7.0 array
was already part of 5.6.
You should consider using this (made it after writing this) or symfony/polyfill-php80 instead of this gist. I am saying this while I use my own polyfills for the 3 string functions as well at this time, but I think I will change to that even though I do not need most of it. Here is why.
- It's done by people who absolutely know what they are doing.
- It exits early if PHP 8.0 is detected.
- It's quite complex how these functions are called, it does not use strict types. First it uses
?string
nullable types and then$haystack ?? ''
to call the actual polyfill that has string types. Meaning that these functions can be called withnull
as arguments that get tuned into empty strings. I assume this is to correctly actually polyfill the functions how they behave natively. That fact that it does not usedeclare(strict_types = 1);
actually means the functions can be called with all kinds of types that will be transformed to string ornull
.
Just look at the code this for example is more lengthy because it has to be the absolute most efficient and best way to polyfill this. In fact, I have something that is in between the above and what is in the symphony polyfill.
public static function str_ends_with(string $haystack, string $needle): bool
{
if ('' === $needle || $needle === $haystack) {
return true;
}
if ('' === $haystack) {
return false;
}
$needleLength = \strlen($needle);
return $needleLength <= \strlen($haystack) && 0 === substr_compare($haystack, $needle, -$needleLength);
}
The mb_
versions are actually not part of PHP so they are not polyfills.
@nextgenthemes Thanks for your review!
Covers PHP 4 - PHP 7
It is actually not true, the code uses
string
type hints that are part of PHP 7.0array
was already part of 5.6.
C'mon, it's just a gist. Sure thing PHP 4
provides no support for type hints. Remove the type hints from the code and you'll achieve the goal.
That simple.
https://onlinephp.io/c/9bbc68fa-4065-43ac-800b-072275ce4b74
You should consider using this (made it after writing this) or symfony/polyfill-php80 instead of this gist. I am saying this while I use my own polyfills for the 3 string functions as well at this time, but I think I will change to that even though I do not need most of it. Here is why.
- It's done by people who absolutely know what they are doing.
- It exits early if PHP 8.0 is detected.
- It's quite complex how these functions are called, it does not use strict types. First it uses
?string
nullable types and then$haystack ?? ''
to call the actual polyfill that has string types. Meaning that these functions can be called withnull
as arguments that get tuned into empty strings. I assume this is to correctly actually polyfill the functions how they behave natively. That fact that it does not usedeclare(strict_types = 1);
actually means the functions can be called with all kinds of types that will be transformed to string ornull
.
Thank you, I'm utilizing Symfony Polyfill
myself if available. The code of these guys is implemented in a strict clear Symphony way
and I like and respect it.
Mine simplified alternatives shared above have been developed the day after the release of PHP 8
as temporary solution till Symfony
delivers their polyfills for PHP 8
, and as soon as symfony/polyfill-php80
bundle arrived, I switched to it.
But, long story short, the fact that Symfony
no doubt implements top quality solutions doesn't mean my alternatives don't serve their job.
The
mb_
versions are actually not part of PHP so they are not polyfills.
C'mon, the preamble at the gist header comment notes clear and crisp «polyfills of string functions str_starts_with, str_contains and str_ends_with, core functions since PHP 8, along with their multibyte implementations». Period.
@GottemHams sad but true, your point is totally correct, I've tested and confirmed this quite unexpected glitch locally and accordingly updated the gist
Thanks a lot for your report on this inconvenience!
But I should note, being still quite surprised with that irrational behaviour of PHP, by logic I consider this misdirection to be a bug of the engine
Let me explain my point
Surely, I understand subtleties and nuances of
false
andfalsy
cases clear and crisp (and the tricks like these are among the valid reasons I wherever possible preferstrict types
, identical comparison operators===
!==
vs equal==
!=
et cetera), and I no wai would refuse well-knownfalsyness
ofzero
But I wanna focus your attention on the key thing that
zero
is treated to befalsy
when it's about value ofInteger
type. Not justnumerical
, butNumber
zero
And I flatly refuse to accept the conception that
String
of characterZero
can befalsy
somehow, 'cause this point is totally irrational. Technically'0'
is symbol with charcode 0x30, which is notfalsy
no wai, as well as any other character in aString
is notfalsy
. The onlyString
declared to befalsy
is an empty one (i.e.String
ofzero
length)Let's take a closer look at the topic with, for example, Javascript
There are no complaints with the above, Javascript behaves like a charm and honors the logic just fine. But now let's review exactly the same code in PHP
I guess this bug in PHP engine takes roots and most likely persists from the time when 3rd generation of PHP engine has been released at early 2000, 'cause this bug seems to be a side effect and consequence of one of the initial key features of the engine which greatly distinguished it between another languages from a scratch
I mean the concept of no strict typing and declarations, as well as total free style of typecasting for a coder on one hand and the silent trasparent automated casting the engine has always provided and implemented in a hardcore way by guess on another one
Cause the only way I see to finish with a
String
of charZero
beingfalse
is the false positive failure with automated typecasting of(bool) '0'
, when engine before casting toBoolean
, casts characterZero
0x30 ('0'
) toNumber
Zero
(0
) at first, thus turning it into afalsy
value and gettingBoolean false
as a final resultBut anyway I still cannot figure out why the hell this typecasting from a
String
into aNumber
is implemented with no preconditions at sign. It could be clear on a flow like'0' + 0
or'0' * 1
, but I see no trigger at all to castString
into aNumber
for this case