巅云智能建站平台搭建版(创业流派版)火爆上线,毕生受权!新增:文章智能收罗+全站真静态打包+都会分站+智能小法式+不法词过滤+H5自顺应+智能链词等功效功效概况
赞助文档Help

浅析PHP编程中10个最罕见的毛病

一佰互联网站建造(www.taishanly.com) 宣布日期 2020-04-29 09:01:07 阅读数: 92

今朝进修PHP良多伴侣,在平常平凡的平常法式开辟工程中总会碰到各类百般的题目,本篇经历将为大师先容PHP开辟中10个最罕见的题目,但愿能够也许对伴侣有所赞助。

毛病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), "
"; 
 
foreach ($array as &$value) {}  // by reference 
echo implode(",", $array), "
"; 
 
foreach ($array as $value) {}   // by value (i.e., copy) 
echo implode(",", $array), "
";

  上面代码的运转成果以下:

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函数烧毁变量:

$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次,那末你就别离履行了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() . "
"; 
 
$res = $connection->query("SELECT `x`,`y` FROM `test` LIMIT 1"); 
echo "Limit 1: " . memory_get_peak_usage() . "
"; 
 
$res = $connection->query("SELECT `x`,`y` FROM `test` LIMIT 10000"); 
echo "Limit 10000: " . memory_get_peak_usage() . "
";

  输入成果以下:

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

  若是你的回覆是输入"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) . "
"; 
}

  或如许也是OK的:

$letters = range("a", "z"); 
 
for ($i = 0; $i < count($letters); $i++) { 
  echo $letters[$i] . "
"; 
}

 毛病9:疏忽编码规范

  虽然说疏忽编码规范不会致使毛病或是bug,但遵守必然的编码规范仍是很主要的。

  不同一的编码规范会使你的名目呈现良多题目。最较着的便是你的名目代码不具备分歧性。更坏的处所在于,你的代码将加倍难以调试、扩大和保护。这也就象征着你的团队效力会下降,包罗做一些良多有意思的休息。

  对PHP开辟者来讲,是比拟荣幸的。由于有PHP编码规范保举(PSR),由上面5个局部构成:

PSR-0:主动加载规范
PSR-1:根基编码规范
PSR-2:编码气概指南
PSR-3:日记接口规范
PSR-4:主动加载
  PSR最初由PHP社区的几个大的集体所建立并遵守。Zend, Drupal, Symfony, Joomla及别的的平台都为此规范做过进献并遵守这个规范。即便是PEAR,早些年也想让本身成为一个规范,但此刻也插手了PSR营垒。

  在某些环境下,操纵甚么编码规范是有关紧急的,只需你操纵一种编码气概并一向对峙操纵便可。可是遵守PSR规范不失为一个好体例,除非你有甚么特别的缘由要 本身弄一套。此刻愈来愈多的名目都起头操纵PSR,大局部的PHP开辟者也在操纵PSR,是以操纵PSR会让新插手你团队的成员更快的熟习名目,写代码时 也会加倍温馨。

 毛病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开辟和域名注册及假造主机办事经历,供给的办事更是天下着名。最近几年来还整合团队上风自立开辟了可视化多用户”“3.0平台版,拖拽排版网站建造设想,轻松实现pc站、手机微网站、小法式、APP一体化全网营销网站扶植 ,已胜利的为天下上百家收集公司供给自助建站平台搭建办事。

相干消息more

04
05月
SEO扼要教程

SEO这方面内容很,并且良多方面都很主要,并且每一个搜刮引擎的算法都是不一样,比方做链接的时辰,对Baidu对Pr与域名的请求时候就不主要,而... >>概况

26
03月
牛商网:百度小法式的这3大上风,此刻是入驻的

简介:流量时期,小法式热度居高不下。本日头条做了本身的小法式,世态炎凉的抖音频频试水小法式,百度智能小法式现也已面向用户开放请求。018年1... >>概况

07
04月
除专业过硬,高等设想师都得把握的5个才能素

咱们已或多或少看过一些优异作品,也晓得了一些设想体例和技能,领会到了一些相干的实际常识,本身也做过一些名目或是操练,整体来讲都把握了必然... >>概况

05
04月
淘宝经营:教你廉价开车引流,一周生效

简介:1、假设你想交换、进修淘宝开店技能,那末能够也许存眷私信我,答复关头词“进修”,就能够也许请求插手我建立的进修群,交换和进修开店的经历心得。2... >>概况

高端网站扶植

美工统筹SEO,为企业电子商务营销助力!

德律风:

023-85725751
建站

产物

域名注册 假造主机 云办事器 企业邮局
智能建站 APP打包 微站/小法式 创业平台
网站推行 媒体营销 智能收罗 AI机械人
400德律风 短信营销 店销机械人
私家定制 流派网站