public function substr($var, int $start, /*?int*/ $length = null) : string
    {
        if ($start < 0) {
            throw new \InvalidArgumentException('Arg start is not non-negative integer.');
        }
        if ($length !== null && (!is_int($length) || $length < 0)) {
            throw new \InvalidArgumentException('Arg length is not non-negative integer or null.');
        }
        $v = '' . $var;
        if (!$length || $v === '') {
            return '';
        }
        if (static::$nativeSupport['mbstring']) {
            return !$length ? mb_substr($v, $start) : mb_substr($v, $start, $length);
        }

        // The actual algo (further down) only works when start is zero.
        if ($start > 0) {
            // Trim off chars before start.
            $v = substr($v,
                strlen(
                    // Offsets multibyte string length.
                    $this->substr($v, 0, $start)
                )
            );
        }
        // And the algo needs a length.
        if (!$length) {
            $length = $this->strlen($v);
        }

        $n = 0;
        $le = strlen($v);
        $leading = false;
        for ($i = 0; $i < $le; $i++) {
            // ASCII.
            if (($ord = ord($v{$i})) < 128) {
                if ((++$n) > $length) {
                    return substr($v, 0, $i);
                }
                $leading = false;
            }
            // Continuation char.
            elseif ($ord < 192) { // continuation char
                $leading = false;
            }
            // Leading char.
            else {
                // A sequence of leadings only counts as a single.
                if (!$leading) {
                    if ((++$n) > $length) {
                        return substr($v, 0, $i);
                    }
                }
                $leading = true;
            }
        }
        return $v;
    }
public function substr($var, int $start, /*?int*/ $length = null) : string
    {
        if ($start < 0) {
            throw new \InvalidArgumentException('Arg start is not non-negative integer.');
        }
        if ($length !== null && (!is_int($length) || $length < 0)) {
            throw new \InvalidArgumentException('Arg length is not non-negative integer or null.');
        }
        $v = '' . $var;
        if (!$length || $v === '') {
            return '';
        }
        if (static::$nativeSupport['mbstring']) {
            return !$length ? mb_substr($v, $start) : mb_substr($v, $start, $length);
        }

        // The actual algo (further down) only works when start is zero.
        if ($start > 0) {
            // Trim off chars before start.
            $v = substr($v,
                strlen(
                    // Offsets multibyte string length.
                    $this->substr($v, 0, $start)
                )
            );
        }
        // And the algo needs a length.
        if (!$length) {
            $length = $this->strlen($v);
        }

        $n = 0;
        $le = strlen($v);
        $leading = false;
        for ($i = 0; $i < $le; $i++) {
            // ASCII.
            if (($ord = ord($v{$i})) < 128) {
                if ((++$n) > $length) {
                    return substr($v, 0, $i);
                }
                $leading = false;
            }
            // Continuation char.
            elseif ($ord < 192) { // continuation char
                $leading = false;
            }
            // Leading char.
            else {
                // A sequence of leadings only counts as a single.
                if (!$leading) {
                    if ((++$n) > $length) {
                        return substr($v, 0, $i);
                    }
                }
                $leading = true;
            }
        }
        return $v;
    }
public function substr ( $ var , int $ start , /*?int*/ $ length = null ) : string { if ( $ start < 0 ) { throw new \ InvalidArgumentException ( 'Arg start is not non-negative integer.' ) ; } if ( $ length !== null && ( ! is_int ( $ length ) || $ length < 0 ) ) { throw new \ InvalidArgumentException ( 'Arg length is not non-negative integer or null.' ) ; } $ v = '' . $ var ; if ( ! $ length || $ v === '' ) { return '' ; } if ( static :: $ nativeSupport [ 'mbstring' ] ) { return ! $ length ? mb_substr ( $ v , $ start ) : mb_substr ( $ v , $ start , $ length ) ; } // The actual algo (further down) only works when start is zero. if ( $ start > 0 ) { // Trim off chars before start. $ v = substr ( $ v , strlen ( // Offsets multibyte string length. $ this -> substr ( $ v , 0 , $ start ) ) ) ; } // And the algo needs a length. if ( ! $ length ) { $ length = $ this -> strlen ( $ v ) ; } $ n = 0 ; $ le = strlen ( $ v ) ; $ leading = false ; for ( $ i = 0 ; $ i < $ le ; $ i ++ ) { // ASCII. if ( ( $ ord = ord ( $ v { $ i } ) ) < 128 ) { if ( ( ++ $ n ) > $ length ) { return substr ( $ v , 0 , $ i ) ; } $ leading = false ; } // Continuation char. elseif ( $ ord < 192 ) { // continuation char $ leading = false ; } // Leading char. else { // A sequence of leadings only counts as a single. if ( ! $ leading ) { if ( ( ++ $ n ) > $ length ) { return substr ( $ v , 0 , $ i ) ; } } $ leading = true ; } } return $ v ; }
Multibyte-safe sub string.

Does not check if arg $v is valid UTF-8.

@param mixed $var
Gets stringified.
@param int $start
@param int|null $length
Default: null; until end of arg str.

@return string

@throws \InvalidArgumentException
Bad arg start or length.
Multibyte - safe sub string .