ホームページ >バックエンド開発 >PHPチュートリアル >FIG-PHP PSR 仕様シリーズ 4 - 自動ローディング

FIG-PHP PSR 仕様シリーズ 4 - 自動ローディング

WBOY
WBOYオリジナル
2016-06-23 13:29:48992ブラウズ

1. PSR-4 仕様: 自動ロード

[PSR-4-Meta] では PSR-4 が PSR-0 仕様の代替ではなく補足であることが指摘されていますが、[ PSR-0] PSR-0 は 2014 年 10 月 21 日に廃止され、PSR-0 の欠点は [PSR-4-Meta] で詳しく説明されており、パッケージの自動ロードに対応できなくなりました。

PSR-4 仕様は、ファイル パスからクラスを自動的にロードする方法を標準化し、自動的にロードされるファイルの場所も標準化します。

1.1 概要

この PSR 仕様では、ファイル パスからのクラスの自動ロードについて説明します。 PSR-0仕様と相互運用性があり、併用可能です。この PSR には、自動ロードされたファイルを配置する場所についても説明されています。

1.2 仕様

1.2.1 「クラス」という用語は、クラス、インターフェイス、特性、およびその他の同様の構造を指します

1.2.2 完全修飾クラス名の形式は次のとおりです。 ; (& lt; subamespacenames & gt;)*& lt; className & gt;

(1) 完全に準拠したクラス名 (Must) には、通常「V.ENDOR 名前空間」と呼ばれる名前空間名があります。

(2) 完全に準拠したクラス名は 1 つ以上のセカンダリ名前空間名 (サブ名前空間名) を持つことができます (MAY)。 「クラス名で終わります。」

(4) 完全に準拠したクラス名のどの部分でも、アンダースコアは特別な意味を持ちません。

(5) 完全に準拠したクラス名では、大文字と小文字を任意に組み合わせて使用​​できます (MAY)。

(6) すべてのクラス名は、大文字と小文字を区別して引用符で囲む必要があります。

1.2.3 完全準拠クラス名に対応するファイルをロードする場合...

(1) 完全準拠クラス名では、先行する名前空間区切り文字は含まれず、トップレベルの名前空間と、Or a で構成されます。少なくとも 1 つの「ベース ディレクトリ」に対応する、複数のセカンダリ名前空間名で構成される名前空間プレフィックス

(2) 名前空間プレフィックスの後のセカンダリ名前空間名は、「ベース ディレクトリ」内のサブディレクトリに対応し、名前空間区切り文字はディレクトリを表します。セパレータ。サブディレクトリ名はセカンダリ名前空間名と一致する必要があります。

(3) 次のクラス名は、接尾辞として .php を付けたファイル名に対応します。このファイル名は、次のクラス名と一致する必要があります (MUST)。

(4) 自動ロード実装は例外をスローしてはならず、あらゆるレベルのエラーを引き起こしてはならず、値を返してはなりません。

1.3. 例

次の表は、完全準拠のクラス名、名前空間プレフィックス、およびベース ディレクトリに対応するファイル パスを示しています。

AcmeLogWriterFile_Writer

AcmeLogWriter

./acme-log-writer/lib//path/to/aura -web/src/./vendor/Symfony/Core/Zend

    备注:以第一行为例来说明,完全合规的类名是“\Acme\Log\Writer\File_Writer”, 去掉前面的命名空间分隔符'\', 则命名空间前缀为"Acme\Log\Writer", 类名为"File_Writer"。这个命名空间前缀对应的base目录为"./acme-log-writer/lib/", 因此最终加载的文件名为:base目录+类名+".php", 即"./acme-log-writer/lib/File_Writer.php"


    遵循本规范的自动加载器的实现举例, 可参见下面的代码样例。这些实现样例一定不能(MUST NOT)被视为本规范的内容,它们可能(MAY)随时发生改变。

2. 代码样例

以下代码展示了遵循PSR-4的类定义,

闭包(Closure)举例:

