PHP中使用strlen和mb_strlen, iconv_strlen的区别

在PHP中,我们常常需要处理字符串长度。比如说,有些情况下需要把过长的字符串截短一点,然后存入数据库或者文件。

今天的程序中,出现了一个bug,很简单的问题,postgresql说无法存储字符串,说是无效的UTF-8字符。很好奇,数据库到程序包括页面都是UTF-8,为什么会出现这个问题。仔细读了程序,原来有一个地方是使用的strlen获取字符串长度,而字符串是中文。根据这个strlen获取了长度后,又继续通过substr进行了字符串截取。这样肯定会有问题,因为里面的字符都是中文的,至少应该处理一下.

那么,可以这么讲,在php的中文编程中,基本上可以先不使用strlen这个方法了,直接使用mb_strlen不可以了。因为mb_strlen能够处理多语言字符,当然你要安装php-mbstring.

在使用mb_strlen的时候,如果不确定运行时是否为utf-8,可以用 mb_internal_encoding(“UTF-8″);来处理。不过用mb_strlen第二个参加添加utf-8也可以。

对于iconv_strlen,我用得不多,不过我觉得这个方法和mb_strlen是一样的,只要设置的字符集没问题。

反正,别用strlen了,除非是判断纯拉丁字符。

PHP文件头部空白影响CSS布局

在编写PHP文件过程中,发现在浏览器预览PHP文件时,顶部会出现一行空白,影响了页面的布局。

关于BOM header的解释如下:

通常情况下,使用Windows系统自带的记事本程序编写网页程序,但在编写或修改php博客系统代码后,进行调试时总是会出现如同以下几点问题:

–不能登入或者不能登出; –页顶出现一条空白; –页顶出现错误警告; –其它不正常的情况。

分析原因: 由于使用UTF-8编码,在编写或修改代码后都保存为utf-8编码格式。虽然现在几乎所有的文本编辑软件都可以显示并编辑UTF-8编码的文件,但是很遗憾的是其中很多软件的表现并不理想。

类 似WINDOWS自带的记事本等软件,在保存一个以UTF-8编码的文件时,会在文件开始的地方插入三个不可见的字符(_0xEF _0xBB _0xBF,即BOM——Byte Order Mark)。它是一串隐藏的字符,用于让记事本等编辑器识别这个文件是否以UTF-8编码。对于一般的文件,这样并不会产生什么麻烦。但对于 PHP来说,PHP在设计时就没有考虑BOM的问题,不会忽略UTF-8编码的文件开头BOM的那三个字符,会把BOM作为该文件开头正文的一部分。由于必须在<?或者<?php后面的代码才会作为PHP代码执行,所以将会造成在页面上输出这三个字符,显示效果就要看浏览器了,一般是一个空行或是一个乱码。由于在html一开头有这3个字符的存在,即使页面的 top padding 设置为0,也无法让整个网页紧贴浏览器顶部。由于受COOKIE送出机制的限制,在这些文件开头已经有BOM的文件中,COOKIE无法送出(因为在 COOKIE送出前PHP已经送出了文件头),所以登入和登出功能失效。一切依赖COOKIE、SESSION实现的功能全部无效。

解决办法:

在编辑、更改任何文本文件时,请务必使用不会乱加BOM的编辑器。Linux下的编辑器应该都没有这个问题。WINDOWS下,请勿使用记事本等编辑器。推荐的编辑器是: Editplus 2.12版本以上; EmEditor; UltraEdit(需要取消‘添加BOM’的相关选项); Dreamweaver(需要取消‘添加BOM’的相关选项); Notepad(需要进行“转换为不带BOM的UTF-8”)等。

对于已经添加了BOM的文件,要取消的话,可以用以上编辑器另存一次。(Editplus需要先另存为gb,再另存为UTF-8。Dreamweaver在”页面属性“的 “包含Unicode 签名(BOM)”取消即可)

转载自:http://www.cnblogs.com/newsouls/archive/2013/01/11/2856518.html

