12.1产生XML当作一个字符串

12.1.1问题
You want to generate XML. For instance, you want to provide an XML version of your data for another program to

parse.

你想要产生XML。例如。你想要规定一个XML版本的数据解析其他程序

12.1.2解答
Loop through your data and print it out surrounded by the correct XML tags:
通过你的数据打印出正确的XML标签

?php
header(’Content-Type: text/xml’);
print ‘<?xml version=”1.0″?>’ . “\n”;
print “<shows>\n”;
$shows = array(array(’name’     => ‘Simpsons’,
                     ‘channel’  => ‘FOX’,
                     ’start’    => ‘8:00 PM’,
                     ‘duration’ => ‘30′),

               array(’name’     => ‘Law & Order’,
                     ‘channel’  => ‘NBC’,
                     ’start’    => ‘8:00 PM’,
                     ‘duration’ => ‘60′));
foreach ($shows as $show) {
    print “    <show>\n”;
    foreach($show as $tag => $data) {
        print “        <$tag>” . htmlspecialchars($data) . “</$tag>\n”;
    }
    print “    </show>\n”;
}
print “</shows>\n”;;

12.1.3讨论
Printing out XML manually mostly involves lots of foreach loops as you iterate through arrays. However, there

are a few tricky details. First, you need to call header( ) to set the correct Content-Type header for the

document. Since you’re sending XML instead of HTML, it should be text/xml.

手动打印出XML主要的foreach当作你重复的数组。尽管这是一个详细资料。首先你需要钓鱼hearder()去获得正确的Content-

Type.对于这个文件。从你过的XML代替HTML.它的text/xml
Next, depending on your settings for the short_open_tag configuration directive, trying to print the XML

declaration may accidentally turn on PHP processing. Since the <? of <?xml version=”1.0″?> is the short PHP

open tag, to print the declaration to the browser you need to either disable the directive or print the line

from within PHP. We do the latter in the Solution.

下面。依靠你的设置去short_open_tag配置指示。试着打印出XML声明可以转换PHP处理。从这个<?到<?xml version=”1.0″?>

是这个短的PHP tag去打印出这个声明到浏览器 你需要的任意显示这个指示或者打印出PHP。我们有解答在后面
Last, entities must be escaped. For example, the & in the show Law & Order needs to be &amp;. Call

htmlspecialchars( ) to escape your data.

最后实体必须逃脱。例如这个&在这个表面&需要到&amo、调用htmlspecialchars()去逃开你的数据
The output from the example in the Solution is shown in Example 12-1.

这个输出这个例子在这个解答在例子12-1

<?xml version="1.0"?>
<shows>
    <show>
        <name>Simpsons</name>
        <channel>FOX</channel>
        <start>8:00 PM</start>
        <duration>30</duration>
    </show>
    <show>
        <name>Law &amp; Order</name>
        <channel>NBC</channel>
        <start>8:00 PM</start>
        <duration>60</duration>
    </show>
</shows>

  

11.7存储适合的结果到一览表

11.7.1问题
You need to collect statistics from log tables that are too large to efficiently query in real time.

你需要收集统计到巨大的表格可以迅速的查询
11.7.2解答
Create a table that stores summary data from the complete log table, and query the summary table to generate

reports in nearly real time.

在全部的表格里创建一个表格去存储主要数据,然后查询一览表去产生报告在瞬间

11.7.3讨论

Let’s say that you are logging search queries that web site visitors use on search engines like Google and

Yahoo! to find your web site, and tracking those queries in MySQL. Your search term tracking log table has

this structure: 
在你的日志搜索查询这个web站点,访问者使用搜索引擎例如Google和Yahoo. 去寻找你的网络站点。然后跟踪这个查询在

MySQL。你查询一览表的结构


CREATE TABLE searches
(
  searchterm    VARCHAR(255) NOT NULL,  # search term determined from HTTP_REFERER
                                        parsing
  dt            DATETIME NOT NULL,      # request date
  source        VARCHAR(15) NOT NULL    # site where search was performed
);

If you are fortunate enough to be logging thousands or tens of thousands of visits from the major search

engines per hour, the searches table could grow to an unmanageable size over a period of several months.
如果你有足够数以千计的访问流量从主要的搜索路径每个小时。这个查询列表会有处理不了的尺寸在单月
You may wish to generate reports that illustrate trends of search terms that have driven traffic to your web

site over time from each major search engine so that you can determine which search engine to purchase

advertising with.

你可以希望产生报告举例搜索查询趋势交易到你的网络站点随着时间过去每个主要搜索引擎,你可以决定购买哪个搜索引擎的

广告

Create a summary table that reflects what your report needs to display, and then query the full dataset hourly

and store the result in the summary table for speedy retrieval during report generation. Your summary table

would have this structure:

创造一个一览表,当报告需要显示。然后每小时查询所有数据存储结果到这个一览表迅速的产生报告。你的一览表需要有它的

结构

CREATE TABLE searchsummary
(
  searchterm    VARCHAR(255) NOT NULL,  # search term
  source        VARCHAR(15) NOT NULL,   # site where search was performed
  sdate         DATE NOT NULL,          # date search performed
  searches      INT UNSIGNED NOT NULL,  # number of searches
  PRIMARY KEY (searchterm, source, sdate)
);

Your report generation script can then use PDO to query the searchsummary table, and if results are not

available, collect them from the searches table and cache the result in searchsummary:

你的报告产生原本的使用者PDO去查询表格。然后如果结果不可利用。收集搜索表格和存储结果到searchsummary

