浅析php接口操作interface关键字的用法及应用实例

 更新时间:2016年11月25日 16:19  点击:2168
interface是面向对象编程语言中接口操作的关键字,功能是把所需成员组合起来,以封装一定功能的集合。本文我们来讲讲interface基本知识及用法实例。

接口是一种约束形式,其中只包括成员定义,不包含成员实现的内容。用接口(interface),你可以指定某个类必须实现哪些方法,但不需要定义这些方法的具体内容。 我们可以通过interface来定义一个接口,就像定义一个标准的类一样,但其中定义所有的方法都是空的。

接口中定义的所有方法都必须是public,这是接口的特性。

实现

  要实现一个接口,可以使用implements操作符。类中必须实现接口中定义的所有方法,否则会报一个fatal错误。如果要实现多个接口,可以用逗号来分隔多个接口的名称。

    Note:  

    实现多个接口时,接口中的方法不能有重名。  

    Note:  

    接口也可以继承,通过使用extends操作符。  

常量

接口中也可以定义常量。接口常量和类常量的使用完全相同。它们都是定值,不能被子类或子接口修改。


范例

Example #1 接口代码示例

<?php

// 声明一个'iTemplate'接口
interface iTemplate
{
   public function setVariable($name, $var);
   public function getHtml($template);
}


// 实现接口
// 下面的写法是正确的
class Template implements iTemplate
{
   private $vars = array();
 
   public function setVariable($name, $var)
   {
       $this->vars[$name] = $var;
   }
 
   public function getHtml($template)
   {
       foreach($this->vars as $name => $value) {
           $template = str_replace('{' . $name . '}', $value, $template);
       }

       return $template;
   }
}

// 下面的写法是错误的,会报错:
// Fatal error: Class BadTemplate contains 1 abstract methods
// and must therefore be declared abstract (iTemplate::getHtml)
class BadTemplate implements iTemplate
{
   private $vars = array();
 
   public function setVariable($name, $var)
   {
       $this->vars[$name] = $var;
   }
}
?>

Example #2 Extendable Interfaces

<?php
interface a
{
   public function foo();
}

interface b extends a
{
   public function baz(Baz $baz);
}

// 正确写法
class c implements b
{
   public function foo()
   {
   }

   public function baz(Baz $baz)
   {
   }
}

// 错误写法会导致一个fatal error
class d implements b
{
   public function foo()
   {
   }

   public function baz(Foo $foo)
   {
   }
}
?>

Example #3 多个接口间的继承

<?php
interface a
{
   public function foo();
}

interface b
{
   public function bar();
}

interface c extends a, b
{
   public function baz();
}

class d implements c
{
   public function foo()
   {
   }

   public function bar()
   {
   }

   public function baz()
   {
   }
}
?>

Example #4 使用接口常量

<?php
interface a
{
   const b = 'Interface constant';
}

// 输出接口常量
echo a::b;

// 错误写法,因为常量的值不能被修改。接口常量的概念和类常量是一样的。
class b implements a
{
   const b = 'Class constant';
}
?>

本文我们来详谈PHP垃圾回收机制,本教程是在PHP 5.3的新垃圾回收机制的特点,这种垃圾回收机制,大大改变内存泄漏问题难以解决的问题。

这部分将说明PHP 5.3的新的垃圾回收机制(也就是GC)的特点。

每个php变量存在一个叫”zval”的变量容器中。一个zval变量容器,除了包含变量的类型和值,还包括两个字节的额外信息。第一个是”is_ref”,是个bool值,用来标识这个变量是否是属于引用集合(reference set)。通过这个字节,php引擎才能把普通变量和引用变量区分开来,由于php允许用户通过使用&来使用自定义引用,zval变量容器中还有一个内部引用计数机制,来优化内存使用。第二个额外字节是”refcount”,用以表示指向这个zval变量容器的变量(也称符号即symbol)个数。所有的符号存在一个符号表中,其中每个符号都有作用域(scope),那些主脚本(比如:通过浏览器请求的的脚本)和每个函数或者方法也都有作用域。

当一个变量被赋常量值时,就会生成一个zval变量容器,如下例这样:

Example #1 Creating a new zval container

<?php
$a = "new string";
?>

 

在上例中,新的变量a,是在当前作用域中生成的。并且生成了类型为 string 和值为new string的变量容器。在额外的两个字节信息中,”is_ref”被默认设置为 FALSE,因为没有任何自定义的引用生成。”refcount” 被设定为 1,因为这里只有一个变量使用这个变量容器. 注意到当”refcount”的值是1时,”is_ref”的值总是FALSE. 如果你已经安装了» Xdebug,你能通过调用函数 xdebug_debug_zval()显示”refcount”和”is_ref”的值。

Example #2 Displaying zval information

<?php
xdebug_debug_zval('a');
?>

 

以上例程会输出:

a: (refcount=1, is_ref=0)='new string'

把一个变量赋值给另一变量将增加引用次数(refcount).

Example #3 Increasing refcount of a zval

<?php
$a = "new string";
$b = $a;
xdebug_debug_zval( 'a' );
?>

以上例程会输出:

a: (refcount=2, is_ref=0)='new string'

这时,引用次数是2,因为同一个变量容器被变量 a 和变量 b关联.当没必要时,php不会去复制已生成的变量容器。变量容器在”refcount“变成0时就被销毁. 当任何关联到某个变量容器的变量离开它的作用域(比如:函数执行结束),或者对变量调用了函数 unset()时,”refcount“就会减1,下面的例子就能说明:

Example #4 Decreasing zval refcount