PHP使用gettext实现i18n (internationalization)国际化

i18n是英文单词internationalization的首字母”i”和末字母”n”的缩写,中间的18代表省略(缩写)了18个字母。

gettext是PHP的一个函数库,主要函数有

  • bind_textdomain_codeset
  • bindtextdomain
  • gettext
  • textdomain
  • dgettext
  • ……

我们首先要先搞明白一些概念,就能很容易的理解这些函数了。

  • .po文件

    把英文内容翻译成本地内容的文件,文件格式:

    msgid "英文内容"
    msgstr "本地内容"
    

    比如:

    msgid "hello, world!"
    msgstr "你好,世界!"
    

    这样我们在程序中使用 gettext("hell, world!"),如果设置成显示中文的话就会显示“你好,世界!”,英文的话就会显示”hello, world!”

  • .mo文件

    .po文件是我们编辑的源文件,就像我们写的代码一样,.mo文件是编译过的二进制文件,gettext读取的就是.mo文件,因为程序读取二进制的效率要高一些。

  • 函数中的domain

    domain就是.mo的文件名,一般.po和.mo的文件名是一致的,除了扩展名不样。bindtextdomain的第一个参数就是.mo的文件名, 比如我们要用default.mo文件,那么bindtextdomain第一个参数就是default,第二个参数是default.mo文件的路径。

  • textdomain

    程序中可以设置绑定多个.mo文件,textdomain是设置默认的.mo文件,比如设置default.mo文件为默认 textdomain('default')之后 就可以使用gettext函数来实现多语言了。

  • dgettext

    上面我们知道gettext函数使用的是默认的.mo文件,那么dgettext函数就是指定.mo文件,二个参数,第一个是.mo文件名,第二个参数同gettext函数的第一个参数一样。

  • bind_textdomain_codeset

    指定.mo文件的编码,一般我们使用utf-8,就需要调用这个bind_textdomain_codeset,你的.po文件是utf-8,那么.mo文件也是utf-8,二个文件会保持一致, 不指定编码的话使用gettext或者dgettext就会是乱码。

  • poedit编辑器

    poedit就是专门编辑.po文件的编辑器软件,他只能翻译内容,不能添加内容。

  • 目录结构

    对于.mo文件是的位置是有一定要求的,比如我们想把.mo文件统一放在./locale目录,可以使用函数 bindtextdomain('default', './locale'); 来设置, 那么我们的中文default.mo文件的位置必须是 ./locale/zh_CN/LC_MESSAGES/default.mo ,zh_CN是中文,LC_MESSAGES是固定的,为什么要有他我也不清楚。

  • setlocale函数

    这个函数是语言切换的核心,比如上面我们有一个中文的翻译文件default.mo保存在zh_CN目录下面,我们只需要调用 setlocale(LC_ALL, 'zh_CN');  即可显示我们在default.mo翻译过的内容。关于第一个参数可以查看php setlocale的文档,这里就不说了。

还有什么问题不明白的可以通过评论告诉我哦。

PHP类属性3种不同访问方式的性能测试

PHP给了我们3种访问类属性的方法,我们来测试以下他们的性能差异。

1.直接访问

class dog { 
    public $name = "";
} 
$rover = new dog(); 
for ($x=0; $x<10; $x++) { 
    $t = microtime(true); 
    for ($i=0; $i<1000000; $i++) { 
        $rover->name = "rover"; 
        $n = $rover->name;
    } 
    echo microtime(true) - $t;echo "\n";
}

2.使用类函数访问

class dog {
    public $name = "";

    public function setName($name) {
        $this->name = $name; 
    }

    public function getName() {
        return $this->name; 
    } 
}

$rover = new dog();
for ($x=0; $x<10; $x++) { 
    $t = microtime(true);
    for ($i=0; $i<1000000; $i++) { 
        $rover->setName("rover");
        $n = $rover->getName();
    }
    echo microtime(true) - $t;echo "\n";
}

3.使用PHP提供的魔术方法__set和__get访问

