浅析PHP编程中10个最常见的荒唐,开发者最不难犯的荒唐

亚洲必赢手机入口 1

亚洲必赢手机入口 2

PHP是一种万分流行的开源服务器端脚本语言,你在万维网看到的大部网站都是利用php开发的。本篇经将为大家介绍PHP开发中10个最广泛的题材,希望可以对情侣有所协助。

目前上学PHP很多恋人,在平时的常备程序开发工程中总会赶上种种各类的难点,本篇经验将为大家介绍PHP开发中10个最广大的题材,希望可以对情侣有所接济。

PHP 语言让 WEB
端程序设计变得简单,那也是它能流行起来的原故。但也是因为它的粗略,PHP
也逐渐前进成一个相持复杂的语言,家常便饭的框架,种种语言特色和本子差距都时常让搞的大家头大,不得不浪费大批量岁月去调节。那篇小说列出了十个最简单出错的地方,值得大家去注意。

file

 错误1:foreach循环后留下悬挂指针

  在foreach循环中,如若大家必要转移迭代的成分或是为了升高成效,运用引用是一个好点子:

1
2
3
4
5
$arr = array(1, 2, 3, 4);
foreach ($arr as &$value) {
    $value = $value * 2;
}
// $arr is now array(2, 4, 6, 8)

  这里有个难题重重人会眩晕。循环截至后,$value并未销毁,$value其实是数组中最后一个因素的引用,那样在屡次三番对$value的使用中,假如不知情那或多或少,会抓住部分莫名奇妙的谬误:)看看上边那段代码:

1
2
3
4
5
6
7
8
$array = [1, 2, 3];
echo implode(',', $array), "\n";
 
foreach ($array as &$value) {}    // by reference
echo implode(',', $array), "\n";
 
foreach ($array as $value) {}     // by value (i.e., copy)
echo implode(',', $array), "\n";

  上边代码的运行结果如下:

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

  你猜对了吗?为何是那个结果吗?

  大家来分析下。第三个巡回过后,$value是数组中最终一个要素的引用。第四个巡回起来:

  • 第一步:复制$arr[0]到$value(注意此时$value是$arr[2]的引用),那时数组变成[1,2,1]
  • 第二步:复制$arr[1]到$value,那时数组变成[1,2,2]
  • 第三步:复制$arr[2]到$value,那时数组变成[1,2,2]

  综上,最后结果就是1,2,2

  防止那种似是而非最好的法门就是在循环后当即用unset函数销毁变量:

1
2
3
4
5
$arr = array(1, 2, 3, 4);
foreach ($arr as &$value) {
    $value = $value * 2;
}
unset($value);   // $value no longer references $arr[3]

荒唐1:foreach循环后留下悬挂指针

易犯错误 #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]

PHP 语言让 WEB
端程序设计变得不难,那也是它能流行起来的缘故。但也是因为它的大约,PHP
也日渐前进成一个针锋相对复杂的言语,司空眼惯的框架,各个语言特征和版本差别都隔三差五让搞的我们头大,不得不浪费大批量小时去调节。这篇文章列出了十个最简单出错的地方,值得大家去注意。

 错误2:对isset()函数行为的谬误领会

  对于isset()函数,变量不存在时会重返false,变量值为null时也会回到false。那种作为很简单把人弄迷糊。。。看上边的代码:

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

  写那段代码的人本意或然是假若$data[‘keyShouldBeSet’]未安装,则实施相应逻辑。但难点在于就是$data[‘keyShouldBeSet’]已安装,但设置的值为null,如故会实施相应的逻辑,那就不适合代码的原意了。

  上边是其余一个例证:

1
2
3
4
5
6
7
8
9
if ($_POST['active']) {
    $postData = extractSomething($_POST);
}
 
// ...
 
if (!isset($postData)) {
    echo 'post not active';
}

  下面的代码若是$_POST[‘active’]为真,那么$postData应该被装置,由此isset($postData)会回来true。反之,下边代码假如isset($postData)重回false的唯一途径就是$_POST[‘active’]也返回false。

  真是如此呢?当然不是!

  即使$_POST[‘active’]回来true,$postData也有大概被装置为null,那时isset($postData)就会回去false。那就不适合代码的本心了。

  假使地方代码的本心仅是检测$_POST[‘active’]是或不是为真,下边那样完成会更好:

1
2
3
4
5
6
7
8
9
if ($_POST['active']) {
    $postData = extractSomething($_POST);
}
 
// ...
 
if ($_POST['active']) {
    echo 'post not active';
}

  判断一个变量是还是不是确实被安装(区分未安装和设置值为null),array_key_exists()函数大概更好。重构上边的率先个例子,如下:

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

  另外,结合get_defined_vars()函数,大家得以进一步可信的检测变量在脚下效劳域内是或不是被设置:

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

  在foreach循环中,借使大家必要转移迭代的因素或是为了升高功用,运用引用是一个好法子:

