PSR-4-新鮮なPHP仕様


FIGが制定したPHP規範は、PSRと略称され、PHP開発の事実基準である.
PSRにはもともと4つの規範があり、それぞれ:
  • PSR-0自動ロード
  • PSR-1基本コード仕様
  • PSR-2コード様式
  • PSR-3ログインタフェース
  • 2013年末、第5の規範であるPSR-4が新たに発表された.
    PSR-4では、クラス定義を自動的にロードするためにファイルパスを指定する方法と、ファイルを自動的にロードする場所を指定します.これは一見PSR-0と重複しているが、実際には機能的に重複している.違いはPSR-4の仕様が比較的清潔で、PHP 5.3以前のバージョンと互換性がある内容を除いて、PSR-0のアップグレード版の感じがします.もちろん、PSR-4もPSR-0に完全に代わるのではなく、必要なときにPSR-0を補充します.もちろん、もしあなたが望むなら、PSR-4もPSR-0に代わることができます.PSR−4は、PSR−0を含む他の自動ローディング機構と共に使用することができる.
    PSR−4とPSR−0の最大の違いはアンダースコアの定義の違いである.PSR-4ではクラス名に下線を使用しても特に意味はありません.PSR−0は、クラス名の下線_がディレクトリ区切り記号に変換されることを規定している.
    コードサンプル
    以下のコードは、複数のネーミングスペースを処理するPSR-4に従うクラス定義を示しています.
    register();
     *      
     *      // register the base directories for the namespace prefix
     *      $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:
     * 
     *      prefixes[$prefix]) === false) {
                $this->prefixes[$prefix] = array();
            }
    
            // retain the base directory for the namespace prefix
            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()
                $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?
            if (isset($this->prefixes[$prefix]) === false) {
                return false;
            }
    
            // look through base directories for this namespace prefix
            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
                $file = $base_dir
                      . str_replace('\\', DIRECTORY_SEPARATOR, $relative_class)
                      . '.php';
                $file = $base_dir
                      . str_replace('\\', '/', $relative_class)
                      . '.php';
    
                // if the mapped file exists, require it
                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;
        }
    }
    

    対応するユニットテストコード
    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);
        }
    }
    

    Composer
    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
    

    以上の2つの構造を比較すると、PSR-4がより簡潔なファイル構造をもたらすことが明らかになった.
    SegmentFaultを書く