浅析PHP编程中拾个最常见的荒谬,开发者最不难犯的一无可取

By admin in 4858.com on 2019年2月19日

4858.com 1

4858.com 2

PHP是一种很是流行的开源服务器端脚本语言,你在万维网看到的绝大部分网站都以采纳php开发的。本篇经将为大家介绍PHP开发中13个最广泛的难题,希望可以对仇人有所协理。

现阶段上学PHP很多情人,在经常的普通程序开发工程中总会赶上各类种种的难点,本篇经验将为我们介绍PHP开发中1贰个最广泛的标题,希望可以对朋友有所辅助。

PHP 语言让 WEB
端程序设计变得简单,那也是它能流行起来的来由。但也是因为它的简易,PHP
也日益前进成1个周旋复杂的言语,不足为奇的框架,各样语言特征和版本差别都日常让搞的大家头大,不得不浪费多量时日去调节。那篇文章列出了十一个最简单出错的位置,值得我们去注意。

file

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

  在foreach循环中,假诺大家须求改变迭代的因素或是为了提升功效,运用引用是3个好办法:

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
仍保留在成效域内,并保留着对数组最终1个成分的引用。之后与 $value
相关的操作会无意中修改数组中最终一个要素的值。

您要铭记 foreach 并不会爆发3个块级功用域。因而,在下面例子中 $value
是2个大局引用变量。在 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 ,为啥?

在成功第1个 foreach 遍历后, $array
并不曾更改,可是像上述解释的那么, $value 留下了2个对 $array
最终多少个因素的危险的引用(因为 foreach 通过引用得到 $value

那造成当运维到第2个 foreach ,那几个”奇怪的东西”暴发了。当 $value4858.com
通过赋值拿到, foreach 按梯次复制每一个 $array 的成分到 $value
时,第二个 foreach 里面的底细是这么的

  • 第一步:复制 $array[0] (也就是 1 )到 $value$value 其实是
    $array末段1个因素的引用,即 $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]浅析PHP编程中拾个最常见的荒谬,开发者最不难犯的一无可取。 以往拾叁分 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,照旧会执行相应的逻辑,那就不符合代码的本心了。

  上面是此外2个例证:

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

