主页»PHP»十个PHP开发者最简略犯的过错

十个PHP开发者最简略犯的过错

来历:segmentfault 发布时刻:2018-04-02 阅览次数:

  PHP 言语让 WEB 端程序规划变得简略,这也是它能盛行起来的原因。但也是由于它的简略,PHP 也渐渐开展成一个相对杂乱的言语,层出不穷的结构,各种言语特性和版别差异都经常让搞的咱们头大,不得不糟蹋许多时刻去调试。这篇文章列出了十个最简略犯错的当地,值得咱们去留意。

 易犯过错 #1: 在 foreach循环后留下数组的引证

  还不清楚 PHP 中 foreach 遍历的作业原理?假定你在想遍历数组时操作数组中每个元素,在 foreach 循环中运用引证会十分便利,例如

$arr = array(1, 2, 3, 4);
foreach ($arr as &$value) {
        $value = $value * 2;
}
// $arr 现在是 array(2, 4, 6, 8)

  问题是,假定你不留意的话这会导致一些意想不到的负面效果。在上述比方,在代码履行完今后,$value 仍保留在效果域内,并保留着对数组终究一个元素的引证。之后与 $value 相关的操作会无意中修正数组中终究一个元素的值。

  你要记住 foreach 并不会发作一个块级效果域。因而,在上面比方中 $value 是一个大局引证变量。在 foreach 遍历中,每一次迭代都会构成一个对 $arr 下一个元素的引证。当遍历结束后, $value 会引证 $arr 的终究一个元素,并保留在效果域中

  这种行为会导致一些不易发现的,令人困惑的bug,以下是一个比方

$array = [1, 2, 3];
echo implode(',', $array), "\n";

foreach ($array as &$value) {}    // 经过引证遍历
echo implode(',', $array), "\n";

foreach ($array as $value) {}     // 经过赋值遍历
echo implode(',', $array), "\n";

  以上代码会输出

1,2,3
1,2,3
1,2,2

  你没有看错,终究一行的终究一个值是 2 ,而不是 3 ,为什么?

  在完结第一个 foreach 遍历后, $array 并没有改动,可是像上述解说的那样, $value 留下了一个对 $array 终究一个元素的风险的引证(由于 foreach 经过引证取得 $value )

  这导致当运转到第二个 foreach ,这个"古怪的东西"发作了。当 $value 经过赋值取得, foreach 按次序仿制每个 $array 的元素到 $value 时,第二个 foreach 里边的细节是这样的

  • 第一步:仿制 $array[0] (也便是 1 )到 $value ($value 其实是 $array终究一个元素的引证,即 $array[2]),所以 $array[2] 现在等于 1。所以 $array 现在包括 [1, 2, 1]
  • 第二步:仿制 $array[1](也便是 2 )到 $value ( $array[2] 的引证),所以 $array[2] 现在等于 2。所以 $array 现在包括 [1, 2, 2]
  • 第三步:仿制 $array[2](现在等于 2 ) 到 $value ( $array[2] 的引证),所以 $array[2] 现在等于 2 。所以 $array 现在包括 [1, 2, 2]

  为了在 foreach 中便利的运用引证而免遭这种费事,请在 foreach 履行结束后 unset() 掉这个保留着引证的变量。例如

$arr = array(1, 2, 3, 4);
foreach ($arr as &$value) {
    $value = $value * 2;
}
unset($value);   // $value 不再引证 $arr[3]

 常见过错 #2: 误解 isset() 的行为

  虽然姓名叫 isset,可是 isset() 不只会在变量不存在的时分回来 false,在变量值为 null 的时分也会回来 false。

  这种行为比开始呈现的问题更为扎手,一同也是一种常见的过错源。

  看看下面的代码: 

