PSR-4 相关示例

以下是 PSR-4 规范的相关示例代码:

闭包的实现示例

  1. <?php
  2. /**
  3. * 一个具体项目的实例
  4. *
  5. * 当使用 SPL 注册此自动加载器后,执行以下语句将从
  6. * /path/to/project/src/Baz/Qux.php 载入 \Foo\Bar\Baz\Qux 类:
  7. *
  8. * new \Foo\Bar\Baz\Qux;
  9. *
  10. * @param string $class 完整的类名
  11. * @return void
  12. */
  13. spl_autoload_register(function ($class) {
  14. // 具体项目命名空间前缀
  15. $prefix = 'Foo\\Bar\\';
  16. // 命名空间前缀的基目录
  17. $base_dir = __DIR__ . '/src/';
  18. // 判断类名是否具有本命名空间前缀
  19. $len = strlen($prefix);
  20. if (strncmp($prefix, $class, $len) !== 0) {
  21. // 不含本命名空间前缀,退出本自动载入器
  22. return;
  23. }
  24. // 截取相应类名
  25. $relative_class = substr($class, $len);
  26. // 将命名空间前缀替作为文件基目录,然后
  27. // 将类名中的命名空间分隔符替换成文件分隔符,
  28. // 最后添加 .php 后缀
  29. $file = $base_dir . str_replace('\\', '/', $relative_class) . '.php';
  30. // 如果以上文件存在,则将其载入
  31. if (file_exists($file)) {
  32. require $file;
  33. }
  34. });

类的实现示例

下面是一个可处理多命名空间的类实例

  1. <?php
  2. namespace Example;
  3. /**
  4. * An example of a general-purpose implementation that includes the optional
  5. * functionality of allowing multiple base directories for a single namespace
  6. * prefix.
  7. *
  8. * Given a foo-bar package of classes in the file system at the following
  9. * paths ...
  10. *
  11. * /path/to/packages/foo-bar/
  12. * src/
  13. * Baz.php # Foo\Bar\Baz
  14. * Qux/
  15. * Quux.php # Foo\Bar\Qux\Quux
  16. * tests/
  17. * BazTest.php # Foo\Bar\BazTest
  18. * Qux/
  19. * QuuxTest.php # Foo\Bar\Qux\QuuxTest
  20. *
  21. * ... add the path to the class files for the \Foo\Bar\ namespace prefix
  22. * as follows:
  23. *
  24. * <?php
  25. * // instantiate the loader
  26. * $loader = new \Example\Psr4AutoloaderClass;
  27. *
  28. * // register the autoloader
  29. * $loader->register();
  30. *
  31. * // register the base directories for the namespace prefix
  32. * $loader->addNamespace('Foo\Bar', '/path/to/packages/foo-bar/src');
  33. * $loader->addNamespace('Foo\Bar', '/path/to/packages/foo-bar/tests');
  34. *
  35. * The following line would cause the autoloader to attempt to load the
  36. * \Foo\Bar\Qux\Quux class from /path/to/packages/foo-bar/src/Qux/Quux.php:
  37. *
  38. * <?php
  39. * new \Foo\Bar\Qux\Quux;
  40. *
  41. * 以下代码将由 /path/to/packages/foo-bar/tests/Qux/QuuxTest.php
  42. * 载入 \Foo\Bar\Qux\QuuxTest 类
  43. *
  44. * <?php
  45. * new \Foo\Bar\Qux\QuuxTest;
  46. */
  47. class Psr4AutoloaderClass
  48. {
  49. /**
  50. * An associative array where the key is a namespace prefix and the value
  51. * is an array of base directories for classes in that namespace.
  52. *
  53. * @var array
  54. */
  55. protected $prefixes = array();
  56. /**
  57. * 在 SPL 自动加载器栈中注册加载器
  58. *
  59. * @return void
  60. */
  61. public function register()
  62. {
  63. spl_autoload_register(array($this, 'loadClass'));
  64. }
  65. /**
  66. * 添加命名空间前缀与文件基目录对
  67. *
  68. * @param string $prefix 命名空间前缀
  69. * @param string $base_dir 命名空间中类文件的基目录
  70. * @param bool $prepend 为 True 时,将基目录插到最前,这将让其作为第一个被搜索到,否则插到将最后。
  71. * @return void
  72. */
  73. public function addNamespace($prefix, $base_dir, $prepend = false)
  74. {
  75. // 规范化命名空间前缀
  76. $prefix = trim($prefix, '\\') . '\\';
  77. // 规范化文件基目录
  78. $base_dir = rtrim($base_dir, '/') . DIRECTORY_SEPARATOR;
  79. $base_dir = rtrim($base_dir, DIRECTORY_SEPARATOR) . '/';
  80. // 初始化命名空间前缀数组
  81. if (isset($this->prefixes[$prefix]) === false) {
  82. $this->prefixes[$prefix] = array();
  83. }
  84. // 将命名空间前缀与文件基目录对插入保存数组
  85. if ($prepend) {
  86. array_unshift($this->prefixes[$prefix], $base_dir);
  87. } else {
  88. array_push($this->prefixes[$prefix], $base_dir);
  89. }
  90. }
  91. /**
  92. * 由类名载入相应类文件
  93. *
  94. * @param string $class 完整的类名
  95. * @return mixed 成功载入则返回载入的文件名,否则返回布尔 false
  96. */
  97. public function loadClass($class)
  98. {
  99. // 当前命名空间前缀
  100. $prefix = $class;
  101. // work backwards through the namespace names of the fully-qualified
  102. // class name to find a mapped file name
  103. while (false !== $pos = strrpos($prefix, '\\')) {
  104. // retain the trailing namespace separator in the prefix
  105. $prefix = substr($class, 0, $pos + 1);
  106. // the rest is the relative class name
  107. $relative_class = substr($class, $pos + 1);
  108. // try to load a mapped file for the prefix and relative class
  109. $mapped_file = $this->loadMappedFile($prefix, $relative_class);
  110. if ($mapped_file) {
  111. return $mapped_file;
  112. }
  113. // remove the trailing namespace separator for the next iteration
  114. // of strrpos()
  115. $prefix = rtrim($prefix, '\\');
  116. }
  117. // 找不到相应文件
  118. return false;
  119. }
  120. /**
  121. * Load the mapped file for a namespace prefix and relative class.
  122. *
  123. * @param string $prefix The namespace prefix.
  124. * @param string $relative_class The relative class name.
  125. * @return mixed Boolean false if no mapped file can be loaded, or the
  126. * name of the mapped file that was loaded.
  127. */
  128. protected function loadMappedFile($prefix, $relative_class)
  129. {
  130. // are there any base directories for this namespace prefix?
  131. if (isset($this->prefixes[$prefix]) === false) {
  132. return false;
  133. }
  134. // look through base directories for this namespace prefix
  135. foreach ($this->prefixes[$prefix] as $base_dir) {
  136. // replace the namespace prefix with the base directory,
  137. // replace namespace separators with directory separators
  138. // in the relative class name, append with .php
  139. $file = $base_dir
  140. . str_replace('\\', DIRECTORY_SEPARATOR, $relative_class)
  141. . '.php';
  142. $file = $base_dir
  143. . str_replace('\\', '/', $relative_class)
  144. . '.php';
  145. // 当文件存在时,在入之
  146. if ($this->requireFile($file)) {
  147. // 完成载入
  148. return $file;
  149. }
  150. }
  151. // 找不到相应文件
  152. return false;
  153. }
  154. /**
  155. * 当文件存在,则从文件系统载入之
  156. *
  157. * @param string $file 需要载入的文件
  158. * @return bool 当文件存在则为 True,否则为 false
  159. */
  160. protected function requireFile($file)
  161. {
  162. if (file_exists($file)) {
  163. require $file;
  164. return true;
  165. }
  166. return false;
  167. }
  168. }