<?php
$a = "new string";
$c = $b = $a;
xdebug_debug_zval( 'a' );
unset( $b, $c );
xdebug_debug_zval( 'a' );
?>

以上例程会输出:

a: (refcount=3, is_ref=0)='new string'

a: (refcount=1, is_ref=0)='new string'

如果我们现在执行 unset($a);,包含类型和值的这个变量容器就会从内存中删除。

复合类型(Compound Types)

当考虑像 array和object这样的复合类型时,事情就稍微有点复杂. 与 标量(scalar)类型的值不同,array和 object类型的变量把它们的成员或属性存在自己的符号表中。这意味着下面的例子将生成三个zval变量容器。

Example #5 Creating a array zval

<?php
$a = array( 'meaning' => 'life', 'number' => 42 );
xdebug_debug_zval( 'a' );
?>

 

以上例程的输出类似于:

a: (refcount=1, is_ref=0)=array (
   'meaning' => (refcount=1, is_ref=0)='life',
   'number' => (refcount=1, is_ref=0)=42
)

这三个zval变量容器是: a,meaning和 number。增加和减少”refcount”的规则和上面提到的一样. 下面, 我们在数组中再添加一个元素,并且把它的值设为数组中已存在元素的值:

Example #6 Adding already existing element to an array

<?php
$a = array( 'meaning' => 'life', 'number' => 42 );
$a['life'] = $a['meaning'];
xdebug_debug_zval( 'a' );
?>

 

以上例程的输出类似于:

a: (refcount=1, is_ref=0)=array (
   'meaning' => (refcount=2, is_ref=0)='life',
   'number' => (refcount=1, is_ref=0)=42,
   'life' => (refcount=2, is_ref=0)='life'
)

从以上的xdebug输出信息,我们看到原有的数组元素和新添加的数组元素关联到同一个”refcount”2的zval变量容器. 尽管 Xdebug的输出显示两个值为‘life’的 zval 变量容器,其实是同一个。 函数xdebug_debug_zval()不显示这个信息,但是你能通过显示内存指针信息来看到。

删除数组中的一个元素,就是类似于从作用域中删除一个变量. 删除后,数组中的这个元素所在的容器的“refcount”值减少,同样,当“refcount”为0时,这个变量容器就从内存中被删除,下面又一个例子可以说明:

Example #7 Removing an element from an array

<?php
$a = array( 'meaning' => 'life', 'number' => 42 );
$a['life'] = $a['meaning'];
unset( $a['meaning'], $a['number'] );
xdebug_debug_zval( 'a' );
?>

以上例程的输出类似于:

a: (refcount=1, is_ref=0)=array (
   'life' => (refcount=1, is_ref=0)='life'
)

现在,当我们添加一个数组本身作为这个数组的元素时,事情就变得有趣,下个例子将说明这个。例中我们加入了引用操作符,否则php将生成一个复制。

Example #8 Adding the array itself as an element of it self

<?php
$a = array( 'one' );
$a[] =& $a;
xdebug_debug_zval( 'a' );
?>

 

以上例程的输出类似于:

a: (refcount=2, is_ref=1)=array (
   0 => (refcount=1, is_ref=0)='one',
   1 => (refcount=2, is_ref=1)=...
)

 

能看到数组变量 (a) 同时也是这个数组的第二个元素(1) 指向的变量容器中“refcount”为 2。上面的输出结果中的”…”说明发生了递归操作, 显然在这种情况下意味着”…”指向原始数组。

跟刚刚一样,对一个变量调用unset,将删除这个符号,且它指向的变量容器中的引用次数也减1。所以,如果我们在执行完上面的代码后,对变量$a调用unset, 那么变量 $a 和数组元素 “1″ 所指向的变量容器的引用次数减1, 从”2″变成”1″. 下例可以说明:

Example #9 Unsetting $a

(refcount=1, is_ref=1)=array (
   0 => (refcount=1, is_ref=0)='one',
   1 => (refcount=1, is_ref=1)=...
)

 

清理变量容器的问题(Cleanup Problems)

尽管不再有某个作用域中的任何符号指向这个结构(就是变量容器),由于数组元素“1”仍然指向数组本身,所以这个容器不能被清除 。因为没有另外的符号指向它,用户没有办法清除这个结构,结果就会导致内存泄漏。庆幸的是,php将在请求结束时清除这个数据结构,但是在php清除之前,将耗费不少空间的内存。如果你要实现分析算法,或者要做其他像一个子元素指向它的父元素这样的事情,这种情况就会经常发生。当然,同样的情况也会发生在对象上,实际上对象更有可能出现这种情况,因为对象总是隐式的被引用。

如果上面的情况发生仅仅一两次倒没什么,但是如果出现几千次,甚至几十万次的内存泄漏,这显然是个大问题。在长时间运行的脚本,比如请求基本上不会结束的守护进程(deamons)或者单元测试中的大的套件(sets)中,在给 eZ 组件库的模板组件做单元测试时,后者(指单元测试中的大的套件)就会出现问题.它将需要耗用2GB的内存,而一般的测试服务器没有这么大的内存空间。

传统上,像以前的 php 用到的引用计数内存机制,无法处理循环的引用内存泄漏。然而 5.3.0 PHP 使用文章» 引用计数系统中的同步周期回收(Concurrent Cycle Collection in Reference Counted Systems)中的同步算法,来处理这个内存泄漏问题。

对算法的完全说明有点超出这部分内容的范围,将只介绍其中基础部分。首先,我们先要建立一些基本规则,如果一个引用计数增加,它将继续被使用,当然就不再在垃圾中。如果引用计数减少到零,所在变量容器将被清除(free)。就是说,仅仅在引用计数减少到非零值时,才会产生垃圾周期(garbage cycle)。其次,在一个垃圾周期中,通过检查引用计数是否减1,并且检查哪些变量容器的引用次数是零,来发现哪部分是垃圾。

 