<?php/** * An example of a project-specific implementation. *  * After registering this autoload function with SPL, the following line * would cause the function to attempt to load the \Foo\Bar\Baz\Qux class * from /path/to/project/src/Baz/Qux.php: *  *      new \Foo\Bar\Baz\Qux; *       * @param string $class The fully-qualified class name. * @return void */spl_autoload_register(function ($class) {    // project-specific namespace prefix    // 项目的命名空间前缀    $prefix = 'Foo\\Bar\\';    // base directory for the namespace prefix    // 命名空间前缀对应的base目录    $base_dir = __DIR__ . '/src/';    // does the class use the namespace prefix?    // 检查$class中是否包含命名空间前缀    $len = strlen($prefix);    if (strncmp($prefix, $class, $len) !== 0) {        // no, move to the next registered autoloader        // 未包含,立即返回        return;    }    // get the relative class name    // 获取相对类名    $relative_class = substr($class, $len);    // replace the namespace prefix with the base directory, replace namespace    // separators with directory separators in the relative class name, append    // with .php    // 用base目录替代命名空间前缀,     // 在相对类名中用目录分隔符'/'来替换命名空间分隔符'\',     // 并在后面追加.php组成$file的绝对路径    $file = $base_dir . str_replace('\\', '/', $relative_class) . '.php';    // if the file exists, require it    // 如果文件存在,则通过require关键字包含文件    if (file_exists($file)) {        require $file;    }});

下面这个类处理多个命名空间:

<?phpnamespace Example;/** * An example of a general-purpose implementation that includes the optional * functionality of allowing multiple base directories for a single namespace * prefix. * 下面例子中在一个命名空间前缀下有多个base目录。 *  * Given a foo-bar package of classes in the file system at the following * paths ... * 在下面路径中foo-bar包中存在以下类: *  *     /path/to/packages/foo-bar/ *         src/ *             Baz.php             # Foo\Bar\Baz *             Qux/ *                 Quux.php        # Foo\Bar\Qux\Quux *         tests/ *             BazTest.php         # Foo\Bar\BazTest *             Qux/ *                 QuuxTest.php    # Foo\Bar\Qux\QuuxTest *  * ... add the path to the class files for the \Foo\Bar\ namespace prefix * as follows: * ...对\Foo\Bar\命名空间前缀,添加类文件的路径 *  *      <?php *      // instantiate the loader *      // 初始化loader  *      $loader = new \Example\Psr4AutoloaderClass; *       *      // register the autoloader *      // 注册autoloader *      $loader->register(); *       *      // register the base directories for the namespace prefix *      // 注册命名空间前缀的多个base目录 *      $loader->addNamespace('Foo\Bar', '/path/to/packages/foo-bar/src'); *      $loader->addNamespace('Foo\Bar', '/path/to/packages/foo-bar/tests'); *  * The following line would cause the autoloader to attempt to load the * \Foo\Bar\Qux\Quux class from /path/to/packages/foo-bar/src/Qux/Quux.php: * 下面代码将用/path/to/packages/foo-bar/src/Qux/Quux.php文件来加载\Foo\Bar\Qux\Quux类。 *  *      <?php *      new \Foo\Bar\Qux\Quux; *  * The following line would cause the autoloader to attempt to load the  * \Foo\Bar\Qux\QuuxTest class from /path/to/packages/foo-bar/tests/Qux/QuuxTest.php: * 下面代码将用/path/to/packages/foo-bar/tests/Qux/QuuxTest.php文件来加载 * \Foo\Bar\Qux\QuuxTest类。 *  *      <?php *      new \Foo\Bar\Qux\QuuxTest; */class Psr4AutoloaderClass{    /**     * An associative array where the key is a namespace prefix and the value     * is an array of base directories for classes in that namespace.     * 定义一个数组:key为命名空间前缀,value为一个数组,每一项表示命名空间中类对应的base目录.     *     * @var array     */    protected $prefixes = array();    /**     * Register loader with SPL autoloader stack.     * 利用SPL自动加载器来注册loader     *      * @return void     */    public function register()    {        spl_autoload_register(array($this, 'loadClass'));    }    /**     * Adds a base directory for a namespace prefix.     * 为一个命名空间前缀添加对应的base目录     *     * @param string $prefix The namespace prefix.     * @param string $base_dir A base directory for class files in the     * namespace.     * @param bool $prepend If true, prepend the base directory to the stack     * instead of appending it; this causes it to be searched first rather     * than last.     * @return void     */    public function addNamespace($prefix, $base_dir, $prepend = false)    {        // normalize namespace prefix        // 规范命名空间前缀        $prefix = trim($prefix, '\\') . '\\';        // normalize the base directory with a trailing separator        // 用'/'字符来规范base目录        $base_dir = rtrim($base_dir, DIRECTORY_SEPARATOR) . '/';        // initialize the namespace prefix array        // 初始化命名空间前缀数组        if (isset($this->prefixes[$prefix]) === false) {            $this->prefixes[$prefix] = array();        }        // retain the base directory for the namespace prefix        // 绑定命名空间前缀对应的base目录        if ($prepend) {            array_unshift($this->prefixes[$prefix], $base_dir);        } else {            array_push($this->prefixes[$prefix], $base_dir);        }    }    /**     * Loads the class file for a given class name.     * 根据类名来加载类文件。     *     * @param string $class The fully-qualified class name.     * @return mixed The mapped file name on success, or boolean false on     * failure.     */    public function loadClass($class)    {        // the current namespace prefix        $prefix = $class;        // work backwards through the namespace names of the fully-qualified        // class name to find a mapped file name        // 从后面开始遍历完全合格类名中的命名空间名称, 来查找映射的文件名        while (false !== $pos = strrpos($prefix, '\\')) {            // retain the trailing namespace separator in the prefix            // 保留命名空间前缀中尾部的分隔符            $prefix = substr($class, 0, $pos + 1);            // the rest is the relative class name            // 剩余的就是相对类名称            $relative_class = substr($class, $pos + 1);            // try to load a mapped file for the prefix and relative class            // 利用命名空间前缀和相对类名来加载映射文件            $mapped_file = $this->loadMappedFile($prefix, $relative_class);            if ($mapped_file) {                return $mapped_file;            }            // remove the trailing namespace separator for the next iteration            // of strrpos()            // 删除命名空间前缀尾部的分隔符,以便用于下一次strrpos()迭代            $prefix = rtrim($prefix, '\\');           }        // never found a mapped file        // 未找到映射文件        return false;    }    /**     * Load the mapped file for a namespace prefix and relative class.     * 根据命名空间前缀和相对类来加载映射文件     *      * @param string $prefix The namespace prefix.     * @param string $relative_class The relative class name.     * @return mixed Boolean false if no mapped file can be loaded, or the     * name of the mapped file that was loaded.     */    protected function loadMappedFile($prefix, $relative_class)    {        // are there any base directories for this namespace prefix?        // 命名空间前缀中有base目录吗?        if (isset($this->prefixes[$prefix]) === false) {            return false;        }        // look through base directories for this namespace prefix        // 遍历命名空间前缀的base目录        foreach ($this->prefixes[$prefix] as $base_dir) {            // replace the namespace prefix with the base directory,            // replace namespace separators with directory separators            // in the relative class name, append with .php            // 用base目录替代命名空间前缀,             // 在相对类名中用目录分隔符'/'来替换命名空间分隔符'\',             // 并在后面追加.php组成$file的绝对路径            $file = $base_dir                  . str_replace('\\', '/', $relative_class)                  . '.php';            // if the mapped file exists, require it            // 若映射文件存在,则require该文件            if ($this->requireFile($file)) {                // yes, we're done                return $file;            }        }        // never found it        return false;    }    /**     * If a file exists, require it from the file system.     *      * @param string $file The file to require.     * @return bool True if the file exists, false if not.     */    protected function requireFile($file)    {        if (file_exists($file)) {            require $file;            return true;        }        return false;    }}
3. 单元测试

    下面是对应的单元测试代码:

<?phpnamespace Example\Tests;class MockPsr4AutoloaderClass extends Psr4AutoloaderClass{    protected $files = array();    public function setFiles(array $files)    {        $this->files = $files;    }    protected function requireFile($file)    {        return in_array($file, $this->files);    }}class Psr4AutoloaderClassTest extends \PHPUnit_Framework_TestCase{    protected $loader;    protected function setUp()    {        $this->loader = new MockPsr4AutoloaderClass;        $this->loader->setFiles(array(            '/vendor/foo.bar/src/ClassName.php',            '/vendor/foo.bar/src/DoomClassName.php',            '/vendor/foo.bar/tests/ClassNameTest.php',            '/vendor/foo.bardoom/src/ClassName.php',            '/vendor/foo.bar.baz.dib/src/ClassName.php',            '/vendor/foo.bar.baz.dib.zim.gir/src/ClassName.php',        ));        $this->loader->addNamespace(            'Foo\Bar',            '/vendor/foo.bar/src'        );        $this->loader->addNamespace(            'Foo\Bar',            '/vendor/foo.bar/tests'        );        $this->loader->addNamespace(            'Foo\BarDoom',            '/vendor/foo.bardoom/src'        );        $this->loader->addNamespace(            'Foo\Bar\Baz\Dib',            '/vendor/foo.bar.baz.dib/src'        );        $this->loader->addNamespace(            'Foo\Bar\Baz\Dib\Zim\Gir',            '/vendor/foo.bar.baz.dib.zim.gir/src'        );    }    public function testExistingFile()    {        $actual = $this->loader->loadClass('Foo\Bar\ClassName');        $expect = '/vendor/foo.bar/src/ClassName.php';        $this->assertSame($expect, $actual);        $actual = $this->loader->loadClass('Foo\Bar\ClassNameTest');        $expect = '/vendor/foo.bar/tests/ClassNameTest.php';        $this->assertSame($expect, $actual);    }    public function testMissingFile()    {        $actual = $this->loader->loadClass('No_Vendor\No_Package\NoClass');        $this->assertFalse($actual);    }    public function testDeepFile()    {        $actual = $this->loader->loadClass('Foo\Bar\Baz\Dib\Zim\Gir\ClassName');        $expect = '/vendor/foo.bar.baz.dib.zim.gir/src/ClassName.php';        $this->assertSame($expect, $actual);    }    public function testConfusion()    {        $actual = $this->loader->loadClass('Foo\Bar\DoomClassName');        $expect = '/vendor/foo.bar/src/DoomClassName.php';        $this->assertSame($expect, $actual);        $actual = $this->loader->loadClass('Foo\BarDoom\ClassName');        $expect = '/vendor/foo.bardoom/src/ClassName.php';        $this->assertSame($expect, $actual);    }}
4. PSR-4应用

    PHP的包管理系统Composer已经支持PSR-4,同时也允许在composer.json中定义不同的prefix使用不同的自动加载机制。

Composer使用PSR-0风格

vendor/    vendor_name/        package_name/            src/                Vendor_Name/                    Package_Name/                        ClassName.php       # Vendor_Name\Package_Name\ClassName            tests/                Vendor_Name/                    Package_Name/                        ClassNameTest.php   # Vendor_Name\Package_Name\ClassName

Composer使用PSR-4风格

vendor/    vendor_name/        package_name/            src/                ClassName.php       # Vendor_Name\Package_Name\ClassName            tests/                ClassNameTest.php   # Vendor_Name\Package_Name\ClassNameTest

     对比以上两种结构,明显可以看出PSR-4带来更简洁的文件结构。

5. 参考资料

[PHP-FIG] php-fig, http://www.php-fig.org/

[PSR-0] Autoloading Standard, http://www.php-fig.org/psr/psr-0/

[PSR-4] Autoloader, http://www.php-fig.org/psr/psr-4/

[PSR-4-Meta] PSR-4 Meta Document, http://www.php-fig.org/psr/psr-4/meta/

[PSR-4-Example] Example Implementations of PSR-4, http://www.php-fig.org/psr/psr-4/examples/

./acme-log-writer/lib/File_Writer.php AuraWebResponseStatus AuraWeb
/path/to/aura-web/src/Response/Status.php SymfonyCoreRequest SymfonyCore
./vendor/Symfony/Core /Request.php ZendAcl
/usr/includes/Zend/ /usr/includes/Zend/Acl.php
声明:
この記事の内容はネチズンが自主的に寄稿したものであり、著作権は原著者に帰属します。このサイトは、それに相当する法的責任を負いません。盗作または侵害の疑いのあるコンテンツを見つけた場合は、admin@php.cn までご連絡ください。