普遍错误 #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
}

易犯错误 #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]

 错误3:混淆重返值和重返引用

  考虑下边的代码:

1
2
3
4
5
6
7
8
9
10
11
12
13
class Config
{
    private $values = [];
 
    public function getValues() {
        return $this->values;
    }
}
 
$config = new Config();
 
$config->getValues()['test'] = 'test';
echo $config->getValues()['test'];

  运行方面的代码,将会输出上面的情节:

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

  难题出在哪呢?难题就在于地点的代码混淆了重回值和重回引用。在PHP中,除非您显得的指定再次来到引用,否则对于数组PHP是值重临,也等于数组的正片。由此地方代码对回到数组赋值,实际是对拷贝数组进行赋值,非原数组赋值。

1
2
3
4
5
6
7
// getValues() returns a COPY of the $values array, so this adds a 'test' element
// to a COPY of the $values array, but not to the $values array itself.
$config->getValues()['test'] = 'test';
 
// getValues() again returns ANOTHER COPY of the $values array, and THIS copy doesn't
// contain a 'test' element (which is why we get the "undefined index" message).
echo $config->getValues()['test'];

  上面是一种大概的化解办法,输出拷贝的数组,而不是原数组:

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

  若是您不怕想要改变原数组,约等于要反回数组引用,这应该如何处理呢?办法就是显示指定再次来到引用即可:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
class Config
{
    private $values = [];
 
    // return a REFERENCE to the actual $values array
    public function &getValues() {
        return $this->values;
    }
}
 
$config = new Config();
 
$config->getValues()['test'] = 'test';
echo $config->getValues()['test'];

  经过改造后,上边代码将会像你指望那样会输出test。

  我们再来看一个例证会让您更迷糊的例证:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
class Config
{
    private $values;
 
    // using ArrayObject rather than array
    public function __construct() {
        $this->values = new ArrayObject();
    }
 
    public function getValues() {
        return $this->values;
    }
}
 
$config = new Config();
 
$config->getValues()['test'] = 'test';
echo $config->getValues()['test'];

  如若您想的是会和上边一样输出“ Undefined
index”错误,那您就错了。代码会正常输出“test”。原因在于PHP对于目的暗中认可就是按引用重临的,而不是按值重返。

  综上所述,大家在动用函数再次来到值时,要弄通晓是值重返仍旧引用重回。PHP中对于目的,专断认同是援引重临,数组和放手基本项目暗许均按值重返。这些要与其他语言分化开来(很多言语对于数组是援引传递)。

  像其它语言,比如java或C#,利用getter或setter来访问或安装类属性是一种更好的方案,当然PHP暗中同意不接济,须要协调达成:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
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');    // echos 'testValue'

  下面的代码给调用者可以访问或设置数组中的任意值而不用给与数组public访问权限。感觉怎么着:)

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

广阔错误 #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
文档,看看它是回到一个值,数组的正片,数组的引用或是对象的引用是必需的。

浅析PHP编程中10个最常见的荒唐,开发者最不难犯的荒唐。固然,我们要认识到应有尽量防止再次来到一个数组或
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数组自个儿进行公开访问的气象下设置大概取得数组中的任意值。

科普错误 #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 存在且被装置为 nullisset($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
}

 错误4:在循环中举行sql查询

  在PHP编程中窥见类似下边的代码并不少见:

1
2
3
4
5
$models = [];
 
foreach ($inputValues as $inputValue) {
    $models[] = $valueRepository->findByValue($inputValue);
}

  当然下边的代码是未曾怎么错误的。难题在于大家在迭代经过中$valueRepository->findByValue()可能每一遍都实施了sql查询:

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

  假如迭代了10000次,那么您就各自执行了10000次sql查询。如若这么的剧本在三八线程程序中被调用,那很或者您的体系就挂了。。。

  在编辑代码进度中,你应该要清楚如曾几何时候应该举办sql查询,尽可能几遍sql查询取出所有数据。

  有一种业务场景,你很或然会犯上述失实。如若一个表单提交了一连串值(假使为IDs),然后为了取出所有ID对应的数据,代码将遍历IDs,分别对逐个ID执行sql查询,代码如下所示:

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

  但一样的目标可以在一个sql中越发飞快的已毕,代码如下:

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

  那里有个难题多几个人会眩晕。循环截至后,value并未销毁,value其实是数组中最终一个因素的引用,那样在持续对$value的选择中,如若不明了这点,会吸引部分莫名奇妙的一无所长:)看看上边那段代码:

广大的荒唐 #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;
    }
}

据此在你的代码直接或直接进行询问请求时,一定要认出那种查询。尽可能的经过一遍询问拿到想要的结果。然而,照旧要战战兢兢,不然就或许会冒出下边我们要讲的另一个易犯的错误…