image

 

为避免不得不检查所有引用计数可能减少的垃圾周期,这个算法把所有可能根(possible roots 都是zval变量容器),放在根缓冲区(root buffer)中(用紫色来标记,称为疑似垃圾),这样可以同时确保每个可能的垃圾根(possible garbage root)在缓冲区中只出现一次。仅仅在根缓冲区满了时,才对缓冲区内部所有不同的变量容器执行垃圾回收操作。看上图的步骤 A。

在步骤 B 中,模拟删除每个紫色变量。模拟删除时可能将不是紫色的普通变量引用数减”1″,如果某个普通变量引用计数变成0了,就对这个普通变量再做一次模拟删除。每个变量只能被模拟删除一次,模拟删除后标记为灰(原文说确保不会对同一个变量容器减两次”1″,不对的吧)。

在步骤 C 中,模拟恢复每个紫色变量。恢复是有条件的,当变量的引用计数大于0时才对其做模拟恢复。同样每个变量只能恢复一次,恢复后标记为黑,基本就是步骤 B 的逆运算。这样剩下的一堆没能恢复的就是该删除的蓝色节点了,在步骤 D 中遍历出来真的删除掉。

算法中都是模拟删除、模拟恢复、真的删除,都使用简单的遍历即可(最典型的深搜遍历)。复杂度为执行模拟操作的节点数正相关,不只是紫色的那些疑似垃圾变量。

现在,你已经对这个算法有了基本了解,我们回头来看这个如何与PHP集成。默认的,PHP的垃圾回收机制是打开的,然后有个 php.ini 设置允许你修改它:zend.enable_gc 。

当垃圾回收机制打开时,每当根缓存区存满时,就会执行上面描述的循环查找算法。根缓存区有固定的大小,可存10,000个可能根,当然你可以通过修改PHP源码文件Zend/zend_gc.c中的常量GC_ROOT_BUFFER_MAX_ENTRIES,然后重新编译PHP,来修改这个10,000值。当垃圾回收机制关闭时,循环查找算法永不执行,然而,可能根将一直存在根缓冲区中,不管在配置中垃圾回收机制是否激活。

当垃圾回收机制关闭时,如果根缓冲区存满了可能根,更多的可能根显然不会被记录。那些没被记录的可能根,将不会被这个算法来分析处理。如果他们是循环引用周期的一部分,将永不能被清除进而导致内存泄漏。

即使在垃圾回收机制不可用时,可能根也被记录的原因是,相对于每次找到可能根后检查垃圾回收机制是否打开而言,记录可能根的操作更快。不过垃圾回收和分析机制本身要耗不少时间。

除了修改配置zend.enable_gc ,也能通过分别调用gc_enable() 和 gc_disable()函数来打开和关闭垃圾回收机制。调用这些函数,与修改配置项来打开或关闭垃圾回收机制的效果是一样的。即使在可能根缓冲区还没满时,也能强制执行周期回收。你能调用gc_collect_cycles()函数达到这个目的。这个函数将返回使用这个算法回收的周期数。

允许打开和关闭垃圾回收机制并且允许自主的初始化的原因,是由于你的应用程序的某部分可能是高时效性的。在这种情况下,你可能不想使用垃圾回收机制。当然,对你的应用程序的某部分关闭垃圾回收机制,是在冒着可能内存泄漏的风险,因为一些可能根也许存不进有限的根缓冲区。因此,就在你调用gc_disable()函数释放内存之前,先调用gc_collect_cycles()函数可能比较明智。因为这将清除已存放在根缓冲区中的所有可能根,然后在垃圾回收机制被关闭时,可留下空缓冲区以有更多空间存储可能根。

性能方面考虑的因素

在上一节我们已经简单的提到:回收可能根有细微的性能上影响,但这是把PHP 5.2与PHP 5.3比较时才有的。尽管在PHP 5.2中,记录可能根相对于完全不记录可能根要慢些,而PHP 5.3中对 PHP run-time 的其他修改减少了这个性能损失。

这里主要有两个领域对性能有影响。第一个是内存占用空间的节省,另一个是垃圾回收机制执行内存清理时的执行时间增加(run-time delay)。我们将研究这两个领域。

内存占用空间的节省

首先,实现垃圾回收机制的整个原因是为了,一旦先决条件满足,通过清理循环引用的变量来节省内存占用。在PHP执行中,一旦根缓冲区满了或者调用gc_collect_cycles() 函数时,就会执行垃圾回收。在下图中,显示了下面脚本分别在PHP 5.2 和 PHP 5.3环境下的内存占用情况,其中排除了脚本启动时PHP本身占用的基本内存。

Example #1 Memory usage example

<?php
class Foo
{
    public $var = '3.1415962654';
}
$baseMemory = memory_get_usage();
for ( $i = 0; $i <= 100000; $i++ )
{
$a = new Foo;
$a->self = $a;
    if ( $i % 500 === 0 )
    {
        echo sprintf( '%8d: ', $i ), memory_get_usage() - $baseMemory, "n";
    }
}
?>

