PHP常见问题总结与最佳实践

PHP常见问题总结与最佳实践

原创部分:

HTTP协议头不让修改,说是已有数据发送,而你还什么也没发呢

  • 问题:[08-Dec-2016 15:33:10] WARNING: [pool www] child 3735 said into stderr: “NOTICE: PHP message: PHP Warning: Cannot modify header information - headers already sent by (output started at common.php:1) in /xxx/cls_template.php on line 53”

原因 :有多种,我遇到的是因为文件编码的问题,个别文件在保存时,保留了UTF-8 POM(windows 的问题?)

解决
使用PhpStorm上下文菜单项的[remove POM],去除utf-8头标识,避免意外的响应输出。

问题:php7的类构造函数和以前的版本相比,有什么改变?

答案


    class ECS
    {
        var $db_name = '';
        var $prefix  = 'ecs_';
        /**
         * 构造函数
         *
         * @access  public
         * @param   string      $db_name   数据库名
         * @param   string      $prefix    表前缀
         *
         */
        function __construct($db_name, $prefix)
        {
            $this->db_name = $db_name;
            $this->prefix  = $prefix;
        }

注意
构造函数在php doc中的说明,返回值 return value 不再是void, 注意拼写,不是 __constructor
不再用类名同名函数, 且默认不调父类构造方法,需要时手动调用parent::__construct(…)

preg_replace 和 preg_replace_callback

这篇文章主要介绍了PHP正则替换函数preg_replace和preg_replace_callback
使用总结,本文是在写一个模板引擎遇到一个特殊需求时总结而来,需要的朋友可以参考下

在编写PHP模板引擎工具类时,以前常用的一个正则替换函数为 preg_replace(),加上正则修饰符 /e,就能够执行强大的回调函数,实现模板引擎编译(其实就是字符串替换)。
详情介绍参考博文:PHP函数preg_replace

正则替换所有符合条件的字符串
应用举例如下:


    <?php
    /**
     * 模板解析类
     */
    class Template {
     public function compile($template) {
      // if逻辑
      $template = preg_replace("/\<\!\-\-\{if\s+(.+?)\}\-\-\>/e", "\$this->ifTag('\\1')", $template);
      return $template;
     }
     /**
      * if 标签
      */
     protected function ifTag($str) {
      //$str = stripslashes($str); // 去反转义
      return '<?php if (' . $str . ') { ?>';
     }
    }
    $template = 'xxx<!--{if $user[\'userName\']}-->yyy<!--{if $user["password"]}-->zzz';
    $tplComplier = new Template();
    $template = $tplComplier->compile($template);
    echo $template;
    ?>

以下内容翻译自 https://phpbestpractices.org/

使用 5.5.9以上版本,ubuntu 14.04更好,问题更少

密码保存和校验

password_hash已经有加盐的功能了,不需要再额外手工处理了

<?php
// 密码散列化.  $hashedPassword 将是60个字符的字符串.
$hashedPassword = password_hash('my super cool password', PASSWORD_DEFAULT);

// 你可安全地把 $hashedPassword 保存到你的数据库中!

// 检查用户提供的密码与已有的散列值 hash 是否 相同
password_verify('the wrong password', $hashedPassword); // false

password_verify('my super cool password', $hashedPassword); // true
?>

使用PDO连接数据库


    <?php
    try{
        // 创建连接
        // 你可能需要将第一个参数年 hostname 替换为 localhost.
        // 注意我们是如何声明字符集 utf8mb4 的.  This alerts the connection that we'll be passing UTF-8 data.  This may not be required depending on your configuration, but it'll save you headaches down the road if you're trying to store Unicode strings in your database.  See "Gotchas".
        // The PDO options we pass do the following:
        // \PDO::ATTR_ERRMODE enables exceptions for errors.  This is optional but can be handy.
        // \PDO::ATTR_PERSISTENT disables persistent connections, which can cause concurrency issues in certain cases.  See "Gotchas".
        $link = new \PDO(   'mysql:host=your-hostname;dbname=your-db;charset=utf8mb4',
                            'your-username',
                            'your-password',
                            array(
                                \PDO::ATTR_ERRMODE => \PDO::ERRMODE_EXCEPTION,
                                \PDO::ATTR_PERSISTENT => false
                            )
                        );

        $handle = $link->prepare('select Username from Users where UserId = ? or Username = ? limit ?');

        // PHP bug: 如果不指明 PDO::PARAM_INT, PDO 有可能会对参数加引号.  从需引起 MySQL 查询的混乱,整型数据是不应加引号的.
        // 参见: https://bugs.php.net/bug.php?id=44639
        // 如果不清楚传进来的参数是否为整型, 可以用 is_int() 函数来判断.
        // 这个bug已在 2016年10月解决, 但早一点的版本都会受影响 PHP ; [参见](https://bugs.php.net/bug.php?id=73234)
        $handle->bindValue(1, 100, PDO::PARAM_INT);
        $handle->bindValue(2, 'Bilbo Baggins');
        $handle->bindValue(3, 5, PDO::PARAM_INT);

        $handle->execute();

        // Using the fetchAll() method might be too resource-heavy if you're selecting a truly massive amount of rows.
        // If that's the case, you can use the fetch() method and loop through each result row one by one.
        // You can also return arrays and other things instead of objects.  See the PDO documentation for details.
        $result = $handle->fetchAll(\PDO::FETCH_OBJ);

        foreach($result as $row){
            print($row->Username);
        }
    }
    catch(\PDOException $ex){
        print($ex->getMessage());
    }
    ?>

注意
1. 整型绑定如果不用PDO::PARAM_INT指定类型, 有时会被引号括起来 参见;
2. 连接字符串不要用utf8mb4编码,会不正确存储数据,取决于数据库配置;
3. 声明utf8mb4字符集还要确保数据表也是用的utf8mb4,为什么用utf8mb4面不是utf8可以参见php有关UTF-8部分;
4. 可持久化连接可能会造成并发问题,这不是php的问题,这是应用层的问题,见Stack Overflow question;
5. 单次方法 execute() 调用可以执行多条sql语句,语句间分号分隔即可,在低版本上可能有问题.

PHP标签的使用

使用 <?php ?>, <?= ?>, 其他的取决于配置,不建议用。

注意
?>后的回车会造成纯php类文件产生输出,进而后续的修改http header后产生错误
文件开头也要避免

自动加载类

php 支持对未加载的类文件自动加载机制,旧的办法是调用 __autoload(), 新机制是通过spl_autoload_register 进行注册自动加载函数。


    <?php
    // First, define your auto-load function.
    function MyAutoload($className){
        include_once($className . '.php');
    }

    // Next, register it with PHP.
    spl_autoload_register('MyAutoload');

    // Try it out!
    // Since we haven't included a file defining the MyClass object, our auto-loader will kick in and include MyClass.php.
    // For this example, assume the MyClass class is defined in the MyClass.php file.
    $var = new MyClass();
    ?>

单双引号对性能真的没啥影响

不用顾及解不解析的差异

define() 对比 const

这两种方法定义常量略有区别
define() 在运行时定义常量, 而 const 是在编译时. 所以 const 稍快一点, 除非软件超大,否则可忽略这个差别.
define() 常量放在全局域, 可引入特定的命名空间. 不能用 define() 定义类的常量.
define() 定义的常量名和值都可以用表达式, const 则都不行. define() 在这点上更灵活.
define() 可以在 if() 块中用, const 不行.

    <?php
    // Let's see how the two methods treat namespaces
    namespace MiddleEarth\Creatures\Dwarves;
    const GIMLI_ID = 1;
    define('MiddleEarth\Creatures\Elves\LEGOLAS_ID', 2);

    echo(\MiddleEarth\Creatures\Dwarves\GIMLI_ID);  // 1
    echo(\MiddleEarth\Creatures\Elves\LEGOLAS_ID);  // 2; note that we used define(), but the namespace is still recognized

    // Now let's declare some bit-shifted constants representing ways to enter Mordor.
    define('TRANSPORT_METHOD_SNEAKING', 1 << 0); // OK!
    const TRANSPORT_METHOD_WALKING = 1 << 1; // Compile error! const can't use expressions as values

    // Next, conditional constants.
    define('HOBBITS_FRODO_ID', 1);

    if($isGoingToMordor){
        define('TRANSPORT_METHOD', TRANSPORT_METHOD_SNEAKING); // OK!
        const PARTY_LEADER_ID = HOBBITS_FRODO_ID // Compile error: const can't be used in an if block
    }

    // Finally, class constants
    class OneRing{
        const MELTING_POINT_CELSIUS = 1000000; // OK!
        define('MELTING_POINT_ELVISH_DEGREES', 200); // Compile error: can't use define() within a class
    }
    ?>

缓存 PHP 字节码opcode

php内置缓存字节码的功能,无需操心

低版本的还是升级吧,或用[APC](sudo apt-get install php-apc)

PHP 与 Memcached

需要分布式缓存用[Memcached](sudo apt-get install php5-memcached), 否则可以考虑用 [APCu](sudo apt-get install php5-apcu)

    <?php
    // Store some values in the APCu cache.  We can optionally pass a time-to-live, but in this example the values will live forever until they're garbage-collected by APCu.
    apc_store('username-1532', 'Frodo Baggins');
    apc_store('username-958', 'Aragorn');
    apc_store('username-6389', 'Gandalf');

    // You can store arrays and objects too.
    apc_store('creatures', array('ent', 'dwarf', 'elf'));
    apc_store('saruman', new Wizard());

    // After storing these values, any PHP script can access them, no matter when it's run!
    $value = apc_fetch('username-958', $success);
    if($success === true)
        print($value); // Aragorn

    $value = apc_fetch('creatures', $success);
    if($success === true)
        print_r($value);

    $value = apc_fetch('username-1', $success); // $success will be set to boolean false, because this key doesn't exist.
    if($success !== true) // Note the !==, this checks for true boolean false, not "falsey" values like 0 or empty string.
        print('Key not found');

    apc_delete('username-958'); // This key will no longer be available.
    ?>

PHP 与正则表达式 regex

请使用 PCRE ( preg_* ) 一族方法,不要用 POSIX (POSIX 扩展, ereg_* ) 方法.

与 Web 服务搭配

请使用 PHP-FPM.

PHP-FPM 与 Apache

安装

    sudo apt-get install apache2-mpm-event libapache2-mod-fastcgi php5-fpm
    sudo a2enmod actions alias fastcgi

使用

    <Directory />
        Require all granted
    </Directory>
    <VirtualHost *:80>
        Action php5-fcgi /php5-fcgi
        Alias /php5-fcgi /usr/lib/cgi-bin/php5-fcgi
        FastCgiExternalServer /usr/lib/cgi-bin/php5-fcgi -socket /var/run/php5-fpm.sock -idle-timeout 120 -pass-header Authorization
        <FilesMatch "\.php$">
            SetHandler  php5-fcgi
        </FilesMatch>
    </VirtualHost>
    sudo service apache2 restart && sudo service php5-fpm restart

注意
用AddHandler 指令替代 SetHandler 会有一定的风险,AddHandler 会让任何地方的php代码得到执行,如evil.php.gif

## 发邮件

使用 PHPMailer,强于内置的mail函数


     <?php
    // Include the PHPMailer library
    require_once('phpmailer-5.2.7/PHPMailerAutoload.php');

    // Passing 'true' enables exceptions.  This is optional and defaults to false.
    $mailer = new PHPMailer(true);

    // Send a mail from Bilbo Baggins to Gandalf the Grey

    // Set up to, from, and the message body.  The body doesn't have to be HTML; check the PHPMailer documentation for details.
    $mailer->Sender = 'bbaggins@example.com';
    $mailer->AddReplyTo('bbaggins@example.com', 'Bilbo Baggins');
    $mailer->SetFrom('bbaggins@example.com', 'Bilbo Baggins');
    $mailer->AddAddress('gandalf@example.com');
    $mailer->Subject = 'The finest weed in the South Farthing';
    $mailer->MsgHTML('<p>You really must try it, Gandalf!</p><p>-Bilbo</p>');

    // Set up our connection information.
    $mailer->IsSMTP();
    $mailer->SMTPAuth = true;
    $mailer->SMTPSecure = 'ssl';
    $mailer->Port = 465;
    $mailer->Host = 'my smtp host';
    $mailer->Username = 'my smtp username';
    $mailer->Password = 'my smtp password';

    // All done!
    $mailer->Send();
    ?>

校验邮箱地址

使用 filter_var() 函数


    <?php
    filter_var('sgamgee@example.com', FILTER_VALIDATE_EMAIL); // Returns "sgamgee@example.com". This is a valid email address.
    filter_var('sauron@mordor', FILTER_VALIDATE_EMAIL); // Returns boolean false! This is *not* a valid email address.
    ?>

清洗html标签输入出输出

使用 htmlentities() 函数,可指定标签的白名单

strip-tags 函数虽然简单,但不保证html语义正确,处理不好标签闭合的问题。
filter-var 函数处理不了换行的问题

简单情况, 转义


    <?php
    // Oh no!  The user has submitted malicious HTML, and we have to display it in our web app!
    $evilHtml = '<div onclick="xss();">Mua-ha-ha!  Twiddling my evil mustache...</div>';

    // Use the ENT_QUOTES flag to make sure both single and double quotes are escaped.
    // Use the UTF-8 character encoding if you've stored the text as UTF-8 (as you should have).
    // See the UTF-8 section in this document for more details.
    $safeHtml = htmlentities($evilHtml, ENT_QUOTES, 'UTF-8'); // $safeHtml is now fully escaped HTML.  You can output $safeHtml to your users without fear!
    ?>

复杂情况,清洗


    <?php
    // Include the HTML Purifier library
    require_once('htmlpurifier-4.6.0/HTMLPurifier.auto.php');

    // Oh no!  The user has submitted malicious HTML, and we have to display it in our web app!
    $evilHtml = '<div onclick="xss();">Mua-ha-ha!  Twiddling my evil mustache...</div>';

    // Set up the HTML Purifier object with the default configuration.
    $purifier = new HTMLPurifier(HTMLPurifier_Config::createDefault());

    $safeHtml = $purifier->purify($evilHtml); // $safeHtml is now sanitized.  You can output $safeHtml to your users without fear!
    ?>

PHP 与 UTF-8

  1. 单字节与多字节字符串函数
    strpos() => mb_strpos()
    strlen() => mb_strlen()

可以在 mb_internal_encoding() 在文件头上用,, mb_http_output() 设置页面输出格式

htmlentities() 应该明确指定用 UTF-8 编码。


    <?php
    // Tell PHP that we're using UTF-8 strings until the end of the script
    mb_internal_encoding('UTF-8');

    // Tell PHP that we'll be outputting UTF-8 to the browser
    mb_http_output('UTF-8');

    // Our UTF-8 test string
    $string = 'Êl síla erin lû e-govaned vîn.';

    // Transform the string in some way with a multibyte function
    // Note how we cut the string at a non-Ascii character for demonstration purposes
    $string = mb_substr($string, 0, 15);

    // Connect to a database to store the transformed string
    // See the PDO example in this document for more information
    // Note that we define the character set as utf8mb4 in the PDO connection string
    $link = new \PDO(   'mysql:host=your-hostname;dbname=your-db;charset=utf8mb4',
                        'your-username',
                        'your-password',
                        array(
                            \PDO::ATTR_ERRMODE => \PDO::ERRMODE_EXCEPTION,
                            \PDO::ATTR_PERSISTENT => false
                        )
                    );

    // Store our transformed string as UTF-8 in our database
    // Your DB and tables are in the utf8mb4 character set and collation, right?
    $handle = $link->prepare('insert into ElvishSentences (Id, Body) values (?, ?)');
    $handle->bindValue(1, 1, PDO::PARAM_INT);
    $handle->bindValue(2, $string);
    $handle->execute();

    // Retrieve the string we just stored to prove it was stored correctly
    $handle = $link->prepare('select * from ElvishSentences where Id = ?');
    $handle->bindValue(1, 1, PDO::PARAM_INT);
    $handle->execute();

    // Store the result into an object that we'll output later in our HTML
    $result = $handle->fetchAll(\PDO::FETCH_OBJ);
    ?><!doctype html>
    <html>
        <head>
            <meta charset="UTF-8" />
            <title>UTF-8 test page</title>
        </head>
        <body>
            <?php
            foreach($result as $row){
                print($row->Body);  // This should correctly output our transformed UTF-8 string to the browser
            }
            ?>
        </body>
    </html>

时间与日期处理

使用 DateTime 类, 不要再用 date(), gmdate(), date_timezone_set(), strtotime() 这些函数了

    <?php
    // Construct a new UTC date.  Always specify UTC unless you really know what you're doing!
    $date = new DateTime('2011-05-04 05:00:00', new DateTimeZone('UTC'));

    // Add ten days to our initial date
    $date->add(new DateInterval('P10D'));

    echo($date->format('Y-m-d h:i:s')); // 2011-05-14 05:00:00

    // Sadly we don't have a Middle Earth timezone
    // Convert our UTC date to the PST (or PDT, depending) time zone
    $date->setTimezone(new DateTimeZone('America/Los_Angeles'));

    // Note that if you run this line yourself, it might differ by an hour depending on daylight savings
    echo($date->format('Y-m-d h:i:s')); // 2011-05-13 10:00:00

    $later = new DateTime('2012-05-20', new DateTimeZone('UTC'));

    // Compare two dates
    if($date < $later)
        echo('Yup, you can compare dates using these easy operators!');

    // Find the difference between two dates
    $difference = $date->diff($later);

    echo('The 2nd date is ' . $difference->days . ' later than 1st date.');
    ?>

注意
如果没设时区的话,DateTime::__construct()会设置与主机一样的时区
创建时间时,最好明确指出 UTC 时区,在 DateTime::__construct() 使用 Unix timestamp 时,时区会是UTC,与第二个参数无关。

“0000-00-00” 对DateTime::__construct()没有意义,不会产生 MySql期待的“0000-00-00”.

DateTime::getTimestamp() 在 32-位系统上只能表示 2038 年以前的日期. 64-位系统上OK.

检查数据是 null 还是 false

使用 === 操作符检查是否为 null 和布尔 false 值.


    <?php
    $x = 0;
    $y = null;

    // Is $x null?
    if($x == null)
        print('Oops! $x is 0, not null!');

    // Is $y null?
    if(is_null($y))
        print('Great, but could be faster.');

    if($y === null)
        print('Perfect!');

    // Does the string abc contain the character a?
    if(strpos('abc', 'a'))
        // GOTCHA!  strpos returns 0, indicating it wishes to return the position of the first character.
        // But PHP interpretes 0 as false, so we never reach this print statement!
        print('Found it!');

    //Solution: use !== (the opposite of ===) to see if strpos() returns 0, or boolean false.
    if(strpos('abc', 'a') !== false)
        print('Found it for real this time!');
    ?>

注意
对于这种既可能返回0,又可能返回false的函数,如 strpos(), 请始终用 === 操作符,或 !== 操作符。

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值