大规模错误 #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数组本人进行精通访问的图景下设置只怕取得数组中的任意值。

 错误5:内存使用低效和错觉

  一回sql查询得到多条记下比每一遍查询拿到一条记下功效肯定要高,但只要你选择的是php中的mysql扩充,那么五回获得多条记下就很可能会导致内存溢出。

  我们得以写代码来尝试下(测试环境: 512MB RAM、MySQL、php-cli):

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
// connect to mysql
$connection = new mysqli('localhost', 'username', 'password', 'database');
 
// create table of 400 columns
$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);
 
// write 2 million rows
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);
}

  以往来看看资源消耗:

1
2
3
4
5
6
7
8
9
// connect to 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";

  输出结果如下:

1
2
3
Before: 224704
Limit 1: 224704
Limit 10000: 224704

  依据内存使用量来看,貌似一切正常。为了进一步确定,试着三次拿到100000条记下,结果程序获取如下输出:

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

  那是怎么回事呢?

  难题出在php的mysql模块的做事措施,mysql模块实际上就是libmysqlclient的一个代理。在询问拿到多条记下的还要,这么些记录会直接保存在内存中。由于那块内存不属于php的内存模块所管理,所以大家调用memory_get_peak_usage()函数所得到的值并非真正使用内存
值,于是便冒出了下边的标题。

  我们可以动用mysqlnd来替代mysql,mysqlnd编译为php本人增添,其内存使用由php内存管理模块所主宰。倘诺大家用mysqlnd来促成地点的代码,则会尤其真实的反应内存使用意况:

1
2
3
Before: 232048
Limit 1: 324952
Limit 10000: 32572912

  尤其糟糕的是,根据php的官方文档,mysql扩张存储查询数据应用的内存是mysqlnd的两倍,由此原来的代码应用的内存是地点突显的两倍左右。

  为了防止此类难点,可以考虑分五次到位查询,减小单次查询数据量:

1
2
3
4
5
6
7
8
$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足以见到,在骨子里的编码进度中,要做到一种平衡,才能既满意功用需要,又能确保质量。

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

foreach ($array as &$value) {}  // by reference 
echo implode(',', $array), "\n"; 

foreach ($array as $value) {}   // by value (i.e., copy) 
echo implode(',', $array), "\n";

科普难点 #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 不可以正常运行。

大规模的荒唐 #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;
    }
}

所以在您的代码直接或直接举办查询请求时,一定要认出那种查询。尽只怕的通过一回查询得到想要的结果。但是,依旧要小心翼翼,不然就或然会出现下边大家要讲的另一个易犯的错误…

 错误6:忽略Unicode/UTF-8问题

  php编程中,在拍卖非ascii字符时,会遇见一些难点,要很小心的去对待,要不然就会错误各处。举个简单的例子,strlen($name),假如$name包罗非ascii字符,那结果就稍微不期而然。在此付出一些提出,尽量避免此类难题:

  • 比方您对unicode和utf-8不是很精通,那么您至少应该通晓部分基础。推荐阅读那篇小说。
  • 最好使用mb_*函数来拍卖字符串,幸免选择老的字符串处理函数。那里要力保PHP的“multibyte”扩张已拉开。
  • 数据库和表最好使用unicode编码。
  • 知道jason_code()函数会转换非ascii字符,但serialize()函数不会。
  • php代码源文件最好利用不含bom的utf-8格式。

  在此推荐一篇小说,更详实的介绍了此类题材: UTF-8 Primer for PHP and
MySQL

  下边代码的运转结果如下:

科普错误 #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』会被转换成 『Schr\u00f6dinger』),但是
    serialize() 不会 转换。
  • 保证 PHP 文件也是 UTF-8
    编码,避防止在连接硬编码字符串只怕配备字符串常量的时候发出争辩。

Francisco
Claria 
在本博客上刊出的 UTF-8 Primer for PHP and
MySQL  是份难得的资源。

周边难点 #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 无法正常运作。

 错误7:假定$_POST总是包涵POST数据

  PHP中的$_POST并非总是蕴含表单POST提交过来的数额。倘若大家透过
jQuery.ajax() 方法向服务器发送了POST请求:

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

  注意代码中的 contentType: ‘application/json’
,我们是以json数据格式来发送的数码。在服务端,我们仅输出$_POST数组:

1
2
// php
var_dump($_POST);

  你会很愕然的发现,结果是底下所示:

1
array(0) { }

  为啥是这么的结果吗?我们的json数据 {a: ‘a’, b: ‘b’} 哪去了啊?

  答案就是PHP仅仅解析Content-Type为 application/x-www-form-urlencoded

multipart/form-data的Http请求。之所以这么是因为历史原因,PHP最初完结$_POST时,最风靡的就是上边两种类型。因而就算未来有些项目(比如application/json)很盛行,但PHP中要么尚未去落实活动处理。

  因为$_POST是全局变量,所以更改$_POST会全局有效。由此对于Content-Type为
application/json
的伸手,大家要求手工去解析json数据,然后修改$_POST变量。

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

  此时,大家再去输出$_POST变量,则会得到大家期望的输出:

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