单元测试

以下是上面代码单元测试的一种实现:

```php
<?php
namespace Example\Tests;

class MockPsr4AutoloaderClass extends Psr4AutoloaderClass
{
protected $files = array();

  1. public function setFiles(array $files)
  2. {
  3. $this->files = $files;
  4. }
  5. protected function requireFile($file)
  6. {
  7. return in_array($file, $this->files);
  8. }

}

class Psr4AutoloaderClassTest extends \PHPUnit_Framework_TestCase
{
protected $loader;

  1. protected function setUp()
  2. {
  3. $this->loader = new MockPsr4AutoloaderClass;
  4. $this->loader->setFiles(array(
  5. '/vendor/foo.bar/src/ClassName.php',
  6. '/vendor/foo.bar/src/DoomClassName.php',
  7. '/vendor/foo.bar/tests/ClassNameTest.php',
  8. '/vendor/foo.bardoom/src/ClassName.php',
  9. '/vendor/foo.bar.baz.dib/src/ClassName.php',
  10. '/vendor/foo.bar.baz.dib.zim.gir/src/ClassName.php',
  11. ));
  12. $this->loader->addNamespace(
  13. 'Foo\Bar',
  14. '/vendor/foo.bar/src'
  15. );
  16. $this->loader->addNamespace(
  17. 'Foo\Bar',
  18. '/vendor/foo.bar/tests'
  19. );
  20. $this->loader->addNamespace(
  21. 'Foo\BarDoom',
  22. '/vendor/foo.bardoom/src'
  23. );
  24. $this->loader->addNamespace(
  25. 'Foo\Bar\Baz\Dib',
  26. '/vendor/foo.bar.baz.dib/src'
  27. );
  28. $this->loader->addNamespace(
  29. 'Foo\Bar\Baz\Dib\Zim\Gir',
  30. '/vendor/foo.bar.baz.dib.zim.gir/src'
  31. );
  32. }
  33. public function testExistingFile()
  34. {
  35. $actual = $this->loader->loadClass('Foo\Bar\ClassName');
  36. $expect = '/vendor/foo.bar/src/ClassName.php';
  37. $this->assertSame($expect, $actual);
  38. $actual = $this->loader->loadClass('Foo\Bar\ClassNameTest');
  39. $expect = '/vendor/foo.bar/tests/ClassNameTest.php';
  40. $this->assertSame($expect, $actual);
  41. }
  42. public function testMissingFile()
  43. {
  44. $actual = $this->loader->loadClass('No_Vendor\No_Package\NoClass');
  45. $this->assertFalse($actual);
  46. }
  47. public function testDeepFile()
  48. {
  49. $actual = $this->loader->loadClass('Foo\Bar\Baz\Dib\Zim\Gir\ClassName');
  50. $expect = '/vendor/foo.bar.baz.dib.zim.gir/src/ClassName.php';
  51. $this->assertSame($expect, $actual);
  52. }
  53. public function testConfusion()
  54. {
  55. $actual = $this->loader->loadClass('Foo\Bar\DoomClassName');
  56. $expect = '/vendor/foo.bar/src/DoomClassName.php';
  57. $this->assertSame($expect, $actual);
  58. $actual = $this->loader->loadClass('Foo\BarDoom\ClassName');
  59. $expect = '/vendor/foo.bardoom/src/ClassName.php';
  60. $this->assertSame($expect, $actual);
  61. }

}