Laravel 5.0 之 表单验证类 (Form Requests)

让人头痛的表单验证

本文译自 Matt Stauffer 的 系列文章 .

只要你曾经在使用 Laravel 框架的过程中试图找到有关用户输入验证的最佳实践, 你就应该了解这是一个争论最多并且几乎没有达成共识的话题. 我们可以在控制器中进行验证, 可以在单独的一个服务层进行验证, 可以在模型中进行验证, 当然还可以在 Javascript 中进行验证 (这只是一个玩笑, 谁都知道不能只依赖于客户端的验证). 但是, 哪一种做法才是最佳的呢?

Laravel 5.0 新引入的表单请求 (Form Request) 特性提供了集规范性 (差不多就是 "最佳实践" 的意思) 和便捷性 (这是比之前任何一种选择都更强大也更便捷的方式) 于一体的, 在 Laravel 中执行数据检查和验证的新手段.

说明: 本文中使用新的 view() 辅助方法代替了旧版本中的 View::make() .

Form Requests 使表单验证不再让人头痛

Laravel 5.0 带来了 Form Requests, 这是一种特殊的类型, 用于在提交表单时进行数据的检查和验证. 每个 Form Request 类至少包含一个 rules() 方法, 这个方法返回一组验证规则. 除此之外还必须包含一个 authorize() 方法, 该方法返回一个布尔值, 代表是否允许用户执行本次请求.

Laravel 会在解析 POST 路由之前自动把用户输入的信息传递给相应的表单请求, 因此我们的所有验证逻辑都可以移到独立于控制器和模型之外的 FormRequest 对象中.

开始实践: 快速创建一个 Laravel 5.0 项目

如果你还没有创建好的 Laravel 5.0 项目, 用下面的命令创建一个:

$ composer create-project laravel/laravel myProject dev-develop --prefer-dist

1. 添加路由

// app/Http/routes.phpRoute::get('/', 'FriendsController@getAddFriend');Route::post('/', 'FriendsController@postAddFriend');

2. 创建控制器

//app/Http/Controllers/FriendsController:namespace App\Http\Controllers;use App\Http\Requests\FriendFormRequest;use Illuminate\Routing\Controller;use Response;use View;class FriendsController extends Controller{	public function getAddFriend()
	{		return view('friends.add');
	}	public function postAddFriend(FriendFormRequest $request)
	{		return Response::make('Friend added!');
	}
}

3. 创建视图

<html><body>
	@foreach ($errors->all() as $error)		<p class="error">{{ $error }}</p>
	@endforeach	<form method="post">
		<label>First name</label><input name="first_name"><br>
		<label>Email address</label><input name="email_address"><br>
		<input type="submit">
	</form></body></html>

4. 创建 FormRequest

// app/http/requests/FriendFormRequest.phpnamespace App\Http\Requests;use Illuminate\Foundation\Http\FormRequest;use Response;class FriendFormRequest extends FormRequest{	public function rules()
	{		return [			'first_name' => 'required',			'email_address' => 'required|email'
		];
	}	public function authorize()
	{		// 只允许登陆用户
		// 返回 \Auth::check();
		// 允许所有用户登入
		return true;
	}	// 可选: 重写基类方法
	public function forbiddenResponse()
	{		// 这个是可选的, 当认证失败时返回自定义的 HTTP 响应. 
		// (框架默认的行为是带着错误信息返回到起始页面)
		// 可以返回 Response 实例, 视图, 重定向或其它信息
		return Response::make('Permission denied foo!', 403);
	}	// 可选: 重写基类方法
	public function response()
	{		// 如果需要自定义在验证失败时的行为, 可以重写这个方法
		// 了解有关基类中这个方法的默认行为,可以查看:
		// https://github.com/laravel/framework/blob/master/src/Illuminate/Foundation/Http/FormRequest.php
	}
}

接下来, 用 php artisan serve 或者你自己喜欢的方式启动服务器. 提交表单, 你可以看到我们并没有往控制器中添加任何一行验证逻辑, 但是验证规则已经生效了.

其它用例

如果对 "新增" 和 "编辑" 有不同的规则, 或者根据不同的输入进行不同的验证, 要怎么办呢? 这里有几个可以参考的例子, 虽然还不能确定这些就是 "最佳实践":

采用分开的 form requests

