TIPI0101-环境搭建

第一节 环境搭建

在开始学习PHP实现之前, 我们首先需要一个实验和学习的环境. 下面介绍一下怎样在*nix环境下准备和搭建PHP环境. (*nix指的是类Unix环境,比如各种Linux发行版,FreeBSD, OpenSolaris, Mac OS X等操作系统)

1.获取PHP源码

为了学习PHP的实现,首先我们要下载源代码.下载源码首选是去PHP官方网站 http://php.net/downloads.php下载, 如果你喜欢是用类似svn/git这些版本控制软件,喜欢svn的读者可以去http://www.php.net/svn.php上 签出源代码,或者如果你喜欢用git, 则可以去http://github.com/php/php-src上clone一个. 个人比较喜欢用版本控制软件签出代码, 这样的好处是能看到php每次修改的内容及日志信息, 如果自己修改了其中的某些内容也能快速的查看到.

2.准备编译环境

在*nix环境下,首先需要gcc编译构建环境. 如果你是用的是Ubuntu或者是用apt做为包管理的系统,可以通过如下命令快速安装.

sudo apt-get install build-essential

如果用的是Mac OS的话,则需要安装Xcode。Xcode可以在Mac OS X的安装盘中找到,如果你有Apple ID的话,也可以登陆苹果开发者网站http://developer.apple.com/下载。

3. 编译

下一步就可以开始编译了,本文只简单介绍基本的编译过程,不包含apache的php支持以及mysql等模块的编译。相关资料请百度或google之。 假设源代码下载到了~/php-src的目录中,执行buildconf命令以生成所需要的Makefile文件

cd ~/php-src
./buildconf

执行完以后就可以开始configure了, configure有很多的参数, 比如指定安装目录, 是否开启相关模块等选项

./configure --help # 查看可用参数

为了尽快得到可以测试的环境,我们就不加其他参数了.直接执行./configure就可以了. 以后如果需要其他功能可以重新编译. 如果configure命令出现错误,可能是缺少php所依赖的库,各个系统的环境可能不一样. 出现错误可根据出错信息上网搜索. 直到完成configure. configure完成后我们就可以开始编译了.

make

在*nix下编译过程序的读者应该都熟悉经典的configure make, make install吧. 执行make之后是否需要make install就取决于你了. 如果install的话 最好在configure的时候是用prefix参数指定安装目录, 不建议安装到系统目录, 避免和系统原有的php冲突.

在make 完以后,~/php-src目录里就已经有了php的可以执行文件. 执行一下命令:

cd ~/php-src
./sapi/cli/php -v

如果看到输出php版本信息则说明咱成功了. 如果是make install的话则执行 $prefix/bin/php 这个路径的php, 当然如果是安装在系统目录或者你的prefix 目录在$PATH环境变量里的话,直接执行php就行了.

后续的学习中可能会需要重复configure make 或者 make && make install 这几个步骤。

作者:TIPI Team

奇技淫巧一:循环加速

奇技淫巧一:循环加速
循环通常是程序性能问题的多发地段。优化某些循环能极大的提高某段程序的性能。
以下的测试环境为:win xampp1.7.3 PHP5.3.1
【数组长度】
也许,maybe,可能,你看到如下类似的代码:

1
2
3
4
5
6
7
8
9
10
<?PHP
$data = range(0, 1000000);
 
$start = xdebug_time_index();
 
for($i = 0; $i < count($data); $i++) {	//	关注点在这行
}
$end = xdebug_time_index();
 
echo $end - $start;

以上的程序运行在我的电脑上基本上是3.3秒到3.4秒的样子。
如果我们把count($data)的计算提前到循环前,以局部变量在循环中迭代。如下所示代码:

1
2
3
4
5
6
7
8
9
10
11
<?PHP
$data = range(0, 1000000);
 
$start = xdebug_time_index();
 
$len = count($data);	//	其实局部变量是很快的。
for($i = 0; $i < $len; $i++) {
}
$end = xdebug_time_index();
 
echo $end - $start;

在同样的环境下运行,基本上是0.32到0.4秒。
对比一下,整整一个数量级的提升。只是优化了一点,性能有如此大的提升。对于这样的操作我们需要在平时注意,养成习惯。虽然PHP内部取数组的长度是从Hash Table中直接取的count值。可是这个相对于局部变量,在性能上还是有较大差别的。
这里想到一个点,如果是HTML中使用JS取DOM集合,然后计算DOM的长度,此时如果有增加或删除节点,可能最后得到的结果并不是你要的那个结果。
【计数变量】
和数组长度一样,计数变量也是一个在循环中每次迭代都会使用的变量。如果我们优化这个点,此时性能上应该也有部分提高。如下所示代码:

