php常见问题模板,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

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

/**

* 模板解析类

*/

class Template {

public function compile($template) {

// if逻辑

$template = preg_replace("/\/e", "\$this->ifTag('\\1')", $template);

return $template;

}

/**

* if 标签

*/

protected function ifTag($str) {

//$str = stripslashes($str); // 去反转义

return '<?php if (' . $str . ') { ?>';

}

}

$template = 'xxxyyyzzz';

$tplComplier = new Template();

$template = $tplComplier->compile($template);

echo $template;

?>

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

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

密码保存和校验

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

// 密码散列化.  $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连接数据库

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 进行注册自动加载函数。

// 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 不行.

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

// 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

使用

Require all granted

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

SetHandler  php5-fcgi

sudo service apache2 restart && sudo service php5-fpm restart

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

## 发邮件

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

// 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('

You really must try it, Gandalf!

-Bilbo

');

// 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() 函数

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 函数处理不了换行的问题

简单情况, 转义

// Oh no!  The user has submitted malicious HTML, and we have to display it in our web app!

$evilHtml = '

Mua-ha-ha!  Twiddling my evil mustache...
';

// 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!

?>

复杂情况,清洗

// 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 = '

Mua-ha-ha!  Twiddling my evil mustache...
';

// 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

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

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

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

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

?>

UTF-8 test page

foreach($result as $row){

print($row->Body);  // This should correctly output our transformed UTF-8 string to the browser

}

?>

时间与日期处理

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

// 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 值.

$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(), 请始终用 === 操作符,或 !== 操作符。

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值