Laravel 并没有规定你不能对 "新增" 和 "编辑" 操作采用不同的 form request 类. 所以你可以创建一个包含所有规则的 FriendFormRequest 作为基类, 然后把它扩展为 addFriendFormRequest 和 editFriendFormRequest 两个子类, 每个子类都可以实现各自的默认行为.

采用条件判断逻辑

rules() 作为一个方法而不是属性, 带来的好处就是你可以在方法中添加判断逻辑:

...class UserFormRequest extends FormRequest{
	...	protected $rules = [	
		'email_address' => 'required',		'password' => 'required|min:8',
	];	public function rules()
	{
		$rules = $this->rules;		// 根据不同的情况, 添加不同的验证规则
		if ($someTestVariableShowingThisIsLoginInsteadOfSignup)
		{
			$rules['password'] = 'min:8';
		}		return $rules;
	}
}

也可以在 authorize 方法中添加逻辑, 比如:

...class FriendFormRequest extends FormRequest{
	...	public function authorize()
	{		if ( ! Auth::check() )
		{			return false;
		}
		$thingBeingEdited = Thing::find(Input::get('thingId'));		// 如果是编辑操作, 或者当前用户不是对象创建者
		if ( ! $thingBeingEdited || $thingBeingEdited->owner != Auth::id()) {			return false;
		}		return true;
	}
}

自定义校验

除了上面的方式, 如果需要对验证逻辑进行更深入的控制, 可以重写提供校验对象实例的方法. 下面是一个简单的实例, 后续会专门写一篇文章来解释:

