课程咨询 :13623629309

太原PHP培训 > 达内新闻 > PHP编程中10个最常见的错误(下)
  • PHP编程中10个最常见的错误(下)

    发布:陈鹏个人博客      来源:陈鹏个人博客      时间:2016-10-26

  • PHP编程中10个最常见的错误(下)

    在编程的过程中有错误是在所难免的,我们应该尽量避免他们。太原php培训班带大家一起跳过。

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

    一次sql查询获取多条记录比每次查询获取一条记录效率肯定要高,但如果你使用的是php中的mysql扩展,那么一次获取多条记录就很可能会导致内存溢出。

    我们可以写代码来实验下(测试环境: 512MB RAMMySQLphp-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

    这是怎么回事呢?

    问题出在phpmysql模块的工作方式,mysql模块实际上就是libmysqlclient的一个代理。在查询获取多条记录的同时,这些记录会直接 保存在内存中。由于这块内存不属于php的内存模块所管理,所以我们调用memory_get_peak_usage()函数所获得的值并非真实使用内存 值,于是便出现了上面的问题。

    我们可以使用mysqlnd来代替mysqlmysqlnd编译为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字符,那结果就有些出乎意料。在此给出一些建议,尽量避免此类问题:

    如果你对unicodeutf-8不是很了解,那么你至少应该了解一些基础。推荐阅读 这篇文章

    最好使用mb_*函数来处理字符串,避免使用老的字符串处理函数。这里要确保PHP“multibyte”扩展已开启。

    数据库和表最好使用unicode编码。

    知道jason_code()函数会转换非ascii字符,但serialize()函数不会。

    php代码源文件最好使用不含bomutf-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-dataHttp请求。之所以这样是因为历史原因,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编码标准推荐(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中,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() 函数,我们要小心的使用,要不然的话就会结果出乎意料,甚至潜在的误导你。

    好了,今天就给大家讲这么多吧,喜欢我的内容可以关注或者分享(微信公众平台:tytedu)选择太原达内培训,不再孤军奋战,轻轻松松做IT高薪白领。太原达内培训带领有明确目标的学子迈向成功之路!

上一篇:PHP编程中10个最常见的错误(上)

下一篇:互联网集体宕机事件,我们该反思什么?

最新开班日期  |  更多

php高级开发名企定制班(剩2个名额)

php高级开发名企定制班(剩2个名额)

开班日期:12-29

php高级开发周末班(剩5个名额)

php高级开发周末班(剩5个名额)

开班日期:12-29

php高级开发免费试听(剩5个名额)

php高级开发免费试听(剩5个名额)

开班日期:12-29

更多高级开发工程师精品班

更多高级开发工程师精品班

开班日期:12-29

  • 地址:山西省太原市小店区学府街长治路高新国际A座24层
  • 课程培训电话:13623629309     全国服务监督电话:400-827-0010
  • 服务邮箱 ts@tedu.cn
  • 2001-2016 达内国际公司(TARENA INTERNATIONAL,INC.) 版权所有 京ICP证08000853号-56