Skip to content

Commit

Permalink
slug(): partial optimizations + fix PHP 8.1
Browse files Browse the repository at this point in the history
  • Loading branch information
allejo committed Jan 9, 2022
1 parent d42b3a8 commit 33300cf
Show file tree
Hide file tree
Showing 2 changed files with 114 additions and 11 deletions.
107 changes: 103 additions & 4 deletions src/__/functions/slug.php
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,88 @@

namespace functions;

class _StringOps
{
private static $hasMultibyteSupport;
private $needsMultibyteSupport;

public function __construct($string)
{
$this->needsMultibyteSupport = preg_match('/[^\x00-\x7F]/', $string) === 1;
$this->assertMultibyte();
}

public function needsMultibyteSupport()
{
return $this->needsMultibyteSupport;
}

/**
* @return string
*/
public function smart_substr()
{
$args = func_get_args();
$fxn = $this->needsMultibyteSupport ? 'mb_substr' : 'substr';

if (!$this->needsMultibyteSupport) {
array_pop($args);
}

return call_user_func_array($fxn, $args);
}

/**
* @return int
*/
public function smart_strlen()
{
$args = func_get_args();

if ($this->needsMultibyteSupport) {
call_user_func_array('mb_strlen', $args);
}

return strlen($args[0]);
}

/**
* @return string
*/
public function smart_strtolower()
{
$args = func_get_args();

if ($this->needsMultibyteSupport) {
return call_user_func_array('mb_strtolower', $args);
}

return strtolower($args[0]);
}

private function assertMultibyte()
{
if (!$this->needsMultibyteSupport) {
return;
}

if ($this->hasMultibyteSupport()) {
return;
}

throw new \RuntimeException('The `mbstring` extension is not available and is required.');
}

private function hasMultibyteSupport()
{
if (self::$hasMultibyteSupport === null) {
self::$hasMultibyteSupport = extension_loaded('mbstring');
}

return self::$hasMultibyteSupport;
}
}

/**
* Create a web friendly URL slug from a string.
*
Expand Down Expand Up @@ -33,8 +115,25 @@
*/
function slug($str, array $options = [])
{
// Make sure string is in UTF-8 and strip invalid UTF-8 characters
$str = \mb_convert_encoding((string)$str, 'UTF-8', \mb_list_encodings());
if (!is_string($str)) {
throw new \InvalidArgumentException('The $str argument expends a string.');
}

$ops = new _StringOps($str);

// Let's not waste resources if we don't need to do multibyte string processing
if ($ops->needsMultibyteSupport()) {
// Make sure string is in UTF-8 and strip invalid UTF-8 characters
/** @var false|string $encodedString */
$encodedString = \mb_convert_encoding($str, 'UTF-8', \mb_list_encodings());

// PHP 8.1.0 has a bug where $encodedString can be an empty string, so
// we only want to override `$str` if we have a good value.
// https://github.com/php/php-src/issues/7898
if ($encodedString !== '' && $encodedString !== false) {
$str = $encodedString;
}
}

$defaults = [
'delimiter' => '-',
Expand Down Expand Up @@ -66,11 +165,11 @@ function slug($str, array $options = [])

// Truncate slug to max. characters
if ($options['limit']) {
$str = \mb_substr($str, 0, ($options['limit'] ?: \mb_strlen($str, 'UTF-8')), 'UTF-8');
$str = $ops->smart_substr($str, 0, ($options['limit'] ?: $ops->smart_strlen($str, 'UTF-8')), 'UTF-8');
}

// Remove delimiter from ends
$str = \trim($str, $options['delimiter']);

return $options['lowercase'] ? \mb_strtolower($str, 'UTF-8') : $str;
return $options['lowercase'] ? $ops->smart_strtolower($str, 'UTF-8') : $str;
}
18 changes: 11 additions & 7 deletions tests/__/Functions/SlugTest.php
Original file line number Diff line number Diff line change
Expand Up @@ -9,15 +9,19 @@

class SlugTest extends TestCase
{
public function testSlug()
public function testSlugWithUtf8()
{
// Arrange
$a = 'Jakieś zdanie z dużą ilością obcych znaków!';
$input = 'Jakieś zdanie z dużą ilością obcych znaków!';
$actual = __::slug($input);

// Act
$x = __::slug($a);
$this->assertEquals('jakies-zdanie-z-duza-iloscia-obcych-znakow', $actual);
}

public function testSlugWithAscii()
{
$input = 'Hello World!';
$actual = __::slug($input);

// Assert
$this->assertEquals('jakies-zdanie-z-duza-iloscia-obcych-znakow', $x);
$this->assertEquals('hello-world', $actual);
}
}

0 comments on commit 33300cf

Please # to comment.