对此那种景观,纵然检查3个变量是还是不是真正存在很紧要(即:区分一个变量是未被安装依旧被安装为 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
仍保存在听从域内,并保留着对数组最后2个成分的引用。之后与 $value
相关的操作会无意中修改数组中最终一个因素的值。

你要切记 foreach 并不会生出1个块级效能域。由此,在地点例子中 $value
是三个大局引用变量。在 foreach 遍历中,每三遍迭代都会形成贰个对 $arr
下贰个成分的引用。当遍历停止后, $value 会引用 $arr
的尾声2个成分,并保存在成效域中

那种表现会促成一些正确觉察的,令人猜疑的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

您没有看错,最后一行的终极3个值是 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 通过引用再次来到2个数组(例如,使用 &),否则 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数组。即便您想要修改原始的数组(例如添加1个test要素),就须要修改getValues()函数,让它回到一个$values数组自个儿的引用。通过在函数名前边添加多少个&来验证那个函数将回到3个引用;例如:

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
文档,看看它是重返三个值,数组的正片,数组的引用或是对象的引用是少不了的。

虽说,大家要认识到应该尽量防止再次回到1个数组或
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);

  即使迭代了一千0次,那么你就各自实施了一千0次sql查询。倘若那样的脚本在三十二线程程序中被调用,这很或许您的系列就挂了。。。

  在编排代码进程中,你应该要精晓几时理应举办sql查询,尽大概一遍sql查询取出全体数据。

  有一种工作场景,你很只怕会犯上述荒唐。假诺2个表单提交了一体系值(倘若为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单独的伸手!如若这么的台本在七个线程中被调用,他会有导致系统崩溃的机要危险。

故此,非常首要的是,当你的代码要拓展询问时,应该尽量的采集要求使用的值,然后在一个询问中拿走具有结果。

2个我们平昔时常能见到查询功能低下的地方(例如:在循环中)是选拔多少个数组中的值 (比如说很多的 ID
)向表发起呼吁。检索每3个 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个易犯的错误…

广泛错误 #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数组。倘诺您想要修改原始的数组(例如添加3个test要素),就须要修改getValues()函数,让它回到三个$values数组本身的引用。通过在函数名后面添加三个&来验证这些函数将回到1个引用;例如:

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
文档,看看它是回到2个值,数组的正片,数组的引用或是对象的引用是必不可少的。

虽说,我们要认识到应有尽量防止再次来到一个数组或
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

  依照内存使用量来看,貌似一切不奇怪。为了进一步分明,试着五次拿到一千00条记下,结果程序获取如下输出:

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的3个代理。在查询拿到多条记下的同时,那么些记录会直接保存在内存中。由于那块内存不属于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 那些剧本真正使用的内存比这里浮现的更加多(大概是两倍)。

为了幸免出现那种题材,考虑范围一下您询问的多少,使用1个较小的数字来循环,像那样:

$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 结合起来考虑的时候,
就会发现到大家的代码可以需求在互相间落成2个平衡。是让查询粒度化和重复化,依旧让单个查询巨大化。生活亦是那般,平衡不可或缺;哪2个可是都不佳,都或者会招致
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单独的伏乞!借使那样的本子在多少个线程中被调用,他会有导致系统崩溃的私房危险。

于是,相当紧要的是,当您的代码要拓展查询时,应该尽或者的征集须要运用的值,然后在多个查询中收获具有结果。

贰个大家平时平日能收看查询功用低下的地方(例如:在循环中)是利用3个数组中的值 (比如说很多的 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)
 而变得臭名昭著。当1个名字包括『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
扩充的时候,这也只怕成为3个导致 libmysqlclient 出现『内存不足』(out
of memory)的原则。

咱俩在3个测试盒里演示一下,该测试盒的条件是:有限的内存(512MB
RAM),MySQL,和 php-cli

大家将像上面这样率领2个数据表:

// 连接 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
那1个剧本真正使用的内存比那里显得的更多(大概是两倍)。

为了防止出现那种难点,考虑范围一下你询问的数码,使用二个较小的数字来循环,像这么:

$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()
模拟3个劳务请求,如下:

// 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 只会活动分析1个 POST
的实惠内容。那其中有历史的原委 — 那三种内容类型是在 PHP 的 $_POST
完结前就已经在采用了的两个关键的项目。所以随便采用其余任何内容类型
(即便是那么些以往很流行的,像 application/json), PHP 也不会自行加载到
POST 的管事内容。

既然 $_POST 是七个一级全局变量,假若大家重写 一次
(在大家的剧本里尽量早的),被涂改的值(包罗 POST
的有效内容)将能够在我们的代码里被引述。那很关键因为 $_POST 已经被 PHP
框架和大致拥有的自定义的剧本普遍使用来赢得和传递请求数据。

故而,举个例子,当处理2个故事情节类型为 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 在 2008 年的中止而闲置了。

这并不意味开发者可以幸免 正确处理
UTF-8
并幸免做出全部字符串必须是『古老的 ASCII』的比方。 没有正确处理非 ASCII
字符串的代码会因为引入粗糙的
海森堡bug(heisenbugs)
而变得臭名昭著。当3个名字包蕴『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 只会活动分析3个 POST
的立见成效内容。那里面有历史的缘由 — 那两种内容类型是在 PHP 的 $_POST
完毕前就已经在选用了的两个重要的档次。所以不管选用此外任何内容类型
(尽管是那多少个以后很盛行的,像 application/json), PHP 也不会自行加载到
POST 的有效性内容。

既然 $_POST 是一个顶级级全局变量,假诺大家重写 一次
(在大家的脚本里尽量早的),被改动的值(包含 POST
的立见成效内容)将可以在大家的代码里被引述。那很重点因为 $_POST 已经被 PHP
框架和大约拥有的自定义的本子普遍选择来博取和传递请求数据。

据此,举个例证,当处理1个内容类型为 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编码标准推荐(PSMurano),由上面多个部分构成:

  • PSPRADO-0:自动加载标准
  • PSPAJERO-1:基本编码标准
  • PS奥迪Q7-2:编码风格指南
  • PSPRADO-3:日志接口标准
  • PS福睿斯-4:自动加载

  PS哈弗最初由PHP社区的多少个大的团队所开创并根据。Zend, Drupal, Symfony,
Joomla及其余的阳台都为此规范做过奉献并坚守这么些标准。尽管是PEA途胜,早些年也想让祥和成为1个正式,但近年来也加盟了PSOdyssey阵营。

  在少数情状下,使用什么编码标准是微不足道的,只要您利用一种编码风格并间接坚定不移使用即可。不过依据PSEscort标准不失为3个好点子,除非你有何极度的原由要
自个儿弄一套。今后越来越多的类型都伊始使用PSEvoque,半数以上的PHP开发者也在应用PS酷路泽,由此选用PS中华V会让新进入你团队的分子更快的熟识项目,写代码时
也会愈发舒适。

  大家来分析下。第③个循环过后,$value是数组中最后3个要素的引用。第2个巡回起来:

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

尽管忽视代码标准并不直接导致急需去调节 PHP
代码,但那大概是具备须要切磋的事情里最重视的一项。

在2个品种中忽略代码规范可以造成大气的难点。最有望的预测,前后代码不雷同(在此以前各个开发者都在“做团结的作业”)。但最差的结果,PHP
代码无法运维依旧很难(有时是不容许的)去顺遂经过,这对于
调试代码、进步质量、维护项目以来也是困难重重。并且那意味着降低你们团队的生产力,扩大大气的附加(或许至少是本不须求的)精力消耗。

幸亏的是对此 PHP 开发者来说,存在 PHP
编码标准提出(PSHaval),它由下边的多个正式结合:

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

PSLAND 初步是由市场上最大的团体平台维护者成立的。 Zend, Drupal, Symfony,
Joomla
和 其他 为这一个规范做出了贡献,并向来按照它们。甚至,多年前总计成为三个正规的
PEALAND ,未来也投入到 PS路虎极光 中来。

某种意义上,你的代码标准是哪些大致是不首要的,只要您依照二个正经并持之以恒下去,但一般来讲,跟随
PS福睿斯是一个很不利的呼声,除非您的种类上有其余令人为难抗拒的说辞。越多的团协会和档次正在遵守PS奥迪Q5 。在那点上,一大半的 PHP 开发者落成了共识,由此使用 PS汉兰达代码标准,有利于使新投入团队的开发者对您的代码标准感到特别的耳熟能详与舒适。

广大错误 #8: 认为 PHP 帮忙单字符数据类型

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

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

设若你的答案是 az,那么您大概会对那是3个谬误答案感到吃惊。

正确,它的确会输出 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()
函数时,也是很危险的。大家来定义三个类,各种类都有2个 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
类的3个不存在的品质,则会收获二个接近下边的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"

到近来截止幸好。

可是以后当大家对中间的每3个都调用 empty() ,让大家看看会暴发什么:

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

嗳。所以只要我们借助 empty() ,我们很恐怕误认为 $magic 的属性 test
是空的,而其实它被安装为 'value'

噩运的是,若是类应用魔术点子 __get()
来获取属性值,那么就没有万无一失的艺术来检查该属性值是不是为空。
在类的功用域之外,你只是只可以反省是不是将回到1个 null
值,那并不代表没有设置相应的键,因为它实质上还只怕被装置为 null

反而,要是大家试图去引用 Regular
类实例中不存在的品质,我们将收获3个接近于以下内容的通告:

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途胜),它由下边的八个正经结合:

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