在这个很理论性的例子中,我们创建了一个对象,这个对象中的一个属性被设置为指回对象本身。在循环的下一个重复(iteration)中,当脚本中的变量被重新复制时,就会发生典型性的内存泄漏。在这个例子中,两个变量容器是泄漏的(对象容器和属性容器),但是仅仅能找到一个可能根:就是被 unset的那个变量。在10,000次重复后(也就产生总共10,000个可能根),当根缓冲区满时,就执行垃圾回收机制,并且释放那些关联的可能根的内存。这从PHP 5.3的锯齿型内存占用图中很容易就能看到。每次执行完10,000次重复后,执行垃圾回收,并释放相关的重复使用的引用变量。在这个例子中由于泄漏的数据结构非常简单,所以垃圾回收机制本身不必做太多工作。从这个图表中,你能看到 PHP 5.3的最大内存占用大概是9 Mb,而PHP 5.2的内存占用一直增加。

执行时间增加(Run-Time Slowdowns)

垃圾回收影响性能的第二个领域是它释放已泄漏的内存耗费的时间。为了看到这个耗时时多少,我们稍微改变了上面的脚本,有更多次数的重复并且删除了循环中的内存占用计算,第二个脚本代码如下:

Example #2 GC performance influences

<?php
class Foo
{
    public $var = '3.1415962654';
}
for ( $i = 0; $i <= 1000000; $i++ )
{
$a = new Foo;
$a->self = $a;
}
echo memory_get_peak_usage(), "n";
?>

我们将运行这个脚本两次,一次通过配置zend.enable_gc 打开垃圾回收机制时,另一次是它关闭时。

Example #3 Running the above script

time php -dzend.enable_gc=0 -dmemory_limit=-1 -n example2.php
# and
time php -dzend.enable_gc=1 -dmemory_limit=-1 -n example2.php

 

在我的机器上,第一个命令持续执行时间大概为10.7秒,而第二个命令耗费11.4秒。时间上增加了7%。然而,执行这个脚本时内存占用的峰值降低了98%,从931Mb 降到 10Mb。这个基准不是很科学,或者并不能代表真实应用程序的数据,但是它的确显示了垃圾回收机制在内存占用方面的好处。好消息就是,对这个脚本而言,在执行中出现更多的循环引用变量时,内存节省的更多的情况下,每次时间增加的百分比都是7%。

PHP内部 GC 统计信息

在PHP内部,可以显示更多的关于垃圾回收机制如何运行的信息。但是要显示这些信息,你需要先重新编译PHP使benchmark和data-collecting code可用。你需要在按照你的意愿运行./configure前,把环境变量CFLAGS设置成-DGC_BENCH=1。下面的命令串就是做这个事:

Example #4 Recompiling PHP to enable GC benchmarking

export CFLAGS=-DGC_BENCH=1
./config.nice
make clean
make

 

当你用新编译的PHP二进制文件来重新执行上面的例子代码,在PHP执行结束后,你将看到下面的信息:

Example #5 GC statistics

GC Statistics
-------------
Runs:               110
Collected:          2072204
Root buffer length: 0
Root buffer peak:   10000

      Possible            Remove from  Marked
        Root    Buffered     buffer     grey
      --------  --------  -----------  ------
ZVAL   7175487   1491291    1241690   3611871
ZOBJ  28506264   1527980     677581   1025731

主要的信息统计在第一个块。你能看到垃圾回收机制运行了110次,而且在这110次运行中,总共有超过两百万的内存分配被释放。只要垃圾回收机制运行了至少一次,根缓冲区峰值(Root buffer peak)总是10000.

结论

通常,PHP中的垃圾回收机制,仅仅在循环回收算法确实运行时会有时间消耗上的增加。但是在平常的(更小的)脚本中应根本就没有性能影响。

然而,在平常脚本中有循环回收机制运行的情况下,内存的节省将允许更多这种脚本同时运行在你的服务器上。因为总共使用的内存没达到上限。

这种好处在长时间运行脚本中尤其明显,诸如长时间的测试套件或者daemon脚本此类。同时,对通常比Web脚本运行时间长的» PHP-GTK应用程序,新的垃圾回收机制,应该会大大改变一直以来认为内存泄漏问题难以解决的看法。

PHP的strtotime() 函数将任何英文文本的日期时间描述解析为 Unix 时间戳。现在我们来讲讲strtotime函数源码分析及使用方法。

源码位置:extdatephp_date.c

 代码如下 复制代码
/* {{{ proto int strtotime(string time [, int now ])
   Convert string representation of date and time to a timestamp */
PHP_FUNCTION(strtotime)
{
    char *times, *initial_ts;
    int   time_len, error1, error2;
    struct timelib_error_container *error;
    long  preset_ts = 0, ts;

    timelib_time *t, *now;
    timelib_tzinfo *tzi;

    tzi = get_timezone_info(TSRMLS_C);

    if (zend_parse_parameters_ex(ZEND_PARSE_PARAMS_QUIET, ZEND_NUM_ARGS() TSRMLS_CC, “sl”, &times, &time_len, &preset_ts) != FAILURE) {
        /* We have an initial timestamp */
        now = timelib_time_ctor();

        initial_ts = emalloc(25);
        snprintf(initial_ts, 24, “@%ld UTC”, preset_ts);
        t = timelib_strtotime(initial_ts, strlen(initial_ts), NULL, DATE_TIMEZONEDB, php_date_parse_tzfile_wrapper); /* we ignore the error here, as this should never fail */
        timelib_update_ts(t, tzi);
        now->tz_info = tzi;
        now->zone_type = TIMELIB_ZONETYPE_ID;
        timelib_unixtime2local(now, t->sse);
        timelib_time_dtor(t);
        efree(initial_ts);
    } else if (zend_parse_parameters(ZEND_NUM_ARGS() TSRMLS_CC, “s|l”, &times, &time_len, &preset_ts) != FAILURE) {
        /* We have no initial timestamp */
        now = timelib_time_ctor();
        now->tz_info = tzi;
        now->zone_type = TIMELIB_ZONETYPE_ID;
        timelib_unixtime2local(now, (timelib_sll) time(NULL));
    } else {
        RETURN_FALSE;
    }

    if (!time_len) {
        timelib_time_dtor(now);  
        RETURN_FALSE;
    }

    t = timelib_strtotime(times, time_len, &error, DATE_TIMEZONEDB, php_date_parse_tzfile_wrapper);
    error1 = error->error_count;
    timelib_error_container_dtor(error);
    timelib_fill_holes(t, now, TIMELIB_NO_CLONE);
    timelib_update_ts(t, tzi);
    ts = timelib_date_to_int(t, &error2);

    timelib_time_dtor(now);
    timelib_time_dtor(t);

    if (error1 || error2) {
        RETURN_FALSE;
    } else {
        RETURN_LONG(ts);
    }
}
/* }}} */