普遍错误 #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" }

广阔错误 #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』会被转换成 『Schr\u00f6dinger』),但是
    serialize() 不会 转换。
  • 管教 PHP 文件也是 UTF-8
    编码,以避免在接连硬编码字符串可能配置字符串常量的时候爆发冲突。

Francisco
Claria
在本博客上登载的 UTF-8 Primer for PHP and
MySQL
是份难得的资源。

 错误8:认为PHP协助字符数据类型

  看看上面的代码,估计下会输出什么:

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

  即使您的答应是出口’a’到’z’,那么你会好奇的发现你的应对是荒谬的。

  不错,上面的代码的确会输出’a’到’z’,但除去,还会输出’aa’到’yz’。大家来分析下怎么会是这般的结果。

 

  在PHP中不设有char数据类型,唯有string类型。精晓这一点,那么对’z’进行递增操作,结果则为’aa’。对于字符串相比较大小,学过C的应有都精通,’aa’是低于’z’的。那也就解释了干吗会有地点的出口结果。

  若是大家想出口’a’到’z’,下边的贯彻是一种科学的章程:

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

  或许那样也是OK的:

1
2
3
4
5
$letters = range('a', 'z');
 
for ($i = 0; $i < count($letters); $i++) {
    echo $letters[$i] . "\n";
}

  你猜对了啊?为什么是以此结果吗?

普遍错误 #8: 认为 PHP 援救单字符数据类型

读书下面的代码并盘算会输出什么:

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

假设您的答案是 az,那么您或者会对那是一个错误答案感到吃惊。

科学,它实在会输出 az,可是,它还会一连输出 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 里 有适用的 格局在循环中输出 az 的值:

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";
}

常见错误 #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" }

 错误9:忽略编码标准

  虽说忽略编码标准不会招致错误大概bug,但根据一定的编码标准仍然很要紧的。

  没有统一的编码标准会使您的门类出现过多难点。最显眼的就是您的品种代码不持有一致性。更坏的地方在于,你的代码将越来越不便调试、扩大和维护。这也就意味着你的团伙作用会降低,包涵做一些众多无意义的劳顿。

  对于PHP开发者来说,是比较幸运的。因为有PHP编码标准推荐(PS哈弗),由上面5个部分组成:

  • PSLAND-0:自动加载标准
  • PS普拉多-1:基本编码标准
  • PS凯雷德-2:编码风格指南
  • PS哈弗-3:日志接口标准
  • PSPAJERO-4:自动加载

  PS本田UR-V最初由PHP社区的几个大的团队所开创并按照。Zend, Drupal, Symfony,
Joomla及其他的阳台都为此规范做过进献并按照那几个专业。固然是PEAPRADO,早些年也想让投机成为一个正规,但以后也参预了PS奥迪Q3阵营。

  在某些情状下,使用什么编码标准是开玩笑的,只要你拔取一种编码风格并一向坚称利用即可。可是依照PS卡宴标准不失为一个好措施,除非您有怎样特殊的缘由要
自身弄一套。以往进一步多的体系都起来应用PSLacrosse,超过半数的PHP开发者也在行使PS宝马X5,由此拔取PS本田UR-V会让新参与你团队的分子更快的熟识项目,写代码时
也会愈来愈舒畅女士。

  大家来分析下。第二个巡回过后,$value是数组中最后一个因素的引用。首个循环开头:

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

即便忽视代码标准并不直接导致急需去调节 PHP
代码,但那或者是持有需求研商的事情里最主要的一项。

在一个品种中忽视代码规范可以造成大气的题目。最无忧无虑的预测,前后代码不雷同(在此之前各个开发者都在“做和好的工作”)。但最差的结果,PHP
代码不可以运作依然很难(有时是不容许的)去顺遂经过,那对于
调试代码、升高质量、维护项目以来也是辛坚苦苦。并且那代表降低你们团队的生产力,增添大气的附加(大概至少是本不必要的)精力消耗。

碰巧的是对于 PHP 开发者来说,存在 PHP
编码标准指出(PS奥迪Q3),它由下边的八个正经结合:

  • PSR-0: 自动加载标准
  • PSR-1: 基础编码标准
  • PSR-2: 编码风格辅导
  • PSR-3: 日志接口
  • PSR-4: 自动加载增强版

PSHaval 开首是由市场上最大的团伙平台维护者创建的。 Zend, Drupal, Symfony,
Joomla
和 其他 为那么些规范做出了进献,并直接依照它们。甚至,多年前准备成为一个标准的
PEA福睿斯 ,未来也加盟到 PSSportage 中来。

某种意义上,你的代码标准是怎么样大约是不重大的,只要你依照一个业内并坚持不渝下去,但一般来讲,跟随
PS奥迪Q5是一个很正确的主意,除非你的体系上有其他令人难以抗拒的说辞。更加多的集体和类型正在听从PSKuga 。在那或多或少上,半数以上的 PHP 开发者完毕了共识,由此利用 PS哈弗代码标准,有利于使新加盟团队的开发者对您的代码标准感到特别的熟知与舒适。

