浅析PHP编程中10个最常见的错误

作者: 今晚开什么码  发布:2019-09-23

日前求学PHP非常多朋友,在常常的平凡程序开拓工程中总会遇到绚丽多彩标题目,本篇经验将为大家介绍PHP开拓中13个最常见的难题,希望能够对朋友有所援助。

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

  在foreach循环中,假设大家需求转移迭代的因素或是为了升高功能,运用引用是二个好措施:

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

  这里有个难点多多人会头昏。循环截止后,value并未有销毁,value其实是数组中最终贰个要素的引用,那样在再而三对$value的应用中,假若不知底那点,会掀起部分莫名美妙的荒唐:)看看上边这段代码:

$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,2

  你猜对了呢?为啥是以此结果吗?

  大家来深入分析下。第二个巡回过后,$value是数组中最后贰个因素的援用。第3个巡回起来:

第一步:复制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函数销毁变量:

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

 错误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次,那么你就分别实施了一千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(); 
}

  但一样的指标可以在贰个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编码标准推荐(PSWrangler),由上边5个部分构成:

PSKuga-0:自动加载规范
PS奥迪Q5-1:基本编码标准
PSLacrosse-2:编码风格指南
PS昂科威-3:日志接口标准
PS福特Explorer-4:自动加载
  PSLAND最先由PHP社区的多少个大的集体所创造并服从。Zend, Drupal, Symfony, Joomla及其他的阳台都为此标准做过贡献并依据那些职业。尽管是PEAQashqai,早些年也想让投机成为二个行业内部,但现行反革命也参与了PS奥迪Q5阵营。

  在好几意况下,使用什么编码标准是可有可无的,只要您使用一种编码风格并直接持之以恒使用就可以。可是依照PS奥迪Q5规范不失为二个好方式,除非您有如何独特的来由要 自身弄一套。未来更是多的门类都起来应用PSCRUISER,超过五成的PHP开垦者也在使用PSOdyssey,因而使用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中,ZendDbTableGateway 在 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() 函数,大家要当心的施用,要不然的话就能够结出意外,以致秘密的误导你。

本文由今晚开什么码发布于今晚开什么码,转载请注明出处:浅析PHP编程中10个最常见的错误

关键词:

上一篇:字符串的replace方法应用浅析
下一篇:没有了