PSPRADO 起先是由市镇上最大的团体平台维护者成立的。 Zend, Drupal, Symfony,
Joomla 和
其他
为这几个标准做出了进献,并向来遵守它们。甚至,多年前总括成为三个正规的
PEALAND ,以往也进入到 PSEscort 中来。

某种意义上,你的代码标准是什么大致是不重大的,只要你依据1个规范并持之以恒下去,但一般来讲,跟随
PSEscort是二个很正确的呼声,除非你的体系上有其他令人难以抗拒的理由。越多的团社团和档次正在听从PSOdyssey 。在那或多或少上,一大半的 PHP 开发者完成了共识,由此利用 PSRubicon代码标准,有利于使新加盟团队的开发者对您的代码标准感到尤其的熟谙与舒适。

  幸免这种错误最好的方法就是在循环后旋即用unset函数销毁变量:

总结

PHP
的易用性让开发者陷入一种虚假的舒适感,语言自个儿的片段细微差距和特质,只怕开销掉你大量的小运去调节。那个或者会造成
PHP 程序无法常常干活,并致使诸如此处所述的难题。

PHP
在其20年的历史中,已经发生了强烈的生成。花时间去熟知语言本人的奥妙之处是值得的,因为它助长保险您编写的软件更具可增加性,健壮和可维护性。

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

常见错误 #10: 滥用 empty()

一些 PHP 开发者喜欢对几乎全部的工作使用 empty()
做布尔值检验。不过,在局部状态下,那会促成混乱。

第1,让大家回到数组和 ArrayObject
实例(和数组类似)。考虑到她们的相似性,很不难假若它们的一言一动是均等的。然则,事实讲明那是1个危险的假若。举例,在
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,照旧会履行相应的逻辑,这就不合乎代码的原意了。

  下边是其余1个例证:

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

  判断3个变量是还是不是确实被装置(区分未设置和设置值为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。

  大家再来看1个事例会让您更迷糊的事例:

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);

  假设迭代了一千0次,那么您就分别实施了一千0次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(); 
}

  但同样的目标可以在1个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

  依照内存使用量来看,貌似一切日常。为了尤其分明,试着五回拿到一千00条记下,结果程序获取如下输出:

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瑞虎),由下边肆个部分构成:

PS奥迪Q7-0:自动加载标准
PSKuga-1:基本编码标准
PS大切诺基-2:编码风格指南
PS卡宴-3:日志接口标准
PS汉兰达-4:自动加载
  PS帕杰罗最初由PHP社区的多少个大的团社团所创办并遵从。Zend, Drupal, Symfony,
Joomla及任何的阳台都为此规范做过贡献并根据这一个标准。纵然是PEAHighlander,早些年也想让祥和成为一个标准,但现行也进入了PS中华V阵营。

  在有些情形下,使用什么编码标准是微不足道的,只要您利用一种编码风格并直接锲而不舍采取即可。可是按照PS智跑标准不失为2个好点子,除非您有怎么着独特的来由要
本身弄一套。以后更是多的档次都早先使用PSCR-V,大多数的PHP开发者也在选用PS凯雷德,由此利用PSMurano会让新加盟你团队的积极分子更快的熟习项目,写代码时
也会越来越酣畅。

 错误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() 函数是1个精明的选料。

  再来看3个用 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
类的多少个不存在的天性,则会获取2个好像下边的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地图
Copyright @ 2010-2019 美高梅手机版4858 版权所有