1
2
3
4
5
6
7
8
9
10
<?PHP
$data = range(0, 1000000);
 
$start = xdebug_time_index();
 
for($i = count($data); $i--;) {	//	我是关注点所在行
}
$end = xdebug_time_index();
 
echo $end - $start;

以上的代码将计数变量从大到小递减,当为0时自动停止。从而将判断语句和计数加1两条语句变成了一条语句。
最后的运行时间大概为0.26到0.27秒。与前面的0.3到0.4秒,又有了大约30%的提升。

只是,只是这些是针对for语句,在PHP中我们还有foreach。
【foreach】
将上面的for语句换成foreach,如下所示代码:

1
2
3
4
5
6
7
8
9
$data = range(0, 1000000);
 
$start = xdebug_time_index();
 
foreach ($data as $row) {
}
$end = xdebug_time_index();
 
echo $end - $start;

运行时间为:0.11~0.13秒,也许此时我们应该去内核里面看下为什么了,只是这不是今天的重点。嘿嘿
结果已经很明显了,如果可以,我们还是用foreach吧。
【循环展开】
在《编程珠玑》中介绍了这样一种方案。只是他是针对特定的数组查找的问题。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
$data = range(0, 1000005);
$start = xdebug_time_index();
 
$len = count($data);
 
$left = $len % 8;
 
$i = 0;
 
while($left-- > 0) {
	$i++;
}
 
for(; $i < $len; $i+=8) {
	$t = $data[$i];
	$t = $data[$i + 1];
	$t = $data[$i + 2];
	$t = $data[$i + 3];
	$t = $data[$i + 4];
	$t = $data[$i + 5];
	$t = $data[$i + 6];
	$t = $data[$i + 7];
}
$end = xdebug_time_index();
 
echo $end - $start;

以上的方法运行时间为0.3多一些,这和前面的方法类似,但是需要考虑到这里多了8个赋值语句。如果去掉这8个赋值语句,其时间为0.04秒。

以上的一些方法只是针对数量级比较大的数组,此时一些微小的优化都会产生巨大的能量。平时中,也许我们使用foreach就足够了。

【纠结的轮子】
最开始生成一百万的数组使用的是如下方式:

1
2
3
4
$data = array();
for ($i = 0; $i < 1000005; $i++) {
	$data[$i] = $i;
}

纠结的轮子!

数据库抽象层和Doctrine DBAL 源码简单分析

数据库抽象层和Doctrine DBAL 源码简单分析

【概述】
 数据库抽象层,顾名思义,将数据库抽象出来,作为一个独立的层存在。它存在的作用是当从一个数据库系统向另一个数据库系统迁移时,几乎不用更改太多的程序代码或者只是改一下配置文件,其它的工作都会交给数据库抽象层去做。数据库抽象层会根据使用的数据库自动调整,这样会提高开发效率,但是由于数据库抽象层是在数据库与业务之间增加了一个额外的代码层,从而可能会产生一些性能上的问题。因此,我们在使用一个数据抽象层之前需要按照自己的需求,权衡抽象层带来的性能和效率开销。
【Doctrine DBAL 简述】
Doctrine DBAL是一个轻量级的类似于PDO的数据库抽象接入层。它是一个面向对象的架构,需求PHP5.3版本的支持,因为它的实现用到了命名空间。除了类似于PDO的操作外,它允许人们定制属于自己的数据库支持。DBAL能够被用于ORM,这在Doctrine ORM项目中有体现。
【Doctrine DBAL源码综述】
以下是我在阅读完Doctrine DBAL后形成的文字。
整体结构:
Driver
包括DBAL目录下的Driver.php, DriverManager.php和Driver目录下的所有文件
如果要在DBAL下为一个新的数据库提供支持,在Driver这块需要实现下面的两个接口:
\Doctrine\DBAL\Driver\Driver
\Doctrine\DBAL\Driver\Statement
这两个接口的方法和PDO完全相同。
Driver.php中包含Driver接口,所有的Driver必须实现这个接口。
DriverManager.php是一个工厂类,它会根据不同的driver返回不同的Doctrine\DBAL\Connection实例。当然,它也可以返回我们自定义的Connection类实例。
Driver目录包括各数据库对Driver的实现以及公用的Connection接口,Statement接口等。如果要定制新的数据库支持,我们也需要在这个目录下创建一个新的目录,并在这个目录下实现Connection等接口。
PDOConnection.php文件和PDOStatement.php文件中的类是对于Connection接口及Statement接口的默认实现。