广泛错误 #8: 认为 PHP 协理单字符数据类型

读书上边的代码并考虑会输出什么:

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

比方你的答案是 az,那么您只怕会对那是一个不当答案感到吃惊。

毋庸置疑,它的确会输出 az,可是,它还会继续输出 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

那也是干吗上面那段简单的代码会输出 az, 然后 继续 输出
aayz。 它停在了 za,那是它碰着的率先个比 z 的:

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

事实上,在 PHP 里 有适当的 方式在循环中输出 az 的值:

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";
}

 错误10:错误使用empty()函数

  一些PHP开发人员喜欢用empty()函数去对变量或表明式做布尔判断,但在好几意况下会令人很怀疑。

  首先大家来探视PHP中的数组Array和数组对象ArrayObject。看上去好像没什么差异,都以均等的。真的这么吗?

1
2
3
4
5
6
// PHP 5.0 or later:
$array = [];
var_dump(empty($array));        // outputs bool(true)  
$array = new ArrayObject();
var_dump(empty($array));        // outputs bool(false)
// why don't these both produce the same output?

  让事情变得更扑朔迷离些,看看下边的代码:

1
2
3
4
5
// Prior to PHP 5.0:
$array = [];
var_dump(empty($array));        // outputs bool(false)  
$array = new ArrayObject();
var_dump(empty($array));        // outputs bool(false)

  很不佳的是,上边那种方法很受欢迎。例如,在Zend Framework
2中,Zend\Db\TableGateway 在 TableGateway::select() 结果集上调用
current() 方法重回数据集时就是那样干的。开发人士很不难就会踩到那一个坑。

  为了防止那几个标题,检查一个数组是不是为空最终的法子是用 count() 函数:

1
2
3
4
5
// Note that this work in ALL versions of PHP (both pre and post 5.0):
$array = [];
var_dump(count($array));        // outputs int(0)
$array = new ArrayObject();
var_dump(count($array));        // outputs int(0)

  在这顺便提一下,因为PHP中会将数值0认为是布尔值false,因而 count()
函数可以一向用在 if
条件语句的原则判断中来判断数组是不是为空。此外,count()
函数对于数组来说复杂度为O(1),由此用 count() 函数是一个明智的挑三拣四。

  再来看一个用 empty() 函数很惊险的例证。当在魔术点子 __get()
中组成使用 empty()
函数时,也是很危险的。大家来定义七个类,各种类都有一个 test 属性。

  首先我们定义 Regular 类,有一个 test 属性:

1
2
3
4
class Regular
{
    public $test = 'value';
}

  然后大家定义 Magic 类,并用 __get() 魔术点子来做客它的 test 属性:

1
2
3
4
5
6
7
8
9
10
11
class Magic
{
    private $values = ['test' => 'value'];
 
    public function __get($key)
    {
        if (isset($this->values[$key])) {
            return $this->values[$key];
        }
    }
}

  好了。大家以往来探望访问各样类的 test 属性会爆发什么:

1
2
3
4
$regular = new Regular();
var_dump($regular->test);    // outputs string(4) "value"
$magic = new Magic();
var_dump($magic->test);      // outputs string(4) "value"

  到近期甘休,都照旧正常的,没有让大家感到头晕。

  但在 test 属性上运用 empty() 函数会怎样啊?

1
2
var_dump(empty($regular->test));    // outputs bool(false)
var_dump(empty($magic->test));      // outputs bool(true)

  结果是或不是很想得到?

  很不幸的是,假使一个类使用魔法 __get()
函数来访问类属性的值,没有简单的措施来检查属性值是还是不是为空或是不存在。在类功效域外,你不得不反省是否再次来到null 值,但那并不一定意味着没有安装相应的键,因为键值能够被设置为 null

  相比较之下,假如大家走访 Regular
类的一个不存在的性质,则会赢得一个好像下边的Notice音信:

1
2
3
4
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()
函数,大家要小心的应用,要不然的话就会结出竟然,甚至秘密的误导你。

第一步:复制arr[0]到value(注意此时value是arr[2]的引用),这时数组变成[1,2,1]
第二步:复制arr[1]到value,那时数组变成[1,2,2]
第三步:复制arr[2]到value,那时数组变成[1,2,2]
  综上,最后结果就是1,2,2

普遍错误 #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()
方法应该被谨慎地使用,因为即使不小心的话它或然造成混乱 — 甚至秘密的误导
— 结果。

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

纵然忽视代码标准并不间接造成急需去调节 PHP
代码,但那或许是拥有必要探究的事情里最要紧的一项。