...class FriendFormRequest extends FormRequest{	public function validator(ValidationService $service)
	{
		$validator = $service->getValidator($this->input());		// 可选: 通过新的 ->after() 方法来进行自定义
		$validator->after(function() use ($validator)) {			// 在这里可以做更多更深入的校验
			$validator->errors()->add('field', 'new error);
		}
	}
}

ValidatesWhenResolved 接口

后续还会有一篇有关 ValidatesWhenResolved 接口的文章, 不过那篇文章重点讨论的是对方法/路由等的校验. IOC 何时提供什么东西, 这个在 Laravel 5.0 版已经分离出一个单独的接口. 官方文档: https://github.com/illuminate/contracts/blob/master/Validation/ValidatesWhenResolved.php

其它可自定义的参数:

$redirect : 校验失败时要重定向到的 URI. $redirectRoute : 校验失败时要重定向到的路由.$redirectAction : 校验失败时要重定向到的方法. $dontFlash : 重定向时不要传递的输入项的键 (默认值: ['password', 'password_confirmation']).

写在最后

通过文本可以看到, Form Requests 对于简化表单请求的数据校验是非常强大和方便的. 如果你阅读本文觉得还不够, 可以观看关于 Form Request 的 这个视频 .

本文写作时, Laravel 5.0 还未正式发布, 因此上述内容最终可能还会有修改, 或者作者遗漏了某些东西. 如果你有建议或者对文章内容的修正, 可以在 给译者发邮件 或者(在 Twitter 上直接联系原作者)

[http://twitter.com/stauffermatt].

Linux 查看服务器开放的端口号

在讨论这个问题前,我们先来了解一下物理端口、逻辑端口、端口号等计算机概念。

 

端口相关的概念:

 

在网络技术中,端口(Port)包括逻辑端口和物理端口两种类型。物理端口指的是物理存在的端口,如ADSL Modem、集线器、交换机、路由器上用 于连接其他网络设备的接口,如RJ-45端口、SC端口等等。逻辑端口是指逻辑意义上用于区分服务的端口,如TCP/IP协议中的服务端口,端口号的范围从0到65535,比如用于浏览网页服务的80端口,用于FTP服务的21端口等。由于物理端口和逻辑端口数量较多,为了对端口进行区分,将每个端口进行了编号,这就是端口号

端口按端口号可以分为3大类:

 

1:公认端口(Well Known Port)

公认端口号从0到1023,它们紧密绑定与一些常见服务,例如FTP服务使用端口21,你在 /etc/services 里面可以看到这种映射关系。

 

2:注册端口(Registered Ports):

从1024到49151。它们松散地绑定于一些服务。也就是说有许多服务绑定于这些端口,这些端口同样用于许多其它目的.

 

3: 动态或私有端口(Dynamic and/or Private Ports)

动态端口,即私人端口号(private port numbers),是可用于任意软件与任何其他的软件通信的端口数,使用因特网的传输控制协议,或用户传输协议。动态端口一般从49152到65535

Linux中有限定端口的使用范围,如果我要为我的程序预留某些端口,那么我需要控制这个端口范围。/proc/sys/net/ipv4/ip_local_port_range定义了本地TCP/UDP的端口范围,你可以在/etc/sysctl.conf里面定义net.ipv4.ip_local_port_range = 1024 65000

[root@localhost ~]# cat /proc/sys/net/ipv4/ip_local_port_range
32768   61000
[root@localhost ~]#  echo 1024 65535 > /proc/sys/net/ipv4/ip_local_port_range

 

关于端口和服务,我曾经拿公共厕所打比方,公共厕所里的每一个厕所就好比系统的每一个端口,为人解决方便就是所谓的服务,你提供了这些服务,那么就必须开放端口(厕所),当有人上厕所时,就是在这些端口建立了链接。如果那个厕所被人占用了,就表示端口号被服务占用了,如果有一天这里不提供公共厕所服务了,这个公共厕所被拆除了,自然也就没有了端口号了。其实更形象的例子,就好比银行大堂,端口号是那些柜台,而那些取号办理业务的人就好比链接到服务器的各种客户端。他们通过端口重定向技术与柜台发送业务联系。再举一个通俗易懂的例子,端口号,好比高铁线上的每个站点,例如,长沙、岳阳等分别代表一个端口号,旅客通过火车票到各自的站点,就好比各个应用程序发往服务器端口的IP包。

 

端口与服务的关系

 

    端口有什么用呢?我们知道,一台拥有IP地址的主机可以提供许多服务,比如Web服务、FTP服务、SMTP服务等,这些服务完全可以通过1个IP地址来实现。那么,主机是怎样区分不同的网络服务呢?显然不能只靠IP地址,因为IP 地址与网络服务的关系是一对多的关系。实际上是通过“IP地址+端口号”来区分不同的服务的。

端口号与相应服务的对应关系存放在/etc/services文件中,这个文件中可以找到大部分端口。

如何检查端口是否开放,其实不整理,还不知道有这么多方法!

 

1:nmap工具检测开放端口

nmap是一款网络扫描和主机检测的工具。nmap的安装非常简单,如下所示rpm安装所示。

[root@DB-Server Server]# rpm -ivh nmap-4.11-1.1.x86_64.rpm
warning: nmap-4.11-1.1.x86_64.rpm: Header V3 DSA signature: NOKEY, key ID 37017186
Preparing...                ########################################### [100%]
   1:nmap                   ########################################### [100%]
[root@DB-Server Server]# rpm -ivh nmap-frontend-4.11-1.1.x86_64.rpm
warning: nmap-frontend-4.11-1.1.x86_64.rpm: Header V3 DSA signature: NOKEY, key ID 37017186
Preparing...                ########################################### [100%]
   1:nmap-frontend          ########################################### [100%]
[root@DB-Server Server]#

关于nmap的使用,都可以长篇大写特写,这里不做展开。如下所示,nmap 127.0.0.1 查看本机开放的端口,会扫描所有端口。 当然也可以扫描其它服务器端口。

[root@DB-Server Server]# nmap 127.0.0.1
 
Starting Nmap 4.11 ( http://www.insecure.org/nmap/ ) at 2016-06-22 15:46 CST
Interesting ports on localhost.localdomain (127.0.0.1):
Not shown: 1674 closed ports
PORT     STATE SERVICE
22/tcp   open  ssh
25/tcp   open  smtp
111/tcp  open  rpcbind
631/tcp  open  ipp
1011/tcp open  unknown
3306/tcp open  mysql
 
Nmap finished: 1 IP address (1 host up) scanned in 0.089 seconds
You have new mail in /var/spool/mail/root
[root@DB-Server Server]#

clip_image001

 

2:netstat 工具检测开放端口

[root@DB-Server Server]# netstat -anlp | grep 3306
tcp        0      0 :::3306                     :::*                        LISTEN      7358/mysqld
[root@DB-Server Server]# netstat -anlp | grep 22
tcp        0      0 :::22                       :::*                        LISTEN      4020/sshd
tcp        0     52 ::ffff:192.168.42.128:22    ::ffff:192.168.42.1:43561   ESTABLISHED 6198/2
[root@DB-Server Server]#

clip_image002

如上所示,这个工具感觉没有nmap简洁明了。当然也确实没有nmap功能强大。

 

3:lsof 工具检测开放端口

 
[root@DB-Server Server]# service mysql start
Starting MySQL......[  OK  ]
[root@DB-Server Server]# lsof -i:3306
COMMAND  PID  USER   FD   TYPE DEVICE SIZE NODE NAME
mysqld  7860 mysql   15u  IPv6  44714       TCP *:mysql (LISTEN)
[root@DB-Server Server]# service mysql stop
Shutting down MySQL..[  OK  ]
[root@DB-Server Server]# lsof -i:3306
[root@DB-Server Server]#

clip_image003

 

[root@DB-Server Server]# lsof -i TCP| fgrep LISTEN
cupsd     3153    root    4u  IPv4   9115       TCP localhost.localdomain:ipp (LISTEN)
portmap   3761     rpc    4u  IPv4  10284       TCP *:sunrpc (LISTEN)
rpc.statd 3797 rpcuser    7u  IPv4  10489       TCP *:1011 (LISTEN)
sshd      4020    root    3u  IPv6  12791       TCP *:ssh (LISTEN)
sendmail  4042    root    4u  IPv4  12876       TCP localhost.localdomain:smtp (LISTEN)

 

4: 使用telnet检测端口是否开放

   服务器端口即使处于监听状态,但是防火墙iptables屏蔽了该端口,是无法通过该方法检测端口是否开放的。

 

5:netcat工具检测端口是否开放。

[root@DB-Server ~]# nc -vv 192.168.42.128 1521
Connection to 192.168.42.128 1521 port [tcp/ncube-lm] succeeded!
[root@DB-Server ~]# nc -z 192.168.42.128 1521; echo $?
Connection to 192.168.42.128 1521 port [tcp/ncube-lm] succeeded!
0
[root@DB-Server ~]#  nc -vv 192.168.42.128 1433
nc: connect to 192.168.42.128 port 1433 (tcp) failed: No route to host

 

关闭端口和开放端口

   

    关闭端口和开放端口应该是两种不同的概念,每个端口都有对应的服务,因此要关闭端口,只要关闭相应的服务就可以了。像下面例子,开启了MySQL服务,端口3306处于监听状态,关闭MySQL服务后,端口3306自然被关闭了

 
[root@DB-Server Server]# service mysql start
Starting MySQL......[  OK  ]
[root@DB-Server Server]# lsof -i:3306
COMMAND  PID  USER   FD   TYPE DEVICE SIZE NODE NAME
mysqld  7860 mysql   15u  IPv6  44714       TCP *:mysql (LISTEN)
[root@DB-Server Server]# service mysql stop
Shutting down MySQL..[  OK  ]
[root@DB-Server Server]# lsof -i:3306
[root@DB-Server Server]#

   

所以,系统里面有些不必要的端口和服务,从安全考虑或资源节省角度,都应该关闭那些不必要的服务。关闭对应的端口。另外,即使服务开启,但是防火墙对对应的端口进行了限制,这样端口也不能被访问,但端口本身并没有关闭,只是端口被屏蔽了。

Service Provider 测试实例

简单归纳一下

1、服务类继承契约接口(为了对类的定义加以约束)

2、创建服务提供者(Service Provider)

3、在服务提供者的register内绑定服务类

4、注册服务提供者到config

5、创建服务容器(Service Container

6、将服务提供者依赖注入服务容器

7、服务容器使用该服务

1、定义服务类

有了上一节有关服务容器的讲述,理解起服务提供者来很简单。我们这里先定义一个绑定到容器的测试类TestService,为了对类的定义加以约束,我们同时还定义一个契约接口TestContract。

定义TestContract如下:

1
2
3
4
5
6
7
8
<?php
 
namespace App\Contracts;
 
interface TestContract
{
    public function callMe($controller);
}

定义TestService如下:

1
2
3
4
5
6
7
8
9
10
11
12
13
<?php
 
namespace App\Services;
 
use App\Contracts\TestContract;
 
class TestService implements TestContract
{
    public function callMe($controller)
    {
        dd('Call Me From TestServiceProvider In '.$controller);
    }
}

2、创建服务提供者

接下来我们定义一个服务提供者TestServiceProvider用于注册该类到容器。创建服务提供者可以使用如下Artisan命令:

1
php artisan make:provider TestServiceProvider

该命令会在app/Providers目录下生成一个TestServiceProvider.php文件,我们编辑该文件内容如下:

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
<?php
 
namespace App\Providers;
 
use Illuminate\Support\ServiceProvider;
use App\Services\TestService;
 
class TestServiceProvider extends ServiceProvider
{
    /**
     * Bootstrap the application services.
     *
     * @return void
     */
    public function boot()
    {
        //
    }
 
    /**
     * Register the application services.
     *
     * @return void
     * @author LaravelAcademy.org
     */
    public function register()
    {
        //使用singleton绑定单例
        $this->app->singleton('test',function(){
            return new TestService();
        });
 
        //使用bind绑定实例到接口以便依赖注入
        $this->app->bind('App\Contracts\TestContract',function(){
            return new TestService();
        });
    }
}

可以看到我们使用了两种绑定方法,更多绑定方法参考服务容器文档。

3、注册服务提供者

定义完服务提供者类后,接下来我们需要将该服务提供者注册到应用中,很简单,只需将该类追加到配置文件config/app.php的providers数组中即可:

1
2
3
4
5
6
'providers' => [
 
    //其他服务提供者
 
    App\Providers\TestServiceProvider::class,
],

4、测试服务提供者

这样我们就可以在应用中使用该服务提供者了,为了测试该服务提供者我们首先使用Artisan命令创建一个资源控制器TestController:

1
php artisan make:controller TestController

然后在路由配置文件routes.php中定义路由:

1
Route::resource('test','TestController');

最后去TestController中编写测试代码:

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
<?php
 
namespace App\Http\Controllers;
 
use Illuminate\Http\Request;
 
use App\Http\Requests;
use App\Http\Controllers\Controller;
 
use App;
use App\Contracts\TestContract;
 
class TestController extends Controller
{
    //依赖注入
    public function __construct(TestContract $test){
        $this->test = $test;
    }
 
    /**
     * Display a listing of the resource.
     *
     * @return Response
     * @author LaravelAcademy.org
     */
    public function index()
    {
        // $test = App::make('test');
        // $test->callMe('TestController');
        $this->test->callMe('TestController');
    }
 
    ...//其他控制器动作
}

然后我们去浏览器中访问http://laravel.app:8000/test

分别测试使用App::make和依赖注入解析绑定类调用callMe方法的输出,结果一样,都是:

1
"Call Me From TestServiceProvider In TestController"

好了,大功告成,是不是很简单?!

此外,Laravel服务提供者还支持延迟加载,具体可参考服务提供者文档

控制反转(DI) 依赖注入(IoC)

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
<?php
Class Fight{
    public function __construct($options){
        //...
    }
}
 
Class Force{
    public function __construct($options){
        //...
    }
}
 
 
Class Shot{
    public function __construct($options){
        //...
    }
}
 
//超能力工厂类
Class SuperModuleFactory{
    public function makeModule($moduleName$options){
        switch($moduleName){
            case 'Fight':
                return new Fight($options[0], $options[1]);
            case 'Force':
                return new Force($options[0]);
            case 'Shot':
                return new Shot($options[0], $options[1], $options[2]);
            // case 'more': .......
            // case 'and more': .......
            // case 'and more': .......
            // case 'oh no! its too many!': .......
        }
    }
}
 
Class Superman{
    protected $power;
 
    public function __construct(array $modules)
    {
        //初始化工厂
        $factory new SuperModuleFactory;
 
        //通过工厂提供的方法制造需要的模块
//        $this->power = $factory->makeModule('Fight',[9,100]);
//        $this->power = $factory->makeModule('Force',[50]);
//        $this->power = $factory->makeModule('Shot',[99,150,20]);
 
        /*
        $this->power = array(
            $factory->makeModule('Force',[45]),
            $factory->makeModule('Shot',[99,40,10])
        );
        */
 
        //通过工厂提供的方法制造需要的模块
        foreach($modules as $moduleName => $moduleOptions){
            $this->power[] = $factory->makeModule($moduleName$moduleOptions);
        }
 
    }
}
 
//创建超人
$superman new Superman([
    'Fight' => [9, 100],
    'Shot' => [99, 50, 2]
]);
//“超人”的创建不再依赖任何一个“超能力”的类,如果修改了或者增加了新的超能力只需要针对修改SuperModuleFactory即可。
 
 
 
 
?>

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
<?php
//当超能力急需拓展的时候,如果依赖超能力工厂就会在switch里堆很多东西,我们需要制定统一接口作为一种“契约”,这样无论是谁创建出的模组,都符合这样的接口,就可以被正常使用。
interface SuperModuleInterface{
    /**
     * 超能力激活方法
     *
     * 任何一个超能力都得有该方法,并拥有一个参数
     *@param array $target 针对目标,可以是一个或多个,自己或他人
     */
    public function activate(array $target);
}
 
/**
 * X-超能量
 */
class XPower implements SuperModuleInterface{
    public function activate(array $target){
        // 这只是个例子。。具体自行脑补
    }
}
 
/**
 * 终极炸弹 (就这么俗)
 */
class UltraBomb implements SuperModuleInterface{
    public function activate(array $target){
        // 这只是个例子。。具体自行脑补
    }
}
 
//提供的模组实例必须是一个SuperModuleInterface接口的实现
//正是由于超人的创造变得容易,一个超人也就不需要太多的超能力,我们可以创造多个超人,并分别注入需要的超能力模组即可。
//开头到现在提到的一系列依赖,只要不是由内部生产(比如初始化、构造函数 __construct 中通过工厂方法、自行手动 new 的),而是由外部以参数或其他形式注入的,都属于依赖注入(DI)
class Superman{
    protected $module;
 
    public function __construct(SuperModuleInterface $module)
    {
        $this->module = $module;
    }
}
 
//下面就是一个典型的依赖注入
 
//超能力模组
$superModule new XPower;
//初始化一个超人,并注入一个超能力模组依赖
$superMan new Superman($superModule);
 
 
 
 
//我们需要自动化 —— 最多一条指令,千军万马来相见
//工厂模式升华 -- IoC容器
class Container{
    protected $binds;
    protected $instance;
 
    public function bind($abstract$concrete){
        if($concrete instanceof Closure){
            //instanceof Closure判断$concrete是否是一个闭包
            $this->binds[$abstract] = $concrete;
        }else{
            // instance 方法绑定一个已存在的对象实例到容器,这里不是绑定只是把concrete放到数组里
            $this->instances[$abstract] = $concrete;
        }
    }
 
    public function make($abstract$parameters = []){
        if(isset($this->instances[$abstract])){
            //已经是实例的 直接返回
            //实例是已经被初始化过的 所以不需要再传入参数实例化
            return $this->instances[$abstract];
        }
 
        array_unshift($parameters$this);
 
        return call_user_func_array($this->binds[$abstract], $parameters);
    }
}
 
 
//创建一个容器(后面称作超级工厂)
$container new Container;
 
//向该超级工厂添加超能力模组的生产脚本
$container->bind('xpower'function($container) {
    return new XPower;
});
 
//同上
$container->bind('ultrabomb'function($container){
    return new UltraBomb;
});
 
//向该超级工厂添加超人的生产脚本
$container->bind('superman'function($container$moduleName){
    return new Superman($container->make($moduleName));
});
 
//绑定后的XPower以及UltraBomb等超能力 使用$container->make()就可以返回实例
//$container->make('superman','xpower')可以实例化superman注入xpower的实例
//因为superman bind的时候闭包实现了对第二个参数(moduleName)的实例化
//所以就可以直接通过超级工厂类穿件superman的实例
//*********************** 华丽丽的分割线 *************************
//开始启动生产
$superman_1 $container->make('superman','xpower');
$superman_1 $container->make('superman','ultrabomb');
$superman_1 $container->make('superman','xpower');
// ...随意添加
 
 
//要添加特异功能的参数我觉得应该修改代码如下,当然也需要修改一下前面的XPower类
$container->bind('xpower',function($container$parameters){
    return new XPower($parameters);
    //然后XPower的构造函数接收参数做相应处理
});
 
$container->bind('superman'function($container$moduleName$parameters){
    return new Superman($container->make($moduleName$parameters));
});
 
$superman_x $container->make('superman','xpower',array(9,50,25));
//代码没有运行过不知道这样可不可行 只是一个思路
 
 
//实际上,真正的 IoC 容器更为高级。
//我们现在的例子中,还是需要手动提供超人所需要的模组参数,但真正的 IoC 容器会根据类的依赖需求,自动在注册、绑定的一堆实例中搜寻符合的依赖需求,并自动注入到构造函数参数中去。
//Laravel 框架的服务容器正是这么做的。实现这种功能其实理论上并不麻烦,但我并不会在本文中写出,因为……我懒得写。
?>

http://laravelacademy.org/post/769.html