根目录下的Connection类实现了Doctrine\DBAL\Driver\Connection接口,并增加了事件,事务隔离级别,配置,模拟事务嵌套,延迟连接等功能
根目录下的Statement类实现了Doctrine\DBAL\Driver\Statement接口,并增加了日志,DBAL类型映射等功能。
Platform
Platform抽象了查询生成及不同的数据间的细微的差别。它将会被Schema和Statement等调用。
Platform的实现全部在Platform目录下,它会针对每个数据库支持实现不同的平台支持。它会在通过Driver中的getDatabasePlatform方法创建实例而进行初始化。不同的Driver调用对应的Platform。
Statement
Statement提供日志及类型的抽象转化功能集成,主要实现在根目录下的Statement.php文件

Schema
Schema抽象了生成表修改SQL的接口,这些修改操作的对象包括表,序列,外键和索引等。
它包括Schema目录下所有文件。
针对索引,表,外链等都有其独立实现,并且对于SchemaManager,各个数据库都有其不同的实现。
Type
Type抽象了所有数据库的类型,将各数据库的数据类型与DBAL中定义的类型一一对应。在程序调用时针根据选择的数据库映射到对应的类型。
它包括Type目录下的所有文件。所有的类型都继承自Type类。
Type的映射替换操作会在Statement绑定值和绑定参数时进行。
Log
Logging目录下所有文件
在2.0.0版本中仅包括SQLLogger接口,使用echo/var_dump输出日志的EchoSQLLogger类,包含执行过程中SQL的DebugStack类。
貌似这两个类都还在一个不完善的阶段。

Event
包括:
根目录下的Events.php文件
Event目录下的所有文件
Common目录下的EventArgs.php,EventManager.php,EventSubscriber.php文件
Events.php文件中的Event类是一个final类,它仅仅是一个包含所有事件的容器。在2.0.0版本中仅包含postConnect事件。
EventManager.php中的EventManager类是整个事件机制中非常重要的类,它负责整个事件的注册及事件的执行。对于postContent事件将会在Doctrine\DBAL\Connection的connect()方法中触发。
Event目录下的ConnectionEventArgs类负责与Connection的联系,它类似于一个中转站,在Doctrine\DBAL\Connection创建连接时会创建ConnectionEventArgs类的实例,并将Connection的实例作为构造方法的参数传递给ConnectionEventArgs实例。当事件触发时需要调用平台的数据库操作,则通过这个传递过来的Connection实例执行。Event目录下的Listeners目录是针对各个数据库平台的监听事件实现。

Exception
异常仅有根目录下的DBALException异常类。类中包含了若干静态方法,以显示不同的异常。对于这点,个人认为可以将不同的异常拆分成新的异常类,这比捕获DBALException异常后再调用相关的静态方法的扩展性会好一些。

Cache
缓存,这在每个系统或者每个大型架构中都会存在的模块。
在Common目录下的Cache文件夹包含了所有的缓存操作。这里提供了Apc,Array,Memcache,Xcache四种缓存。

【Doctrine DBAL调用过程】
以初始化一个连接,执行一条查询语句为例:

1
2
3
4
5
6
7
8
9
10
11
12
13
$config = new \Doctrine\DBAL\Configuration();
$connectionParams = array(
    'dbname' => 'mydb',
    'user' => 'user',
    'password' => 'secret',
    'host' => 'localhost',
    'driver' => 'pdo_mysql',
);
$conn = DriverManager::getConnection($connectionParams);
 
$statement = $conn->prepare('SELECT * FROM user');
$statement->execute();
$users = $statement->fetchAll();

第1~9行,配置并通过一个DriverManager创建一个Connection。这是一个工厂方法,它会依据driver调用不同的Driver类,这里会调用 Doctrine\DBAL\Driver\PDOMySql\Driver类,创建mysql的Driver。它会创建包装器实例,默认情况下是Doctrine\DBAL\Connection,我们可以通过在函数的参数中指定wrapperClass,从而创建自定义的包装器。最后返回一个包装类的实现。
第11行,调用Connection的prepare准备SQL语句,这里它会连接数据库,在连接时会依据Driver的不同,连接不同的数据库,并且会触发定义在connect方法中的Events::postConnect事件。最后,返回一个创建的Statement实例。
第12行,调用Statement的execute方法。在这个方法中会调用Log模块,记录SQL的日志,并且通过包装器中转调用相对应数据库的Statement实例的execute方法。
第13行,取数据。和上面的execute类似,调用对应Satement实例的fetchAll方法返回数据。

–EOF–