在一个档次中不经意代码规范能够导致大批量的题材。最乐观的估量,前后代码不相同(在此此前每一个开发者都在“做协调的政工”)。但最差的结果,PHP
代码无法运作依旧很难(有时是不容许的)去顺遂经过,这对于
调试代码、升高质量、维护项目来说也是惨淡。并且那意味下降你们团队的生产力,扩张大气的额外(或许至少是本不须求的)精力消耗。

万幸的是对此 PHP 开发者来说,存在 PHP
编码标准提议(PS本田CR-V),它由下边的三个专业结合:

  • PSR-0:
    自动加载标准
  • PSR-1:
    基础编码标准
  • PSR-2:
    编码风格指引
  • PSR-3:
    日志接口
  • PSR-4:
    自动加载增强版

PS汉兰达 起先是由市场上最大的协会平台维护者创立的。 Zend, Drupal, Symfony,
Joomla 和
其他
为那个专业做出了孝敬,并直接坚守它们。甚至,多年前准备成为一个正规的
PEA途乐 ,以往也参与到 PSHighlander 中来。

某种意义上,你的代码标准是什么样大约是不主要的,只要你依据一个规范并锲而不舍下去,但一般来讲,跟随
PS酷路泽是一个很科学的呼声,除非您的类型上有其他令人难以抗拒的理由。愈来愈多的团社团和档次正在听从PS奔驰M级 。在那或多或少上,一大半的 PHP 开发者已毕了共识,因而使用 PSRAV4代码标准,有利于使新参与团队的开发者对您的代码标准感到尤其的耳熟能详与舒适。

  防止那种错误最好的艺术就是在循环后立刻用unset函数销毁变量:

总结

PHP
的易用性让开发者陷入一种虚假的舒适感,语言本人的有的细微差异和特质,大概开支掉你大量的日子去调节。这么些或者会导致
PHP 程序不能正常工作,并造成诸如此处所述的题材。

PHP
在其20年的野史中,已经发生了分明的转变。花时间去熟谙语言本身的微妙之处是值得的,因为它助长保障您编写的软件更具可扩充性,健壮和可维护性。

越多现代化 PHP 知识,请前往 Laravel / PHP
知识社区

广阔错误 #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()
方法应该被谨慎地使用,因为若是不小心的话它可能导致混乱 — 甚至秘密的误导
— 结果。

$arr = array(1, 2, 3, 4); 
foreach ($arr as &$value) { 
  $value = $value * 2; 
} 
unset($value);  // $value no longer references $arr[3]

总结

PHP
的易用性让开发者陷入一种虚假的舒适感,语言本身的一对细微差别和特质,只怕费用掉你大批量的时间去调节。这个可能会导致
PHP 程序无法正常干活,并造成诸如此处所述的难题。

PHP
在其20年的历史中,已经爆发了明显的生成。花时间去熟稔语言自个儿的奥妙之处是值得的,因为它助长确保您编写的软件更具可伸张性,健壮和可维护性。

更加多现代化 PHP 知识,请前往 Laravel / PHP
知识社区

 错误2:对isset()函数行为的谬误通晓

  对于isset()函数,变量不设有时会重临false,变量值为null时也会回去false。那种表现很不难把人弄迷糊。。。看下边的代码:

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

  写那段代码的人本意可能是只要data[′keyShouldBeSet′]未设置,则履行相应逻辑。但难点在于就是data[‘keyShouldBeSet’]已安装,但设置的值为null,如故会履行相应的逻辑,那就不切合代码的本意了。

  上面是其它一个例证:

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

// ... 

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

  上边的代码假若POST[′active′]为真,那么postData应该被安装,因而isset(postData)会回到true。反之,上面代码假如isset(postData)重临false的绝无仅有途径就是$_POST[‘active’]也返回false。

  真是如此啊?当然不是!

  即使POST[′active′]回来true,postData也有大概被安装为null,那时isset($postData)就会回到false。那就不相符代码的本心了。

  假若下面代码的原意仅是检测$_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 
}

  另外,结合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() returns a COPY of the $values array, so this adds a 'test' element 
// to a COPY of the $values array, but not to the $values array itself. 
$config->getValues()['test'] = 'test'; 

// getValues() again returns ANOTHER COPY of the $values array, and THIS copy doesn't 
// contain a 'test' element (which is why we get the "undefined index" message). 
echo $config->getValues()['test'];

  上边是一种恐怕的化解办法,输出拷贝的数组,而不是原数组:

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

  假若你尽管想要改变原数组,也等于要反回数组引用,那应该如何处理啊?办法就是展现指定重返引用即可:

class Config 
{ 
  private $values = []; 

  // return a REFERENCE to the actual $values array 
  public function &getValues() { 
    return $this->values; 
  } 
} 

$config = new Config(); 

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

  经过改造后,下边代码将会像你指望那样会输出test。

  我们再来看一个例子会让您更迷糊的例证:

class Config 
{ 
  private $values; 

  // using ArrayObject rather than array 
  public function __construct() { 
    $this->values = new ArrayObject(); 
  } 

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

$config = new Config(); 

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

