Composer

Composer设计原理与基本用法

PHP最原始的引入库的写法

requirerequire_onceincludeinclude_once

1
2
3
require 'class1.php';
require 'class2.php';
require 'class3.php';

当库越来越多的时候,需要引入一堆的库;
或者你可以放到一个文件里面然后引用该文件即可,但是问题来了:这种一次引入全部库做法,运行效率低、耗内存,而且有些库也并不一定能用到;
为了解决这个问题PHP5提供__autoload这个魔术方法。

__autoload

当你要使用的类PHP找不到时候,它会将类名当成字串丢给这个函数,PHP在报错之前做做最后的尝试:

1
2
3
4
5
6
7
8
9
10
11
12
13
// autoload.php
function __autoload($classname) {
if ($classname === 'class1.php'){
$filename = "./libs/". $classname .".php";
include_once($filename);
} else if ($classname === 'class2.php'){
$filename = "./libs/". $classname .".php";
include_once($filename);
} else if ($classname === 'class3.php'){
$filename = "./libs/". $classname .".php";
include_once($filename);
}
}

这个方法似乎已经解决了很多问题,但是还可以似乎还不够智能,需要自己写判断,当类库到达一定量的时候,__autoload就臃肿不堪,难以维护。

spl_autoload_register

从PHP5.1.2开始,这个函数诞生了。功能类似__autoload但是可以针对不同的用途多次用autoload了。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
spl_autoload_register('base_libs');
spl_autoload_register('my_videos_libs');
spl_autoload_register('my_pics_libs');

function base_libs($classname) {
$filename = "./libs/base_libs/". $classname .".php";
include_once($filename);
}
function my_videos_libs($classname) {
$filename = "./libs/my_videos_libs/". $classname .".php";
include_once($filename);
}
function my_pics_libs($classname) {
$filename = "./libs/my_pics_libs/". $classname .".php";
include_once($filename);
}

现在问题又来了,添加以上代码我又觉得太烦了,库多了又要写,能不能给我自动找吖,我告诉你库的目录你就好了~你自己找去吖~~~

这样,懒人专用Composer就应运而生。

Composer

安装

1
2
php -r "readfile('https://getcomposer.org/installer');" | php
sudo mv composer.phar /usr/bin/composer

composer.json

新建一个composer.json

1
2
3
4
5
6
7
8
{
"autoload": {
"classmap": [
"libs/base_libs",
"libs/my_videos_libs"
]
}
}

1
$ composer install

使用第三方库

1
2
3
4
5
6
7
8
9
10
11
12
13
{
"require": {
"laravel/framework": "4.2.*",
"cartalyst/sentry": "2.1.*",
"anahkiasen/former": "3.4.1",
},

"autoload": {
"classmap": [
"lib/base_libs"
]
}
}

然后composer install指令除了自动加载你的类库之外、还会自动下载 require 下面的第三方库,require vendor/autoload.php就可以直接使用喇~~

Composer进阶

namespace

解决类名重复的问题,PHP5.3之后才支持的namespace的
分别在my_pics_libs目录下新建两个类

1
2
3
4
5
6
// Friend.php
<?php
namespace Friend;
class Photo{
//...
}

1
2
3
4
5
6
// Family.php
<?php
namespace Family;
class Photo{
//...
}

在autoload之后,如果在同一个方法或者文件仲使用到需要用use声明或者直接使用namespace+className的方式引用。

1
2
3
4
5
6
7
8
9
//使用
use Friend\Photo as FriendPhoto
use Family\Photo as FamilyPhtot
$friendPhoto = new FriendPhoto();
$familyPhtot = new FamilyPhtot();

//在不是用use的声明的时候
$friendPhoto = new Friend\Photo();
$familyPhtot = new Family\Photo();

PSR-0

PSR-0是PHP跨框架相容性统一标准组织订出来的自动加载标准

