Home > Article > Backend Development > PSR-4 Example
以下是一个实现PSR-4规范的例子:
Closure Example
<code><span><?php</span><span>/** * 一个具体项目实现的例子. * 使用SPL注册了autoload函数之后,以下代码将触发autoload函数从 * /path/to/project/src/Baz/Qux.php 文件加载 * \Foo\Bar\Baz\Qux 类 * new \Foo\Bar\Baz\Qux; * *<span> @param</span> string $class The fully-qualified class name. *<span> @return</span> void */</span> spl_autoload_register(<span><span>function</span><span>(<span>$class</span>)</span> {</span><span>// 项目约定的命名空间前缀</span><span>$prefix</span> = <span>'Foo\\Bar\\'</span>; <span>// 命名空间前缀的基础目录</span><span>$base_dir</span> = <span>__DIR__</span> . <span>'/src/'</span>; <span>// 这个类是否使用了命名空间前缀?</span><span>$len</span> = strlen(<span>$prefix</span>); <span>if</span> (strncmp(<span>$prefix</span>, <span>$class</span>, <span>$len</span>) !== <span>0</span>) { <span>// 没有,尝试下个已注册的 autoloader</span><span>return</span>; } <span>// 获取相对类名(截取命名空间前缀后剩下的部分)</span><span>$relative_class</span> = substr(<span>$class</span>, <span>$len</span>); <span>// 用base目录替换命名空间前缀;</span><span>// 用目录分隔符替换命名空间分隔符;</span><span>// 坠上 .php</span><span>$file</span> = <span>$base_dir</span> . str_replace(<span>'\\'</span>, <span>'/'</span>, <span>$relative_class</span>) . <span>'.php'</span>; <span>// 如果文件存在,加载文件。</span><span>if</span> (file_exists(<span>$file</span>)) { <span>require</span><span>$file</span>; } });</code>
Class Example
以下是一个处理多命名空间的类的例子。
<code><span><?php</span><span>namespace</span><span>Example</span>; <span>/** * 这个例子是一个针对 一个命名空间前缀对应多个base谬的通用实现。 * * 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 * * ... 以下代码将类文件的路径添加到 \Foo\Bar\命名空间前缀下。 * * <span><?php</span> * // 初始化 loader * $loader = new \Example\Psr4AutoloaderClass; * * // 注册 autoloader * $loader->register(); * * // 为命名空间前缀注册base目录。 * $loader->addNamespace('Foo\Bar', '/path/to/packages/foo-bar/src'); * $loader->addNamespace('Foo\Bar', '/path/to/packages/foo-bar/tests'); * * 下面的代码将触发 autoloader 尝试从 * /path/to/packages/foo-bar/src/Qux/Quux.php 加载 * \Foo\Bar\Qux\Quux 类: * * <span><?php</span> * new \Foo\Bar\Qux\Quux; * * 以下代码触发autoloader尝试从 * /path/to/packages/foo-bar/tests/Qux/QuuxTest.php * 加载\Foo\Bar\Qux\QuuxTest类 : * * <span><?php</span> * new \Foo\Bar\Qux\QuuxTest; */</span><span><span>class</span><span>Psr4AutoloaderClass</span> {</span><span>/** * An associative array where the key is a namespace prefix and the value * is an array of base directories for classes in that namespace. * *<span> @var</span> array */</span><span>protected</span><span>$prefixes</span> = <span>array</span>(); <span>/** * Register loader with SPL autoloader stack. * *<span> @return</span> void */</span><span>public</span><span><span>function</span><span>register</span><span>()</span> {</span> spl_autoload_register(<span>array</span>(<span>$this</span>, <span>'loadClass'</span>)); } <span>/** * Adds a base directory for a namespace prefix. * *<span> @param</span> string $prefix The namespace prefix. *<span> @param</span> string $base_dir A base directory for class files in the * namespace. *<span> @param</span> 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. *<span> @return</span> void */</span><span>public</span><span><span>function</span><span>addNamespace</span><span>(<span>$prefix</span>, <span>$base_dir</span>, <span>$prepend</span> = false)</span> {</span><span>// normalize namespace prefix</span><span>$prefix</span> = trim(<span>$prefix</span>, <span>'\\'</span>) . <span>'\\'</span>; <span>// normalize the base directory with a trailing separator</span><span>$base_dir</span> = rtrim(<span>$base_dir</span>, DIRECTORY_SEPARATOR) . <span>'/'</span>; <span>// initialize the namespace prefix array</span><span>if</span> (<span>isset</span>(<span>$this</span>->prefixes[<span>$prefix</span>]) === <span>false</span>) { <span>$this</span>->prefixes[<span>$prefix</span>] = <span>array</span>(); } <span>// retain the base directory for the namespace prefix</span><span>if</span> (<span>$prepend</span>) { array_unshift(<span>$this</span>->prefixes[<span>$prefix</span>], <span>$base_dir</span>); } <span>else</span> { array_push(<span>$this</span>->prefixes[<span>$prefix</span>], <span>$base_dir</span>); } } <span>/** * Loads the class file for a given class name. * *<span> @param</span> string $class The fully-qualified class name. *<span> @return</span> mixed The mapped file name on success, or boolean false on * failure. */</span><span>public</span><span><span>function</span><span>loadClass</span><span>(<span>$class</span>)</span> {</span><span>// the current namespace prefix</span><span>$prefix</span> = <span>$class</span>; <span>// work backwards through the namespace names of the fully-qualified</span><span>// class name to find a mapped file name</span><span>while</span> (<span>false</span> !== <span>$pos</span> = strrpos(<span>$prefix</span>, <span>'\\'</span>)) { <span>// retain the trailing namespace separator in the prefix</span><span>$prefix</span> = substr(<span>$class</span>, <span>0</span>, <span>$pos</span> + <span>1</span>); <span>// the rest is the relative class name</span><span>$relative_class</span> = substr(<span>$class</span>, <span>$pos</span> + <span>1</span>); <span>// try to load a mapped file for the prefix and relative class</span><span>$mapped_file</span> = <span>$this</span>->loadMappedFile(<span>$prefix</span>, <span>$relative_class</span>); <span>if</span> (<span>$mapped_file</span>) { <span>return</span><span>$mapped_file</span>; } <span>// remove the trailing namespace separator for the next iteration</span><span>// of strrpos()</span><span>$prefix</span> = rtrim(<span>$prefix</span>, <span>'\\'</span>); } <span>// never found a mapped file</span><span>return</span><span>false</span>; } <span>/** * Load the mapped file for a namespace prefix and relative class. * *<span> @param</span> string $prefix The namespace prefix. *<span> @param</span> string $relative_class The relative class name. *<span> @return</span> mixed Boolean false if no mapped file can be loaded, or the * name of the mapped file that was loaded. */</span><span>protected</span><span><span>function</span><span>loadMappedFile</span><span>(<span>$prefix</span>, <span>$relative_class</span>)</span> {</span><span>// are there any base directories for this namespace prefix?</span><span>if</span> (<span>isset</span>(<span>$this</span>->prefixes[<span>$prefix</span>]) === <span>false</span>) { <span>return</span><span>false</span>; } <span>// look through base directories for this namespace prefix</span><span>foreach</span> (<span>$this</span>->prefixes[<span>$prefix</span>] <span>as</span><span>$base_dir</span>) { <span>// replace the namespace prefix with the base directory,</span><span>// replace namespace separators with directory separators</span><span>// in the relative class name, append with .php</span><span>$file</span> = <span>$base_dir</span> . str_replace(<span>'\\'</span>, <span>'/'</span>, <span>$relative_class</span>) . <span>'.php'</span>; <span>// if the mapped file exists, require it</span><span>if</span> (<span>$this</span>->requireFile(<span>$file</span>)) { <span>// yes, we're done</span><span>return</span><span>$file</span>; } } <span>// never found it</span><span>return</span><span>false</span>; } <span>/** * If a file exists, require it from the file system. * *<span> @param</span> string $file The file to require. *<span> @return</span> bool True if the file exists, false if not. */</span><span>protected</span><span><span>function</span><span>requireFile</span><span>(<span>$file</span>)</span> {</span><span>if</span> (file_exists(<span>$file</span>)) { <span>require</span><span>$file</span>; <span>return</span><span>true</span>; } <span>return</span><span>false</span>; } }</span></span></code>
Unit Tests
The following example is one way of unit testing the above class loader:
<code><span><?php</span><span>namespace</span><span>Example</span>\<span>Tests</span>; <span><span>class</span><span>MockPsr4AutoloaderClass</span><span>extends</span><span>Psr4AutoloaderClass</span> {</span><span>protected</span><span>$files</span> = <span>array</span>(); <span>public</span><span><span>function</span><span>setFiles</span><span>(array <span>$files</span>)</span> {</span><span>$this</span>->files = <span>$files</span>; } <span>protected</span><span><span>function</span><span>requireFile</span><span>(<span>$file</span>)</span> {</span><span>return</span> in_array(<span>$file</span>, <span>$this</span>->files); } } <span><span>class</span><span>Psr4AutoloaderClassTest</span><span>extends</span> \<span>PHPUnit_Framework_TestCase</span> {</span><span>protected</span><span>$loader</span>; <span>protected</span><span><span>function</span><span>setUp</span><span>()</span> {</span><span>$this</span>->loader = <span>new</span> MockPsr4AutoloaderClass; <span>$this</span>->loader->setFiles(<span>array</span>( <span>'/vendor/foo.bar/src/ClassName.php'</span>, <span>'/vendor/foo.bar/src/DoomClassName.php'</span>, <span>'/vendor/foo.bar/tests/ClassNameTest.php'</span>, <span>'/vendor/foo.bardoom/src/ClassName.php'</span>, <span>'/vendor/foo.bar.baz.dib/src/ClassName.php'</span>, <span>'/vendor/foo.bar.baz.dib.zim.gir/src/ClassName.php'</span>, )); <span>$this</span>->loader->addNamespace( <span>'Foo\Bar'</span>, <span>'/vendor/foo.bar/src'</span> ); <span>$this</span>->loader->addNamespace( <span>'Foo\Bar'</span>, <span>'/vendor/foo.bar/tests'</span> ); <span>$this</span>->loader->addNamespace( <span>'Foo\BarDoom'</span>, <span>'/vendor/foo.bardoom/src'</span> ); <span>$this</span>->loader->addNamespace( <span>'Foo\Bar\Baz\Dib'</span>, <span>'/vendor/foo.bar.baz.dib/src'</span> ); <span>$this</span>->loader->addNamespace( <span>'Foo\Bar\Baz\Dib\Zim\Gir'</span>, <span>'/vendor/foo.bar.baz.dib.zim.gir/src'</span> ); } <span>public</span><span><span>function</span><span>testExistingFile</span><span>()</span> {</span><span>$actual</span> = <span>$this</span>->loader->loadClass(<span>'Foo\Bar\ClassName'</span>); <span>$expect</span> = <span>'/vendor/foo.bar/src/ClassName.php'</span>; <span>$this</span>->assertSame(<span>$expect</span>, <span>$actual</span>); <span>$actual</span> = <span>$this</span>->loader->loadClass(<span>'Foo\Bar\ClassNameTest'</span>); <span>$expect</span> = <span>'/vendor/foo.bar/tests/ClassNameTest.php'</span>; <span>$this</span>->assertSame(<span>$expect</span>, <span>$actual</span>); } <span>public</span><span><span>function</span><span>testMissingFile</span><span>()</span> {</span><span>$actual</span> = <span>$this</span>->loader->loadClass(<span>'No_Vendor\No_Package\NoClass'</span>); <span>$this</span>->assertFalse(<span>$actual</span>); } <span>public</span><span><span>function</span><span>testDeepFile</span><span>()</span> {</span><span>$actual</span> = <span>$this</span>->loader->loadClass(<span>'Foo\Bar\Baz\Dib\Zim\Gir\ClassName'</span>); <span>$expect</span> = <span>'/vendor/foo.bar.baz.dib.zim.gir/src/ClassName.php'</span>; <span>$this</span>->assertSame(<span>$expect</span>, <span>$actual</span>); } <span>public</span><span><span>function</span><span>testConfusion</span><span>()</span> {</span><span>$actual</span> = <span>$this</span>->loader->loadClass(<span>'Foo\Bar\DoomClassName'</span>); <span>$expect</span> = <span>'/vendor/foo.bar/src/DoomClassName.php'</span>; <span>$this</span>->assertSame(<span>$expect</span>, <span>$actual</span>); <span>$actual</span> = <span>$this</span>->loader->loadClass(<span>'Foo\BarDoom\ClassName'</span>); <span>$expect</span> = <span>'/vendor/foo.bardoom/src/ClassName.php'</span>; <span>$this</span>->assertSame(<span>$expect</span>, <span>$actual</span>); } }</span></code>
以上就介绍了PSR-4 实例,包括了方面的内容,希望对PHP教程有兴趣的朋友有所帮助。