$data = fetchRecordFromStorage($storage, $identifier);
if (!isset($data['keyShouldBeSet']) {
    // do something here if 'keyShouldBeSet' is not set
}

  开发者想必是想承认 keyShouldBeSet 是否存在于 $data 中。可是,正如上面说的,假定 $data['keyShouldBeSet'] 存在而且值为 null 的时分, isset($data['keyShouldBeSet']) 也会回来 false。所以上面的逻辑是不慎重的。

  咱们来看别的一个比方:

if ($_POST['active']) {
    $postData = extractSomething($_POST);
}

// ...

if (!isset($postData)) {
    echo 'post not active';
}

  上述代码,一般以为,假定 $_POST['active'] 回来 true,那么 postData 必将存在,因而 isset($postData) 也将回来 true。反之, isset($postData) 回来 false 的仅有或许是 $_POST['active'] 也回来 false。

  可是现实并非如此!

  如我所言,假定$postData 存在且被设置为 null, isset($postData) 也会回来 false 。 也便是说,即便 $_POST['active'] 回来 true, isset($postData) 也或许会回来 false 。 再一次阐明上面的逻辑不慎重。

  趁便一提,假定上面代码的目的真的是再次承认 $_POST['active'] 是否回来 true,依靠 isset() 来做,不论关于哪种场景来说都是一种糟糕的决议。更好的做法是再次查看 $_POST['active'],即:

if ($_POST['active']) {
    $postData = extractSomething($_POST);
}

// ...

if ($_POST['active']) {
    echo 'post not active';
}

  关于这种状况,虽然查看一个变量是否真的存在很重要(即:差异一个变量是未被设置仍是被设置为 null);可是运用 array_key_exists() 这个函数却是个更强健的处理途径。

  比方,咱们可以像下面这样重写上面第一个比方:

$data = fetchRecordFromStorage($storage, $identifier);
if (! array_key_exists('keyShouldBeSet', $data)) {
    // do this if 'keyShouldBeSet' isn't set
}

  别的,经过结合 array_key_exists() 和 get_defined_vars(), 咱们能愈加牢靠的判别一个变量在当前效果域中是否存在: 

if (array_key_exists('varShouldBeSet', get_defined_vars())) {
    // variable $varShouldBeSet exists in current scope
}

 常见过错 #3:关于经过引证回来与经过值回来的困惑

  考虑下面的代码片段:

class Config
{
    private $values = [];

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

$config = new Config();

$config->getValues()['test'] = 'test';
echo $config->getValues()['test'];

  假定你运转上面的代码,将得到下面的输出:

PHP Notice:  Undefined index: test in /path/to/my/script.php on line 21

  出了什么问题?

  上面代码的问题在于没有搞清楚经过引证与经过值回来数组的差异。除非你明晰告知 PHP 经过引证回来一个数组(例如,运用 &),否则 PHP 默许将会「经过值」回来这个数组。这意味着这个数组的一份复制将会被回来,因而被调函数与调用者所拜访的数组并不是相同的数组实例。

  所以上面临 getValues() 的调用将会回来 $values 数组的一份复制,而不是对它的引证。考虑到这一点,让咱们从头回忆一下以上比方中的两个要害行:

// getValues() 回来了一个 $values 数组的复制
// 所以`test`元素被添加到了这个复制中,而不是 $values 数组自身。
$config->getValues()['test'] = 'test';


// getValues() 又回来了另一份 $values 数组的复制
// 且这份复制中并不包括一个`test`元素(这便是为什么咱们会得到 「未界说索引」 音讯)。
echo $config->getValues()['test'];

  一个或许的修正办法是存储第一次经过 getValues() 回来的 $values 数组复制,然后后续操作都在那份复制上进行;例如:

$vals = $config->getValues();
$vals['test'] = 'test';
echo $vals['test'];

  这段代码将会正常作业(例如,它将会输出test而不会发作任何「未界说索引」音讯),可是这个办法或许并不能满意你的需求。特别是上面的代码并不会修正原始的$values数组。假定你想要修正原始的数组(例如添加一个test元素),就需求修正getValues()函数,让它回来一个$values数组自身的引证。经过在函数名前面添加一个&来阐明这个函数将回来一个引证;例如:

class Config
{
    private $values = [];

    // 回来一个 $values 数组的引证
    public function &getValues() {
        return $this->values;
    }
}

$config = new Config();

$config->getValues()['test'] = 'test';
echo $config->getValues()['test'];

  这会输出等待的test。

  可是现在让作业更困惑一些,请考虑下面的代码片段:

class Config
{
    private $values;

    // 运用数组目标而不是数组
    public function __construct() {
        $this->values = new ArrayObject();
    }

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

$config = new Config();

$config->getValues()['test'] = 'test';
echo $config->getValues()['test'];

  假定你以为这段代码会导致与之前的数组比方相同的「未界说索引」过错,那就错了。实际上,这段代码将会正常运转。原因是,与数组不同,PHP 永久会将目标按引证传递。(ArrayObject 是一个 SPL 目标,它彻底仿照数组的用法,可是却是以目标来作业。)

  像以上比方阐明的,你应该以引证仍是复制来处理一般不是很明显就能看出来。因而,了解这些默许的行为(例如,变量和数组以值传递;目标以引证传递)而且细心查看你即将调用的函数 API 文档,看看它是回来一个值,数组的复制,数组的引证或是目标的引证是必要的。

  虽然如此,咱们要认识到应该尽量防止回来一个数组或 ArrayObject,由于这会让调用者可以修正实例目标的私有数据。这就破坏了目标的封装性。所以最好的办法是运用传统的「getters」和「setters」,例如:

class Config
{
    private $values = [];

    public function setValue($key, $value) {
        $this->values[$key] = $value;
    }

    public function getValue($key) {
        return $this->values[$key];
    }
}

$config = new Config();

$config->setValue('testKey', 'testValue');
echo $config->getValue('testKey');    // 输出『testValue』

  这个办法让调用者可以在不对私有的$values数组自身进行揭露拜访的状况下设置或许获取数组中的恣意值。

 常见的过错 #4:在循环中履行查询

  假定像这样的话,必定不难见到你的 PHP 无法正常作业。

$models = [];

foreach ($inputValues as $inputValue) {
    $models[] = $valueRepository->findByValue($inputValue);
}

  这儿或许没有实在的过错, 可是假定你跟跟着代码的逻辑走下去, 你或许会发现这个看似无害的调用$valueRepository->findByValue() 终究履行了这样一种查询,例如:

$result = $connection->query("SELECT `x`,`y` FROM `values` WHERE `value`=" . $inputValue);

  成果每轮循环都会发作一次对数据库的查询。 因而,假定你为这个循环供给了一个包括 1000 个值的数组,它会对资源发作 1000 独自的恳求!假定这样的脚本在多个线程中被调用,他会有导致体系溃散的潜在风险。

  因而,至关重要的是,当你的代码要进行查询时,应该尽或许的搜集需求用到的值,然后在一个查询中获取一切成果。

  一个咱们平经常常能见到查询功率低下的当地 (例如:在循环中)是运用一个数组中的值 (比方说许多的 ID )向表主张恳求。检索每一个 ID 的一切的数据,代码将会迭代这个数组,每个 ID 进行一次SQL查询恳求,它看起来常常是这样:

$data = [];
foreach ($ids as $id) {
    $result = $connection->query("SELECT `x`, `y` FROM `values` WHERE `id` = " . $id);
    $data[] = $result->fetch_row();
}

  可是 只用一条 SQL 查询句子就可以更高效的完结相同的作业,比方像下面这样:

$data = [];
if (count($ids)) {
    $result = $connection->query("SELECT `x`, `y` FROM `values` WHERE `id` IN (" . implode(',', $ids));
    while ($row = $result->fetch_row()) {
        $data[] = $row;
    }
}

  因而在你的代码直接或直接进行查询恳求时,必定要认出这种查询。尽或许的经过一次查询得到想要的成果。可是,依然要小心翼翼,否则就或许会呈现下面咱们要讲的另一个易犯的过错...

 常见问题 #5: 内存运用诈骗与低效

  一次取多条记载肯定是比一条条的取高效,可是当咱们运用 PHP 的 mysql 扩展的时分,这也或许成为一个导致 libmysqlclient 呈现『内存不足』(out of memory)的条件。

  咱们在一个测验盒里演示一下,该测验盒的环境是:有限的内存(512MB RAM),MySQL,和 php-cli。

  咱们将像下面这样引导一个数据表:

// 衔接 mysql
$connection = new mysqli('localhost', 'username', 'password', 'database');

// 创立 400 个字段
$query = 'CREATE TABLE `test`(`id` INT NOT NULL PRIMARY KEY AUTO_INCREMENT';
for ($col = 0; $col < 400; $col++) {
    $query .= ", `col$col` CHAR(10) NOT NULL";
}
$query .= ');';
$connection->query($query);

// 写入 2 百万行数据
for ($row = 0; $row < 2000000; $row++) {
    $query = "INSERT INTO `test` VALUES ($row";
    for ($col = 0; $col < 400; $col++) {
        $query .= ', ' . mt_rand(1000000000, 9999999999);
    }
    $query .= ')';
    $connection->query($query);
}

  OK,现在让咱们一同来看一下内存运用状况:

// 衔接 mysql
$connection = new mysqli('localhost', 'username', 'password', 'database');
echo "Before: " . memory_get_peak_usage() . "\n";

$res = $connection->query('SELECT `x`,`y` FROM `test` LIMIT 1');
echo "Limit 1: " . memory_get_peak_usage() . "\n";

$res = $connection->query('SELECT `x`,`y` FROM `test` LIMIT 10000');
echo "Limit 10000: " . memory_get_peak_usage() . "\n";

  输出成果是:

Before: 224704
Limit 1: 224704
Limit 10000: 224704

  Cool。 看来就内存运用而言,内部安全地办理了这个查询的内存。

  为了愈加明晰这一点,咱们把约束进步一倍,使其到达 100,000。 额~假定真这么干了,咱们将会得到如下成果:

PHP Warning:  mysqli::query(): (HY000/2013):
              Lost connection to MySQL server during query in /root/test.php on line 11

  终究发作了啥?

  这就涉及到 PHP 的 mysql 模块的作业办法的问题了。它其实只是个 libmysqlclient 的署理,专门担任干脏活累活。每查出一部分数据后,它就立即把数据放入内存中。由于这块内存还没被 PHP 办理,所以,当咱们在查询里添加约束的数量的时分, memory_get_peak_usage() 不会显现任何添加的资源运用状况 。咱们被『内存办理没问题』这种骄傲的思维所诈骗了,所以才会导致上面的演示呈现那种问题。 老实说,咱们的内存办理的确是有缺点的,而且咱们也会遇到如上所示的问题。

  假定运用 mysqlnd 模块的话,你至少可以防止上面那种诈骗(虽然它自身并不会提高你的内存利用率)。 mysqlnd 被编译成原生的 PHP 扩展,而且的确 会 运用 PHP 的内存办理器。

  因而,假定运用 mysqlnd 而不是 mysql,咱们将会得到更实在的内存利用率的信息:

Before: 232048
Limit 1: 324952
Limit 10000: 32572912

  趁便一提,这比方才更糟糕。依据 PHP 的文档所说,mysql 运用 mysqlnd 两倍的内存来存储数据, 所以,本来运用 mysql 那个脚本实在运用的内存比这儿显现的更多(大约是两倍)。

  为了防止呈现这种问题,考虑约束一下你查询的数量,运用一个较小的数字来循环,像这样:

$totalNumberToFetch = 10000;
$portionSize = 100;

for ($i = 0; $i <= ceil($totalNumberToFetch / $portionSize); $i++) {
    $limitFrom = $portionSize * $i;
    $res = $connection->query(
                         "SELECT `x`,`y` FROM `test` LIMIT $limitFrom, $portionSize");
}

  当咱们把这个常见过错和上面的 常见过错 #4 结合起来考虑的时分, 就会意识到咱们的代码抱负需求在两者间完成一个平衡。是让查询粒度化和重复化,仍是让单个查询巨大化。日子亦是如此,平衡不行或缺;哪一个极点都不好,都或许会导致 PHP 无法正常运转。

 常见过错 #6: 疏忽 Unicode/UTF-8 的问题

  从某种意义上说,这实际上是PHP自身的一个问题,而不是你在调试 PHP 时遇到的问题,可是它从未得到妥善的处理。 PHP 6 的中心便是要做到支撑 Unicode。可是跟着 PHP 6 在 2010 年的暂停而放置了。

  这并不意味着开发者可以防止 正确处理 UTF-8 并防止做出一切字符串有必要是『陈旧的 ASCII』的假定。 没有正确处理非 ASCII 字符串的代码会由于引进粗糙的 海森堡bug(heisenbugs)  而变得臭名远扬。当一个姓名包括 『Schrödinger』的人注册到你的体系时,即便简略的 strlen($_POST['name']) 调用也会呈现问题。

  下面是一些可以防止呈现这种问题的清单:

  • 假定你对 UTF-8 还不了解,那么你至少应该了解下根底的东西。 这儿 有个很好的引子。
  • 保证运用 mb_* 函数替代老旧的字符串处理函数(需求先保证你的 PHP 构建版别敞开了『多字节』(multibyte)扩展)。
  • 保证你的数据库和表设置了 Unicode 编码(许多 MySQL 的构建版别依然默许运用 latin1  )。
  • 记住 json_encode() 会转化非 ASCII 标识(比方: 『Schrödinger』会被转化成 『Schru00f6dinger』),可是 serialize() 不会 转化。
  • 保证 PHP 文件也是 UTF-8 编码,以防止在衔接硬编码字符串或许装备字符串常量的时分发作抵触。

  Francisco Claria  在本博客上宣布的 UTF-8 Primer for PHP and MySQL  是份名贵的资源。

 常见过错 #7: 以为 $_POST 总是包括你 POST 的数据

  不论它的称号,$_POST 数组不是总是包括你 POST 的数据,他也有或许会是空的。 为了了解这一点,让咱们来看一下下面这个比方。假定咱们运用 jQuery.ajax() 模仿一个服务恳求,如下:

// js
$.ajax({
    url: 'http://my.site/some/path',
    method: 'post',
    data: JSON.stringify({a: 'a', b: 'b'}),
    contentType: 'application/json'
});

  (顺带一提,留意这儿的 contentType: 'application/json' 。咱们用 JSON 类型发送数据,这在接口中十分盛行。这在 AngularJS $http service 里是默许的发送数据的类型。)

  在咱们举比方的服务端,咱们简略的打印一下 $_POST 数组:

// php
var_dump($_POST);

  古怪的是,成果如下:

array(0) { }

  为什么?咱们的 JSON 串 {a: 'a', b: 'b'} 终究发作了什么?

  原因在于 当内容类型为 application/x-www-form-urlencoded 或许 multipart/form-data 的时分 PHP 只会主动解析一个 POST 的有用内容。这儿边有前史的原因 --- 这两种内容类型是在 PHP 的 $_POST 完成前就现已在运用了的两个重要的类型。所以不论运用其他任何内容类型 (即便是那些现在很盛行的,像 application/json), PHP 也不会主动加载到 POST 的有用内容。

  已然 $_POST 是一个超级大局变量,假定咱们重写 一次 (在咱们的脚本里尽或许早的),被修正的值(包括 POST 的有用内容)将可以在咱们的代码里被引证。这很重要由于 $_POST 现已被 PHP 结构和简直一切的自界说的脚本遍及运用来获取和传递恳求数据。

  所以,举个比方,当处理一个内容类型为 application/json 的 POST 有用内容的时分 ,咱们需求手动解析恳求内容(decode 出 JSON 数据)而且掩盖 $_POST 变量,如下:

// php
$_POST = json_decode(file_get_contents('php://input'), true);

  然后当咱们打印 $_POST 数组的时分,咱们可以看到他正确的包括了 POST 的有用内容;如下:

array(2) { ["a"]=> string(1) "a" ["b"]=> string(1) "b" }

 常见过错 #8: 以为 PHP 支撑单字符数据类型

  阅览下面的代码并考虑会输出什么:

for ($c = 'a'; $c <= 'z'; $c++) {
    echo $c . "\n";
}

  假定你的答案是 a 到 z,那么你或许会对这是一个过错答案感到吃惊。

  没错,它的确会输出 a 到 z,可是,它还会持续输出 aa 到 yz。咱们一同来看一下这是为什么。

  PHP 中没有 char 数据类型; 只能用 string 类型。记住一点,在 PHP 中添加 string 类型的 z 得到的是 aa:

php> $c = 'z'; echo ++$c . "\n";
aa

  没那么令人混杂的是,aa 的字典次序是 小于  z 的:

php> var_export((boolean)('aa' < 'z')) . "\n";
true

  这也是为什么上面那段简略的代码会输出 a 到 z, 然后 持续 输出 aa到 yz。 它停在了 za,那是它遇到的第一个比 z 大 的:

php> var_export((boolean)('za' < 'z')) . "\n";
false

  现实上,在 PHP 里 有适宜的 办法在循环中输出 a 到 z 的值:

for ($i = ord('a'); $i <= ord('z'); $i++) {
    echo chr($i) . "\n";
}

  或许是这样:

$letters = range('a', 'z');

for ($i = 0; $i < count($letters); $i++) {
    echo $letters[$i] . "\n";
}

 常见 过错 #9: 忽视代码规范

  虽然忽视代码规范并不直接导致需求去调试 PHP 代码,但这或许是一切需求议论的作业里最重要的一项。

  在一个项目中忽视代码规范可以导致许多的问题。最达观的估计,前后代码纷歧致(在此之前每个开发者都在“做自己的作业”)。但最差的成果,PHP 代码不能运转或许很难(有时是不或许的)去顺畅经过,这关于 调试代码、提高功能、保护项目来说也是困难重重。而且这意味着下降你们团队的生产力,添加许多的额定(或许至少是本不必要的)精力耗费。

  走运的是关于 PHP 开发者来说,存在 PHP 编码规范主张(PSR),它由下面的五个规范组成:

  • PSR-0: 主动加载规范
  • PSR-1: 根底编码规范
  • PSR-2: 编码风格辅导
  • PSR-3: 日志接口
  • PSR-4: 主动加载增强版

  PSR 起初是由市场上最大的安排渠道保护者发明的。 Zend, Drupal, Symfony, Joomla 和 其他 为这些规范做出了奉献,并一向恪守它们。乃至,多年前企图成为一个规范的 PEAR ,现在也参加到 PSR 中来。

  某种意义上,你的代码规范是什么简直是不重要的,只需你遵照一个规范并坚持下去,但一般来讲,跟从 PSR 是一个很不错的主见,除非你的项目上有其他让人难以抵抗的理由。越来越多的团队和项目正在遵照 PSR 。在这一点上,大部分的 PHP 开发者达成了一致,因而运用 PSR 代码规范,有利于使新参加团队的开发者对你的代码规范感到愈加的了解与舒适。

 常见过错 #10:  乱用 empty()

  一些 PHP 开发者喜爱对简直一切的作业运用 empty() 做布尔值查验。不过,在一些状况下,这会导致紊乱。

  首要,让咱们回到数组和 ArrayObject 实例(和数组相似)。考虑到他们的相似性,很简略假定它们的行为是相同的。可是,现实证明这是一个风险的假定。举例,在 PHP 5.0 中:

// PHP 5.0 或后续版别:
$array = [];
var_dump(empty($array));        // 输出 bool(true)
$array = new ArrayObject();
var_dump(empty($array));        // 输出 bool(false)
// 为什么这两种办法不发作相同的输出呢?

  更糟糕的是,PHP 5.0之前的成果或许是不同的:

// PHP 5.0 之前:
$array = [];
var_dump(empty($array));        // 输出 bool(false)
$array = new ArrayObject();
var_dump(empty($array));        // 输出 bool(false)

  这种办法上的不幸是十分遍及的。比方,在 Zend Framework 2 下的 Zend\Db\TableGateway 的 TableGateway::select() 成果中调用 current() 时回来数据的办法,正如文档所标明的那样。开发者很简略就会变成此类数据过错的受害者。

  为了防止这些问题的发作,更好的办法是运用 count() 去查验空数组结构:

// 留意这会在 PHP 的一切版别中发挥效果 (5.0 前后都是):
$array = [];
var_dump(count($array));        // 输出 int(0)
$array = new ArrayObject();
var_dump(count($array));        // 输出 int(0)

  趁便说一句, 由于 PHP 将 0 转化为 false , count() 可以被运用在 if() 条件内部去查验空数组。相同值得留意的是,在 PHP 中, count() 在数组中是常量杂乱度 (O(1) 操作) ,这更明晰的标明它是正确的挑选。

  另一个运用 empty() 发作风险的比方是当它和戏法办法 _get() 一同运用。咱们来界说两个类并使其都有一个 test 特点。

  首要咱们界说包括 test 公共特点的 Regular 类。

class Regular
{
    public $test = 'value';
}

  然后咱们界说 Magic 类,这儿运用戏法办法 __get() 来操作去拜访它的 test 特点:

class Magic
{
    private $values = ['test' => 'value'];

    public function __get($key)
    {
        if (isset($this->values[$key])) {
            return $this->values[$key];
        }
    }
}

  好了,现在咱们测验去拜访每个类中的 test 特点看看会发作什么:

$regular = new Regular();
var_dump($regular->test);    // 输出 string(4) "value"
$magic = new Magic();
var_dump($magic->test);      // 输出 string(4) "value"

  到目前为止还好。

  可是现在当咱们对其间的每一个都调用 empty() ,让咱们看看会发作什么:

var_dump(empty($regular->test));    // 输出 bool(false)
var_dump(empty($magic->test));      // 输出 bool(true)

  咳。所以假定咱们依靠 empty() ,咱们很或许误以为 $magic 的特点 test 是空的,而实际上它被设置为 'value'。

  不幸的是,假定类运用戏法办法 __get() 来获取特点值,那么就没有满有把握的办法来查看该特点值是否为空。

  在类的效果域之外,你只是只能查看是否将回来一个 null 值,这并不意味着没有设置相应的键,由于它实际上还或许被设置为 null 。

  相反,假定咱们企图去引证 Regular 类实例中不存在的特点,咱们将得到一个相似于以下内容的告知:

Notice: Undefined property: Regular::$nonExistantTest in /path/to/test.php on line 10

Call Stack:
    0.0012     234704   1. {main}() /path/to/test.php:0

  所以这儿的首要观念是 empty() 办法应该被慎重地运用,由于假定不小心的话它或许导致紊乱 -- 乃至潜在的误导 -- 成果。

 总结

  PHP 的易用性让开发者堕入一种虚伪的舒适感,言语自身的一些细微差别和特质,或许花费掉你许多的时刻去调试。这些或许会导致 PHP 程序无法正常作业,并导致比如此处所述的问题。

  PHP 在其20年的前史中,现已发作了明显的改变。花时刻去了解言语自身的奇妙之处是值得的,由于它有助于保证你编写的软件更具可扩展性,强健和可保护性。

QQ群:凯发娱乐官网官方群(515171538),验证音讯:10000
微信群:加小编微信 849023636 邀请您参加,验证音讯:10000
提示:更多精彩内容重视微信大众号:全栈开发者中心(fsder-com)
PHP
网友议论(共0条议论) 正在载入议论......
沉着议论文明上网,回绝歹意咒骂 宣布议论 / 共0条议论
登录会员中心