PSR-0规范

  1. 一个完全合格的命名空间和类名必须有以下的结构“\<提供者名称>(<命名空间>)*<类名>”
  2. 每个命名空间必须有顶级的命名空间(“提供者”)
  3. 每个命名空间可以有任意多个子命名空间
  4. 每个命名空间在被从文件系统加载时必须被转换为“操作系统路径分隔符”(DIRECTORY_SEPARATOR )
  5. 每个“”字符在“类名”中被转换为DIRECTORY_SEPARATOR 。“”符号在命名空间中没有这个含义
  6. 符合命名标准的命名空间和类名必须以“.php”结尾来加载文件
  7. 提供商名称,命名空间,类名可以由大小写字母组成,其中命名空间和类名是大小写敏感的以保证多系统兼容性
  8. 如果文件不存在需要返回false

命名空间和类名

1
2
3
4
\Doctrine\Common\IsolatedClassLoader => /path/to/project/lib/vendor/Doctrine/Common/IsolatedClassLoader.php
\Symfony\Core\Request => /path/to/project/lib/vendor/Symfony/Core/Request.php
\Zend\Acl => /path/to/project/lib/vendor/Zend/Acl.php
\Zend\Mail\Message => /path/to/project/lib/vendor/Zend/Mail/Message.php

下划线在命名空间和类名中的使用

1
2
\namespace\package\Class_Name => /path/to/project/lib/vendor/namespace/package/Class/Name.php
\namespace\package_name\Class_Name => /path/to/project/lib/vendor/namespace/package_name/Class/Name.php

composer.json这样写:

1
2
3
4
5
6
7
{
"autoload": {
"psr-0": {
"Misael\\Acme\\": "my_videos_libs/"
}
}
}

扩展例子

提供一个函数来展示如何使用上述标准
1
2
3
4
5
6
7
8
9
10
11
12
13
14
function autoload($className)
{

$className = ltrim($className, '\\');
$fileName = '';
$namespace = '';
if ($lastNsPos = strripos($className, '\\')) {
$namespace = substr($className, 0, $lastNsPos);
$className = substr($className, $lastNsPos + 1);
$fileName = str_replace('\\', DIRECTORY_SEPARATOR, $namespace) . DIRECTORY_SEPARATOR;
}
$fileName .= str_replace('_', DIRECTORY_SEPARATOR, $className) . '.php';

require $fileName;
}
SplClassloader的实现

接下来这个gist实现了SplClassLoader可以加载你按照上面标准来实现的通用类库,这是5.3里面推荐的加载方式http://gist.github.com/221634
因为这个标准提到了如果文件不存在的时候应该范围false,但是在上面函数的例子中并没有实现该机制,所有有人实现了优化的SplClassLoader。

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
class ClassLoader
{

/**
* @var array Contains namespace/class prefix as key and sub path as value
*/

protected $paths;

/**
* Construct a loader instance
*
* @param array $paths Containing class/namespace prefix as key and sub path as value
*/

public function __construct( array $paths )
{

$this->paths = $paths;
}

/**
* Load classes/interfaces following PSR-0 naming
*
* @param string $className
* @return null|boolean Null if no match is found, bool if match and found/not found.
*/

public function load( $className )
{

if ( $className[0] === '\\' )
$className = substr( $className, 1 );

foreach ( $this->paths as $prefix => $subPath )
{
if ( strpos( $className, $prefix ) !== 0 )
continue;

$lastNsPos = strripos( $className, '\\' );
$prefixLen = strlen( $prefix ) + 1;
$fileName = $subPath . DIRECTORY_SEPARATOR;

if ( $lastNsPos > $prefixLen )
{
// Replacing '\' to '/' in namespace part
$fileName .= str_replace(
'\\',
DIRECTORY_SEPARATOR,
substr( $className, $prefixLen, $lastNsPos - $prefixLen )
) . DIRECTORY_SEPARATOR;
}

// Replacing '_' to '/' in className part and append '.php'
$fileName .= str_replace( '_', DIRECTORY_SEPARATOR, substr( $className, $lastNsPos + 1 ) ) . '.php';

if ( ( $fileName = stream_resolve_include_path( $fileName ) ) === false )
return false;

require $fileName;
return true;
}
}
}

Via:https://github.com/andrerom/fig-standards/blob/psr2/proposed/PSR-2.md

PSR-1 基本代码规范

PSR-2 代码样式

PSR-3 日志接口

PSR-4