class dog { 
    private $data = array();
    function __set($name, $value) {
        $this->data[$name] = $value;
    }
    function __get($name) {
        if(array_key_exists($name, $this->data)) {
            return $this->data[$name];
        }
        return null;
    }
} 
$rover = new dog(); 
for ($x=0; $x<10; $x++) { 
    $t = microtime(true); 
    for ($i=0; $i<1000000; $i++) { 
        $rover->name = "rover"; 
        $n = $rover->name;
    } 
    echo microtime(true) - $t;echo "\n";
}

执行结果分别为:

1.直接访问

0.59869480133057
0.59985685348511
0.61411023139954
0.61089015007019
0.59671401977539
0.60318994522095
0.60059809684753
0.59980893135071
0.59794902801514
0.59721207618713

2.使用类函数访问

4.107736825943
4.0344598293304
4.009449005127
4.0501089096069
4.070415019989
4.0576939582825
4.0664989948273
4.0609030723572
4.0520219802856
4.0485868453979

3.使用PHP提供的魔术方法__set和__get访问

7.6956799030304
7.810909986496
7.7758450508118
7.7235188484192
7.7205560207367
7.7772619724274
7.9401290416718
7.8542349338531
7.8687150478363
7.9177348613739

差距还是挺大的。

为了让大家方便自己测试,下面把3种直接可以在命令行执行的代码整理出来:

php -d implicit_flush=off -r  'class dog { public $name = "";} $rover = new dog(); for ($x=0; $x<10; $x++) { $t = microtime(true); for ($i=0; $i<1000000; $i++) { $rover->name = "rover"; $n = $rover->name;} echo microtime(true) - $t;echo "\n";}'

php -d implicit_flush=off -r  'class dog {public $name = "";public function setName($name) {$this->name = $name; }public function getName() {return $this->name; } }$rover = new dog();for ($x=0; $x<10; $x++) { $t = microtime(true);for ($i=0; $i<1000000; $i++) { $rover->setName("rover");$n = $rover->getName();}echo microtime(true) - $t;echo "\n";}'

php -d implicit_flush=off -r 'class dog { private $data = array();function __set($name, $value) {$this->data[$name] = $value;}function __get($name) {if(array_key_exists($name, $this->data)) {return $this->data[$name];}return null;}} $rover = new dog(); for ($x=0; $x<10; $x++) { $t = microtime(true); for ($i=0; $i<1000000; $i++) { $rover->name = "rover"; $n = $rover->name;} echo microtime(true) - $t;echo "\n";}'

你们结果如何?

(End)

代码优化,重构

今天把09年写的一个类拿来使用,浏览了一下,看着之前写的烂代码挺难读的,随手就优化了一下,直接看代码。

老代码:

$getStr = '';
if($_GET) {
    $getStr = '?';
    foreach ($_GET as $k => $v) {
        if($k != 'p' && $k != 'ps') {
            if(!in_array($k,$this->mIgnoreGet)) {
                $getStr .= $k . '=' . $v . '&';
            }
        }
    }
    if($getStr == '?') {
        $getStr = '';
    }
}

优化之后的代码:

parse_str($_SERVER['QUERY_STRING'], $query_array);
if(!empty($query_array)) {
    unset($query_array['p']);
    foreach (array_keys($this->mIgnoreGet) as $v) {
        unset[$v];
    }
}
$getStr = http_build_query($query_array);
$getStr = empty($getStr) ? '' : '?' . $getStr;

对于结构的优化就不写了。

这就是一次很小的重构。真的很小,用时很短。但是真实的改善了代码结构, 减少代码行数,提升代码的可读性。

PHP官方文档中preg_match_all函数有错误

在PHP官方文档中preg_match_all函数中看函数的定义第三个参数$matches参数为可选的,但是在实际使用中发现这个参数如何不传,这个preg_match_all函数是不会正常工作的,该函数的调用永远返回false。

下面为文档中对函数的说明:

int preg_match_all ( string $pattern , string $subject [, array &$matches [, int $flags = PREG_PATTERN_ORDER [, int $offset = 0 ]]] )

