三、实现自动加载器
在这一步中,我们将设置自动类加载。在此之后,当我们需要一个类文件时,我们将不需要include
或require
语句来为我们加载它。在继续之前,您应该查看关于自动加载器的 PHP 文档–http://www.php.net/manual/en/language.oop5.autoload.php 。
PSR-0
PHP land 中有许多不同的自动加载器建议。我们将使用它来更新遗留应用程序,它基于一种称为PSR-0
的东西。
PSR-0 是一个 PHP 框架互操作性组建议,用于构建类文件。该建议源于 PHP4 时代使用“类到文件”命名约定的项目的悠久历史。该公约起源于 Horde 和 PEAR,早期的 PHP5 项目(如 Solar 和 Zend Framework)以及后来的 Symfony2 项目都采用了该公约。
我们使用 PSR-0 而不是较新的 PSR-4 建议,因为我们处理的是遗留代码,这些代码可能是在 PHP5.3 名称空间出现之前开发的。PHP5.3 之前编写的代码无法访问名称空间分隔符,因此遵循类到文件命名约定的作者通常会在类名中使用下划线作为伪名称空间分隔符。PSR-0 允许使用较旧的非 PHP-5.3 伪名称空间,使其更适合我们的遗留需求,而 PSR-4 则没有。
在 PSR-0 下,类名直接映射到文件系统子路径。给定一个完全限定的类名,任何 PHP5.3 命名空间分隔符都将转换为目录分隔符,名称的类部分中的下划线也将转换为目录分隔符。(名称空间部分中的下划线本身是而不是转换为目录分隔符。)结果以基本目录位置作为前缀,并以.php
作为后缀,以创建可以找到类文件的文件路径。例如,在 UNIX 风格的文件系统上,完全限定的类名\Foo\Bar\Baz_Dib
可以在名为Foo/Bar/Baz/Dib.php
的子路径中找到。
课程的单一地点
在我们实现 PSR-0 自动加载器之前,我们需要在代码库中选择一个目录位置来保存将在代码库中使用的每个类。一些项目已经有了这样的位置;它可以被称为includes
、classes
、src
、lib
或类似的名称。
如果这样的位置已经存在,请仔细检查。它是否只有类文件,或者它是类文件和其他类型文件的组合?如果其中除了类文件之外还有其他内容,或者不存在这样的位置,则创建一个新的目录位置,并将其称为类(或其他适当的描述性名称)。
该目录将是整个项目中使用的所有类的中心位置。稍后,我们将开始将类从项目中分散的位置移动到这个中心位置。
添加自动加载器代码
一旦我们有了类文件的中央目录位置,我们需要设置自动加载器在该位置查找类。我们可以将 autoloader 创建为静态方法、实例方法、匿名函数或常规全局函数。(我们使用哪一个并没有实际执行自动加载那么重要。)然后,在调用任何类之前,我们将在引导或设置代码的早期向spl_autoload_register()
注册它。
作为一个全局函数
也许实现我们新的自动加载器代码最简单的方法是作为一个全局函数。下面,我们找到要使用的自动加载器代码;函数名的前缀为mlaphp_
,以确保它不会与任何现有函数名冲突。
setup.php
1 <?php
2 // ... setup code ...
3
4 // define an autoloader function in the global namespace
5 function mlaphp_autoloader($class)
6 {
7 // strip off any leading namespace separator from PHP 5.3
8 $class = ltrim($class, '\\');
9
10 // the eventual file path
11 $subpath = '';
12
13 // is there a PHP 5.3 namespace separator?
14 $pos = strrpos($class, '\\');
15 if ($pos !== false) {
16 // convert namespace separators to directory separators
17 $ns = substr($class, 0, $pos);
18 $subpath = str_replace('\\', DIRECTORY_SEPARATOR, $ns)
19 . DIRECTORY_SEPARATOR;
20 // remove the namespace portion from the final class name portion
21 $class = substr($class, $pos + 1);
22 }
23
24 // convert underscores in the class name to directory separators
25 $subpath .= str_replace('_', DIRECTORY_SEPARATOR, $class);
26
27 // the path to our central class directory location
28 $dir = '/path/to/app/classes';
29
30 // prefix with the central directory location and suffix with .php,
31 // then require it.
32 $file = $dir . DIRECTORY_SEPARATOR . $subpath . '.php';
33 require $file;
34 }
35
36 // register it with SPL
37 spl_autoload_register('mlaphp_autoloader');
38 ?>
注意,变量$dir
表示一个绝对目录,作为我们的中心类目录的基本路径。作为 PHP5.3 及更高版本的替代方案,在该变量中使用__DIR__
常量是完全可以接受的,因此绝对路径不再是硬编码的,而是相对于函数所在的文件。例如:
1 <?php
2 // go "up one directory" for the central classes location
3 $dir = dirname(__DIR__) . '/classes';
4 ?>
如果由于某种原因而停留在 PHP5.2 上,__DIR__
常量不可用。在这种情况下,您可以将dirname(__DIR__)
替换为dirname(dirname(__FILE__))
。
作为结束
如果我们使用 PHP5.3,我们可以创建 autoloader 代码作为闭包,并在一个步骤中将其注册到 SPL:
setup.php
1 <?php
2 // ... setup code ...
3
4 // register an autoloader as an anonymous function
5 spl_autoload_register(function ($class) {
6 // ... the same code as in the global function ...
7 });
8
9 // ... other setup code ...
10 ?>
作为静态或实例方法
这是我设置自动装弹机的首选方式。我们不使用函数,而是在类中创建自动加载程序代码作为实例方法或静态方法。我建议使用实例方法而不是静态方法,但您的情况将决定哪种方法更合适。
首先,我们在中心类目录位置创建 autoloader 类文件。如果我们使用 PHP5.3 或更高版本,我们应该使用一个合适的名称空间;否则,我们使用下划线作为伪名称空间分隔符。
下面是一个 PHP5.3 示例。在 PHP5.3 之前的版本中,我们将省略namespace
声明,并将类命名为Mlaphp_Autoloader
。无论哪种方式,文件都应位于子路径Mlaphp/Autoloader.php
:
/path/to/app/classes/Mlaphp/Autoloader.php
1 <?php
2 namespace Mlaphp;
3
4 class Autoloader
5 {
6 // an instance method alternative
7 public function load($class)
8 {
9 // ... the same code as in the global function ...
10 }
11
12 // a static method alternative
13 static public function loadStatic($class)
14 {
15 // ... the same code as in the global function ...
16 }
17 }
18 ?>
然后,在 setup 或 bootstrap 文件中,require_once
类文件,如果需要,实例化它,并用 SPL 注册方法。注意,我们在这里使用数组可调用格式,其中第一个数组元素是类名或对象实例,第二个元素是要调用的方法:
setup.php
1 <?php
2 // ... setup code ...
3
4 // require the autoloader class file
5 require_once '/path/to/app/classes/Mlaphp/Autoloader.php';
6
7 // STATIC OPTION: register a static method with SPL
8 spl_autoload_register(array('Mlaphp\Autoloader', 'loadStatic'));
9
10 // INSTANCE OPTION: create the instance and register the method with SPL
11 $autoloader = new \Mlaphp\Autoloader();
12 spl_autoload_register(array($autoloader, 'load'));
13
14 // ... other setup code ...
15 ?>
请选择实例方法或静态方法,而不是同时选择两者。一个不是另一个的退路。
使用自动加载()函数
如果出于某种原因,我们无法使用 PHP5.0,我们可以使用__autoload()
函数代替 SPL 自动加载注册表。这样做有缺点,但在 PHP5.0 下,这是我们唯一的选择。我们不需要向 SPL 注册它(事实上,我们不能,因为 SPL 直到 PHP5.1 才被引入)。我们将无法在此实现中混合和匹配其他自动加载器;只允许使用一个__autoload()
功能。如果已经定义了一个__autoload()
函数,我们需要将此代码与该函数中已有的任何代码合并:
setup.php
1 <?php
2 // ... setup code ...
3
4 // define an __autoload() function
5 function __autoload($class)
6 {
7 // ... the global function code ...
8 }
9
10 // ... other setup code ...
11 ?>
我强烈建议不要在 PHP5.1 及更高版本中使用这种实现。
自动加载器优先级
无论如何实现我们的自动加载程序代码,我们都需要在在代码库中调用任何类之前可用。将自动加载器注册为我们的代码库中最早的逻辑位之一不会有什么坏处,可能是在安装或引导脚本中。
常见问题
如果我已经有了自动装弹机怎么办?
某些遗留应用程序可能已经安装了自定义自动加载器。如果这是我们的情况,我们有一些选择:
- 按原样使用现有的自动加载器:如果应用程序类文件已经有一个中心目录位置,这是我们的最佳选择。
- 修改现有自动加载器以添加 PSR-0 行为:如果自动加载器不符合 PSR-0 建议,这是一个很好的选择。
- 除现有自动装弹机外,还应向 SPL 注册本章所述的 PSR-0 自动装弹机。当现有自动加载器不符合 PSR-0 建议时,这是另一个好的选择。
其他遗留代码库可能有第三方自动加载器,例如 Composer。如果 Composer 存在,我们可以获取它的 autoloader 实例,并添加我们的中心类目录位置以进行自动加载,如下所示:
1 <?php
2 // get the registered Composer autoloader instance from the vendor/
3 // subdirectory
4 $loader = require '/path/to/app/vendor/autoload.php';
5
6 // add our central class directory location; do not use a class prefix as
7 // we may have more than one top-level namespace in the central location
8 $loader->add('', '/path/to/app/classes');
9 ?>
有了它,我们可以为自己的目的选择 Composer,使我们自己的自动加载代码变得不必要。
自动加载的性能影响是什么?
有理由认为,与使用include
相比,使用自动加载器可能会造成轻微的性能阻力,但证据是复杂的,且取决于具体情况。如果自动加载确实相对较慢,那么预期性能会受到多大的影响?
我断言,在对遗留应用程序进行现代化时,这可能不是一个重要的考虑因素。与遗留应用程序中的其他可能的性能问题(如数据库交互)相比,自动加载导致的任何性能损失都是微不足道的。
在大多数遗留应用程序中,甚至在大多数现代应用程序中,试图在自动加载时优化性能就是试图在错误的资源上进行优化。还有其他资源可能是更糟糕的罪犯,只是我们没有看到或没有想到的资源。
如果自动加载是遗留应用程序中最糟糕的性能瓶颈,那么您的状态就非常好。(在这种情况下,你应该归还这本书,要求退款,然后告诉我你是否在招聘,因为我想为你工作。)
类名如何映射到文件名?
PSR-0 规则可能令人困惑。下面是一些类到文件的映射示例,以说明其期望:
Foo => Foo.php
Foo_Bar => Foo/Bar.php
Foo => Foo/Bar.php
Foo_Bar\Bar => Foo_Bar/Baz.php
Foo\Bar\Baz => Foo/Bar/Baz.php # ???
Foo\Baz_Bar => Foo/Bar/Baz.php # ???
Foo_Bar_Baz => Foo/Bar/Baz.php # ???
我们可以看到在最后三个例子中有一些意想不到的行为。这源于 PSR-0 的过渡性质:Foo\Bar\Baz, Foo\Bar_Baz
和Foo_Bar_Baz
都映射到同一个文件。为什么会这样?
回想一下,PHP-5.3 之前的代码基没有名称空间,因此使用下划线作为伪名称空间分隔符。PHP5.3 引入了一个真正的名称空间分隔符。PSR-0 标准必须同时适应这两种情况,因此它将相对类名中的下划线(即完全限定名的最后一部分)作为目录分隔符,而命名空间部分中的下划线则被单独保留。
这里的教训是,如果您使用的是 PHP5.3,则绝对不要在相对类名中使用下划线(尽管名称空间中的下划线也可以)。如果您使用的是 PHP5.3 之前的版本,您别无选择,只能使用下划线,因为只有类名,没有实际的命名空间部分;在这种情况下,将下划线解释为命名空间分隔符。
回顾和下一步
在这一点上,我们没有对遗留应用程序进行太多修改。我们已经添加并注册了一些自动加载程序代码,但实际上还没有调用它。
不管怎样拥有一个自动加载器对于实现遗留应用程序现代化的下一步至关重要。使用自动加载程序将允许我们开始删除只加载类和函数的include
语句。其余的include
语句将是逻辑流,向我们展示系统的哪些部分是逻辑的,哪些只是定义。这是我们从面向包含的体系结构向面向类的体系结构过渡的开始。
版权属于:月萌API www.moonapi.com,转载请注明出处