舉例來說,假設我們的項目想要使用 monolog 這個日志工具,就需要在composer.json里告訴composer我們需要它:
1
2
3
4
5
|
{ "require" : { "monolog/monolog" : "1.*" } } |
之后執行:
1
|
php composer.phar install |
好,現在安裝完了,該怎么使用呢?Composer自動生成了一個autoload文件,你只需要引用它
1
|
require '/path/to/vendor/autoload.php' ; |
然后就可以非常方便的去使用第三方的類庫了,是不是感覺很棒啊!對于我們需要的monolog,就可以這樣用了:
1
2
3
4
5
6
7
8
|
use Monolog\Logger; use Monolog\Handler\StreamHandler; // create a log channel $log = new Logger( 'name' ); $log ->pushHandler( new StreamHandler( '/path/to/log/log_name.log' , Logger::WARNING)); // add records to the log $log ->addWarning( 'Foo' ); $log ->addError( 'Bar' ); |
在這個過程中,Composer做了什么呢?它生成了一個autoloader,再根據各個包自己的autoload配置,從而幫我們進行自動加載的工作。(如果對autoload這部分內容不太了解,可以看我之前的 一篇文章
)接下來讓我們看看Composer是怎么做的吧。
對于第三方包的自動加載,Composer提供了四種方式的支持,分別是 PSR-0和PSR-4的自動加載(我的一篇文章也有介紹過它們),生成class-map,和直接包含files的方式。
PSR-4是composer推薦使用的一種方式,因為它更易使用并能帶來更簡潔的目錄結構。在composer.json里是這樣進行配置的:
1
2
3
4
5
6
7
|
{ "autoload" : { "psr-4" : { "Foo\\" : "src/" , } } } |
key和value就定義出了namespace以及到相應path的映射。按照PSR-4的規則,當試圖自動加載 "Foo\\Bar\\Baz" 這個class時,會去尋找 "src/Bar/Baz.php" 這個文件,如果它存在則進行加載。注意, "Foo\\"
并沒有出現在文件路徑中,這是與PSR-0不同的一點,如果PSR-0有此配置,那么會去尋找
"src/Foo/Bar/Baz.php"
這個文件。
另外注意PSR-4和PSR-0的配置里,"Foo\\"結尾的命名空間分隔符必須加上并且進行轉義,以防出現"Foo"匹配到了"FooBar"這樣的意外發生。
在composer安裝或更新完之后,psr-4的配置換被轉換成namespace為key,dir path為value的Map的形式,并寫入生成的 vendor/composer/autoload_psr4.php 文件之中。
1
2
3
4
5
6
7
|
{ "autoload" : { "psr-0" : { "Foo\\" : "src/" , } } } |
最終這個配置也以Map的形式寫入生成的
vendor/composer/autoload_namespaces.php
文件之中。
Class-map方式,則是通過配置指定的目錄或文件,然后在Composer安裝或更新時,它會掃描指定目錄下以.php或.inc結尾的文件中的class,生成class到指定file path的映射,并加入新生成的 vendor/composer/autoload_classmap.php 文件中,。
1
2
3
4
5
|
{ "autoload" : { "classmap" : [ "src/" , "lib/" , "Something.php" ] } } |
例如src/下有一個BaseController類,那么在autoload_classmap.php文件中,就會生成這樣的配置:
'BaseController' => $baseDir . '/src/BaseController.php'
Files方式,就是手動指定供直接加載的文件。比如說我們有一系列全局的helper functions,可以放到一個helper文件里然后直接進行加載
1
2
3
4
5
|
{ "autoload" : { "files" : [ "src/MyLibrary/functions.php" ] } } |
它會生成一個array,包含這些配置中指定的files,再寫入新生成的
vendor/composer/autoload_files.php
文件中,以供autoloader直接進行加載。
下面來看看composer autoload的代碼吧
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
|
<?php // autoload_real.php @generated by Composer class ComposerAutoloaderInit73612b48e6c3d0de8d56e03dece61d11 { private static $loader ; public static function loadClassLoader( $class ) { if ( 'Composer\Autoload\ClassLoader' === $class ) { require __DIR__ . '/ClassLoader.php' ; } } public static function getLoader() { if (null !== self:: $loader ) { return self:: $loader ; } spl_autoload_register( array ( 'ComposerAutoloaderInit73612b48e6c3d0de8d56e03dece61d11' , 'loadClassLoader' ), true, true); self:: $loader = $loader = new \Composer\Autoload\ClassLoader(); spl_autoload_unregister( array ( 'ComposerAutoloaderInit73612b48e6c3d0de8d56e03dece61d11' , 'loadClassLoader' )); $vendorDir = dirname(__DIR__); //verdor第三方類庫提供者目錄 $baseDir = dirname( $vendorDir ); //整個應用的目錄 $includePaths = require __DIR__ . '/include_paths.php' ; array_push ( $includePaths , get_include_path()); set_include_path(join(PATH_SEPARATOR, $includePaths )); $map = require __DIR__ . '/autoload_namespaces.php' ; foreach ( $map as $namespace => $path ) { $loader ->set( $namespace , $path ); } $map = require __DIR__ . '/autoload_psr4.php' ; foreach ( $map as $namespace => $path ) { $loader ->setPsr4( $namespace , $path ); } $classMap = require __DIR__ . '/autoload_classmap.php' ; if ( $classMap ) { $loader ->addClassMap( $classMap ); } $loader ->register(true); $includeFiles = require __DIR__ . '/autoload_files.php' ; foreach ( $includeFiles as $file ) { composerRequire73612b48e6c3d0de8d56e03dece61d11( $file ); } return $loader ; } } function composerRequire73612b48e6c3d0de8d56e03dece61d11( $file ) { require $file ; } |
首先初始化ClassLoader類,然后依次用上面提到的4種加載方式來注冊/直接加載,ClassLoader的一些核心代碼如下:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
|
/** * @param array $classMap Class to filename map */ public function addClassMap( array $classMap ) { if ( $this ->classMap) { $this ->classMap = array_merge ( $this ->classMap, $classMap ); } else { $this ->classMap = $classMap ; } } /** * Registers a set of PSR-0 directories for a given prefix, * replacing any others previously set for this prefix. * * @param string $prefix The prefix * @param array|string $paths The PSR-0 base directories */ public function set( $prefix , $paths ) { if (! $prefix ) { $this ->fallbackDirsPsr0 = ( array ) $paths ; } else { $this ->prefixesPsr0[ $prefix [0]][ $prefix ] = ( array ) $paths ; } } /** * Registers a set of PSR-4 directories for a given namespace, * replacing any others previously set for this namespace. * * @param string $prefix The prefix/namespace, with trailing '\\' * @param array|string $paths The PSR-4 base directories * * @throws \InvalidArgumentException */ public function setPsr4( $prefix , $paths ) { if (! $prefix ) { $this ->fallbackDirsPsr4 = ( array ) $paths ; } else { $length = strlen ( $prefix ); if ( '\\' !== $prefix [ $length - 1]) { throw new \InvalidArgumentException( "A non-empty PSR-4 prefix must end with a namespace separator." ); } $this ->prefixLengthsPsr4[ $prefix [0]][ $prefix ] = $length ; $this ->prefixDirsPsr4[ $prefix ] = ( array ) $paths ; } } /** * Registers this instance as an autoloader. * * @param bool $prepend Whether to prepend the autoloader or not */ public function register( $prepend = false) { spl_autoload_register( array ( $this , 'loadClass' ), true, $prepend ); } /** * Loads the given class or interface. * * @param string $class The name of the class * @return bool|null True if loaded, null otherwise */ public function loadClass( $class ) { if ( $file = $this ->findFile( $class )) { includeFile( $file ); return true; } } /** * Finds the path to the file where the class is defined. * * @param string $class The name of the class * * @return string|false The path if found, false otherwise */ public function findFile( $class ) { //這是PHP5.3.0 - 5.3.2的一個bug 詳見https://bugs.php.net/50731 if ( '\\' == $class [0]) { $class = substr ( $class , 1); } // class map 方式的查找 if (isset( $this ->classMap[ $class ])) { return $this ->classMap[ $class ]; } //psr-0/4方式的查找 $file = $this ->findFileWithExtension( $class , '.php' ); // Search for Hack files if we are running on HHVM if ( $file === null && defined( 'HHVM_VERSION' )) { $file = $this ->findFileWithExtension( $class , '.hh' ); } if ( $file === null) { // Remember that this class does not exist. return $this ->classMap[ $class ] = false; } return $file ; } private function findFileWithExtension( $class , $ext ) { // PSR-4 lookup $logicalPathPsr4 = strtr ( $class , '\\' , DIRECTORY_SEPARATOR) . $ext ; $first = $class [0]; if (isset( $this ->prefixLengthsPsr4[ $first ])) { foreach ( $this ->prefixLengthsPsr4[ $first ] as $prefix => $length ) { if (0 === strpos ( $class , $prefix )) { foreach ( $this ->prefixDirsPsr4[ $prefix ] as $dir ) { if ( file_exists ( $file = $dir . DIRECTORY_SEPARATOR . substr ( $logicalPathPsr4 , $length ))) { return $file ; } } } } } // PSR-4 fallback dirs foreach ( $this ->fallbackDirsPsr4 as $dir ) { if ( file_exists ( $file = $dir . DIRECTORY_SEPARATOR . $logicalPathPsr4 )) { return $file ; } } // PSR-0 lookup if (false !== $pos = strrpos ( $class , '\\' )) { // namespaced class name $logicalPathPsr0 = substr ( $logicalPathPsr4 , 0, $pos + 1) . strtr ( substr ( $logicalPathPsr4 , $pos + 1), '_' , DIRECTORY_SEPARATOR); } else { // PEAR-like class name $logicalPathPsr0 = strtr ( $class , '_' , DIRECTORY_SEPARATOR) . $ext ; } if (isset( $this ->prefixesPsr0[ $first ])) { foreach ( $this ->prefixesPsr0[ $first ] as $prefix => $dirs ) { if (0 === strpos ( $class , $prefix )) { foreach ( $dirs as $dir ) { if ( file_exists ( $file = $dir . DIRECTORY_SEPARATOR . $logicalPathPsr0 )) { return $file ; } } } } } // PSR-0 fallback dirs foreach ( $this ->fallbackDirsPsr0 as $dir ) { if ( file_exists ( $file = $dir . DIRECTORY_SEPARATOR . $logicalPathPsr0 )) { return $file ; } } // PSR-0 include paths. if ( $this ->useIncludePath && $file = stream_resolve_include_path( $logicalPathPsr0 )) { return $file ; } } /** * Scope isolated include. * * Prevents access to $this/self from included files. */ function includeFile( $file ) { include $file ; } |