使用file_get_contents指定出口IP和端口

如果我们的机器有多个IP,可能是多个网卡,也可能是一个网卡绑定多个IP,那么我们使用file_get_contents获取网页内容的时候就可以指定使用某个IP和端口作为出口,让对方网站获取我们的IP时候可以做到我们预想的结果。

首先来准备一个显示IP和端口的页面:

getip.php
<?php
echo $_SERVER['REMOTE_ADDR'].':'.$_SERVER['REMOTE_PORT'];

假定我们现在有2个IP,一个127.0.0.1,一个192.168.1.2

<?php
$opts1 = array(
    'socket' => array(
        'bindto' =>'127.0.0.1:0'
    )
);
$opts2 = array(
    'socket' => array(
        'bindto' =>'192.168.1.2:0'
    )
);
$opts3 = array(
    'socket' => array(
        'bindto' =>'0:8888'
    )
);
$opts4 = array(
    'socket' => array(
        'bindto' =>'127.0.0.1:8888'
    )
);

$url = 'http://192.168.1.2/getip.php';

$context1 = stream_context_create($opts1);
$context2 = stream_context_create($opts2);
$context3 = stream_context_create($opts3);
$context4 = stream_context_create($opts4);

echo file_get_contents($url, false, $context1)."\r\n";
echo file_get_contents($url, false, $context2)."\r\n";
echo file_get_contents($url, false, $context3)."\r\n";
echo file_get_contents($url, false, $context4)."\r\n";

上面代码运行结果为:

127.0.0.1:54873
192.168.1.2:54874
192.168.1.2:8888
127.0.0.1:8888

PHP5.4新特性:traits

从PHP 5.4开始实现了代码重用的的新方法traits。

traits是一种单继承语言(比如PHP)的代码重用机制,是为了解决单继承语言的局限性,在避免多重继承的复杂性的同时,又兼顾多重继承的代码重用的优点。

traits类似于class,traits不能被实例化,不能被继承,只能被横向的组合(使用use)。

trait实例:

<?php
trait ezcReflectionReturnInfo {
    function getReturnType() { /*1*/ }
    function getReturnDescription() { /*2*/ }
}

class ezcReflectionMethod extends ReflectionMethod {
    use ezcReflectionReturnInfo;
    /* ... */
}

class ezcReflectionFunction extends ReflectionFunction {
    use ezcReflectionReturnInfo;
    /* ... */
}
?>

优先权

如果一个从基类继承的方法和一个使用trait组合的方法名如果一样的化,那么trait中的方法将会覆盖从基类继承的方法。

<?php
class Base {
    public function sayHello() {
        echo 'Hello ';
    }
}

trait SayWorld {
    public function sayHello() {
        parent::sayHello();
        echo 'World!';
    }
}

class MyHelloWorld extends Base {
    use SayWorld;
}

$o = new MyHelloWorld();
$o->sayHello();
?>

上面程序会输出:

Hello World!

<?php
trait HelloWorld {
    public function sayHello() {
        echo 'Hello World!';
    }
}

class TheWorldIsNotEnough {
    use HelloWorld;
    public function sayHello() {
        echo 'Hello Universe!';
    }
}

$o = new TheWorldIsNotEnough();
$o->sayHello();
?>

上面程序输出:

Hello Universe!

组合多个trait

使用逗号可以组合多个trait

<?php
trait Hello {
    public function sayHello() {
        echo 'Hello ';
    }
}

trait World {
    public function sayWorld() {
        echo 'World';
    }
}

class MyHelloWorld {
    use Hello, World;
    public function sayExclamationMark() {
        echo '!';
    }
}

$o = new MyHelloWorld();
$o->sayHello();
$o->sayWorld();
$o->sayExclamationMark();
?>

以上程序输出:

Hello World!

冲突

如果在一个类中插入多个trait,并且trait中定义的方法有重名的情况,将会产生一个致命错误,如果没有显示定义引入的方法。