strtotime函数在使用strtotime(“-1 month”)求上一个月的今天时会出一些状况,

因此也引出写这篇文章,本文包括如下内容:

    strtotime函数的一些用法
    strtotime函数的实现基本原理
    strtotime(“-1 month”)求值失败的原因

strtotime函数的一些用法

1、 strtotime(“JAN”)和strtotime(“January”)

这两个用法的效果是一样的,都是返回指定月份的今天,如果指定月份没有今天,则顺延到下一个月。 如在2011-03-31计算二月,代码:

echo date("Y-m-d H:i:s", strtotime("feb", strtotime("2011-03-31")));

程序会输出: 2011-03-03 00:00:00。 从表象来看,这个结果也许不一定是我们想要的,但是这也算是一种解决方案,这种方案是由什么决定的呢? strtotime函数在执行月份的计算时只计算了月份,相当于直接将月份设置为指定的月份的值,而如jan,january都会有一个对应内部数值。

2、 first关键字

first是一个辅助型的关键字,它可以与星期,天等可以指定确认值的关键字组合使用,如求2011年的第一个星期天:

echo date("Y-m-d H:i:s", strtotime("second sunday", strtotime("2011-01-01"))), "<br />";

在PHP的源码中,对于first与星期和天的组合使用是分开的,即first day对应一个处理操作, 在最终的C实现中,天的值指定为1,即time结构中的d字段指定为1,如下代码:

switch (time->relative.first_last_day_of) {
    case 1: /* first */
        time->d = 1;
        break;
    case 2: /* last */
        time->d = 0;
        time->m++;
        break;
}

3、previous和next关键字

与first类似,previous关键字可以与星期,天组合使用,表示指定时间的前一个星期几或前一天。如下所示代码:

echo date("Y-m-d H:i:s", strtotime("previous sunday", strtotime("2011-02-01"))), "<br />";

程序会输出:2011-01-30 00:00:00
程序求2011-02-01的前一个星期天。

next关键字与previous相反,它表示下一个星期几或后一天。

4、 last关键字

last关键字既可以作为上一个,也可以作为最后一个。如求上一个星期天的日期:

echo date("Y-m-d H:i:s", strtotime("last sunday", strtotime("2011-02-05"))), "<br />";

程序会输出: 2011-01-30 00:00:00

当程序作为最后时,其应用场景是指定日期所在月的最后一天,相当于date(“t”)的结果。如求2000年2月的最后一天:

echo date("Y-m-d H:i:s", strtotime("last day", strtotime("2000-02-01"))), "<br />";

first、previous、last和this关键字在re文件中属于同一组。

5、 back和front关键字

这两个关键字是对一天中的小时的向前和向后操作,其调用格式如下:

echo date("Y-m-d H:i:s", strtotime("back of 24", strtotime("2011-02-01"))), "<br />";
echo date("Y-m-d H:i:s", strtotime("front of 24", strtotime("2011-02-01"))), "<br />";

    back表示将时间设置指定小时值的后一个小时的15分的位置。如果是24点,则算到第二天的0点15分。
    front表示将时间设置指定小时值的前一个小时的45分的位置。如果是0点,则算前一天的23点45分。

上面的代码输出:2011-02-02 00:15:00 2011-02-01 23:45:00。 其中back of和front of后接的数组必须大于等于0并且小于等于24。
strtotime函数的实现基本原理

官方文档对于strtotime函数的说明是这样的:本函数预期接受一个包含美国英语日期格式的字符串并尝试将其解析为 Unix 时间戳 (自 January 1 1970 00:00:00 GMT 起的秒数),其值相对于 now 参数给出的时间,如果没有提供此参数则用系统当前时间。

这是一个标准PHP内置函数,从PHP4起就已经存在。strtotime函数是以一个扩展的方式加载进来的,在ext/date目录下有其全部实现。 作为一个标准的内置函数,其定义格式也是标准的,如下:

PHP_FUNCTION(strtotime)
    //  处理输入,对于是否有第二个参数有没的处理

    //  调用相关函数,实现字符串的解析和结果计算

    //  返回结果
}

在输入处理中,先识别两个参数都存在的情况并进行处理,如果不是此种状态,则处理第二个参数不存在的情况, 如果都没有,则报错,返回FALSE。

strtotime函数的第一个参数是一个字符串,对于这个字符串,由于其复杂性,PHP使用了其词法解析一样的工具:re2c。在/ext/date/lib目录下,从parse_date.re文件我们可以看到其原始的re文件。 当用户以参数的形式传入一个字符串,此字符串将交给此程序处理,针对其字符串的不同,匹配不同的处理函数。 如strtotime(“yesterday”)调用,分析字符串时,将匹配yesterday字符串,此字符串对应函数如下:

 代码如下 复制代码
