向后不兼容的更改

PHP 内核

字符串到数字比较

数字与非数字字符串之间的非严格比较现在通过将数字转换为字符串并比较字符串来工作。数字与数字字符串之间的比较继续按以前的方式工作。值得注意的是,这意味着 0 == "not-a-number" 现在被认为是错误的。

比较 之前 之后
0 == "0" true true
0 == "0.0" true true
0 == "foo" true false
0 == "" true false
42 == " 42" true true
42 == "42foo" true false

其他不兼容的更改

  • match 现在是保留关键字。

  • mixed 现在是保留字,因此不能用于命名类、接口或特性,并且也不允许在命名空间中使用。

  • 断言失败现在默认抛出异常。如果需要旧的行为,可以在 INI 设置中设置 assert.exception=0

  • 与类同名的方法不再被解释为构造函数。应该使用 __construct() 方法。

  • 以静态方式调用非静态方法的能力已被移除。因此,当检查具有类名的非静态方法时,is_callable() 将失败(必须使用对象实例进行检查)。

  • (real)(unset) 转换已被移除。

  • track_errors ini 指令已被移除。这意味着 php_errormsg 不再可用。可以使用 error_get_last() 函数代替。

  • 定义不区分大小写的常量的能力已被移除。define() 的第三个参数不再可以是 true

  • 使用 __autoload() 函数指定自动加载器的能力已被移除。应该使用 spl_autoload_register() 代替。

  • errcontext 参数将不再传递给使用 set_error_handler() 设置的自定义错误处理程序。

  • create_function() 已被移除。可以使用匿名函数代替。

  • each() 已被移除。可以使用 foreachArrayIterator 代替。

  • 使用 Closure::fromCallable()ReflectionMethod::getClosure() 从方法创建的闭包中解除绑定 this 的能力已被移除。

  • 从包含 this 使用的适当闭包中解除绑定 this 的能力也已被移除。

  • 使用 array_key_exists() 与对象的能力已被移除。可以使用 isset()property_exists() 代替。

  • 关于 key 参数类型的 array_key_exists() 的行为已与 isset() 和普通数组访问保持一致。所有键类型现在都使用通常的强制转换,数组/对象键会抛出 TypeError

  • 任何以数字 n 作为其第一个数字键的数组将使用 n+1 作为其下一个隐式键,即使 n 为负数。

  • 默认的 error_reporting 级别现在是 E_ALL。之前它排除了 E_NOTICEE_DEPRECATED

  • display_startup_errors 现在默认启用。

  • 在没有父类的类中使用 parent 现在将导致致命的编译时错误。

  • @ 运算符将不再静默致命错误(E_ERRORE_CORE_ERRORE_COMPILE_ERRORE_USER_ERRORE_RECOVERABLE_ERRORE_PARSE)。期望 error_reporting 为 0 时使用 @ 的错误处理程序应该调整为使用掩码检查代替

    <?php
    // 替换
    function my_error_handler($err_no, $err_msg, $filename, $linenum) {
    if (
    error_reporting() == 0) {
    return
    false;
    }
    // ...
    }

    // 使用
    function my_error_handler($err_no, $err_msg, $filename, $linenum) {
    if (!(
    error_reporting() & $err_no)) {
    return
    false;
    }
    // ...
    }
    ?>

    此外,应该注意的是,错误消息不应该在生产环境中显示,这会导致信息泄露。请确保在使用错误日志记录的情况下使用 display_errors=Off

  • #[ 不再被解释为注释的开始,因为此语法现在用于属性。

  • 由于不兼容的方法签名(LSP 违规)导致的继承错误现在将始终生成致命错误。之前在某些情况下会生成警告。

  • 连接运算符的优先级相对于位移和加法以及减法已经改变。

    <?php
    echo "Sum: " . $a + $b;
    // 之前被解释为:
    echo ("Sum: " . $a) + $b;
    // 现在被解释为:
    echo "Sum:" . ($a + $b);
    ?>

  • 具有默认值在运行时解析为 null 的参数将不再隐式将参数类型标记为可为空。必须使用显式可为空类型或显式 null 默认值代替。

    <?php
    // 替换
    function test(int $arg = CONST_RESOLVING_TO_NULL) {}
    // 使用
    function test(?int $arg = CONST_RESOLVING_TO_NULL) {}
    // 或者
    function test(int $arg = null) {}
    ?>

  • 一些警告已被转换为 Error 异常

    • 尝试写入非对象的属性。之前,这会为 null、false 和空字符串隐式创建一个 stdClass 对象。
    • 尝试将元素追加到已使用 PHP_INT_MAX 键的数组。
    • 尝试使用无效类型(数组或对象)作为数组键或字符串偏移量。
    • 尝试写入标量值的数组索引。
    • 尝试解包非数组/可遍历对象。
    • 尝试访问未定义的非限定常量。之前,非限定常量访问会生成警告,并被解释为字符串。
    • 向非可变参数内置函数传递错误数量的参数将抛出 ArgumentCountError
    • 将无效的可计数类型传递给 count() 将抛出 TypeError

    许多通知已转换为警告

    • 尝试读取未定义的变量。
    • 尝试读取未定义的属性。
    • 尝试读取未定义的数组键。
    • 尝试读取非对象的属性。
    • 尝试访问非数组的数组索引。
    • 尝试将数组转换为字符串。
    • 尝试使用资源作为数组键。
    • 尝试使用 null、布尔值或浮点数作为字符串偏移量。
    • 尝试读取超出范围的字符串偏移量。
    • 尝试将空字符串分配给字符串偏移量。

  • 尝试将多个字节分配给字符串偏移量现在将发出警告。

  • 源文件中的意外字符(例如字符串之外的 NUL 字节)现在将导致 ParseError 异常,而不是编译警告。

  • 未捕获的异常现在将经过“干净关闭”,这意味着析构函数将在未捕获的异常后被调用。

  • 编译时致命错误“Only variables can be passed by reference”已延迟到运行时,并转换为“Argument cannot be passed by reference” Error 异常。

  • 一些“Only variables should be passed by reference”通知已转换为“Argument cannot be passed by reference”异常。

  • 匿名类的生成名称已更改。它现在将包含第一个父类或接口的名称

    <?php
    new class extends ParentClass {};
    // -> ParentClass@anonymous
    new class implements FirstInterface, SecondInterface {};
    // -> FirstInterface@anonymous
    new class {};
    // -> class@anonymous
    ?>

    上面显示的名称后面仍然是 NUL 字节和唯一的后缀。

  • 特征别名适配中的非绝对特征方法引用现在需要是明确的

    <?php
    class X {
    use
    T1, T2 {
    func as otherFunc;
    }
    function
    func() {}
    }
    ?>

    如果 T1::func()T2::func() 都存在,此代码以前被静默接受,func 被假定为引用 T1::func。现在它将改为生成致命错误,并且需要显式编写 T1::funcT2::func

  • 特征中定义的抽象方法的签名现在将针对实现类方法进行检查

    <?php
    trait MyTrait {
    abstract private function
    neededByTrait(): string;
    }

    class
    MyClass {
    use
    MyTrait;

    // Error, because of return type mismatch.
    private function neededByTrait(): int { return 42; }
    }
    ?>

  • 禁用函数现在被视为与不存在的函数完全相同。调用禁用函数将将其报告为未知,并且现在可以重新定义禁用函数。

  • data:// 流包装器不再可写,这与记录的行为相匹配。

  • 算术和位运算符 +-*/**%<<>>&|^~++-- 现在将始终在其中一个操作数为 arrayresource 或非重载 object 时抛出 TypeError。对此的唯一例外是数组 + 数组合并操作,该操作仍然受支持。

  • 浮点数到字符串的强制转换现在将始终以与区域设置无关的方式执行。

    <?php
    setlocale
    (LC_ALL, "de_DE");
    $f = 3.14;
    echo
    $f, "\n";
    // Previously: 3,14
    // Now: 3.14
    ?>

    请参阅 printf()number_format()NumberFormatter() 以了解自定义数字格式的方法。

  • 对用于偏移量访问的已弃用花括号的支持已被删除。

    <?php
    // Instead of:
    $array{0};
    $array{"key"};
    // Write:
    $array[0];
    $array["key"];
    ?>

  • 对私有方法应用 final 修饰符现在将产生警告,除非该方法是构造函数。

  • 如果对象构造函数 exit(),对象析构函数将不再被调用。这与构造函数抛出异常时的行为一致。

  • 命名空间名称不再包含空格:虽然 Foo\Bar 将被识别为命名空间名称,但 Foo \ Bar 不会。相反,保留关键字现在允许作为命名空间段,这也会改变代码的解释:new\x 现在与 constant('new\x') 相同,而不是 new \x()

  • 嵌套三元运算符现在需要显式括号。

  • debug_backtrace()Exception::getTrace() 将不再提供对参数的引用。通过回溯更改函数参数将不再可能。

  • 数字字符串处理已被更改为更直观且更不容易出错。为了与领先空格的处理方式保持一致,数字字符串中现在允许尾随空格。这主要影响

    • is_numeric() 函数
    • 字符串到字符串的比较
    • 类型声明
    • 增量和减量运算

    “领先数字字符串”的概念已被大部分放弃;此功能仍然存在是为了简化迁移。发出 E_NOTICE “A non well-formed numeric value encountered” 的字符串现在将发出 E_WARNING “A non-numeric value encountered”,并且所有发出 E_WARNING “A non-numeric value encountered” 的字符串现在将抛出 TypeError。这主要影响

    • 算术运算
    • 位运算

    E_WARNINGTypeError 更改也影响了针对非法字符串偏移量的 E_WARNING “Illegal string offset 'string'”。从字符串到 int/float 的显式强制转换的行为没有改变。

  • 如果魔术方法声明了参数和返回值类型,它们现在将被检查。签名应与以下列表匹配

    • __call(string $name, array $arguments): mixed
    • __callStatic(string $name, array $arguments): mixed
    • __clone(): void
    • __debugInfo(): ?array
    • __get(string $name): mixed
    • __invoke(mixed $arguments): mixed
    • __isset(string $name): bool
    • __serialize(): array
    • __set(string $name, mixed $value): void
    • __set_state(array $properties): object
    • __sleep(): array
    • __unserialize(array $data): void
    • __unset(string $name): void
    • __wakeup(): void

  • call_user_func_array() 数组键现在将被解释为参数名称,而不是被静默忽略。

  • 在命名空间中声明名为 assert() 的函数不再允许,并且会发出 E_COMPILE_ERRORassert() 函数会受到引擎的特殊处理,这可能会导致在定义具有相同名称的命名空间函数时出现不一致的行为。

资源到对象的迁移

几个 resource 已迁移到 object。使用 is_resource() 检查返回值应替换为检查 false

COM 和 .Net (Windows)

从类型库导入不区分大小写的常量的功能已被删除。 com_load_typelib() 的第二个参数不再可以是 false; com.autoregister_casesensitive 不再可以禁用; com.typelib_file 中不区分大小写的标记会被忽略。

CURL

CURLOPT_POSTFIELDS 不再接受对象作为数组。要将对象解释为数组,请执行显式 (array) 转换。同样适用于接受数组的其他选项。

日期和时间

mktime()gmmktime() 现在至少需要一个参数。 time() 可用于获取当前时间戳。

DOM

DOM 扩展中没有行为且包含测试数据的未实现类已被删除。这些类也在最新版本的 DOM 标准中被删除。

  • DOMNameList
  • DomImplementationList
  • DOMConfiguration
  • DomError
  • DomErrorHandler
  • DOMImplementationSource
  • DOMLocator
  • DOMUserDataHandler
  • DOMTypeInfo
  • DOMStringExtend

DOM 扩展中没有行为的未实现方法已被删除。

  • DOMNamedNodeMap::setNamedItem()
  • DOMNamedNodeMap::removeNamedItem()
  • DOMNamedNodeMap::setNamedItemNS()
  • DOMNamedNodeMap::removeNamedItemNS()
  • DOMText::replaceWholeText()
  • DOMNode::compareDocumentPosition()
  • DOMNode::isEqualNode()
  • DOMNode::getFeature()
  • DOMNode::setUserData()
  • DOMNode::getUserData()
  • DOMDocument::renameNode()

Enchant

Exif

read_exif_data() 已被删除;应使用 exif_read_data()

Filter

GD

GMP

gmp_random() 已被删除。应使用 gmp_random_range()gmp_random_bits()

Iconv

不再支持在错误情况下不正确设置 errno 的 iconv 实现。

IMAP

国际化函数

  • 已弃用的常量 INTL_IDNA_VARIANT_2003 已被删除。

  • 已弃用的 Normalizer::NONE 常量已被删除。

LDAP

MBString

OCI8

ODBC

OpenSSL

正则表达式 (Perl 兼容)

当传递无效的转义序列时,它们不再被解释为字面量。以前,这种行为需要 X 修饰符,现在该修饰符已被忽略。

PHP 数据对象

  • 默认错误处理模式已从“静默”更改为“异常”。有关详细信息,请参阅 错误和错误处理

  • 一些 PDO 方法的签名已更改

    • PDO::query(string $query, ?int $fetchMode = null, mixed ...$fetchModeArgs)
    • PDOStatement::setFetchMode(int $mode, mixed ...$args)

PDO ODBC

已移除 php.ini 指令 pdo_odbc.db2_instance_name

PDO MySQL

PDO::inTransaction() 现在报告连接的实际事务状态,而不是 PDO 维持的近似状态。如果执行了受到“隐式提交”影响的查询,PDO::inTransaction() 将随后返回 false,因为事务不再处于活动状态。

PostgreSQL

  • 已弃用的 pg_connect() 语法,它使用多个参数而不是连接字符串,现在不再受支持。

  • 已弃用的 pg_lo_import()pg_lo_export() 签名,它将连接作为最后一个参数传递,现在不再受支持。连接应作为第一个参数传递。

  • pg_fetch_all() 现在将返回一个空数组,而不是对于具有零行的结果集返回 false

Phar

与 phar 关联的元数据将不再被自动反序列化,以解决由于对象实例化、自动加载等导致的潜在安全漏洞。

反射

  • 方法签名

    • ReflectionClass::newInstance($args)
    • ReflectionFunction::invoke($args)
    • ReflectionMethod::invoke($object, $args)

    已更改为

    • ReflectionClass::newInstance(...$args)
    • ReflectionFunction::invoke(...$args)
    • ReflectionMethod::invoke($object, ...$args)

    必须与 PHP 7 和 PHP 8 都兼容的代码可以使用以下签名来与两个版本兼容

    • ReflectionClass::newInstance($arg = null, ...$args)
    • ReflectionFunction::invoke($arg = null, ...$args)
    • ReflectionMethod::invoke($object, $arg = null, ...$args)

  • ReflectionType::__toString() 方法现在将返回类型的完整调试表示,并且不再被弃用。特别是,结果将包含可空类型的可空性指示器。返回值的格式不稳定,可能会在不同 PHP 版本之间发生变化。

  • 已移除 Reflection export() 方法。相反,可以将反射对象转换为字符串。

  • ReflectionMethod::isConstructor()ReflectionMethod::isDestructor() 现在也针对接口的 __construct()__destruct() 方法返回 true。之前,这仅适用于类和特征的方法。

  • ReflectionType::isBuiltin() 方法已移至 ReflectionNamedTypeReflectionUnionType 没有该方法。

套接字

标准 PHP 库 (SPL)

标准库

  • assert() 将不再评估字符串参数,而是将它们视为任何其他参数。建议使用 assert($a == $b) 而不是 assert('$a == $b')assert.quiet_eval ini 指令和 ASSERT_QUIET_EVAL 常量也被移除,因为它们不再有任何效果。

  • parse_str() 现在不能在不指定结果数组的情况下使用。

  • 已移除 string.strip_tags 过滤器。

  • strpos()strrpos()stripos()strripos()strstr()strchr()strrchr()stristr()needle 参数现在始终被解释为字符串。以前,非字符串类型的 needle 被解释为 ASCII 码点。可以通过显式调用 chr() 来恢复之前的行为。

  • strpos()strrpos()stripos()strripos()strstr()stristr()strrchr()needle 参数现在可以为空。

  • 针对 substr()substr_count()substr_compare()iconv_substr() 函数的 length 参数现在可以为 nullnull 值的行为将与未提供长度参数相同,因此将返回字符串的剩余部分,而不是空字符串。

  • 针对 array_splice() 函数的 length 参数现在可以为 nullnull 值的行为与省略该参数相同,因此将从 offset 到数组末尾删除所有内容。

  • vsprintf()vfprintf()vprintf() 函数的 args 参数现在必须是数组。以前接受任何类型。

  • password_hash() 函数的 'salt' 选项不再受支持。如果使用 'salt' 选项,则会生成警告,提供的盐会被忽略,而改为使用生成的盐。

  • quotemeta() 函数现在如果传递空字符串,则将返回空字符串。以前返回 false

  • 以下函数已被删除

  • FILTER_SANITIZE_MAGIC_QUOTES 已被删除。

  • 以相反顺序 ($pieces, $glue) 的参数调用 implode() 函数不再受支持。

  • parse_url() 函数现在将区分缺失和空的查询和片段

    • http://example.com/foo → query = null, fragment = null
    • http://example.com/foo? → query = "", fragment = null
    • http://example.com/foo# → query = null, fragment = ""
    • http://example.com/foo?# → query = "", fragment = ""
    以前所有情况都会导致 query 和 fragment 为 null

  • var_dump()debug_zval_dump() 函数现在将使用 serialize_precision 而不是 precision 打印浮点数。在默认配置中,这意味着这些调试函数现在将以完全精度打印浮点数。

  • 如果 __sleep() 方法返回的数组包含不存在的属性,这些属性现在将被静默忽略。以前,这些属性将被序列化,就好像它们的值为 null

  • 启动时的默认区域设置现在始终为 "C"。默认情况下,不会从环境继承任何区域设置。以前,LC_ALL 设置为 "C",而 LC_CTYPE 从环境继承。但是,某些函数在没有显式调用 setlocale() 函数的情况下,不会遵守继承的区域设置。现在始终需要显式调用 setlocale() 函数,如果要从默认值更改区域设置组件。

  • crypt() 函数中已弃用的 DES 回退已被删除。如果将未知的盐格式传递给 crypt() 函数,该函数现在将使用 *0 失败,而不是回退到弱 DES 哈希。

  • 为 SHA256/SHA512 crypt() 函数指定超出范围的轮数现在将使用 *0 失败,而不是钳制到最接近的限制。这与 glibc 行为一致。

  • 如果数组包含比较结果相等的元素,排序函数的结果可能已更改。

  • 任何接受回调但未明确指定接受引用参数的函数现在将发出警告,如果使用带有引用参数的回调。例如,array_filter()array_reduce() 函数。以前大多数函数(但并非所有函数)已经是这样了。

  • HTTP 流包装器(如 file_get_contents() 函数所用)现在默认情况下会宣传 HTTP/1.1 而不是 HTTP/1.0。这不会改变客户端的行为,但可能会导致服务器做出不同的响应。要保留旧的行为,请设置 'protocol_version' 流上下文选项,例如

    <?php
    $ctx
    = stream_context_create(['http' => ['protocol_version' => '1.0']]);
    echo
    file_get_contents('http://example.org', false, $ctx);
    ?>

  • 不再支持在没有显式盐的情况下调用 crypt() 函数。如果您希望使用自动生成的盐生成强哈希,请改为使用 password_hash() 函数。

  • substr()mb_substr()iconv_substr()grapheme_substr() 函数现在会一致地将超出边界偏移量钳制到字符串边界。以前,在某些情况下,会返回 false 而不是空字符串。

  • 在 Windows 上,使用 shell 的程序执行函数(proc_open()exec()popen() 等)现在会一致地执行 %comspec% /s /c "$commandline",这与执行 $commandline(不带额外的引号)的效果相同。

Sysvsem

  • sem_get() 函数的 auto_release 参数已更改为接受 bool 值,而不是 int 值。

Tidy

Tokenizer

XMLReader

XMLReader::open()XMLReader::xml() 现在是静态方法。它们仍然可以作为实例方法调用,但继承的类需要在重写这些方法时将它们声明为静态方法。

XML-RPC

XML-RPC 扩展已移至 PECL,不再是 PHP 发行版的一部分。

Zip

ZipArchive::OPSYS_Z_CPM 已被删除(此名称为拼写错误)。请改用 ZipArchive::OPSYS_CPM

Zlib

Windows PHP 测试包

测试运行器已从 run-test.php 重命名为 run-tests.php,以与其在 php-src 中的名称匹配。

添加注释

用户贡献的注释 3 个注释

retry, abort, fail
2 年前
上面提到的字符串到整数比较的更改(例如,'' == 0 现在等于 false)会产生一些其他糟糕的后果

$a = '';

// php 8

if ( $a < 0 ) echo 'true'; // 输出 true
if ( $a < -1) echo 'true'; // 输出 true
if ( $a < -100 ) echo 'true'; // 输出 true

// php 7

if ( $a < 0 ) echo 'true'; // 没有输出
if ( $a < -1) echo 'true'; // 没有输出
if ( $a < -100 ) echo 'true'; // 没有输出

因此,在您可能拥有 Web 表单输入并期望空值等于 0 的情况下,不仅要警惕 == 0、!= 0 和 <= 0 比较,还要警惕与负整数的所有 < 或 <= 比较。
aphpguy at galaxy dot za dot net
1 年前
如果您有由于宽松比较问题导致 PHP7 到 PHP8 迁移时出现故障的旧项目

即,如果 ($a == 0) 在 PHP 7 和 PHP 8 中表现不同
(对于像 $a = "" 或 $a = "123foo" 以及上面列出的其他情况)

在旧代码中替换

if ($a == 0) { .. }



if (cmp_eq($a, $b)) { .. }

在各种情况下进行了测试,甚至针对数组、布尔值、文件句柄、管道句柄、对象、标量和数字进行了测试。

因此,旧代码的行为仍然与以前一样。
然后,对于宽松比较,PHP8.x 和旧版 PHP(直至 7.x 版)将给出完全相同的布尔真或假输出。

function cmp_eq($a, $b) {
// 如果 $a 和 $b 的类型都是字符串,则将它们作为字符串进行比较
if (is_string($a) && is_string($b)) { return $a == $b; } // 可能不会 ===,因为 php 说 '42' 等于 '042' 为真,但 '42' === '042' 为假。

// 如果 $a 和 $b 都是数字字符串,则将它们作为数字进行比较
if (is_numeric($a) && is_numeric($b)) { return $a == $b; }

// 如果 $a 是空字符串,$b 是 0,反之亦然,则返回 true
if (($a === '' && $b === 0) || ($a === 0 && $b === '')) { return true; }

// 如果 $a 是非数字字符串,$b 是 0,反之亦然,则返回 true
if ((is_string($a) && ($a !== '') && ($b === 0)) || (($a === 0) && is_string($b) && ($b !== ''))) {
return true;
}
// 特殊情况 '123abc' == 123 .. php 7 将 123abc 转换为 123,然后 123 == 123 结果为 true。让我们模仿一下。
if ((is_string($a) && ($a !== '') && (is_numeric($b)) && ((bool)$b))) {
$number = filter_var($a, FILTER_SANITIZE_NUMBER_FLOAT, FILTER_FLAG_ALLOW_FRACTION); //"-234.56xyz"
return $number == $b;
}
if (is_numeric($a) && ((bool)$a) && is_string($b) && ($b !== '')) {
$number = filter_var($b, FILTER_SANITIZE_NUMBER_FLOAT, FILTER_FLAG_ALLOW_FRACTION); //"-234.56xyz"
return $a == $number;
}

// 如果 $a 是数字,$b 是非数字字符串,则将 $a 转换为字符串并比较
if (is_numeric($a) && is_string($b)) { return strval($a) == $b; }

// 如果 $b 是数字,$a 是非数字字符串,则将 $b 转换为字符串并比较
if (is_string($a) && is_numeric($b)) { return $a == strval($b); }

// 如果 $a 和 $b 都是非数字字符串,则直接比较它们,如果它们相同,我们应该返回 true
return $a == $b;
} // end func cmp_eq

注意:更好的方法是将代码移植到 PHP 8,使用严格的变量类型,并利用 === 和 !== 运算符。

但在某些情况下,对于需要快速修补的大量旧代码,这可能会有所帮助。
1035041238 at qq dot com
1 年前
在 PHP 8 中,空字符串小于任何数字,英文字母始终大于任何数字。

更有趣的是标点符号和非单词字符,有些大于数字,有些小于数字

$string = '`~!@#$%^&*()-_=+[]{};:\'"\\|,.<>/?';

$number = 999999999999;

$str_len = strlen($string);

$bigger = $smaller = $equal = [];
for ( $i = 0; $i < $str_len; ++$i ) {
if ( $string[$i] > $number ) {
$bigger[] = $string[$i];
} elseif ( $string[$i] < $number ) {
$smaller[] = $string[$i];
} else {
$equal[] = $string[$i];
}
}

var_dump( $bigger ); //['`', '~', '@', '^', '_', '=', '[', ']', '{', '}', ';', ':', '\', '|', '<', '>', '?']
var_dump( $smaller); //['!', '#', '$', '%', '&', '*', '(', ')', '-', '+', ''', '"', ',', '.', '/']
var_dump( $equal); //[]
To Top