标签归档:PHP应用

数据库抽象层和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–

PHP中的urlencode,rawurlencode和JS中的encodeURI,encodeURIComponent

PHP中的urlencode,rawurlencode和JS中的encodeURI,encodeURIComponent

【PHP中的urlencode和rawurlencode】
urlencode之前有看过其源码实现PHP 源码阅读笔记二十三 :urlencode函数
二都的区别仅在” “空格上,rawurlencode()会把空格编码为%20,而urlencode会把空格编码为+

【JS中的encodeURI和encodeURIComponent】
encodeURI 方法不会对下列字符进行编码:”:”、”/”、”;” 和 “?”,而encodeURIComponent会编码这些字符

【urlencode与encodeURI】
首先,我们看下这4种编码方式针对ASCII的127个字符编码后的差别,显示代码如下:

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
27
28
29
30
31
32
33
<?php
/**
 * 生成urlencode,rawurlencode,encodeURI,encodeURIComponent的编码结果 2010-10-29 sz
 * @author phppan.p#gmail.com  http://www.phppan.com
 * 哥学社成员(http://www.blog-brother.com/)
 * @package test
 */
header("Content-type:text/html;charset=utf-8");
 
echo <<<STYLE
<style type="text/css">
    table {
cursor:default;
font-family:Verdana,Helvetica,sans-serif;
font-size:8pt;
}
td {
background:none repeat scroll 0 0 #EFEFEF;
text-align:center;
width:100px;
}
</style>
STYLE;
echo '<table >';
echo _tr(_td("ASCII") . _td("urlenocde") . _td("rawurlencode") . _td("encodeURI") . _td("encodeURIComponent"));
for ($i = 0; $i < 128; $i++) {
    $ch = chr($i);
    $td = _td($ch) . _td(urlencode($ch)) . _td(rawurlencode($ch));
    $td .= _td(_encodeURI($ch)) . _td(_encodeURIComponent($ch));
 
    echo _tr($td);
}
echo "</table>";

对比urlencode和encodeURI的不同,可以看到#$&+,/:;=?@这些符号编码结果不同,
于是对于需要在PHP中编码后,给js的encodeURI使用的操作可以使用如下函数:

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
27
28
29
30
31
32
33
<?php
/**
 * urlencode适用于js版本 2010-10-29 sz
 * @author phppan.p#gmail.com  http://www.phppan.com
 * 哥学社成员(http://www.blog-brother.com/)
 * @package test
 */
header("Content-type:text/html;charset=utf-8");
 
function urlencode_js($str) {
    $str_len = strlen($str);
 
    $new = array();
    for ($i = 0; $i < $str_len; $i++) {
        $ch = $str[$i];
        if (strpos("#$&+,/:;=?@", $ch) !== FALSE) {
            $new[] = $ch;
        } else {
            $new[] = urlencode($ch);
        }
    }
 
    return implode("", $new);
}
 
$encode_str = urlencode_js("a汉bc中文 章+aa#$&+,/:;=?@a汉bc中文 章+aa");
 
echo <<<HTML
<script type="text/javascript">
    document.write(decodeURI("$encode_str") + "<br />");
 </script>
HTML;
die();

【urlencode和urldecode的PHP实现】(折腾一个重复轮子玩)
以下代码纯属折腾,如有雷同,不胜荣幸

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
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
 
<?php
/**
 * urlencode和urldecode的PHP版本 2010-10-29 sz
 * @author phppan.p#gmail.com  http://www.phppan.com
 * 哥学社成员(http://www.blog-brother.com/)
 * @package test
 */
header("Content-type:text/html;charset=utf-8");
 
$str = "a汉bc中文 章+aa:/;?()'!-.*_~";
 
/**
 * urlencode的PHP实现
 * 纯属折腾 其C实现请参照PHP源码  url.c文件中php_url_encode函数
 * @param <type> $str
 * @return <type>
 */
function myurlencode($str) {
    $len = strlen($str);
 
    $rs = array();
    for ($i = 0; $i < $len; $i++) {
        $ch = $str[$i];
        if ($ch == ' ') {
            $rs[] = '+';
        } else if (!encodecheck($ch)) {
            $rs[] = strtoupper('%' . dechex(ord($ch) >> 4) . dechex(ord($ch) & 15));
        } else {
            $rs[] = $ch;
        }
    }
 
    return implode("", $rs);
}
 
/**
 * 判断是否为字符和字线以及_-.
 * 相当于c中的!isalnum(c) && strchr("_-.", c) == NULL(PHP源码)
 * @param <type> $ch
 * @return <type>
 */
function encodecheck($ch) {
    $pattern = "/[a-zA-Z0-9_\-\.]/";
    return preg_match($pattern, strval($ch));
}
 
/**
 * 判断是否为16进制数
 * @param <type> $ch
 * @return <type>
 */
function checkhex($ch) {
    $hexstr = "0123456789ABCDEF";
    return strpos($hexstr, strval($ch)) === FALSE ? FALSE : TRUE;
}
 
/**
 * urldecode的PHP实现
 * 纯属折腾
 * @param <type> $str
 * @return <type>
 */
function myurldecode($str) {
    $len = strlen($str);
 
    $rs = array();
    for ($i = 0; $i < $len; $i++) {
        $ch = $str[$i];
        if ($ch == '+') {
            $rs[] = ' ';
        } else if ($ch == '%' && isset($str[$i + 1]) && checkhex($str[$i + 1]) && isset($str[$i + 2]) && checkhex($str[$i + 2])) {
            $rs[] = chr(hexdec($str[$i + 1] . $str[$i + 2]));
            $i += 2;
        } else {
            $rs[] = $ch;
        }
    }
 
    return implode("", $rs);
}
 
/* 测试 */
echo $str, '<br />';
echo urldecode(myurlencode($str)), '<br />';
echo myurldecode(urlencode($str)), '<br />';
die();

以上算是对urlencode和urldecode实现的一次复习吧。

–EOF–

PHP页面仅输出了一部分内容的原因:程序报错

上周,vsgeping跟我讨论一个问题,关于PHP页面在显示时,仅显示了一部分,一开始我以为是缓冲的问题,即内容被写到stdout后,没有输出到浏览器。纠结了许久
然后在周末时,忽然想起前段时间由于json版本的问题也导致了类似情况的出现,于是怀疑是程序在那个点就停止了,或者说是报错了,程序由于关闭了报错,于是就没有了内容。
有代码,有真相
看如下的一个例子
【PHP报错终止仅显示报错前内容的示例】

错误之前的内容
错误之后的内容

可以看到程序输出:

错误之前的内容
Fatal error: Call to undefined function json() in

上面的程序由于调用了未定义的函数导致报错
如果此时我们去掉error_reporting(E_ALL)
那我们会看到程序仅输出:

错误之前的内容

在此感谢vsgeping