'yesterday'
{
    DEBUG_OUTPUT("yesterday");
    TIMELIB_INIT;
    TIMELIB_HAVE_RELATIVE();
    TIMELIB_UNHAVE_TIME();

    s->time->relative.d = -1;
    TIMELIB_DEINIT;
    return TIMELIB_RELATIVE;
}



这里有几个关键的结构体:

 代码如下 复制代码
typedef struct Scanner {
    int           fd;
    uchar        *lim, *str, *ptr, *cur, *tok, *pos;
    unsigned int  line, len;
    struct timelib_error_container *errors;

    struct timelib_time *time;
    const timelib_tzdb  *tzdb;
} Scanner;

typedef struct timelib_time {
    timelib_sll      y, m, d;     /* Year, Month, Day */
    timelib_sll      h, i, s;     /* Hour, mInute, Second */
    double           f;           /* Fraction */
    int              z;           /* GMT offset in minutes */
    char            *tz_abbr;     /* Timezone abbreviation (display only) */
    timelib_tzinfo  *tz_info;     /* Timezone structure */
    signed int       dst;         /* Flag if we were parsing a DST zone */
    timelib_rel_time relative;

    timelib_sll      sse;         /* Seconds since epoch */

    unsigned int   have_time, have_date, have_zone, have_relative, have_weeknr_day;

    unsigned int   sse_uptodate; /* !0 if the sse member is up to date with the date/time members */
    unsigned int   tim_uptodate; /* !0 if the date/time members are up to date with the sse member */
    unsigned int   is_localtime; /*  1 if the current struct represents localtime, 0 if it is in GMT */
    unsigned int   zone_type;    /*  1 time offset,
                                  *  3 TimeZone identifier,
                                  *  2 TimeZone abbreviation */
} timelib_time;

typedef struct timelib_rel_time {
    timelib_sll y, m, d; /* Years, Months and Days */
    timelib_sll h, i, s; /* Hours, mInutes and Seconds */

    int weekday; /* Stores the day in 'next monday' */
    int weekday_behavior; /* 0: the current day should *not* be counted when advancing forwards; 1: the current day *should* be counted */

    int first_last_day_of;
    int invert; /* Whether the difference should be inverted */
    timelib_sll days; /* Contains the number of *days*, instead of Y-M-D differences */

    timelib_special  special;
    unsigned int   have_weekday_relative, have_special_relative;
} timelib_rel_time;


s->time->relative.d = -1;所表示的意思是当前时间的相对天数是-1。 这只是中间词法解析的中间结果,但是最后结果是通过这些中间结果计算出来的。
strtotime(“-1 month”)求值失败的原因

虽然strtotime(“-1 month”)这种方法对于后一个月比前一个月的天数的情况会求值失败,但是从其本质上来说,这并没有错。 PHP这样实现也无可厚非。只是我们的需求决定了我们不能使用这种方法,因此我们称其为求值失败。

我们来看它的实现过程,由于没有第二个参数,所以程序使用默认的当前时间。 第一个参数传入的是-1 month字符串,这个字符串所对应的re文件中的正则为:

reltextunit = (('sec'|'second'|'min'|'minute'|'hour'|'day'|'fortnight'|'forthnight'|'month'|'year') 's'?) | 'weeks' | daytext;

relnumber = ([+-]*[ t]*[0-9]+);
relative = relnumber space? (reltextunit | 'week' );

最终relative会对应一系列操作,程序会识别出前面的-1 和后面的month字符串,month对应一种操作类型:TIMELIB_MONTH。 在此之后,根据识别出来的数字和操作类型执行操作,如下代码:

case TIMELIB_MONTH:  s->time->relative.m += amount * relunit->multiplier; break;

如上代码,则是直接记录月份的相对值减一。 但是对于类似于3月31号这样的情况,2月没有31号,程序会自动将日期计算到下一个月。

在Web,js,css文件会越多,那么对就增加了http请求数,解决该问题的一个好的方法就是合并js,css文件。下面就简单介绍一个方法,十分简单。本文以实现原理为主,代码可能会有出入,如果大家直接用请调试一下。

HTML:

 代码如下 复制代码
<link rel="stylesheet" type="text/css" href="cssmin.php?get=base,style1,style2,global&path=css/&v=20131023" />
<script type="text/javascript" src="jsmin.php?get=jquery-1.6.4.min.js,minjquery.js,minjquery.ui.js,test.js,global.js&path=js/&v=20131023"></script>

PHP:

 代码如下 复制代码
//输出JS
header ("Content-type:Application/x-javascript; Charset: utf-8");
if(isset($_GET)) {
    $files = explode(",", $_GET['get']);
    $str = '';
    foreach ($files as $key => $val){
        $str .= file_get_contents($_GET['path'].$val);
    }

    $str = str_replace("t", "", $str); //清除空格
    $str = str_replace("rn", "", $str);
    $str = str_replace("n", "", $str);

    // 删除单行注释
    $str = preg_replace("///s*[a-zA-Z0-9_x7f-xff][a-zA-Z0-9_x7f-xff]*/", "", $str);
    // 删除多行注释
    $str = preg_replace("//*[^/]**//s", "", $str);

    echo $str;
}

//输出CSS
header ("content-type:text/css; charset: utf-8");
if(isset($_GET)) {
    $files = explode(",", $_GET['get']);
    $fc = '';
    foreach ($files as $key => $val){
        $fc .= file_get_contents($_GET['path'].$val.".css");
    }
    $fc = str_replace("t", "", $fc); //清除空格
    $fc = str_replace("rn", "", $fc);
    $fc = str_replace("n", "", $fc);
    $fc = preg_replace("//*[^/]**//s", "", $fc);
    echo $fc;
}