$st = $db->prepare('SELECT COUNT(*)
                     FROM
                        searchsummary
                     WHERE
                        sdate = ?');
$st->execute(array(date('Y-m-d', strtotime('yesterday'))));
$row = $st->fetch();
// no matches in cache
if ($row[0] == 0) {
    $st2 = $db->prepare('SELECT
                          searchterm,
                          source,
                          FROM_DAYS(TO_DAYS(dt)) AS sdate,
                          COUNT(*) as searches
                       WHERE
                          TO_DAYS(dt) = ?');
   $st2->execute(array(date('Y-m-d', strtotime('yesterday'))));

   $stInsert = $db->prepare('INSERT INTO searchsummary
                             (searchterm,source,sdate,searches)
                             VALUES (?,?,?,?)');
   while ($row->fetch(PDO::FETCH_NUM)) {
       $stInsert->execute($row);
   }
}

Using this technique, your script will only incur the overhead of querying the full log table once, and all

subsequent requests will retrieve a single row of summary data per search term.

使用这个方法。你的原本将唯一招致全部查询到表格然后给所有后来的请求会重新找回一个单一行的主要数据
   

11.6存储任意数据到公用存储器

11.6.1问题
You want a chunk of data to be available to all web server processes through shared memory.

你想要将大量的服务器进行可利用的数据存储到公用存储器
11.6.2解答

Use the pc_Shm class shown in Example 11-3. For example, to store a string in shared memory, used

the pc_Shm::save( ) method, which accepts a key/value pair
使用这个Pc_Shm类例如在例子11-3,例如去存储字符串到公用存储器。使用这个pc_Shm::save()方法,哪个认可一

个key/value

?php
$shm = new pc_Shm();
$secret_code = 'land shark';
$shm->save('mysecret', $secret_code);

Another process can then access that data from shared memory with the pc_Shm::fetch( ) method:

另外的处理方法可以从公用存储器里访问数据使用pc_Shm::fetch()方法

?php
$shm = new pc_Shm();
print $shm->fetch('mysecret');

11.6.3讨论

Occasionally there are times when you want to cache a value or set of values in shared memory for

rapid retrieval. If your web server is busy with disk I/O, it may make sense to leverage the shmop

functions to achieve greater performance with storage and retrieval of information in that cache.
有时候当你想要隐藏一个数值或者移动数值到共有存储器,如果你的网络服务器正工作于I/O磁盘,它可以起平衡作

用使这个shmop函数去完成存储和修复信息在这个存储区

The pc_Shm class has two convenient methods, pc_Shm::fetch( ) and pc_Shm::save( ), which abstract

away the need to set memory addresses or explictly open and close the shared memory segments.
这个pc_Shm类有两个方便的方法。pc_Shm::fetch()和pc_Shm::save().哪个主要函数需要获得存储地址或者打开关

闭这个公用存储器

<?php
$shm = new pc_Shm();
$shm->setSize(24576); // 24k
$shm->save('longstring', 'Lorem ipsum pri eu simul nominati...');

11.5存储Session到公用存储器

11.5.1问题
You want to store session data in shared memory to maximize performance.

你想要存储session数据到公用存储器,以保持最佳状态

11.5.2解答

Use the pc_Shm_Session class shown in Example 11-3. For example:

使用这个pc_Shm_Session 类例如在例子11-3、例如

?php
$s = new pc_Shm_Session();
ini_get('session.auto_start') or session_start();

11.5.3讨论
As discussed in Recipe 11.4, the session module allows users to define their own session handling

methods. While this flexibility is most commonly used to store session data in a database, you may

find that performance suffers with the overhead of the database connection and the subsequent

queries. If sharing session data across a bunch of web servers is not a concern, you can boost

session handling performance by storing that data in shared memory.
在第11.4的讨论中,这个session模块允许使用者定义他们自己的session讨论方法,它适用大多一般的经常存储

session数据在一个数据库,你可以找到受损害的session在数据库链接上面和后发的问题。如果共享session数据

横过web服务器而不涉及,你可以推进session处理执行存储这个数据在公用存储器
Before deciding to use shared memory for session storage, make sure that you can spare the amount of

memory that your traffic plus your average session data size will consume. The performance boost of

shared memory session storage won’t matter if your site’s sessions consume all available memory on

your system!
决定使用公用存储器存储session。确认你可以节省内存数量加上你的平均session数据量可以消耗。这个执行推进

公用存储器存储session.不用担心如果你的站点sessions消耗所有可利用的内容在你的系统

To store session data in shared memory, you need to have the shared memory functions explicitly

enabled by building PHP with –enable-shmop. You will also need the pc_Shm class shown in Example

11-3, as well as the pc_Shm_Session class shown in Example 11-3.

存储session数据在公用存储器,你需要明确公用存储器的函数记忆功能。激活建立PHP–enable-shmop你同样需要

这个pc_Shm 类例如在例子11-3当作pc_Shm_Session类。例如在例子11-3


class pc_Shm {
  var $tmp;
  var $size;
  var $shm;
  var $keyfile;
  function pc_Shm($tmp = '') {
    if (!function_exists('shmop_open')) {
      trigger_error('pc_Shm: shmop extension is required.', E_USER_ERROR);
      return;
    }
    if ($tmp != '' && is_dir($tmp) && is_writable($tmp)) {
      $this->tmp = $tmp;
    } else {
      $this->tmp = '/tmp';
    }
    // default to 16k
    $this->size = 16384;
    return true;
  }
  function __construct($tmp = '') {
    return $this->pc_Shm($tmp);
  }
  function setSize($size) {
    if (ctype_digit($size)) {
      $this->size = $size;
    }
  }
  function open($id) {
    $key = $this->_getKey($id);
    $shm = shmop_open($key, 'c', 0644, $this->size);
    if (!$shm) {
      trigger_error('pc_Shm: could not create shared memory segment', E_USER_ERROR);
      return false;
    }
    $this->shm = $shm;
    return true;
  }
  function write($data) {
    $written = shmop_write($this->shm, $data, 0);
    if ($written != strlen($data)) {
      trigger_error('pc_Shm: could not write entire length of data', E_USER_ERROR);
      return false;
    }
    return true;
  }
  function read() {
    $data = shmop_read($this->shm, 0, $this->size);
    if (!$data) {
      trigger_error('pc_Shm: could not read from shared memory block', E_USER_ERROR);
      return false;
    }
    return $data;
  }
  function delete() {
    if (shmop_delete($this->shm)) {
      if (file_exists($this->tmp . DIRECTORY_SEPARATOR . $this->keyfile)) {
        unlink($this->tmp . DIRECTORY_SEPARATOR . $this->keyfile);
      }
    }
    return true;
  }
  function close() {
    return shmop_close($this->shm);
  }
  function fetch($id) {
    $this->open($id);
    $data = $this->read();
    $this->close();
    return $data;
  }
  function save($id, $data) {
    $this->open($id);
    $result = $this->write($data);
    if (! (bool) $result) {
      return false;
    } else {
      $this->close();
      return $result;
    }
  }
  function _getKey($id) {
    $this->keyfile = 'pcshm_' . $id;
    if (!file_exists($this->tmp . DIRECTORY_SEPARATOR . $this->keyfile)) {
      touch($this->tmp . DIRECTORY_SEPARATOR . $this->keyfile);
    }
    return ftok($this->tmp . DIRECTORY_SEPARATOR . $this->keyfile, 'R');
  }
}

The pc_Shm class provides an object-oriented wrapper around PHP’s shmop functions. The

pc_Shm::_getKey( ) method provides a convenient way to transparently calculate a memory address,

which is often the biggest obstacle for people getting familiar with the shmop functions. By

abstracting the memory address, reading and writing from shared memory is as easy as manipulating a

value in an associative array.

这个pc_Shm类规定一个object_oriented环绕PHP的shmop函数。这个pc_Shm::_getKey()方法规定一个方便的方法去

明显的计算存储地址,那个是经常使用的大妨碍物人们获得熟悉的shmop函数,显示这个存储地址。读写公用存储

器更加简单熟练的一个值在一个联合数组 

pc_Shm creates 16k memory blocks by default. To adjust the size of the blocks used, pass a value in

bytes to the pc_Shm::setSize( ) method.

pc_Shm 创造16K存储区。整修使用者大小通过一个数值在pc_Shm::setSize()方法

With pc_Shm defined, pc_Shm_Session has what it needs to easily provide custom methods for

session_set_save_handler( ). Example 11-3 shows the pc_Shm_Session class.

定义pc_Shm 它可以容易的规定习惯方法对于session_set_save_handler(),例子11-3显示这个pc_Shm_Session类

class pc_Shm_Session {
  var $shm;
  function pc_Shm_Session($tmp = '') {
    if (!function_exists('shmop_open')) {
      trigger_error("pc_Shm_Session: shmop extension is required.",E_USER_ERROR);
      return;
    }
    if (! session_set_save_handler(array(&$this, '_open'),
                     array(&$this, '_close'),
                     array(&$this, '_read'),
                     array(&$this, '_write'),
                     array(&$this, '_destroy'),
                     array(&$this, '_gc'))) {
      trigger_error('pc_Shm_Session: session_set_save_handler() failed', E_USER_ERROR);
      return;
    }
    $this->shm = new pc_Shm();
    return true;
  }
  function __construct() {
    return $this->pc_Shm_Session();
  }
  function setSize($size) {
    if (ctype_digit($size)) {
      $this->shm->setSize($size);
    }
  }
  function _open() {
    return true;
  }
  function _close() {
    return true;
  }
  function _read($id) {
    $this->shm->open($id);
    $data = $this->shm->read();
    $this->shm->close();
    return $data;
  }
  function _write($id, $data) {
    $this->shm->open($id);
    $this->shm->write($data);
    $this->shm->close();
    return true;
  }
  function _destroy($id) {
    $this->shm->open($id);
    $this->shm->delete();
    $this->shm->close();
  }
  function _gc($maxlifetime) {
    $d = dir($this->tmp);
    while (false !== ($entry = $d->read())) {
      if (substr($entry, 0, 6) == 'pcshm_') {
        $tmpfile = $this->tmp . DIRECTORY_SEPARATOR . $entry;
        $id = substr($entry, 6);
        $fmtime = filemtime($tmpfile);
        $age = now() - $fmtime;
        if ($age >= $maxlifetime) {
          $this->shm->open($id);
          $this->shm->delete();
          $this->shm->close();
        }
      }
    }
    $d->close();
    return true;
  }
}

Versions of Microsoft Windows prior to Windows 2000 do not support shared memory. Also, when using

PHP in a Windows server environment, shmop functions will only work if PHP is running as a web

server module, such those provided by Apache or IIS. CLI and CGI interfaces to PHP do not support

shmop functions under Windows.

微软2000操作系统之前的版本都不支持公用存储器,同样当使用PHP在一个Windows服务器环境。shmop函数仅仅可

以工作。如果PHP运行在一个web服务器模块 例如那些Apache或者IIS。CLT和CGI界面不支持shmop函数在Windows


?php
ini_set('session.save_path', '/dev/shm');
ini_get('session.auto_start') or session_start();

 

11.4将session存储到数据库

11.4.1问题
You want to store session data in a database instead of in files. If multiple web servers all have access to the same database, the

session data is then mirrored across all the web servers.
你想要存储session数据在一个数据库代替在文件里。如果多样化网络服务器都有权使用一样的数据库。这个session数据在这个DOS访问所有 web服务器
11.4.2解答
Use a class or a set of functions in conjunction with the session_set_save_handler( ) function to define database-aware routines for

session management. For example, use PEAR’s HTTP_Session package for convenient database session storage:

使用一个类或者一个函数集和这个session_set_save_handler()函数定义database-aware程序管理、例如。使用REAR’s HTTP_Session 便利的存储session到

数据库


?php
require_once 'HTTP/Session/Container/DB.php';
$s = new HTTP_Session_Container_DB('mysql://user:password@localhost/db');
ini_get('session.auto_start') or session_start();

11.4.3讨论
One of the most powerful aspects of the session module is its abstraction of how sessions get saved. The session_set_save_handler( )

function tells PHP to use different functions for the various session operations such as saving a session and reading session data.

一个session 模块最有力的方面是怎么获得session.这个session_set_save_handler()函数告诉PHP去使用不同的函数在不同的session操作。例如获得一个

session和读出session的数据
The PEAR HTTP_Session package provides classes that take advantage of PEAR’s DB, MDB, and MDB2 database abstraction packages to store

session data in a database. If the database is shared between multiple web servers, users’ session information is portable across all

those web servers. So if you have a bunch of web servers behind a load balancer, you don’t need any fancy tricks to ensure that a user’s

session data is accurate no matter which web server she gets sent to.

这个PEAR HTTP_Session 规定利用PEAR’sDB 和MDB2数据库提交数据库包去存储session数据,如果这个数据库共享多个浏览器。使用者的session信息轻松越

过这些服务器。所有如果你有一个网络服务器的加载权衡,你不需要一些特别的窍门去确保使用者的session数据正确性

To use HTTP_Session_Container_DB, pass a data source name (DSN) to the class when you instantiate it. The session data is stored in a

table called sessiondata whose structure is:
去使用HTTP_Session_Container_DB。通过一个数据发起姓名(DSN)到这个类。当你示例时。这个session数据存储在一个sessiondata的表格里


CREATE TABLE sessiondata
(
  id CHAR(32) NOT NULL,
  data MEDIUMBLOB,
  expiry INT UNSIGNED NOT NULL,
  PRIMARY KEY (id)
);

If you want the table name to be different than sessiondata, you can set a new table name with an options array when instantiating the

HTTP_Session_Container_DB class:

如果你想要表格名字不同于sessiondata。 你可以设置一个新的表格名和一个选项数组当示例这个HTTP_Session_Container_DB类


?php
require_once 'HTTP/Session/Container/DB.php';
$options = array(
  'table'  => 'php_session',
  'dsn'    => 'mysql://user:password@localhost/db'
);
$s = new HTTP_Session_Container_DB($options);
ini_get('session.auto_start') or session_start();

11.3防止SESSION固定

11.3.1问题
You want to make sure that your application is not vulnerable to session fixation attacks.

你想要确认你的应用软件不容易遭到攻击session可以抵御攻击

11.3.2解答
Require the use of session cookies without session identifiers appended to URLs, and generate a new session ID frequently:

命令使用session cookies和session 表示符附加到URLs,然后产生一个新的session ID


ini_set('session.use_only_cookies', true);
session_start();
if (!isset($_SESSION['generated'])
    || $_SESSION['generated'] < (time() - 30)) {
    session_regenerate_id();
    $_SESSION['generated'] = time();
}

11.3.3讨论
In this example, we start by setting PHP’s session behavior to use cookies only. This overrides PHP’s default behavior of transparently

appending values such as ?PHPSESSID=12345678 to any URL on a page whenever a visitor’s session is started if he doesn’t have cookies

enabled in his browser.

在这个例子,我们首先安装PHP’s session去使用唯一cookies、它不考虑PHP的默认行为附加值例如PHPSESSID=1234567到一些URL的页面,当访问者的

session已经开始,否则cookies在浏览器激活
Once the session is started, we set a value that will keep track of the last time a session ID was generated. By requiring a new one to

be generated on a regular basis’every 30 seconds in this example’the opportunity for an attacker to obtain a valid session ID is

dramatically reduced.

一旦session启动。我们给定值,它会产生上次的一个session ID,它命令新的一个session在每30秒产生一个正规的元素。在这个例子攻击者的机会是获得

一个有效的session ID简化

These two approaches combine to virtually eliminate the risk of session fixation. An attacker has a hard time obtaining a valid session

ID because it changes so often, and since sessions IDs can only be passed in cookies, a URL-based attack is not possible. Finally, since

we enabled the session.use_only_cookies setting, no session cookies will be left lying around in browser histories or in server referrer

logs.

这两个联合方法事实上排除了有风险的固定session。攻击者很困难去获得一个有效的session ID.因为它是经常变化的。从前的session IDs已经被传递到

cookies。一个基于URL的攻击不可能存在。最后我们激活这个session.use_only_cookies设置。没有session cookies将被丢失在浏览器历史或者在浏览器提

交日志

11.2防止Session丢失

11.2.1问题
You want make sure an attacker can’t access another user’s session
你想要确认攻击者不能访问其他使用者的session

11.2.2解答
Allow passing of session IDs via cookies only, and generate an additional session token that is passed via URLs. Only requests that

contain a valid session ID and a valid session token may access the session:

允许通过session IDs到唯一的cookies。然后产生一个另外的session表示通过URLs、唯一的请求包含一个有效的session ID和一个有效的session表示可以

访问这个session


?php
ini_set('session.use_only_cookies', true);
session_start();
$sat     = 'YourSpecialValueHere';
$tokenstr = (str) date('W') . $salt;
$token    = md5($tokenstr);
if (!isset($_REQUEST['token']) || $_REQUEST['token'] != $token) {
    // prompt for login
    exit;
}
$_SESSION['token'] = $token;
output_add_rewrite_var('token', $token);

If you’re using a PHP version earlier than 4.3.0, output_add_rewrite_var( ) is not available. Instead, use the code in Example 11-1.
如果你使用一个早于4.3.0的PHP版本。output_add_rewrite_var()不可用。代替它,使用这个代码在例子11-1
Adding a session token to links
增加一个session的链接

?php
ini_set('session.use_only_cookies', true);
session_start();
$salt     = 'YourSpecialValueHere';
$tokenstr = (str) date('W') . $salt;
$token    = md5($tokenstr);
if (!isset($_REQUEST['token']) || $_REQUEST['token'] != $token) {
    // prompt for login
    exit;
}
$_SESSION['token'] = $token;
ob_start('inject_session_token');
function inject_session_token($buffer)
{
    $hyperlink_pattern = "/<a[^>]+href=\"([^\"]+)/i";
    preg_match_all($hyperlink_pattern, $buffer, $matches);
    foreach ($matches[1] as $link) {
        if (strpos($link, '?') === false) {
            $newlink = $link . '?token=' . $_SESSION['token'];
        } else {
            $newlink = $link .= '&token=' . $_SESSION['token'];
        }
        $buffer = str_replace($link, $newlink, $buffer);
    }
    return $buffer;
}

The regular expression for matching hyperlinks in the inject_session_token( ) function isn’t bulletproof; it will not catch hyperlinks

with href attributes quoted with single quotes.
这个正则表达式匹配超链接在这个inject_session_token()函数不是bulletproof.他不能捕捉超链接和href使用单引号

11.2.3讨论
This example creates an auto-shifting token by joining the current week number together with a salt term of your choice. With this

technique, tokens will be valid for a reasonable period of time without being fixed.

这个例子创造一个auto-shifting表示连接通用的星期数加之你选择的一个salt期限。用这个方法表示一个有效合理的时段

We then check for the token in the request, and if it’s not found, we prompt for a new login.

然后我们阻止这个请求。然后如果它找不到。我们提示一个新的注册

If it is found, it needs to be added to generated links. output_add_rewrite_var( ) does this easily. Without output_add_rewrite_var( ),

we continue generating the page and declare an output buffer callback function that will make sure that any hyperlinks on the page are

modified to contain the current token before the page is displayed.

如果建立。它需要增加连接。output_add_rewrite_var()可以更加简单。使用output_add_rewrite_var(),我们连续产生这个页面声明输出缓冲器回收函数

。它可以确认一些超链接在这个页面修正、包含当前通用的页面显示
Note that the inject_session_token( ) function in the example does not address imagemaps, form submissions, or Ajax calls; make sure that

you adjust any such functionality on a page to include the session token that’s been generated and stored in the session
注意这个inject_session_token()函数在这个例子不能显示图像。或者Ajax钓鱼。确认你调整这些功能性在一个页面包含session产生存储在这个session

11.1使用Session跟踪

11.1使用Session跟踪

11.1.1问题
You want to maintain information about a user as she moves through your site
你想要维护使用者的信息移动到你的站点

11.1.2解答
Use the sessions module. The session_start( ) function initializes a session, and accessing an element in the auto-global $_SESSION array tells PHP to keep track of the corresponding variable:

使用者这个session模块。这个seesion_start()函数初始化一个session。然后访问一个元素在全局变量$_SESSION数组告诉PHP相应的变量


?php
session_start();
$_SESSION['visits']++;
print 'You have visited here '.$_SESSION['visits'].' times.';

11.1.3讨论
The session function keep track of users by issuing them cookies with randomly generated session IDs.

这个session函数保存使用者的cookies和未知的sessionIDs

By default, PHP stores session data in files in the /tmp directory on your server. Each session is stored in its own file. To change the directory in which the files are saved, set the session.save_path configuration directive to the new directory in php.ini or with ini_set( ). You can also call session_save_path( ) with the new directory to change directories, but you need to do this before starting the session or accessing any session variables

By default。PHP存储session数据在文件在你的服务器目录、每个session存储它自己的文件。去改变目录这个文件保存在哪里。移动这个session.save_path配置指示新的目录在php.ini或者ini_set(),你同样可以调用session_save_path()和这个新的目录去改变目录,但是你需要之前已经开始的session或者访问一些session的变量
To start a session automatically on each request, set session.auto_start to 1 in php.ini. With session.auto_start, there’s no need to call session_start( ).

自动开始一个session在每个索引。设置session.auto_start到1在php.ini.使用session.auto_start,就不需要调用session_start()

With the session.use_trans_sid configuration directive turned on, if PHP detects that a user doesn’t accept the session ID cookie, it automatically adds the session ID to URLs and forms.[] For example, consider this code that prints a URL:

使用这个session.use_trans_sid配置指示开始。如果PHP发现这个使用者不接受session ID cookie。它自动的增加session Id 到URLs和forms.[]例如考虑这个代码打印出一个URL

?php
print '<a href="train.php" mce_href="train.php">Take the A Train</a>';

If sessions are enabled, but a user doesn’t accept cookies, what’s sent to the browser is something like:

如果sessions激活。但是使用者不接受cookies。发送浏览器重要

?php
<a href="train.php?PHPSESSID=2eb89f3344520d11969a79aea6bd2fdd" mce_href="train.php?PHPSESSID=2eb89f3344520d11969a79aea6bd2fdd">Take the A Train</a>

 

In this example, the session name is PHPSESSID and the session name is 2eb89f3344520d11969a79aea6bd2fdd. PHP adds those to the URL so they are passed along to the next page. Forms are modified to include a hidden element that passes the session ID.

在这些例子。这个session名字是PHPSESSID和这个session名字是2eb89f3344520d11969a79aea6bd2fdd.PHP增加这些URL,所以它通过下一个页面。改良包括隐藏的元素到这个sessionID
Due to a variety of security concerns relating to embedding session IDs in URLs, this behavior is disabled by default. To enable transparent session IDs in URLs, you need to turn on session.use_trans_sid in php.ini or through the use of ini_set(’session.use_trans_sid’, true) in your scripts before the session is started.

由于一个多种安全涉及embedding session IDs在URLs.这些行为是丧失能力的。能够显然的sessions IDs在URLs.你需要转换session.ues_trans_sid在php.ini或者通过这些使用ini_set(’session.use_trans_sid’,ture)在你的原本session开始
Although session.use_trans_sid is convenient, it can cause you some security-related headaches. Because URLs have session IDs in them, distribution of such a URL lets anybody who receives the URL act as the user to whom the session ID was given. A user that copies a URL from his web browser and pastes it into an email message sent to friends unwittingly allows all those friends (and anybody else to whom the message is forwarded) to visit your site and impersonate him.

通过session.use_trans_sid很方便。它可以使你有一些安全,因为URLs有sessionIDs在这些。分配这些就像一个URL任何接收URL获得使用者的ID.一个使用者复制一个URL从它的浏览器,然后粘贴它到一个email信息无意的发送到一个好友允许所有好友访问你的站点
Separately, redirects with the Location header aren’t automatically modified, so you have to add a session ID to them yourself using the SID constant:

个别的改变方向位置标题不能自动修改。所有你需要增加一个session ID到这些yourself使用者SID常量


$redirect_url = 'http://www.example.com/airplane.php';
if (defined('SID') && (!isset($_COOKIE[session_name()]))) {
    $redirect_url .= '?' . SID;
}
header("Location: $redirect_url");
</code>
The session_name( ) function returns the name of the cookie to the session ID is stored in, so this code appends the SID constant to $redirect_url if the constant is defined, and the session cookie isn't set.

这个session_name()函数返回cookie的名字到session ID存储它,所以这个代码附加这个SID常量到$redirect_url。如果常量是定义过的。这个session cookie不移动
;
?php
ini_set('session.use_only_cookies', true);
session_start();
$salt     = 'YourSpecialValueHere';
$tokenstr = (str) date('W') . $salt;
$token    = md5($tokenstr);
if (!isset($_REQUEST['token']) || $_REQUEST['token'] != $token) {
    // prompt for login
    exit;
}
$_SESSION['token'] = $token;
ob_start('inject_session_token');
function inject_session_token($buffer)
{
    $hyperlink_pattern = "/<a[^>]+href=\"([^\"]+)/i";
    preg_match_all($hyperlink_pattern, $buffer, $matches);
    foreach ($matches[1] as $link) {
        if (strpos($link, '?') === false) {
            $newlink = $link . '?token=' . $_SESSION['token'];
        } else {
            $newlink = $link .= '&token=' . $_SESSION['token'];
        }
        $buffer = str_replace($link, $newlink, $buffer);
    }
    return $buffer;
}

11.0介绍

As web applications have matured, the need for statefulness has become a common requirement. Stateful web applications, meaning applications that keep

track of a particular visitor’s information as he travels throughout a site, are now so common that they are taken for granted.

当中web软件应用成熟。它需要充满规定丰富的成为一个必要条件。规定web应用。意思是应用详细的访问者的信息当中一个地点,既然是一个共有的。它们认可
Given the prevalence of web applications that keep track of things for their visitors’such as shopping carts, online banking, personalized home page

portals, and social networking community sites’it is hard to imagine the Internet we use every day without stateful applications.

获得流行的web应用软件,访问者当作购物车。联机银行的私人主页入口和社会网络站点,假设我们每天使用Internet的应用软件
HTTP, the protocol that web servers and clients use to talk to each other, is a stateless protocol by design. However, since PHP 4.0, developers who’ve

built applications with PHP have had a convenient set of session management functions that have made the challenge of implementing statefulness much

easier. This chapter focuses on several good practices to keep in mind while developing stateful applications.
HTTP.这个网络浏览器协议和客户机程序使用彼此。它是一个无国界协议,尽管从PHP4.0开发者建立应用软件和PHP有一套方便的管理函数。它已经获得这个挑战执行非常简

单。这一章集中几个单独的有良好习惯做法的发展中的应用软件
Sessions are focused on maintaining visitor-specific state between requests. Some applications also require an equivalent type of lightweight storage

of non-visitor-specific state for a period of time at the server-side level. This is known as data persistence.

集中维护特殊访问者的请求。一些应用软件同样命令一个相等的类型不匹配的特殊访问者,它们是持续的
Recipe 11.1 explains PHP’s session module, which lets you easily associate persistent data with a user as he moves through your site. Recipes 11.2 and

11.3 explore session hijacking and session fixation vulnerabilities and how to avoid them.

第11.1章说明PHP的模块。哪个是不容易结合持久的数据和一个使用者当作你的移动站点。第11.2章和11.3章探测session hijackin and session fixation弱点和消除他们
Session data is stored in flat files in the server’s /tmp directory by default. Recipes 11.4 and 11.5 explain how to store session data in alternate

locations, such as a database and shared memory, and discusses the pros and cons of these different approaches.

Session数据存储在flat文件在这个服务器/tmp目录默认。第11.4章和11.5章说明怎么去存储session数据在交替的位置。例如一个数据库和公用存储器讨论赞成与反对 

这些不同的方法

Recipe 11.6 demonstrates how to use shared memory for more than just session data storage, and Recipe 11.7 illustrates techniques for longer-term

storage of summary information that has been gleaned from logfiles.
第11.6章证明这么去使用公用存储器对于这些正好的数据保存,在第11.7章举例证明比较久的存储技术概要信息来自这些文件

10.16程序:存储一个有线状图案装饰的留言板

Storing and retrieving threaded messages requires extra care to display the threads in the correct order. Finding the children of each message and building the tree of message relationships can easily lead to a recursive web of queries. Users generally look at a list of messages and read individual messages far more often then they post messages. With a little extra processing when saving a new message to the database, the query that retrieves a list of messages to display is simpler and much more efficient.
存储和重新找回有线状图案装饰留言板很注意显示这个思路在这个正确的规则。发现每个子信息建立三个子信息关联很容易的通向一个递归的web查询,使用者通常注意一列信息阅读单一的信息对于更多经常使用的信息和一个小量的额外处理当获得一个新的信息到这个数据库这个查询重新找回一列信息显示更加简单直接

Store messages in a table structured like this:
存储信息在一个表格结构例如

CREATE TABLE pc_message (
  id INT UNSIGNED NOT NULL,
  posted_on DATETIME NOT NULL,
  author CHAR(255),
  subject CHAR(255),
  body MEDIUMTEXT,
  thread_id INT UNSIGNED NOT NULL,
  parent_id INT UNSIGNED NOT NULL,
  level INT UNSIGNED NOT NULL,
  thread_pos INT UNSIGNED NOT NULL,
  PRIMARY KEY(id)
);
</code>
The primary key, id, is a unique integer that identifies a particular message. The time and date that a message is posted is stored in posted_on, and author, subject, and body are (surprise!) a message's author, subject, and body. The remaining four fields keep track of the threading relationships between messages. The integer tHRead_id identifies each thread.

这个主要的键。id是一个唯一的整数确认一个特殊的信息。这个时间和日期的信息是一个主要的村粗在posted_on和作者,题目和主要(surprise!)作者信息题目。保留的4个文件关联信息之间、这个整数tHread_id鉴别每个思路

<code>
<?php
$board = new MessageBoard();
$board->go();
class MessageBoard {
    protected $db;
    protected $form_errors = array();
    protected $inTransaction = false;
    public function __construct() {
        set_exception_handler(array($this,'logAndDie'));
        $this->db = new PDO('sqlite:/usr/local/data/message.db');
        $this->db->setAttribute(PDO::ATTR_ERRMODE,PDO::ERRMODE_EXCEPTION);
    }
    public function go() {
        // The value of $_REQUEST['cmd'] tells us what to do
        $cmd = isset($_REQUEST['cmd']) ? $_REQUEST['cmd'] : 'show';
        switch ($cmd) {
            case 'read':          // read an individual message
              $this->read();
              break;
            case 'post':          // display the form to post a message
              $this->post();
              break;
            case 'save':          // save a posted message
              if ($this->valid()) { // if the message is valid,
                  $this->save();    // then save it
                  $this->show();    // and display the message list
              } else {
                  $this->post();    // otherwise, redisplay the posting form
              }
              break;
            case 'show':          // show a message list by default
            default:
              $this->show();
              break;
        }
    }
    // save() saves the message to the database
    protected function save() {
        $parent_id = isset($_REQUEST['parent_id']) ?
                     intval($_REQUEST['parent_id']) : 0;
        // Make sure pc_message doesn't change while we're working with it.
        $this->db->beginTransaction();
        $this->inTransaction = true;
        // is this message a reply?
        if ($parent_id) {
            // get the thread, level, and thread_pos of the parent message
            $st = $this->db->prepare("SELECT thread_id,level,thread_pos
                                FROM pc_message WHERE id = ?");
            $st->execute(array($parent_id));
            $parent = $st->fetch();
            // a reply's level is one greater than its parent's
            $level = $parent['level'] + 1;
            /* what's the biggest thread_pos in this thread among messages
            with the same parent? */
            $st = $this->db->prepare('SELECT MAX(thread_pos) FROM pc_message
                    WHERE thread_id = ? AND parent_id = ?');
            $st->execute(array($parent['thread_id'], $parent_id));
            $thread_pos = $st->fetchColumn(0);
            // are there existing replies to this parent?
            if ($thread_pos) {
                // this thread_pos goes after the biggest existing one
                $thread_pos++;
            } else {
                // this is the first reply, so put it right after the parent
                $thread_pos = $parent['thread_pos'] + 1;
            }
            /* increment the thread_pos of all messages in the thread that
            come after this one */
            $st = $this->db->prepare('UPDATE pc_message SET thread_pos = thread_pos + 1
                          WHERE thread_id = ? AND thread_pos >= ?');
            $st->execute(array($parent['thread_id'], $thread_pos));
            // the new message should be saved with the parent's thread_id
            $thread_id = $parent['thread_id'];
        } else {
            // the message is not a reply, so it's the start of a new thread
            $thread_id = $this->db->query('SELECT MAX(thread_id) + 1 FROM pc_message')
                           ->fetchColumn(0);
            $level = 0;
            $thread_pos = 0;
        }
        /* insert the message into the database. Using prepare() and execute()
        makes sure that all fields are properly quoted */
        $st = $this->db->prepare("INSERT INTO pc_message (id,thread_id,parent_id,
                       thread_pos,posted_on,level,author,subject,body)
                       VALUES (?,?,?,?,?,?,?,?,?)");

        $st->execute(array(null,$thread_id,$parent_id,$thread_pos,
                             date('c'),$level,$_REQUEST['author'],
                             $_REQUEST['subject'],$_REQUEST['body']));
        // Commit all the operations
        $this->db->commit();
        $this->inTransaction = false;
    }
    // show() displays a list of all messages
    protected function show() {
        print '<h2>Message List</h2><p>';
        /* order the messages by their thread (thread_id) and their position
        within the thread (thread_pos) */
        $st = $this->db->query("SELECT id,author,subject,LENGTH(body) AS body_length,
                       posted_on,level FROM pc_message
                       ORDER BY thread_id,thread_pos");
        while ($row = $st->fetch()) {
            // indent messages with level > 0
            print str_repeat('&nbsp;',4 * $row['level']);
            // print out information about the message with a link to read it
            print "<a href='" . htmlentities($_SERVER['PHP_SELF']) .
            "?cmd=read&amp;id={$row['id']}'>" .
            htmlentities($row['subject']) . '</a> by ' .
            htmlentities($row['author']) . ' @ ' .
            htmlentities($row['posted_on']) .
            " ({$row['body_length']} bytes) <br/>";
        }
        // provide a way to post a non-reply message
        print "<hr/><a href='" .
              htmlentities($_SERVER['PHP_SELF']) .
              "?cmd=post'>Start a New Thread</a>";
    }
    // read() displays an individual message
    public function read() {
        /* make sure the message id we're passed is an integer and really
        represents a message */
        if (! isset($_REQUEST['id'])) {
            throw new Exception('No message ID supplied');
        }
        $id = intval($_REQUEST['id']);
        $st = $this->db->prepare("SELECT author,subject,body,posted_on
                                 FROM pc_message WHERE id = ?");
        $st->execute(array($id));
        $msg = $st->fetch();
        if (! $msg) {
            throw new Exception('Bad message ID');
        }
        /* don't display user-entered HTML, but display newlines as
        HTML line breaks */
        $body = nl2br(htmlentities($msg['body']));

        // display the message with links to reply and return to the message list
        $self = htmlentities($_SERVER['PHP_SELF']);
        $subject = htmlentities($msg['subject']);
        $author = htmlentities($msg['author']);
        print<<<_HTML_
<h2>$subject</h2>
<h3>by $author</h3>
<p>$body</p>
<hr/>
<a href="$self?cmd=post&parent_id=$id">Reply</a>
<br/>
<a href="$self?cmd=list">List Messages</a>
_HTML_;
    }
    // post() displays the form for posting a message
    public function post() {
        $safe =  array();
        foreach (array('author','subject','body') as $field) {
            // escape characters in default field values
            if (isset($_POST[$field])) {
                $safe[$field] = htmlentities($_POST[$field]);
            } else {
                $safe[$field] = '';
            }
            // make the error messages display in red
            if (isset($this->form_errors[$field])) {
                $this->form_errors[$field] = '<span style="color: red">' .
                   $this->form_errors[$field] . '</span><br/>';
            } else {
                $this->form_errors[$field] = '';
            }
        // is this message a reply
        if (isset($_REQUEST['parent_id']) &&
        $parent_id = intval($_REQUEST['parent_id'])) {
        // send the parent_id along when the form is submitted
        $parent_field =
            sprintf('<input type="hidden" name="parent_id" value="%d" />',
                    $parent_id);
        // if no subject's been passed in, use the subject of the parent
        if (! strlen($safe['subject'])) {
             $st = $this->db->prepare('SELECT subject FROM pc_message WHERE id = ?');
             $st->execute(array($parent_id));
             $parent_subject = $st->fetchColumn(0);
            /* prefix 'Re: ' to the parent subject if it exists and
               doesn't already have a 'Re:' */
            $safe['subject'] = htmlentities($parent_subject);
            if ($parent_subject && (! preg_match('/^re:/i',$parent_subject))) {
                $safe['subject'] = "Re: {$safe['subject']}";
            }
          }
        } else {
            $parent_field = '';
        }
    // display the posting form, with errors and default values
    $self = htmlentities($_SERVER['PHP_SELF']);
    print<<<_HTML_
<form method="post" action="$self">
<table>
<tr>
 <td>Your Name:</td>
 <td>{$this->form_errors['author']}
     <input type="text" name="author" value="{$safe['author']}" />
</td>
<tr>
 <td>Subject:</td>
 <td>{$this->form_errors['subject']}
     <input type="text" name="subject" value="{$safe['subject']}" />
</td>
<tr>
 <td>Message:</td>
 <td>{$this->form_errors['body']}
     <textarea rows="4" cols="30" wrap="physical"
               name="body">{$safe['body']}</textarea>
</td>
<tr><td colspan="2"><input type="submit" value="Post Message" /></td></tr>
</table>
$parent_field
<input type="hidden" name="cmd" value="save" />
</form>
_HTML_;
    }
    // validate() makes sure something is entered in each field
    public function valid() {
        $this->form_errors = array();
        if (! (isset($_POST['author']) && strlen(trim($_POST['author'])))) {
            $this->form_errors['author'] = 'Please enter your name.';
        }
        if (! (isset($_POST['subject']) && strlen(trim($_POST['subject'])))) {
            $this->form_errors['subject'] = 'Please enter a message subject.';
        }
        if (! (isset($_POST['body']) && strlen(trim($_POST['body'])))) {
            $this->form_errors['body'] = 'Please enter a message body.';
        }
        return (count($this->form_errors) == 0);
    }
    public function logAndDie(Exception $e) {
        print 'ERROR: ' . htmlentities($e->getMessage());
        if ($this->db && $this->db->inTransaction) {
            $this->db->rollback();
        }
        exit();
    }
}

To properly handle concurrent usage, save( ) needs exclusive access to the msg table between the time it starts calculating the tHRead_pos of the new message and when it actually inserts the new message into the database. We’ve used PDO’s beginTransaction( ) and commit( ) methods to accomplish this. Note that logAndDie( ), the exception handler, rolls back the transaction when appropriate if an error occured inside the transaction. Although PDO always calls rollback( ) at the end of a script if a transaction was started, explicitly including the call inside logAndDie( ) makes clearer what’s happening to someone reading the code.

去适当的操作并发的用法。save()需要唯一有权使用这个msg表格它开始计算这个tHread_pos的新的信息和当它嵌入这个新的信息到这个数据库我们使用PDO的beginTransaction()和commit()方法去完成它注意logAndDie()。这个例外的管理者压低到标准水平处理它当适当的时候。如果一个错误出现在这个处理。尽管PDO总是调用rollback()在这个最后的一个版本如果一个处理开始明确的包括调用logAndDie()更清楚当发生在更多的代码
The level field can be used when displaying messages to limit what you retrieve from the database. If discussion threads become very deep, this can help prevent your pages from growing too large. Example 10-44 shows how to display just the first message in each thread and any replies to that first message.

这个标准的文件可以使用当显示信息限制当你从数据库重新找回。如果讨论思路非常深。它可以帮助你的页面更加大。例子10-44显示怎么正好显示这第一个信息在每个思路和任何回答第一个信息

?php
$st = $this->db->query(
    "SELECT * FROM pc_message WHERE level <= 1 ORDER BY thread_id,thread_pos");
while ($row = $st->fetch()) {
    // display each message
}