  若是您想的是会和方面一样输出 Undefined index
错误,那您就错了。代码会正常输出 test
。原因在于PHP对于目的暗中认同就是按引用再次来到的,而不是按值重返。

  综上所述,大家在行使函数再次回到值时,要弄精晓是值重返仍然引用重回。PHP中对此目的,暗中同意是援引再次来到,数组和停放基本类型暗中同意均按值重返。这么些要与此外语言分化开来(很多语言对于数组是引用传递)。

  像其余语言,比如java或C#,利用getter或setter来访问或设置类属性是一种更好的方案,当然PHP暗许不支持,需求本身完成:

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');  // echos 'testValue'

  下面的代码给调用者可以访问或安装数组中的任意值而不用给与数组public访问权限。感觉什么:)

 错误4:在循环中履行sql查询

  在PHP编程中发觉类似上边的代码并不少见:

$models = []; 

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

  当然下面的代码是未曾什么样错误的。难题在于大家在迭代经过中$valueRepository->findByValue()只怕每一遍都推行了sql查询:

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

  假诺迭代了10000次,那么您就各自实施了10000次sql查询。如若那样的剧本在四线程程序中被调用,那很恐怕您的系统就挂了。。。

  在编写代码进度中,你应该要明了哪些时候理应推行sql查询,尽恐怕两次sql查询取出所有数据。

  有一种工作场景,你很大概会犯上述荒唐。借使一个表单提交了一连串值(如若为IDs),然后为了取出所有ID对应的数额,代码将遍历IDs,分别对各样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:内存使用低效和错觉

  两遍sql查询得到多条记下比每回查询拿到一条记下作用肯定要高,但一旦您利用的是php中的mysql增添,那么两回拿走多条记下就很只怕会招致内存溢出。

  大家得以写代码来实验下(测试环境: 512MB RAM、MySQL、php-cli):

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

// create table of 400 columns 
$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); 

// write 2 million rows 
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); 
}

  未来来探望资源消耗:

// connect to 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

  依据内存使用量来看,貌似一切正常。为了尤其确定,试着一遍拿走100000条记下,结果程序取得如下输出:

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

  这是怎么回事呢?

  问题出在php的mysql模块的做事格局,mysql模块实际上就是libmysqlclient的一个代理。在查询拿到多条记下的还要,那么些记录会直接保存在内存中。由于那块内存不属于php的内存模块所管理,所以大家调用memory_get_peak_usage()函数所得到的值并非真正使用内存
值,于是便冒出了上边的标题。

  大家可以运用mysqlnd来顶替mysql,mysqlnd编译为php自己增添,其内存使用由php内存管理模块所控制。若是大家用mysqlnd来完成地点的代码,则会愈加真实的反应内存使用景况:

Before: 232048 
Limit 1: 324952 
Limit 10000: 32572912

  尤其糟糕的是,依照php的合法文档,mysql增添存储查询数据运用的内存是mysqlnd的两倍,因而原来的代码应用的内存是下边显示的两倍左右。

  为了幸免此类题材,可以设想分一回到位查询,减小单次查询数据量:

$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得以观望,在其实的编码进程中,要完结一种平衡,才能既满意功效须求,又能担保质量。

 错误6:忽略Unicode/UTF-8问题

  php编程中,在处理非ascii字符时,会境遇有的题目,要很小心的去对待,要不然就会错误四处。举个简单的事例,strlen(name),假如name包蕴非ascii字符,那结果就不怎么意外。在此付出一些指出,尽量幸免此类难点:

万一您对unicode和utf-8不是很精通,那么你至少应当了然一些基础。推荐阅读那篇小说。
最好利用mb_*函数来拍卖字符串,防止采用老的字符串处理函数。那里要保险PHP的
multibyte 扩充已打开。
数据库和表最好利用unicode编码。
知道jason_code()函数会转换非ascii字符,但serialize()函数不会。
php代码源文件最好应用不含bom的utf-8格式。
  在此推荐一篇小说,更详实的牵线了此类难点: UTF-8 Primer for PHP and
MySQL

 错误7:假定$_POST总是包涵POST数据

  PHP中的$_POST并非总是包罗表单POST提交过来的数量。若是大家因而jQuery.ajax() 方法向服务器发送了POST请求:

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

  注意代码中的 contentType: ‘application/json’
,大家是以json数据格式来发送的数据。在服务端,我们仅输出$_POST数组:

// php 
var_dump($_POST);

  你会很愕然的发现,结果是上边所示:

array(0) { }

  为啥是如此的结果吗?大家的json数据 {a: ‘a’, b: ‘b’} 哪去了吗?

  答案就是PHP仅仅解析Content-Type为 application/x-www-form-urlencoded

multipart/form-data的Http请求。之所以如此是因为历史由来,PHP最初落成$_POST时,最盛行的就是地方两连串型。因而即使将来有些序列(比如application/json)很盛行,但PHP中恐怕没有去贯彻自动处理。

  因为POST是全局变量,所以更改_POST会全局有效。因而对于Content-Type为