只是个简单原型,没有封装。另外,合并后的文件记得配合缓存。

附上一个相关的开源项目:
http://code.google.com/p/minify/

 

很多站长都兼职卖域名(赚点小外块,不要鄙视),都希望能够在自己的网站上实现域名查询,看看域名是否可以购卖,现在我们就来讲讲用PHP+AJAX如何实现国际域名查询系统。

PHP+AJAX 域名查询预备知识

本查询系统利用 PHP 和 JQUery 的 Ajax 功能实现了对域名信息的查询(这里主要实现了域名是否已经注册的查询)。系统主要用到了万网提供的域名查询 API 接口,相关知识点罗列如下:

    JQUery Ajax 的实现:这部分内容具体可以参见 JQuery API 文档或本站即将推出的《JQUery 教程》。
    file_get_contents 函数:把整个文件读入一个字符串,这里用于读取一个网页(万网 API 返回结果页面)。
    simplexml_load_string 函数:用于解析一个 xml 文档到对象中。
    strrpos 函数:用于定位字符串第一次出现的位置,这里用来搜索关键字。

域名查询系统需求分析

    根据用户输入的域名,查询该域名是否已经被注册。
    对域名注册信息(whois)进行查询,该功能本教程没有实现,可以参考已有功能来实现。

页面/文件信息

    domain.html:表单提交及查询结果信息显示页面。
    domain_check.php:处理查询域名信息的 PHP 文件。

万网域名查询 API 接口

接口采用HTTP,POST,GET协议。

调用URL:http://panda.www.net.cn/cgi-bin/check.cgi

参数名称:area_domain 值为标准域名,例:5idev.com

调用举例:http://panda.www.net.cn/cgi-bin/check.cgi?area_domain=5idev.com

返回XML:

 代码如下 复制代码
<?xml version="1.0" encoding="gb2312"?>
<property>
<returncode>200</returncode>
<key>5idev.com</key>
<original>211 : Domain name is not available</original>
</property>

XML 结果说明:

    returncod:接口调用状态。
    key:表示当前 check 的域名。
    original:域名 check 的结果。

original 有4个结果:

    210 : Domain name is available:表示域名可以注册
    211 : Domain name is not available:表示域名已经被注册
    212 : Domain name is invalid:表示域名参数传输错误
    214 : Unknown error:表示未知错误或查询异常


domain.html 页面关键代码

domain.html 页面实现了表单 Ajax 提交及域名查询结果信息显示。完整的代码如下(注意是utf-8编码):

 

 代码如下 复制代码

<!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Transitional//EN" "http://www.w3.org/TR/xhtml1/DTD/xhtml1
-transitional.dtd">
<html>
<head>
<title>域名注册查询</title>
<meta http-equiv="Content-Type" content="text/html; charset=utf-8" />
<script src="js/jquery-1.4.2.min.js"></script>
<script type="text/javascript">
function domain_check()
{
    var domain = $.trim($("#domain").val());
    if( domain == ''){
        $("#check_result").html('请输入要查询的域名信息,如:5idev.com');
        $("#domain").focus();
        return false;
    }
    $("#domain_result").html('<img src="images/loading.gif" /> 正在查询,请稍后...');
    $.ajax({
        type:"get",
        cache:false,
        datatype: "text",
        url:"domain_check.php?domain="+domain,
        success:function(data){
            $("#domain_result").html(data);
        }
    });
}
</script>
</head>

<body>
<h1>域名注册查询</h1>
<div>
<form >
请输入要查询的域名:www.<input id="domain" type="text" value="5idev.com" onfocus="this.value=''" />
<input type="button" value=" 查 询 " onclick="domain_check()" />
</form>
</div>
<div id="domain_result"></div>

</body>
</html>

 

这里利用 JAuery 实现了 Ajax GET 方式的表单提交,并对输入的表单进行了初步非空检测。
域名查询 PHP 代码

下面是完整的用于查询域名信息和回应 Ajax 请求 PHP 源代码:

 代码如下 复制代码
<?php
$domain = htmlspecialchars(trim($_GET['domain']));
if( !$domain ){
    echo '请输入要查询的域名,如:5idev.com';
    exit;
}
// 调用万网域名查询API
$area_domain = iconv("utf-8", "gb2312",$domain);
$domain_api = 'http://panda.www.net.cn/cgi-bin/check.cgi?area_domain='.$area_domain;
$contents = file_get_contents($domain_api);
$xml = simplexml_load_string($contents);
if (!empty($xml)) {
    switch($xml->original)
    {
        case '210 : Domain name is available':$result = '该域名可以注册';break;
        case '211 : Domain name is not available':$result = '该域名已经被注册';break;
        case '212 : Domain name is invalid':$result = '域名参数错误,请输入的域名格式';break;
        case '214 : Unknown error':$result='查询异常,请稍后再试';break;
    }
} else {
    // 备用,只能查询国际域名
    $url = 'http://www.checkdomain.com/cgi-bin/checkdomain.pl?domain='.$_GET<'domain'>;
    $fp = file_get_contents($url);
    if( strpos($fp, ', has already been registered') ){
        $result = '该域名已经被注册';
    } else {
        $result =  '该域名可以注册';
    }
}
echo '<b>'.$domain.'</b>:'.$result;
?>