解决一个类中插入trait重名的方法,可以使用insteadof操作符来定义具体引用哪个trait中的方法。

使用insteadof只能引入一个方法,可以同时使用as操作符给同名的方法换一个方法名。

<?php
trait A {
    public function smallTalk() {
        echo 'a';
    }
    public function bigTalk() {
        echo 'A';
    }
}

trait B {
    public function smallTalk() {
        echo 'b';
    }
    public function bigTalk() {
        echo 'B';
    }
}

class Talker {
    use A, B {
        B::smallTalk insteadof A;
        A::bigTalk insteadof B;
    }
}

class Aliased_Talker {
    use A, B {
        B::smallTalk insteadof A;
        A::bigTalk insteadof B;
        B::bigTalk as talk;
    }
}
?>

改变方法的可见性

使用as语法,可以改变方法的可见性

<?php
trait HelloWorld {
    public function sayHello() {
        echo 'Hello World!';
    }
}

// Change visibility of sayHello
class MyClass1 {
    use HelloWorld { sayHello as protected; }
}

// Alias method with changed visibility
// sayHello visibility not changed
class MyClass2 {
    use HelloWorld { sayHello as private myPrivateHello; }
}
?>

trait中包含trait

通类一样,在trait中同样可以使用use语法来组合多个trait成为一个新的trait。

<?php
trait Hello {
    public function sayHello() {
        echo 'Hello ';
    }
}

trait World {
    public function sayWorld() {
        echo 'World!';
    }
}

trait HelloWorld {
    use Hello, World;
}

class MyHelloWorld {
    use HelloWorld;
}

$o = new MyHelloWorld();
$o->sayHello();
$o->sayWorld();
?>

上面代码输出:

Hello World!

抽象的trait成员

traits支持使用抽象(abstract)方法。

<?php
trait Hello {
    public function sayHelloWorld() {
        echo 'Hello'.$this->getWorld();
    }
    abstract public function getWorld();
}

class MyHelloWorld {
    private $world;
    use Hello;
    public function getWorld() {
        return $this->world;
    }
    public function setWorld($val) {
        $this->world = $val;
    }
}
?>

静态trait成员

在trait的方法中可以使用静态(static)变量,方法也可以定义为静态方法,但是不能定义为trait的静态变量。

静态变量实例

<?php
trait Counter {
    public function inc() {
        static $c = 0;
        $c = $c + 1;
        echo "$c\n";
    }
}

class C1 {
    use Counter;
}

class C2 {
    use Counter;
}

$o = new C1(); $o->inc(); // echo 1
$p = new C2(); $p->inc(); // echo 1
?>

静态方法实例

<?php
trait StaticExample {
    public static function doSomething() {
        return 'Doing something';
    }
}

class Example {
    use StaticExample;
}

Example::doSomething();
?>

属性

traits同样支持属性

<?php
trait PropertiesTrait {
    public $x = 1;
}

class PropertiesExample {
    use PropertiesTrait;
}

$example = new PropertiesExample;
$example->x;
?>

如果trait中定义一个属性,那么引用这个trait的类中就不能定义相同的属性名,如果类中定义的同名属性和trait中定义的属性值相同将会引发一个E_STRICT,值不一样的化将会引发一个致命错误。

curl, file_get_contents和fsockopen速度比较

测试环境:

  • Apache 2.2
  • PHP 5.3

测试代码:

function curl_test() {
    $url = 'http://www.google.com.hk/';

    $ch = curl_init();
    curl_setopt($ch, CURLOPT_URL, $url);
    curl_setopt($ch, CURLOPT_RETURNTRANSFER, 1);
    $data = curl_exec($ch);
    curl_close($ch);
    return $data;
}

function file_get_content_test() {
    $url = 'http://www.google.com.hk/';
    $data = file_get_contents($url);
    return $data;
}

