开放的编程资料库

当前位置:我爱分享网 > PHP教程 > 正文

PHP 5.3 新语言特性入门

在过去的一个月里,我和我的团队开始着手开发ZendFramework2.0,我一直沉浸在PHP5.3中。PHP5.3提供了大量新的语言特性,其中许多是为了帮助框架和库开发人员而开发的。大多数时候,这些功能都很简单,您可以简单地使用它们;然而,在其他情况下,我们会遇到意想不到的行为。这篇文章将详细介绍其中的几个,因此要么不会遇到相同的问题,要么可以利用我们的一些发现。

闭包、匿名函数和Lambda,我的天啊!

简而言之,这些都是单个PHP结构匿名函数的同义词(上下文略有不同):

$callback = function ($param) {
    // do something
};

您可以将匿名函数分配给变量,或将其作为回调参数内联传递给函数或方法调用。该构造实现了一些非常灵活的设计,并且对于各种数组函数和preg_replace_callback()特别有用。如果您在代码库中看到任何create_function构造,请立即用匿名函数替换它们;它们不仅更易于阅读(在create_function()中转义代码内容总是很痛苦),而且速度更快,并且如果可用的话还可以从操作码缓存中获益。

不过,我们发现了一个有趣的问题。PHP不喜欢序列化闭包;这样做会引发异常(“不允许序列化‘Closure’”)。这有很多含义:

  • 如果您需要更改SPL自动加载器堆栈,请小心使用闭包。例如,我们的测试台通过存储spl_autoload_functions()的返回值来缓存自动加载器,然后在测试期间重置它。不幸的是,如果您使用spl_autoload_register注册闭包,您可能会在执行此操作时遇到错误。(注意:这似乎已在5.3.2及更高版本中修复。)
  • 如果您正在序列化具有引用闭包属性的类,则需要向__sleep()__wakeup()以确保这些属性未被序列化,并在反序列化时重新创建它们。

此外,即使内部匿名函数通过Closure类表示,您也不能对该类进行类型提示;测试变量是否为闭包的唯一方法是使用is_callable()

可调用对象

PHP5的一个有趣的新特性是魔法方法__invoke(),它允许您像调用函数一样调用对象:

class Greeting
{
    public function __invoke($name)
    {
        return "Hello, $name";
    }
}

$greeting = new Greeting;
echo $greeting('world'); // "Hello, world"

与其他魔术方法不同,它实际上比替代方法更快。当简单地返回一个值时,它比在同一对象上调用方法快25%;当与call_user_func_array()一起使用时,它比使用普通的数组式回调(例如,array($o,'greet'))快30%—即使它“代理到另一种方法!

所以,听起来像是一个很棒的新功能,对吧?是的……但是有些事情你应该知道。

  • 像闭包一样,您不能为__invoke()显式键入提示;您必须使用is_callable()或创建定义它的接口:

    interface Filter
    {
        public function filter($value);
    }
    
    interface CallableFilter
    {
        public function __invoke($value);
    }
    
    class IntFilter implements Filter, CallableFilter
    {
        public function filter($value)
        {
            return (int) $value;
        }
    
        public function __invoke($value)
        {
            return $this->filter($value);
        }
    }
    
    $filter = new IntFilter;
    if ($filter instanceof CallableFilter) {
        // matches
    }
    
  • 小心关于使用实现__invoke()的对象作为对象属性;他们不做你期望的。例如,请考虑以下内容:

    class Foo
    {
        public function __invoke()
        {
            return 'foo';
        }
    }
    
    class Bar
    {
        public $foo;
    
        public function __construct()
        {
            $this->foo = new Foo;
        }
    }
    
    $bar = new Bar;
    echo $bar->foo();
    

    您可能希望它回显“foo”——但它不会。相反,它会引发一个E_FATAL,声称“调用未定义的方法Bar::foo()”。如果要执行该属性,则必须先将其分配给一个临时变量,或者显式调用__invoke()

    $foo = $bar->foo;
    echo $foo();
    
    // or:
    
    $bar->foo->__invoke();
    

命名空间的乐趣和利润

请搁置您对在PHP中选择名称空间分隔符的意见;此时它是桥下的水,并且有很好的选择技术原因。我们有一个实现,所以让我们使用它吧。

首先,您在文件顶部声明您的命名空间:

namespace Zend\Filter;

或者你可以在同一个文件中有多个命名空间,只要你没有松散的代码:

namespace Zend\Filter;
// some namespaced code here...

namespace Zend\Validator;
// some namespaced code here...

虽然上述内容有效,但如果您在单个文件中使用多个命名空间,PHP手册建议使用大括号:

namespace Zend\Filter 
{
    // some namespaced code here...
}

namespace Zend\Validator 
{
    // some namespaced code here...
}

您可以使用use构造从其他命名空间导入代码。此结构还允许您使用as修饰符别名命名空间(或命名空间中的类、常量或函数):

namespace Foo;
use Zend\Filter;
use Zend\Validator\Int as IntValidator;

$validator = new IntValidator;  // Zend\Validator\Int
if ($validator->isValid($foo) {
    $filter = new Filter\Int(); // Zend\Filter\Int
    echo $filter($foo);
}

关于命名空间的一些快速规则:

  • 完全限定的命名空间(FQN)以命名空间分隔符(\\)开头。类、函数、常量和静态成员引用使用FQN将始终解析。

  • 命名空间声明始终被认为是完全限定的,并且不应以命名空间分隔符作为前缀。

  • use语句中引用的命名空间始终被认为是完全合格的;您可以使用命名空间分隔符作为前缀,但这不是必需的。

  • 在命名空间中引用命名空间类时,请注意来源:如果您不这样做’完全限定命名空间,假设将是:

    • 当前命名空间的子命名空间
    • 对导入时定义的别名之一的引用

    例如,考虑以下代码:

    namespace Foo;
    use Zend\Filter; // imports are always considered FQN
    
    $foo       = new Bar\Baz;             // actual; Foo\Bar\Baz
    $filter    = new Filter\Int;          // actual; Zend\Filter\Int
    $validator = new Zend\Validator\Int;  // actual: Foo\Zend\Validator\Int
    $validator = new \Zend\Validator\Int; // actual: Zend\Validator\Int
    

我们的一个发现是,您可以拥有一个名称空间,该名称空间与类的接口共享相同的名称。例如:

namespace Foo 
{
    interface Adapter 
    {
        // definition here...
    }
}

namespace Foo\Adapter
{
    use Foo\Adapter as FooAdapter;

    class Concrete implements FooAdapter
    {
        // ...
    }
}

这一发现使我们能够在组件中定义更多“顶级”接口,并在与接口匹配的名称空间中实现具体实现。这减少了一些冗长的文字,定义了更好的类层次结构,并使代码关系更加语义化。

最后,我们发现命名空间的一个巨大好处是在单元测试时:我们可以为单元测试定义一个单独的命名空间,以及为每个组件定义单独的命名空间。如果我们将这些命名空间用于测试工件——单元测试使用的类和模拟适配器——我们确保每个测试套件都被完全封装。这减少了命名冲突问题。

在结束…

PHP5.3提供了大量新功能——我在这里介绍的只是一些比较突出的功能。如果您还没有开始使用5.3,您应该开始—它绝对是PHP的未来,您将看到越来越多的库和框架使用它。

未经允许不得转载:我爱分享网 » PHP 5.3 新语言特性入门

感觉很棒!可以赞赏支持我哟~

赞(0) 打赏