需要注意的几个问题

    支持中文域名查询。
    由于域名要在页面显示,因此利用 htmlspecialchars 函数做了特殊 html 代码转换,以防止非法输入,更严格的可以使用正则表达式做检测。
    由于万网接口提供的是 gb2312 编码,因此在这里利用进行了 iconv() 函数进行了编码转换,如果不使用 utf-8 编码则无需转换。
    如果使用 gb2312 编码,在返回 Ajax 结果是可能需要将显示结果进行 gb2312 到 utf-8 编码的转换。
    当万网接口无法返回结果时,启用备用接口进行查询,但只能查询国际域名。

[!--infotagslink--]

相关文章

  • C#中using的三种用法

    using 指令有两个用途: 允许在命名空间中使用类型,以便您不必限定在该命名空间中使用的类型。 为命名空间创建别名。 using 关键字还用来创建 using 语句 定义一个范围,将在此...2020-06-25
  • C# 中的var关键字详细介绍

    这篇文章主要介绍了C# 中的var关键字详细介绍的相关资料,需要的朋友可以参考下...2020-06-25
  • C#中的yield关键字的使用方法介绍

    yield这个关键字是和迭代器挂钩的,而且是与return一起以yield return的形式合用的,用来返回迭代器中的条目。...2020-06-25
  • iscroll.js 用法介绍

    最新版下载: http://www.csdn123.com/uploadfile/2015/0428/20150428062734485.zip 概要 iScroll 4 这个版本完全重写了iScroll这个框架的原始代码。这个项目的产生...2016-05-19
  • C++中cin的用法详细

    这篇文章主要介绍了C++中cin的用法详细,文中通过示例代码介绍的非常详细,对大家的学习或者工作具有一定的参考学习价值,需要的朋友们下面随着小编来一起学习学习吧...2020-04-25
  • C#中的try catch finally用法分析

    这篇文章主要介绍了C#中的try catch finally用法,以实例形式分析了try catch finally针对错误处理时的不同用法,具有一定的参考借鉴价值,需要的朋友可以参考下...2020-06-25
  • php关键字仅替换一次的实现函数

    对于批量替换关键字的问题,仅替换而言没什么可说的,但这里需要的是每个关键字仅需要替换一次就可以了。查阅了php相关函数文档,发现php本身是没有函数实现这个功能的,所以不得不自己解决了。总结了几种可行方法,小记一下! (1...2015-10-30
  • c# volatile 关键字的拾遗补漏

    这篇文章主要介绍了c# volatile 关键字的相关资料,帮助大家更好的理解和学习c#的相关知识,感兴趣的朋友可以了解下...2020-12-08
  • Spring Data JPA 关键字Exists的用法说明

    这篇文章主要介绍了Spring Data JPA 关键字Exists的用法说明,具有很好的参考价值,希望对大家有所帮助。如有错误或未考虑完全的地方,望不吝赐教...2021-06-10
  • 用python获取txt文件中关键字的数量

    这篇文章主要介绍了如何用python获取txt文件中关键字的数量,帮助大家更好的理解和使用python,感兴趣的朋友可以了解下...2020-12-24
  • 示例详解react中useState的用法

    useState 通过在函数组件里调用它来给组件添加一些内部 state,React 会在重复渲染时保留这个 state,接下来通过一个示例来看看怎么使用 useState吧...2021-06-04
  • PHP中print_r、var_export、var_dump用法介绍

    文章详细的介绍了关于PHP中print_r、var_export、var_dump区别比较以及这几个在php不同的应用中的用法,有需要的朋友可以参考一下 可以看出print_r跟var_export都...2016-11-25
  • Java类的访问权限关键字用法说明

    这篇文章主要介绍了Java类的访问权限关键字用法说明,具有很好的参考价值,希望对大家有所帮助。一起跟随小编过来看看吧...2020-09-29
  • php中php://input的用法详细

    在使用xml-rpc的时候,server端获取client数据,主要是通过php输入流input,而不是$_POST数组。所以,这里主要探讨php输入流php://input。 下面的例子摘取的是wordpres...2016-11-25
  • JS跨浏览器解析XML应用过程详解

    这篇文章主要介绍了JS跨浏览器解析XML应用过程详解,文中通过示例代码介绍的非常详细,对大家的学习或者工作具有一定的参考学习价值,需要的朋友可以参考下...2020-10-16
  • MySQL中的主键以及设置其自增的用法教程

    1、声明主键的方法: 您可以在创建表的时候就为表加上主键,如: CREATE TABLE tbl_name ([字段描述省略...], PRIMARY KEY(index_col_name)); 也可以更新表结构时为表加上主键,如: ALTER TABLE tbl_name ADD PRIMARY KEY (in...2015-11-24
  • vivo X9如何查出后台偷跑流量应用?vivo X9查出后台偷跑流量应用的方法

    vivo X9如何查看后台流量偷跑的情况?小编教你轻松查到!还不了解的小伙伴快来看看吧! 1)打开手机自带的【i管家】应用,打开后点击【流量监控】选项。(如下图) 2)接着选...2016-12-31
  • Delphi常用关键字用法详解

    这篇文章主要介绍了Delphi常用关键字用法,包括了各个常用的关键字及其详细用法,需要的朋友可以参考下...2020-06-30
  • C#中this的用法集锦

    本文给大家汇总介绍了C#中的几种this用法,相信大家应该有用过,但你用过几种?以下是个人总结的this几种用法,欢迎大家拍砖,废话少说,直接列出用法及相关代码。...2020-06-25
  • PHP云存储Redis的应用场景与Redis实现排行榜功能

    Redis是一个开源的使用ANSI C语言编写、支持网络、可基于内存亦可持久化的日志型、Key-Value数据库,并提供多种语言的API。本文我们来讲解Redis的应用场景实例。 C...2016-11-25