function fsockopen_test() {
    $url = 'http://www.google.com.hk/';
    $urls = parse_url($url);
    if(!isset($urls['port'])) {
        $urls['port'] = '80';
    }
    if(!isset($urls['path'])) {
        $urls['path'] = '/';
    }
    $path = $urls['path'];
    if(isset($urls['query'])) {
        $path .= '?'.$urls['query'];
    }

    $data = '';
    $fp = fsockopen($urls['host'], $urls['port'], $errno, $errstr, 10);
    if(!$fp) {
        echo "ERROR: $errno - $errstr <br />\n";
    }
    else {
        $put = "GET ".$path." HTTP/1.1\r\n";
        $put .= "Host: ".$urls['host']."\r\n";
        $put .= "Connection: Close\r\n\r\n";
        fwrite($fp, $put);
        while (!feof($fp)) {
            $data .= fgets($fp);
        }
        fclose($fp);
        if($data) {
            $data = substr($data, strpos($data, "\r\n\r\n")+4);
        }
    }

    return $data;
}

require 'Benchmark/Iterate.php';

$benchmark = new Benchmark_Iterate();

$funcs = array('curl_test', 'file_get_content_test', 'fsockopen_test');
foreach ($funcs as $func) {
    $benchmark->run(10, $func);
    $result = $benchmark->get();
    echo $func;
    var_dump($result);
}

测试结果:

curl_test
array (size=12)
  1 => string '0.439183' (length=8)
  2 => string '0.423215' (length=8)
  3 => string '0.416775' (length=8)
  4 => string '0.416882' (length=8)
  5 => string '0.414696' (length=8)
  6 => string '0.416986' (length=8)
  7 => string '0.418402' (length=8)
  8 => string '0.416467' (length=8)
  9 => string '0.415329' (length=8)
  10 => string '0.417352' (length=8)
  'mean' => string '0.419528' (length=8)
  'iterations' => int 10

file_get_content_test
array (size=12)
  1 => string '0.468215' (length=8)
  2 => string '0.469789' (length=8)
  3 => string '0.473641' (length=8)
  4 => string '0.471970' (length=8)
  5 => string '0.470277' (length=8)
  6 => string '0.472823' (length=8)
  7 => string '0.470583' (length=8)
  8 => string '0.470775' (length=8)
  9 => string '0.470803' (length=8)
  10 => string '0.470506' (length=8)
  'mean' => string '0.470938' (length=8)
  'iterations' => int 10

fsockopen_test
array (size=12)
  1 => string '0.384669' (length=8)
  2 => string '0.386607' (length=8)
  3 => string '0.388593' (length=8)
  4 => string '0.395739' (length=8)
  5 => string '0.390402' (length=8)
  6 => string '0.388805' (length=8)
  7 => string '0.388581' (length=8)
  8 => string '0.390463' (length=8)
  9 => string '0.388577' (length=8)
  10 => string '0.387762' (length=8)
  'mean' => string '0.389019' (length=8)
  'iterations' => int 10

PHP类中的$this和self的区别

$this代表当前对象(类)的实例,self代表当前类,当前类的实例很好理解,那么什么叫当前类呢? 看看下面例子:

<?php
class Person {
    private $name;

    public function __construct($name) {
        $this->name = $name;
    }

    public function getName() {
        return $this->name;
    }

    public function getTitle() {
        return $this->getName()." the person";
    }

    public function sayHello() {
        echo "Hello, I'm ".$this->getTitle()."<br/>";
    }

    public function sayGoodbye() {
        echo "Goodbye from ".self::getTitle()."<br/>";
    }
}

class Geek extends Person {
    public function __construct($name) {
        parent::__construct($name);
    }

    public function getTitle() {
        return $this->getName()." the geek";
    }
}

$geekObj = new Geek("Ludwig");
$geekObj->sayHello();
$geekObj->sayGoodbye();

上面代码将会输出:

Hello, I'm Ludwig the geek
Goodbye from Ludwig the person

子类覆盖了父类的方法之后,在外面我们是无法访问父类被覆盖的方法,那么在子类中和在父类中是可以访问的:

  • 在子类中可以通过parent::method()访问
  • 在父类中可以通过self::method()访问