application/json
的请求,大家要求手工去解析json数据,然后修改$_POST变量。

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

  此时,大家再去输出$_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类型。精晓那一点,那么对’z’举办递增操作,结果则为’aa’。对于字符串相比较大小,学过C的应当都精通,’aa’是稍低于’z’的。那也就解释了为啥会有上边的出口结果。

  如果咱们想出口’a’到’z’,下边的落实是一种科学的措施:

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

  恐怕那样也是OK的:

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

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

 错误9:忽略编码标准

  虽说忽略编码标准不会促成错误恐怕bug,但按照一定的编码标准依然很要紧的。

  没有统一的编码标准会使你的项目出现过多题材。最明确的就是您的品种代码不负有一致性。更坏的地点在于,你的代码将进一步难以调试、增添和爱抚。那也就代表你的协会功效会稳中有降,包罗做一些居多无意义的分神。

  对于PHP开发者来说,是比较幸运的。因为有PHP编码标准推荐(PS奥迪Q5),由上边5个部分构成:

PS奥迪Q3-0:自动加载标准
PSCR-V-1:基本编码标准
PSRAV4-2:编码风格指南
PSGL450-3:日志接口标准
PS冠道-4:自动加载
  PSMurano最初由PHP社区的几个大的团体所开创并依照。Zend, Drupal, Symfony,
Joomla及任何的平台都为此标准做过奉献并按照那几个正式。固然是PEA库罗德,早些年也想让本人变成一个标准,但以往也进入了PS哈弗阵营。

  在某些情形下,使用什么编码标准是可有可无的,只要你利用一种编码风格并一贯坚称利用即可。可是依据PS福睿斯标准不失为一个好方法,除非您有哪些越发的来由要
本身弄一套。将来更是多的品类都从头接纳PSR,超过半数的PHP开发者也在行使PS卡宴,因而利用PSPRADO会让新参与你团队的分子更快的耳熟能详项目,写代码时
也会进一步舒畅女士。

 错误10:错误使用empty()函数

  一些PHP开发人士喜欢用empty()函数去对变量或表明式做布尔判断,但在少数意况下会令人很困惑。

  首先大家来探望PHP中的数组Array和数组对象ArrayObject。看上去好像没什么不一致,都以同一的。真的这么吧?

// PHP 5.0 or later: 
$array = []; 
var_dump(empty($array));    // outputs bool(true) 
$array = new ArrayObject(); 
var_dump(empty($array));    // outputs bool(false) 
// why don't these both produce the same output?

  让事情变得更扑朔迷离些,看看上面的代码:

// Prior to PHP 5.0: 
$array = []; 
var_dump(empty($array));    // outputs bool(false) 
$array = new ArrayObject(); 
var_dump(empty($array));    // outputs bool(false)

  很不佳的是,下面那种格局很受欢迎。例如,在Zend Framework
2中,Zend\Db\TableGateway 在 TableGateway::select() 结果集上调用
current() 方法重临数据集时就是如此干的。开发人员很不难就会踩到那么些坑。

  为了防止这个题材,检查一个数组是还是不是为空最终的方式是用 count() 函数:

// Note that this work in ALL versions of PHP (both pre and post 5.0): 
$array = []; 
var_dump(count($array));    // outputs int(0) 
$array = new ArrayObject(); 
var_dump(count($array));    // outputs int(0)

  在那顺便提一下,因为PHP中会将数值0认为是布尔值false,因而 count()
函数可以直接用在 if
条件语句的标准判断中来判定数组是或不是为空。此外,count()
函数对于数组来说复杂度为O(1),因此用 count() 函数是一个精明的拔取。

  再来看一个用 empty() 函数很危险的事例。当在魔术点子 __get()
中结成使用 empty()
函数时,也是很惊险的。咱们来定义多个类,每一个类都有一个 test 属性。

  首先我们定义 Regular 类,有一个 test 属性:

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);  // outputs string(4) "value" 
$magic = new Magic(); 
var_dump($magic->test);   // outputs string(4) "value"

  到近来甘休,都仍然正常的,没有让大家觉得头晕目眩。

  但在 test 属性上行使 empty() 函数会怎么着呢?

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

  结果是还是不是很想得到?

  很糟糕的是,如果一个类应用魔法 __get()
函数来访问类属性的值,没有不难的法子来检查属性值是还是不是为空或是不存在。在类作用域外,你不得不反省是还是不是重临null 值,但那并不一定意味着没有安装相应的键,因为键值可以被安装为 null

  相比较之下,假设大家访问 Regular
类的一个不存在的品质,则会拿走一个类似上面的Notice新闻:

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()
函数,大家要小心的施用,要不然的话就会结出意外,甚至秘密的误导你。

发表评论

电子邮件地址不会被公开。 必填项已用*标注

网站地图xml地图