Laravl 是什么
Laravel是一套简洁、优雅的PHP Web开发框架 (PHP Web Framework) 。在世界(不含中国)PHP框架的占有率超过40%。
设置系统
检查composer是否安装,如果没有安装,请参考Ubuntu PHP 开发环境配置 或者Cent OS 7 PHP 开发环境配置
1
2
3
composer --version
#输出
Composer version 1.0.0-alpha9 2014-12-07 17:15:20
建立 Laravel 项目
1
2
3
cd /var/www/html/
composer create-project laravel/laravel learning-laravel-5 --prefer-dist
#需要等待
测试项目
1
2
3
cd learning-laravel-5/
#x.x.x.x 为虚拟机地址
php -S x.x.x.x:8888 -t public
主机浏览器测试
路由、控制器和视图简介
1
Route :: get ( '/' , 'WelcomeController@index' );
@是一个界定符,前面是控制器,后面是动作,表示当用户请求url / 的时候,执行控制器 WelcomeController 中的 index 方法
app->http->controllers->welcomecontroller.php1
2
3
4
public function index ()
{
return view ( 'welcome' );
}
当前默认返回一个视图,视图的名字叫做 welcome,实际上是 welcome.blade.php,blade是laravel的视图模板。
可以查看 resources->views->welcome.blade.php 修改welcomecontroller.php 1
2
3
4
5
public function index ()
{
// return view('welcome');
return 'hello, laravel' ;
}
在浏览器中测试,得到一个简单的反馈。
我们新建一个路由,在routes.php中增加:
1
Route :: get ( '/contact' , 'WelcomeController@contact' );
可以新建一个路由,但是现在我们还是直接使用默认的控制器,在 WelcomeController.php 中添加:
1
2
3
public function contact () {
return 'Contact Me' ;
}
在浏览器终测试新增加的路由。
我们可以返回简单的字符串,也可以返回json或者html文件,所有的视图文件存储在resource->views中。
例如:return view('welcome') ,我们不需要考虑路径,也不要添加.blade.php扩展名,框架自动为我们完成。如果在views目录中需要子目录,例如views/forum子目录,只需要return view('forum/xxx'),或者跟简单而明确的方式是:return view('forum.xxx')。😄
1
2
3
public function contact () {
return view ( 'pages.contact' );
}
在 views 目录下创建 pages目录,然后创建contact.blade.php 1
2
3
4
5
6
7
8
9
10
<!doctype html>
< html lang = "en" >
< head >
< meta charset = "UTF-8" >
< title > Document</ title >
</ head >
< body >
< h1 > Contact</ h1 >
</ body >
</ html >
向视图传送数据
1
Route :: get ( 'about' , 'PagesController@about' );
在浏览器中浏览会获得一个错误,错误信息仅仅是一个提示信息,缺少细节,在生产环境 It’ ok,但是开发阶段我们希望获得详细信息。
这将显示详细的错误信息,PagesController 不存在。但在生产环境一定要设置为 false
我们可以手工新建控制器,但更快的方式是利用 laravel 提供的生成器。在命令行当前项目目录中运行: 可以看到laravel提供的功能。
1
php artisan make:controller PagesController
ok,在 app->http->controller 下面生成了 PagesController.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
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
<? php namespace App\Http\Controllers ;
use App\Http\Requests ;
use App\Http\Controllers\Controller ;
use Illuminate\Http\Request ;
class PagesController extends Controller {
/**
* Display a listing of the resource.
*
* @return Response
*/
public function index ()
{
//
}
/**
* Show the form for creating a new resource.
*
* @return Response
*/
public function create ()
{
//
}
/**
* Store a newly created resource in storage.
*
* @return Response
*/
public function store ()
{
//
}
/**
* Display the specified resource.
*
* @param int $id
* @return Response
*/
public function show ( $id )
{
//
}
/**
* Show the form for editing the specified resource.
*
* @param int $id
* @return Response
*/
public function edit ( $id )
{
//
}
/**
* Update the specified resource in storage.
*
* @param int $id
* @return Response
*/
public function update ( $id )
{
//
}
/**
* Remove the specified resource from storage.
*
* @param int $id
* @return Response
*/
public function destroy ( $id )
{
//
}
}
这样生成的controller包含了全部所需要的RESTful方法,我们可以简化一下。删除生成的PagesController.php,在命令行运行:
1
php artisan make:controller PagesController --plain
再看一下生成的结果
1
2
3
4
5
6
7
8
9
10
11
12
<? php namespace App\Http\Controllers ;
use App\Http\Requests ;
use App\Http\Controllers\Controller ;
use Illuminate\Http\Request ;
class PagesController extends Controller {
//
}
基本上是一个空的controller,所有的方法我们需要自己创建。
如果你想知道到底有什么参数我们可以在命令行执行,你可以运行下面的命令来查看帮助
1
php artisan help make:controller
ok, 你可以经常使用help命令来帮助你了解这些参数。
在PagesController中建立about方法。 1
2
3
public function about () {
return 'About Page' ;
}
在浏览器冲查看结果,错误消失,返回简单的信息。
返回视图
我们当然希望返回html文档,修改about方法的返回:
1
2
3
public function about () {
return view ( 'pages.about' );
}
注意:返回的结果是 pages.about ,这表示在 views 子目录中的 pages 子目录中的 about.balde.php 文件。让我们创建 resources\views\pages\about.balde.php 文件
1
2
3
4
5
6
7
8
9
10
<!doctype html>
< html lang = "en" >
< head >
< meta charset = "UTF-8" >
< title > Document</ title >
</ head >
< body >
< h1 > About</ h1 >
</ body >
</ html >
That’s it. 运行浏览器查看吧,😄
修改PagesController.php
1
2
3
4
public function about () {
$name = 'Zhang Jinlgin' ;
return view ( 'pages.about' ) -> with ( 'name' , $name );
}
修改我们的视图文件 about.blade.php
1
< h1 > About <?= $name ?> </ h1 >
Bingo,查看结果吧。
我们使用的laravel使用了blade模板,我们可以利用这个好处修改视图:
1
< h1 > About {{ $name }}</ h1 >
看起来更好了,在blade中,{{}}是转义html的语义的,让我来修改一个数据:
1
$name = '<span style="color: red">Zhang Jinlgin</span>' ;
查看结果,发现所有的html元素都被转义了。但是如果不需要转义html,可以使用 {!! !!},修改视图:
1
< h1 > About {!! $name !!}</ h1 >
再看结果,👌
我们不仅仅可以向视图传送一个数据,同样我们可以传送Array 1
2
3
4
5
6
7
public function about ()
{
return view ( 'pages.about' ) -> with ([
'first' => 'Zhang' ,
'last' => 'Jinglin'
]);
}
1
< h1 > About {{ $first }} {{ $last }}</ h1 >
一种简洁的方式是这样:
1
2
3
4
5
6
7
8
public function about ()
{
$data = [];
$data [ 'first' ] = 'Zhang' ;
$data [ 'last' ] = 'Jinglin' ;
return view ( 'pages.about' , $data );
}
结果相同,更简单的是这样:
1
2
3
4
5
6
public function about ()
{
$first = 'Zhang' ;
$last = 'Jinglin' ;
return view ( 'pages.about' , compact ( 'first' , 'last' ));
}
compact 把参数变成数组,extract相反。可以查看php手册了解 compact ,顺道了解一下 extract。
Blade 简介
在多个页面中我们可能包含相同的内容,像是文件头,链接的css或者js等。我们可以利用布局文件完成这个功能。
让我们新建一个布局文件,例如 views/layout.blade.php
1
2
3
4
5
6
7
8
9
10
11
12
13
<!doctype html>
< html lang = "en" >
< head >
< meta charset = "UTF-8" >
< title > Document</ title >
< link rel = "stylesheet" href = "http://cdn.bootcss.com/bootstrap/3.3.4/css/bootstrap.min.css" >
</ head >
< body >
< div class = "container" >
@yield('content')
</ div >
</ body >
</ html >
我们创建了不解的结构,引入了bootstrap,注意 @yield 是blade的布局占位符,未来我们的页面内容将填充到这里,修改 about.blade.php
1
2
3
4
5
6
7
@extends('layout')
@section('content')
< h1 > About {{ $first }} {{ $last }}</ h1 >
@stop
上面的代码表示我们使用布局文件 layout.blade.php, 然后在 content 段中添加内容。
在 routes.php 中添加:
1
2
Route :: get ( 'about' , 'PagesController@about' );
Route :: get ( 'contact' , 'PagesController@contact' );
在 PagesController.php 中添加:
1
2
3
public function contact () {
return view ( 'pages.contact' );
}
新建视图 pages/contact.blade.php
1
2
3
4
5
@extends('layout')
@section('content')
< h1 > Contact Me!</ h1 >
@stop
Check it out!
在布局文件中我们可以添加多个 @yield , 比如在 layout.blade.php 中添加 @yield('footer') :
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
<!doctype html>
< html lang = "en" >
< head >
< meta charset = "UTF-8" >
< title > Document</ title >
< link rel = "stylesheet" href = "http://cdn.bootcss.com/bootstrap/3.3.4/css/bootstrap.min.css" >
</ head >
< body >
< div class = "container" >
@yield('content')
</ div >
@yield('footer')
</ body >
</ html >
```
比如 `contact.blade.php` 中有一段脚本,就可以放在这个段中。
```html
@extends('layout')
@section('content')
< h1 > Contact Me!</ h1 >
@stop
@section('footer')
< script >
alert ( 'Contact from scritp' )
</ script >
@stop
访问contact会有对话框,而about仍然是普通显示
1
2
3
4
5
6
7
8
9
@extends('layout')
@section('content')
@if ($first = 'Zhang')
< h1 > Hello, Zhang</ h1 >
@else
< h1 > Hello, nobody</ h1 >
@endif
@stop
也可以视同 @unless 等同于 if !, 还有 @foreach 等。
1
2
3
4
5
6
7
8
9
public function about ()
{
$people = [
'zhang san' ,
'li si' ,
'wang wu'
];
return view ( 'pages.about' , compact ( 'people' ));
}
1
2
3
4
5
6
7
8
9
10
@extends('layout')
@section('content')
< h1 > Person:</ h1 >
< ul >
@foreach($people as $person)
< li > {{ $person }}</ li >
@endforeach
</ ul >
@stop
有一种情况,数据可能来自数据库,集合可能是空,像是这样:
处理这种情况,请添加 @if 处理
1
2
3
4
5
6
7
8
9
10
11
12
13
14
@extends('layout')
@section('content')
@if (count($people))
< h1 > Person:</ h1 >
< ul >
@foreach($people as $person)
< li > {{ $person }}</ li >
@endforeach
</ ul >
@endif
< h2 > Other info</ h2 >
@stop
That’s better.
环境与配置
.env 文件是配置文件,包括数据库配置信息,查看 config->database.php ,connections 里面包含了所有数据库的配置,可以在 default 中选择要使用的数据库。在数据库配置中,有关 env('DB_HOST', 'localhost') 就是读取 .env 配置文件的信息。第二个参数是缺省参数。
1
2
3
4
DB_HOST = localhost
DB_DATABASE = laravel
DB_USERNAME = root
DB_PASSWORD =
1
2
3
mysql -u root
CREATE DATABASE laravel
1
2
3
4
5
6
7
8
9
10
11
'mysql' => [
'driver' => 'mysql' ,
'host' => env ( 'DB_HOST' , 'localhost' ),
'database' => env ( 'DB_DATABASE' , 'forge' ),
'username' => env ( 'DB_USERNAME' , 'forge' ),
'password' => env ( 'DB_PASSWORD' , '' ),
'charset' => 'utf8' ,
'collation' => 'utf8_unicode_ci' ,
'prefix' => '' ,
'strict' => false ,
],
在 config 子目录中,包含了全部的配置文件,查看一下。 数据库迁移
database migrations 是laravel最强大的功能之一。数据库迁移可以理解为数据库的版本控制器。
在 database/migrations 目录中包含两个迁移文件,一个建立用户表,一个用于用户密码重置。
在迁移文件中,up 方法用于创建数据表,down方法用于回滚,也就是删除数据表。
1
2
3
4
5
php artisan migrate
#输出
Migration table created successfully.
Migrated: 2014_10_12_000000_create_users_table
Migrated: 2014_10_12_100000_create_password_resets_table
查看mysql数据库,可以看到产生了三张表。 migratoins 表是迁移记录表,users 和 pasword_resets。
1
2
3
4
php artisan migrate:rollback
#输出
Rolled back: 2014_10_12_100000_create_password_resets_table
Rolled back: 2014_10_12_000000_create_users_table
再次查看mysql数据库,就剩下 migrations 表了, users password_resets 被删除了。
修改迁移文件,再次执行迁移。
1
2
3
php artisan make:migration create_article_table --create= 'articles'
#输出
Created Migration: 2015_03_28_050138_create_article_table
在 database/migrations 下生成了新的文件。
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
<? php
use Illuminate\Database\Schema\Blueprint ;
use Illuminate\Database\Migrations\Migration ;
class CreateArticleTable extends Migration {
/**
* Run the migrations.
*
* @return void
*/
public function up ()
{
Schema :: create ( 'articles' , function ( Blueprint $table )
{
$table -> increments ( 'id' );
$table -> timestamps ();
});
}
/**
* Reverse the migrations.
*
* @return void
*/
public function down ()
{
Schema :: drop ( 'articles' );
}
}
自动添加了 id列,自动增长,timestamps() 会自动产生 created_at 和 updated_at 两个时间列。我们添加一些字段:
1
2
3
4
5
6
7
8
9
10
11
public function up ()
{
Schema :: create ( 'articles' , function ( Blueprint $table )
{
$table -> increments ( 'id' );
$table -> string ( 'title' );
$table -> text ( 'body' );
$table -> timestamp ( 'published_at' );
$table -> timestamps ();
});
}
执行迁移:
现在有了新的数据表了。😄
假设我们需要添加一个新的字段,你可以回滚,然后修改迁移文件,再次执行迁移,或者可以直接新建一个迁移文件
1
php artisan make:migration add_excerpt_to_articels_table
查看新产生的迁移文件
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
<? php
use Illuminate\Database\Schema\Blueprint ;
use Illuminate\Database\Migrations\Migration ;
class AddExcerptToArticelsTable extends Migration {
/**
* Run the migrations.
*
* @return void
*/
public function up ()
{
//
}
/**
* Reverse the migrations.
*
* @return void
*/
public function down ()
{
//
}
}
只有空的 up 和 down 方法。我们可以手工添加代码,或者我们让laravel为我们生成基础代码。删除这个文件,重新生成迁移文件,注意添加参数:
1
php artisan make:migration add_excerpt_to_articels_table --table= 'articles'
现在,up 方法里面有了初始代码。
1
2
3
4
5
6
7
public function up ()
{
Schema :: table ( 'articles' , function ( Blueprint $table )
{
//
});
}
添加实际的数据修改代码:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
public function up ()
{
Schema :: table ( 'articles' , function ( Blueprint $table )
{
$table -> text ( 'excerpt' ) -> nullable ();
});
}
public function down ()
{
Schema :: table ( 'articles' , function ( Blueprint $table )
{
$table -> dropColumn ( 'excerpt' );
});
}
nullable() 表示字段也可以为空。
再次执行迁移并检查数据库。
如果我们为了好玩,执行回滚
1
php artisan migrate:rollback
excerpt 列没有了。
Eloquent (laravel 的ORM)
1
2
3
4
php artisan make:model Article
#输出
Model created successfully.
Created Migration: 2015_03_28_062517_create_articles_table
查看一下生成的文件 app/Article.php
1
2
3
4
5
6
7
8
9
<? php namespace App ;
use Illuminate\Database\Eloquent\Model ;
class Article extends Model {
//
}
没什么特别的,除了继承自 Model 以外,但是具有强大的功能,这些都封装在laravel的Model中。模型自动具有了 save() update() findXXX() 等强大的功能。
tinker 是 laravel提供的命令行工具,可以和项目进行交互。 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
php artisan tinker
#以下是在tinker中的交互输入
Psy Shell v0.4.1 ( PHP 5.4.16 — cli) by Justin Hileman
>>> $name = 'zhang jinglin' ;
= > "zhang jinglin"
>>> $name
= > "zhang jinglin"
>>> $article = new App\A rticle;
= > <App\A rticle #000000005c4b7ee400000000ab91a676> {}
>>> $article ->title = 'My First Article' ;
= > "My First Article"
>>> $article ->body = 'Some content...' ;
= > "Some content..."
>>> $article ->published_at = Carbon\C arbon::now() ;
= > <Carbon\C arbon #000000005c4b7ee600000000ab91dcb6> {
date: "2015-03-28 06:37:22" ,
timezone_type: 3,
timezone: "UTC"
}
>>> $article ;
= > <App\A rticle #000000005c4b7ee400000000ab91a676> {
title: "My First Article" ,
body: "Some content..." ,
published_at: <Carbon\C arbon #000000005c4b7ee600000000ab91dcb6> {
date: "2015-03-28 06:37:22" ,
timezone_type: 3,
timezone: "UTC"
}
}
>>> $article ->toArray() ;
= > [
"title" = > "My First Article" ,
"body" = > "Some content..." ,
"published_at" = > <Carbon\C arbon #000000005c4b7ee600000000ab91dcb6> {
date: "2015-03-28 06:37:22" ,
timezone_type: 3,
timezone: "UTC"
}
]
>>> $article ->save() ;
= > true
#查看数据结果,添加了一条记录
>>> App\A rticle::all() ->toArray() ;
= > [
[
"id" = > "1" ,
"title" = > "My First Article" ,
"body" = > "Some content..." ,
"published_at" = > "2015-03-28 06:37:22" ,
"created_at" = > "2015-03-28 06:38:53" ,
"updated_at" = > "2015-03-28 06:38:53"
]
]
>>> $article ->title = 'My First Update Title' ;
= > "My First Update Title"
>>> $article ->save() ;
= > true
>>> App\A rticle::all() ->toArray() ;
= > [
[
"id" = > "1" ,
"title" = > "My First Update Title" ,
"body" = > "Some content..." ,
"published_at" = > "2015-03-28 06:37:22" ,
"created_at" = > "2015-03-28 06:38:53" ,
"updated_at" = > "2015-03-28 06:42:03"
]
]
>>> $article = App\A rticle::find( 1) ;
= > <App\A rticle #000000005c4b7e1600000000ab91a676> {
id: "1" ,
title: "My First Update Title" ,
body: "Some content..." ,
published_at: "2015-03-28 06:37:22" ,
created_at: "2015-03-28 06:38:53" ,
updated_at: "2015-03-28 06:42:03"
}
>>> $article = App\A rticle::where( 'body' , 'Some content...' ) ->get() ;
= > <Illuminate\D atabase\E loquent\C ollection #000000005c4b7e1800000000ab91a676> [
<App\A rticle #000000005c4b7e1b00000000ab91a676> {
id: "1" ,
title: "My First Update Title" ,
body: "Some content..." ,
published_at: "2015-03-28 06:37:22" ,
created_at: "2015-03-28 06:38:53" ,
updated_at: "2015-03-28 06:42:03"
}
]
>>> $article = App\A rticle::where( 'body' , 'Some content...' ) ->first() ;
= > <App\A rticle #000000005c4b7e1900000000ab91a676> {
id: "1" ,
title: "My First Update Title" ,
body: "Some content..." ,
published_at: "2015-03-28 06:37:22" ,
created_at: "2015-03-28 06:38:53" ,
updated_at: "2015-03-28 06:42:03"
}
>>>
>>> $article = App\A rticle::create([ 'title' = > 'New Article' , 'body' = > 'New body' , 'published_at' = > Carbon\C arbon::now()]) ;
Illuminate\D atabase\E loquent\M assAssignmentException with message 'title'
MassAssignmentException,laravel保护我们不能直接插入记录。比如,在一些特殊情况下我们需要直接利用表单的信息填充数据库记录,但是如果我们并没有在表单中添加密码字段,而黑客产生了密码字段连同我们的其他字段一起送回服务器,这将产生修改密码的危险,所以我们必须明确的告诉laravel我们的模型那些字段是可以直接填充的。
修改我们的模型文件 Article.php
1
2
3
4
5
6
7
8
9
10
11
12
13
<? php namespace App ;
use Illuminate\Database\Eloquent\Model ;
class Article extends Model {
protected $fillable = [
'title' ,
'body' ,
'published_at'
];
}
表示,title, body, published_at 是可以直接填充的。
退出 tinker,重新进入
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
>>> $article = App\A rticle::create([ 'title' = > 'New Article' , 'body' = > 'New body' , 'published_at' = > Carbon\C arbon::now()]) ;
= > <App\A rticle #000000005051b2c7000000007ec432dd> {
title: "New Article" ,
body: "New body" ,
published_at: <Carbon\C arbon #000000005051b2c6000000007ec4081d> {
date: "2015-03-28 06:55:19" ,
timezone_type: 3,
timezone: "UTC"
} ,
updated_at: "2015-03-28 06:55:19" ,
created_at: "2015-03-28 06:55:19" ,
id: 2
}
# It's ok
>>> App\A rticle::all() ->toArray() ;
= > [
[
"id" = > "1" ,
"title" = > "My First Update Title" ,
"body" = > "Some content..." ,
"published_at" = > "2015-03-28 06:37:22" ,
"created_at" = > "2015-03-28 06:38:53" ,
"updated_at" = > "2015-03-28 06:42:03"
] ,
[
"id" = > "2" ,
"title" = > "New Article" ,
"body" = > "New body" ,
"published_at" = > "2015-03-28 06:55:19" ,
"created_at" = > "2015-03-28 06:55:19" ,
"updated_at" = > "2015-03-28 06:55:19"
]
]
>>> $article = App\A rticle::find( 2) ;
= > <App\A rticle #000000005051b22b000000007ec432dd> {
id: "2" ,
title: "New Article" ,
body: "New body" ,
published_at: "2015-03-28 06:55:19" ,
created_at: "2015-03-28 06:55:19" ,
updated_at: "2015-03-28 06:55:19"
}
>>> $article ->update([ 'body' = > 'New Updaet Body' ]) ;
= > true
#update自动调用save()
模型、控制器、视图基础流程
1
Route :: get ( 'artiles' , 'ArticlesController@index' );
1
php artisan make:controller ArticlesController --plain
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
<? php namespace App\Http\Controllers ;
use App\Article ;
use App\Http\Requests ;
use App\Http\Controllers\Controller ;
use Illuminate\Http\Request ;
class ArticlesController extends Controller {
public function index () {
$articles = Article :: all ();
return $articles ;
}
}
可以在浏览器中看到返回的 JSON 结果,cool!
修改控制器,返回视图
1
2
3
4
5
public function index () {
$articles = Article :: all ();
return view ( 'articles.index' , compact ( 'articles' ));
}
创建视图
1
2
3
4
5
6
7
8
9
10
11
12
13
@extends('layout')
@section('content')
< h1 > Articles</ h1 >
@foreach($articles as $article)
< article >
< h2 > {{$article->title}}</ h2 >
< div class = "body" > {{$article->body}}</ div >
</ article >
@endforeach
@stop
浏览结果,COOL!!!!
添加显示详细信息的路由
1
Route :: get ( 'articles/{id}' , 'ArticlesController@show' );
其中,{id} 是参数,表示要显示的文章的 id,修改控制器:
1
2
3
4
5
6
7
8
9
10
11
public function show ( $id ) {
$article = Article :: find ( $id );
//若果找不到文章
if ( is_null ( $article ))
{
//生产环境 APP_DEBUG=false
abort ( 404 );
}
return view ( 'articles.show' , compact ( 'article' ));
}
laravel 提供了更加方便的功能,修改控制器:
1
2
3
4
5
public function show ( $id ) {
$article = Article :: findOrFail ( $id );
return view ( 'articles.show' , compact ( 'article' ));
}
It’s cool.
新建视图
1
2
3
4
5
6
7
8
9
@extends('layout')
@section('content')
< h1 > {{$article->title}}</ h1 >
< article >
{{$article->body}}
</ article >
@stop
在浏览器中尝试访问:/articles/1 /articles/2
修改index视图
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
@extends('layout')
@section('content')
< h1 > Articles</ h1 >
< hr />
@foreach($articles as $article)
< article >
< h2 >
{{--这种方式可以--}}
< a href = "/articles/{{$article->id}}" > {{$article->title}}</ a >
{{--这种方式更加灵活,不限制路径--}}< br >
< a href = "{{action('ArticlesController@show', [$article->id])}}" > {{$article->title}}</ a >
{{--还可以使用--}}< br >
< a href = "{{url('/articles', $article->id)}}" > {{$article->title}}</ a >
</ h2 >
< div class = "body" > {{$article->body}}</ div >
</ article >
@endforeach
@stop
表单
首先让我们修改路由,能够增加一个文章的发布。
1
Route :: get ( 'articles/create' , 'ArticlesController@create' );
然后修改控制器
1
2
3
public function create () {
return view ( 'articles.create' );
}
我们返回一个视图,新建这个视图。我们当然可以直接使用HTML建立表单,但我们有功能更好的办法。我们使用一个开源库,Jeffrey Way 开发的illuminate\html 。安装依赖库:
1
composer require illuminate/html
laravel的库需要注册到laravel中才能使用。在 config/app.php 中,我们可以看到 laravel 提供的 provider 字段,这里描述了laravel的库功能。在Laravel Framewirk Service Providers... 最后添加我们新增的 HtmlProvider
1
'Illuminate\Html\HtmlServiceProvider' ,
我们不希望使用 Illuminate\Html\FromFacade 这么长的名字来引入,我们需要简短的名字。在当前的 app.php 中找到 aliases 段,在最后添加别名。
1
2
'Form' => 'Illuminate\Html\FormFacade' ,
'Html' => 'Illuminate\Html\HtmlFacade' ,
OK,现在我们来创建视图,views/articles/create.blade.php
1
2
3
4
5
6
7
8
9
10
11
12
13
@extends('layout')
@section('content')
< h1 > Write a New Article</ h1 >
< hr />
{{--使用我们添加的 illuminate\html 开源库--}}
{!! Form::open() !!}
{!! Form::close() !!}
@stop
访问 /articles/create 看到了错误,Why? 让我们测试一下,到底是哪里出了问题。在控制器中做出下面的修改:
1
2
3
4
5
public function show ( $id ) {
dd ( 'show' );
$article = Article :: findOrFail ( $id );
return view ( 'articles.show' , compact ( 'article' ));
}
没错,你没看错,就是在 show 方法中添加 dd() 方法,这个方法简单的输出一个信息然后死掉。我们再来访问 /articles/create ,你看到了什么,你看到输出了 show 。
为什么我们访问 create 结果路由给了我们 show ? 我们来查看一下路由,到底发生了什么。
1
2
3
Route :: get ( 'articles' , 'ArticlesController@index' );
Route :: get ( 'articles/{id}' , 'ArticlesController@show' );
Route :: get ( 'articles/create' , 'ArticlesController@create' );
上面是我们的路由,注意到 articles/{id} 意味着这是一个通配符,所有在 articles/ 后面的东西都会匹配,你知道了么?我们的 /articles/create 也被他匹配了。OMG!
解决方案就是调整顺序:
1
2
3
Route :: get ( 'articles' , 'ArticlesController@index' );
Route :: get ( 'articles/create' , 'ArticlesController@create' );
Route :: get ( 'articles/{id}' , 'ArticlesController@show' );
也就是从特殊到普通,以后的路由设置中要时刻注意这个问题。现在我们在访问 articles/create 一切OK了。
在浏览器中查看一下源代码,你会发现不仅生成了 method 和 action 同时生成了一个隐藏的 _token 字段作为服务器对窗体的验证,避免黑客的伪造攻击。
让我们修改我们的视图,添加字段:
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
@extends('layout')
@section('content')
< h1 > Write a New Article</ h1 >
< hr />
{{--使用我们添加的 illuminate\html 开源库--}}
{!! Form::open() !!}
< div class = "form-group" >
{!! Form::label('title', 'Title:') !!}
{!! Form::text('title', null, ['class' => 'form-control']) !!}
</ div >
< div class = "form-group" >
{!! Form::label('body', 'Body:') !!}
{!! Form::textarea('body', null, ['class' => 'form-control']) !!}
</ div >
< div class = "form-group" >
{!! Form::submit('Add Article', ['class' => 'btn btn-primary form-control']) !!}
</ div >
{!! Form::close() !!}
@stop
当表单提交的时候,实际上是使用 post 方法提交到 articles/create 上的,但根据RESTful的习惯,我们希望能够 post 到 /articles 上,我们来修改视图的表单方法,设定提交的路径。
1
{!! Form::open(['url' => 'articles']) !!}
然后我们在路由中处理表单提交事件。
1
Route :: post ( '/articles' , 'ArticlesController@store' );
我们来处理控制器
1
2
3
4
5
6
7
8
9
10
11
12
13
14
//注意:将下面的 use 语句删除,我们使用 facade 接口中的 Request
//use App\Http\Requests\Request;
//引入下面的命名空间中的 Request
use Illuminate\Support\Facades\Request ;
public function store () {
//使用 Illuminate\Html\Request 来返回全部的表单输入字段
$input = Request :: all ();
//我们直接返回$input,来看一下
return $input ;
}
我们可以直接看到输入表单的json结果。如果只需要 title 字段的值,则可以使用 Request::get('titel') 。
如何添加到数据库中呢?借助模型,我们可以直接采用下面的方法,
1
Article :: create ( $input );
###就这么简单,就是这么任性
###如果没有忘记 Mass Assignment,在我们的模型中我们定义了 $fillable 数组,来定义那些字段可以直接在 create 的时候直接填充。
修改控制器,添加到模型中,并存储到数据库。
1
2
3
4
5
6
public function store () {
$input = Request :: all ();
Article :: create ( $input );
return redirect ( 'articles' );
}
添加一条记录试试,非常棒。但别忘了。我们还有一个字段叫做 published_at ,让我们来处理它。
1
2
3
4
5
6
7
8
public function store () {
$input = Request :: all ();
$input [ 'published_at' ] = Carbon :: now ();
Article :: create ( $input );
return redirect ( 'articles' );
}
添加新纪录在测试一下。
还有一个问题,新添加的应该显示在最前面,我们来修改以下控制器。
1
2
3
4
5
6
7
8
9
public function index () {
//倒序获取文章
//可以这样
//$articles = Article::orderBy('published_at', 'desc')->get();
//简单方式,当然还有 oldest()
$articles = Article :: latest ( 'published_at' ) -> get ();
return view ( 'articles.index' , compact ( 'articles' ));
}
日期,Mutator 和 Scope
在我们前面的解决方案中,直接给 published_at 赋值为当前日期实际上是一个临时解决方案,我们需要设定发布日期,可能是未来2天后才发布,让我们修改这个问题。
首先修改控制器:
1
2
3
4
public function store () {
Article :: create ( Request :: all ());
return redirect ( 'articles' );
}
然后修改视图,添加发布日期字段
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
@extends('layout')
@section('content')
< h1 > Write a New Article</ h1 >
< hr />
{{--使用我们添加的 illuminate\html 开源库--}}
{!! Form::open(['url' => 'articles']) !!}
< div class = "form-group" >
{!! Form::label('title', 'Title:') !!}
{!! Form::text('title', null, ['class' => 'form-control']) !!}
</ div >
< div class = "form-group" >
{!! Form::label('body', 'Body:') !!}
{!! Form::textarea('body', null, ['class' => 'form-control']) !!}
</ div >
< div class = "form-group" >
{!! Form::label('published_at', 'Publish On:') !!}
{!! Form::input('date', 'published_at', date('Y-m-d'), ['class' => 'form-control']) !!}
</ div >
< div class = "form-group" >
{!! Form::submit('Add Article', ['class' => 'btn btn-primary form-control']) !!}
</ div >
{!! Form::close() !!}
@stop
ok,让我们添加一个新的文章,并且把日期设置为未来的某一天,但是文章直接显示在最开始了,这不是我们需要的。我们需要到了那天才显示出来。当然,我们需要更具体一点,比如在早上 8:00 显示,而不是0点显示。我们可以添加一个mutator(也就是其他语言的属性设置器),修改我们的model
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
<? php namespace App ;
use DateTime ;
use Illuminate\Database\Eloquent\Model ;
class Article extends Model {
protected $fillable = [
'title' ,
'body' ,
'published_at'
];
//属性设置其要遵守格式约定
// set属性Attribute
public function setPublishedAtAttribute ( $date ) {
$this -> attributes [ 'published_at' ] = Carbon :: createFromFormat ( 'Y-m-d' , $date ) -> hour ( 8 ) -> minute ( 0 ) -> second ( 0 );
}
}
添加一个新的纪录,查看数据库,我们已经将时间设置正确了,但是我们的首页仍然显示未来的才发布的文章,我们修改它。
1
2
3
4
5
6
public function index () {
//$articles = Article::latest('published_at')->get();
$articles = Article :: latest ( 'published_at' ) -> where ( 'published_at' , '<=' , Carbon :: now ()) -> get ();
return view ( 'articles.index' , compact ( 'articles' ));
}
上面的解决方法可以工作,但是查询语句太长了。我们可以使用 Laravel 提供的 scope ,来简化我们的工作。所谓scope可以理解为是查询过程中使用的中间查询结果,比如我们定义一个published scope,他可以返回所有当前已经发布的文章,让我们修改模型。
1
2
3
4
//设置scope,遵守命名规则
public function scopePublished ( $query ) {
$query -> where ( 'published_at' , '<=' , Carbon :: now ());
}
修改控制器使用 scope
1
2
3
4
5
6
7
public function index () {
//$articles = Article::latest('published_at')->get();
//$articles = Article::latest('published_at')->where('published_at', '<=', Carbon::now())->get();
$articles = Article :: latest ( 'published_at' ) -> published () -> get ();
return view ( 'articles.index' , compact ( 'articles' ));
}
结果相同,但在复杂的查询中我们可以使用scope来分解我们的任务,或者复用查询。
我们来增加一个新的查询,查询所有还没有发布的文章。在模型中添加scope
1
2
3
public function scopeUnpublished ( $query ) {
$query -> where ( 'published_at' , '>' , Carbon :: now ());
}
修改控制器使用unpulished
1
2
3
4
5
6
7
8
public function index () {
//$articles = Article::latest('published_at')->get();
//$articles = Article::latest('published_at')->where('published_at', '<=', Carbon::now())->get();
//$articles = Article::latest('published_at')->published()->get();
$articles = Article :: latest ( 'published_at' ) -> Unpublished () -> get ();
return view ( 'articles.index' , compact ( 'articles' ));
}
one more thing! 如果我们在 show 方法中使用 dd($article->published_at) 查看的结果,与 dd($article->created_at); 结果不一样,前者我们使我们自己的字段,后者是在 CreateArticleTable 中通过 $table->timestamp() 自动生成的。自动生成的字段显示出来是 Carbon类型,而我们的是字符串。使用 Crabon类型有很多的好处,例如你可以输出 dd($article->created_at->diffForHumans()); ,这种 1 hour ago 结果,但我们的published_at 不可以。怎么修改?修改模型,告诉laravel,published_at 是日期即可。
1
protected $dates = [ 'published_at' ];
再次使用 dd($article->published_at->diffForHumans()); ,结果显示为 3 days from now,Bingo!
表单验证
在建立一个文章的时候,如果你什么都不输入直接提交,ok,你获得了一个空的文章,没有任何错误提示,这是不对的。在命令行下运行 php artisan 可以看到一个选项 make:request,新建一个form request类。在命令行执行
1
php artisan make:request CreateArticleRequest
生成的文件在 app/http/requests 目录下。在文件中我们可以看到两个方法:
1
2
3
4
5
6
7
8
9
10
11
public function authorize()
{
return false;
}
public function rules()
{
return [
//
];
}
authorize 表示用户在提交表单的时候是否需要是认证用户,我们不需要认证,返回 true。rules是我们的规则方法。让我们修改这个方法:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
public function authorize()
{
//修改为 true,表示不需要认证,或者是通过认证
return true;
}
public function rules()
{
return [
'title' => 'required|min:3',
'body' => 'required',
'published_at' => 'required|date'
];
}
其他的约束可以插看 laravel 的文档。上面的约束表示 title 是必须输入的,最少3个字符,body 是必须的,published_at 是必须的而且是日期。
在视图中,我们总是可以访问 $errors 变量来判断我们是否有错误,修改视图
1
2
3
4
5
6
7
8
9
10
11
@if ($errors->any())
< ul class = "alert alert-danger" >
@foreach($errors->all() as $error)
< li > {{ $error }}</ li >
@endforeach
</ ul >
@endif
{{--使用我们添加的 illuminate\html 开源库--}}
{!! Form::open(['url' => 'articles']) !!}
修改控制器,引入我们的 Request 类。
1
2
3
4
5
public function store ( Requests\CreateArticleRequest $request ) {
Article :: create ( $request -> all ());
return redirect ( 'articles' );
}
再次提交表单,什么都不填,可以看到错误信息。
修改提示信息变为中文
显示的是英文的错误信息,实际上laravel考虑到了国际化的问题,首先修改 config/app.php ,
将 locale 语言设置为中文,然后再 resources/lang 下面新建文件夹 zh, 拷贝 resources/lang/en/validation.php 文件到 zh 目录下,修改:
1
2
3
4
5
6
7
8
"min" => [
"numeric" => "The :attribute must be at least :min.",
"file" => "The :attribute must be at least :min kilobytes.",
"string" => ":attribute 至少要包含 :min 字符。",
"array" => "The :attribute must have at least :min items.",
],
"required" => ":attribute 必须填写。",
其他的可以自行翻译。再次提交空表单,错误信息为中文了。而且 min:3 的判断也为最少3个中文。
–
laravel 也在控制器中集成了 validate 方法,换句话说,我们不一定要生成 request 类,这些工作我们可以直接在控制器中完成。
修改控制器:
1
2
3
4
5
6
7
8
9
10
11
12
13
//注意 Request 的命名空间,不要弄错了
public function store ( \Illuminate\Http\Request $request ) {
$this -> validate ( $request , [
'title' => 'required|min:3' ,
'body' => 'required' ,
'published_at' => 'required|date'
]);
Article :: create ( $request -> all ());
return redirect ( 'articles' );
}
结果相同,这样可以更快速的完成简单的验证。
子视图和表单复用
我们需要处理编辑文章的问题。当然我们可以手工添加新的路由,就像这样:
1
Route :: get ( '/articles/{id}/edit' , 'ArticleController@edit' );
让我们在命令行下使用 artisan 的 route:list 来查看我们当前的路由:
在符合 RESTful 的情况下,可能直接使用 laravel 的 resource 路由是一种好的选择,然我们将所有的路由都去掉,只添加唯一的一个:
1
Route :: resource ( 'articles' , 'ArticlesController' );
再次使用 php artisan route:list 查看路由,哇,一堆的符合我们期望的路由产生了。每一项都仔细查看一下。
现在在控制器中添加方法:
1
2
3
4
5
6
public function edit ( $id ) {
$article = Article :: findOrFail ( $id );
return view ( 'articles.edit' , compact ( 'article' ));
}
现在创建视图
1
2
3
4
5
6
7
8
@extends('layout')
@section('content')
< h1 > Edit: {!! $article->title !!} </ h1 >
< hr />
...
好吧,我承认这些代码都是从 create.blade.php 中拷贝出来的,修改了一下,问题是我们需要重复吗?后面我们会处理这个问题,现在来看一下表单的提交问题。在路由中 php artisan route:list ,再看一遍,修改使用了 PATCH 方法,我们来修改视图:
1
{!! Form::open(['method' => 'PATCH', 'url' => 'articles/' . $article->id]) !!}
在浏览器中访问 /articles/1/edit ,查看一下源代码,发现laravel自动生成了 _method=PATCH 的隐藏字段。
一问题是,我们编辑文章,但是文章的信息并没有显示出来,我们修改一下视图:
1
{!! Form::model($article, ['method' => 'PATCH', 'url' => 'articles/' . $article->id]) !!}
OK,everything’s ok,除了 published_on 字段仍然设置为当前日期,后面我们来处理。
现在在控制器中添加方法:
1
2
3
4
5
6
public function update ( $id , \Illuminate\Http\Request $request ) {
$article = Article :: findOrFail ( $id );
$article -> update ( $request -> all ());
return redirect ( 'articles' );
}
我们在修改的过程中也需要验证,让我们复用我们的 Request 类,将 CreateArticleRequest 更名为更通用的 ArticleRequest,别忘了修改 store 方法中的参数。
1
2
3
4
5
6
public function update ( $id , Requests\ArticleRequest $request ) {
$article = Article :: findOrFail ( $id );
$article -> update ( $request -> all ());
return redirect ( 'articles' );
}
现在剩下的问题是我们的新建和编辑使用了大部分相同的代码,比如显示错误,但他们存在两份,我们来修改这个问题。
我们直接在 views/articles 下面新建文件 list.blade.php,并把错误处理代码从 create.blade.php 中拷贝出来:
1
2
3
4
5
6
7
@if ($errors->any())
<ul class="alert alert-danger">
@foreach($errors->all() as $error)
<li>{{ $error }}</li>
@endforeach
</ul>
@endif
在 create.blade.php 只需用下面语句替换错误处理代码即可:
1
@include('articles.list')
让我们再来处理表单代码,表单代码中除了 form 不大一样和提交按钮有区别,其他都差不多。我们创建一个视图 articles/form_partial.blade.php,将代码拷贝出来
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
< div class = "form-group" >
{!! Form::label('title', 'Title:') !!}
{!! Form::text('title', null, ['class' => 'form-control']) !!}
</ div >
< div class = "form-group" >
{!! Form::label('body', 'Body:') !!}
{!! Form::textarea('body', null, ['class' => 'form-control']) !!}
</ div >
< div class = "form-group" >
{!! Form::label('published_at', 'Publish On:') !!}
{!! Form::input('date', 'published_at', date('Y-m-d'), ['class' => 'form-control']) !!}
</ div >
< div class = "form-group" >
{{--这里要设置变量,依据是编辑还是修改来改变,当然也可以不放置在partial中--}}
{!! Form::submit($submitButtonText, ['class' => 'btn btn-primary form-control']) !!}
</ div >
修改 create.blade.php
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
@ extends ( 'layout' )
@ section ( 'content' )
< h1 > Write a New Article </ h1 >
< hr />
@ include ( 'articles.list' )
{{ -- 使用我们添加的 illuminate\html 开源库 -- }}
{ !! Form :: open ([ 'url' => 'articles' ]) !! }
@ include ( 'articles.form_partial' , [ 'submitButtonText' => 'Add Article' ])
{ !! Form :: close () !! }
@ stop
修改 edit.blade.php
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
@ extends ( 'layout' )
@ section ( 'content' )
< h1 > Edit : { !! $article -> title !! } </ h1 >
< hr />
@ include ( 'articles.list' )
{{ -- 使用我们添加的 illuminate\html 开源库 -- }}
{ !! Form :: model ( $article , [ 'method' => 'PATCH' , 'url' => 'articles/' . $article -> id ]) !! }
@ include ( 'articles.form_partial' , [ 'submitButtonText' => 'Update Article' ])
{ !! Form :: close () !! }
@ stop
Eloquent 关系
一个用户可能有多个文章,一个文章是某个用户书写的,这就是关系。同样文章中可能包含多个 TAG,而一个 TAG 可能关联多个文章。
在项目中,我们已经有了 User.php,也就是用户模型,查看一下,相当简单。我们希望直接使用 $user->articles() 的形式获取全部文章,让我们修改 user 模型:
1
2
3
public function articles () {
return $this -> hasMany ( 'App\Article' );
}
但是我们仅仅完成了关系中的一端,让我们来处理另一端。我们希望的形式是 $article->user() ,让我们修改 article 模型。
1
2
3
public function user () {
return $this -> belongsTo ( 'App\User' );
}
在我们的数据库中,文章模型没有用户的外键,我们需要设置,修改 create_article_table.php
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
Schema :: create ( 'articles' , function ( Blueprint $table )
{
$table -> increments ( 'id' );
//指定外键列
$table -> integer ( 'user_id' ) -> unsigned ();
$table -> string ( 'title' );
$table -> text ( 'body' );
$table -> timestamp ( 'published_at' );
$table -> timestamps ();
//生成外键
$table -> foreign ( 'user_id' )
-> references ( 'id' )
-> on ( 'users' )
-> onDelete ( 'cascade' );
});
因为我们只是在开发阶段,还没有上线运行,我们可以直接修改数据库迁移文件,回滚然后迁移即可,但是如果上线运行的话,应该新建一个迁移。
1
2
3
4
5
6
7
8
9
10
11
php artisan migrate:refresh
#输出信息
Rolled back: 2015_03_28_050138_create_article_table
Rolled back: 2014_10_12_100000_create_password_resets_table
Rolled back: 2014_10_12_000000_create_users_table
Nothing to rollback.
Migrated: 2014_10_12_000000_create_users_table
Migrated: 2014_10_12_100000_create_password_resets_table
Migrated: 2015_03_28_050138_create_article_table
Migrated: 2015_03_28_051200_add_excerpt_to_articels_table
现在让我们使用 tinker 来创建一个用户。
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
php artisan tinker
Psy Shell v0.4.1 ( PHP 5.4.16 — cli) by Justin Hileman
#下面是执行过程
>>> $user = new App\U ser;
= > <App\U ser #000000007f1ad61a000000006497cc4f> {}
>>> $user ->name = 'zhang jinglin' ;
= > "zhang jinglin"
>>> $user ->email = 'zjl@example.com' ;
= > "zjl@example.com"
>>> $user ->password = bcrypt( 'pass' ) ;
= > " $2 y $10$Nbl2b9wqd .rXqKEsd3pRSOoIyFAFIhbqf71BufwDfS3Guv21SlEx2"
>>> $user ->save() ;
= > true
>>> App\U ser::first() ->toArray() ;
= > [
"id" = > "1" ,
"name" = > "zhang jinglin" ,
"email" = > "zjl@example.com" ,
"created_at" = > "2015-03-31 03:24:55" ,
"updated_at" = > "2015-03-31 03:24:55"
]
>>>
现在我们需要新发布的文章和用户关联,我们暂时先修改 form_partial.blade.php 来隐藏一个用户id,只是暂时:
1
2
{{--临时处理--}}
{!! Form::hidden('user_id', 1) !!}
同时要修改模型的 $fillabel 属性,以便我们的 Mass Assisment。
1
2
3
4
5
6
protected $fillable = [
'title' ,
'body' ,
'published_at' ,
'user_id' //临时设置
];
OK,添加一个文章。我们使用 tinker 来查看一下。
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
php artisan tinker
Psy Shell v0.4.1 ( PHP 5.4.16 — cli) by Justin Hileman
>>> App\A rticle::first() ->toArray() ;
= > [
"id" = > "1" ,
"user_id" = > "1" ,
"title" = > "User 1 Article" ,
"body" = > "User 1 Body" ,
"published_at" = > "2015-03-31 08:00:00" ,
"created_at" = > "2015-03-31 04:17:58" ,
"updated_at" = > "2015-03-31 04:17:58" ,
"excerpt" = > null
]
#获取用户
>>> $user = App\U ser::first() ;
= > <App\U ser #0000000051cbb9d70000000073e11a3e> {
id: "1" ,
name: "zhang jinglin" ,
email: "zjl@example.com" ,
created_at: "2015-03-31 03:24:55" ,
updated_at: "2015-03-31 03:24:55"
}
#获取用户文章
>>> $user ->articles() ->toArray() ;
BadMethodCallException with message 'Call to undefined method Illuminate\Database\Query\Builder::toArray()'
>>> $user ->articles->toArray() ;
= > [
[
"id" = > "1" ,
"user_id" = > "1" ,
"title" = > "User 1 Article" ,
"body" = > "User 1 Body" ,
"published_at" = > "2015-03-31 08:00:00" ,
"created_at" = > "2015-03-31 04:17:58" ,
"updated_at" = > "2015-03-31 04:17:58" ,
"excerpt" = > null
]
]
#为什么使用 $user->articles 而不是 #user->articles()?
#事实上,$user->articles()返回的是关系,如果你想用 articles() 你需要这样用
>>> $user ->articles() ->get() ->toArray() ;
= > [
[
"id" = > "1" ,
"user_id" = > "1" ,
"title" = > "User 1 Article" ,
"body" = > "User 1 Body" ,
"published_at" = > "2015-03-31 08:00:00" ,
"created_at" = > "2015-03-31 04:17:58" ,
"updated_at" = > "2015-03-31 04:17:58" ,
"excerpt" = > null
]
]
#你只能使用 articles() 来进行下一步的工作,比如下面的查询
$user ->articles() ->where( 'title' , 'User 1 Article' ) ->get() ;
#我们也可以通过 article 获取 user
>>> $article = App\A rticle::first() ;
= > <App\A rticle #0000000051cbb9d60000000073e11a3e> {
id: "1" ,
user_id: "1" ,
title: "User 1 Article" ,
body: "User 1 Body" ,
published_at: "2015-03-31 08:00:00" ,
created_at: "2015-03-31 04:17:58" ,
updated_at: "2015-03-31 04:17:58" ,
excerpt: null
}
>>> $article ->user;
= > <App\U ser #0000000051cbb92d0000000073e11a3e> {
id: "1" ,
name: "zhang jinglin" ,
email: "zjl@example.com" ,
created_at: "2015-03-31 03:24:55" ,
updated_at: "2015-03-31 03:24:55"
}
>>>
认证
Laravel 出厂已经带有了用户认证系统,我们来看一下 routes.php,如果删除了,添加上:
1
2
3
4
Route::controllers([
'auth' => 'Auth\AuthController',
'password' => 'Auth\PasswordController'
]);
可以使用 php artisan route:list 查看一下。浏览器中访问 /auth/login,可以看到登陆界面,最好把系统默认的 app.blade.php 中关于 google 的东西注释起来,要不然你会疯掉的。
你可以使用 register、login甚至 forget password。
实际注册一个用户,提交后失败了,实际上没有失败,只是larave自动跳转到了 /home,我们已经删除了这个控制器。你可以使用 tinker 看一下,用户已经建立了。
在 Auth\AuthController 中实际上使用了 trait,什么是 triat?well,php只支持单继承,在php5.4中添加了trait,一个trait实际上是一组方法的封装,你可以把它包含在另一个类中。像是抽象类,你不能直接实例化他。
在 Auth\AuthController 中有对 trait 的引用:
1
use AuthenticatesAndRegistersUsers ;
让我们找到他,看一下注册后是怎么跳转的。他隐藏的挺深的,在 vendor/laravel/framework/src/Illuminate/Foundation/Auth/AuthenticatesAndregistersUsers.php,wow。
1
2
3
4
5
6
7
8
9
10
public function redirectPath ()
{
if ( property_exists ( $this , 'redirectPath' ))
{
return $this -> redirectPath ;
}
//如果用户设置了 redirectTo 属性,则跳转到用户设置的属性,否则到home
return property_exists ( $this , 'redirectTo' ) ? $this -> redirectTo : '/home' ;
}
OK,我们知道了,只要设定 redirectTo 这个属性就可以自定义注册后的跳转了。我们在 Auth\AuthContotroller 中修改:
1
protected $redirectTo = 'articles' ;
我们先使用 /auth/logout 确保我们退出,如果出错了不要害怕,我们没有默认的主页,重新访问:auth/register 新建一个用户,这次应该ok了。
再次logout,然后使用 login 登陆一下。
现在我们可以删除 form_partial 中临时设置的隐藏字段了,然后修改控制器:
1
2
3
4
5
6
7
8
9
10
11
12
public function store ( Requests\ArticleRequest $request ) {
//你可以这样
//$request = $request->all();
//$request['user_id'] = Auth::id();
//更简单的方法
$article = Article :: create ( $request -> all ());
//laravel 自动完成外键关联
Auth :: user () -> articles () -> save ( $article );
return redirect ( 'articles' );
}
添加一个文章,然后使用 tinker 查看一下。
中间件
我们当然不希望任何人都能发布文章,至少是登陆用才可以。我们在控制器中添加保护:
1
2
3
4
5
6
public function create () {
if ( Auth :: guest ()) {
return redirect ( 'articles' );
}
return view ( 'articles.create' );
}
上面的代码可以工作,有一个问题,我们需要在每一个需要保护的方法中都进行上面的处理,这样做太傻了,幸好我们有中间件。
中间件可以理解为一个处理管道,中间件在管道中的某一时刻进行处理,这个时刻可以是请求也可以是响应。依据中间件的处理规则,可能将请求重定向,也可能通过请求。
在 app/http/middleware 中包含了三个中间件,名字就可以看出是干什么,好好查看一下,注意,Closure $next 代表了下一个中间件。
在 app/http/kernel.php 中对中间件进行登记。$middleware 段声明了对所有http都进行处理的中间件,$routeMiddleware 仅仅对路由进行处理,而且你必须显示的声明要使用这其中的某一个或几个中间件。
假设我们想对整个的 ArticlesController 进行保护,我们直接在构造函数中添加中间件:
1
2
3
public function __construct () {
$this -> middleware ( 'auth' );
}
现在,任何方法都收到了保护。
但我们可能不想整个控制器都受到保护,如果只是其中的一两个方法呢?我们可以这样处理:
1
2
3
4
5
public function __construct () {
$this -> middleware ( 'auth' , [ 'only' => 'create' ]);
//当然可以反过来
//$this->middleware('auth', ['except' => 'index']);
}
我们不一定在控制器的构造函数中引入中间件,我们可以直接在路由中声明:
1
Route :: get ( 'about' , [ 'middleware' => 'auth' , 'uses' => 'PagesController@about' ]);
在 kernel.php 中提供的系统中间件,比如 'Illuminate\Foundation\Http\Middleware\CheckForMaintenanceMode' 是可以让我们进入到维护模式,比如系统上线了,但现在需要临时关闭一段时间进行处理,我们可以在命令行进行处理,看一下这个中间件的工作:
访问一下网站,可以看到任何 url 的请求都是马上回来。网站上线:
我们来做一个自己的中间件:
1
php artisan make:middleware Demo
然后添加代码:
1
2
3
4
5
6
7
8
9
public function handle ( $request , Closure $next )
{
//如果请求中含有 foo,我们就回到控制器首页
if ( $request -> has ( 'foo' )) {
return redirect ( 'articles' );
}
return $next ( $request );
}
如果希望在全部的请求使用中间件,需要在 kernel.php 中的 $middleware 中登记:
1
2
3
4
5
protected $middleware = [
...
'App\Http\Middleware\Demo' ,
];
现在我们可以测试一下,假设我们访问 /articles/create?foo=bar ,我们被重定向到了首页。
让我们去除这个显示中间件,我们来创建一个真正有用的中间件。假设我们想保护某个页面,这个页面必须是管理者才能访问的。
1
php artisan make:middleware RedirectIfNotAManager
我们来添加处理代码:
1
2
3
4
5
6
7
8
public function handle ( $request , Closure $next )
{
if ( ! $request -> user () || ! $request -> user () -> isATeamManager ()) {
return redirect ( 'articles' );
}
return $next ( $request );
}
下面修改我们的模型:
1
2
3
public function isATeamManager () {
return false ;
}
简单起见,我们直接返回false。这次我们把中间件放置在 kernel.php 中的$routeMiddleware 中。
1
2
3
4
protected $routeMiddleware = [
...
'manager' => 'App\Http\Middleware\RedirectIfNotAManager' ,
];
我们做一个测试路由测试一下:
1
2
3
Route :: get ( 'foo' , [ 'middleware' => 'manager' , function () {
return 'This page may only be viewed by manager' ;
}]);
guest身份访问或者登录身份访问都会返回主页,但是如果修改 isATeamManager() 返回 true,登录身份访问可以看到返回的信息。
路由模型绑定
什么事路由模型绑定?让我们看一下下面的代码:
1
2
3
4
5
6
public function show ( $id ) {
//我们需要自己查询依据 id 获取的模型
$article = Article :: findOrFail ( $id );
return view ( 'articles.show' , compact ( 'article' ));
}
可以肯定的是,我们需要在很多的地方获取Id,然后找到模型,这个事情可以交给 Laravel 来做,这就是路由模型绑定。
我们需要先查看一下我们的路由:
我们可以看到生成的匹配符 articles/{articles},修改 app/provider/RouteServiceProvider.php
1
2
3
4
5
6
7
8
public function boot ( Router $router )
{
parent :: boot ( $router );
//绑定路由模型 匹配符是 {articles}
$router -> model ( 'articles' , 'App\Article' );
}
有了上面的处理,我们在路由中获取到的就不在是id了,而是 Article 对象。修改控制器:
1
2
3
4
public function show ( Article $article ) {
//有了路由模型绑定,我们不需要自己查找数据了
return view ( 'articles.show' , compact ( 'article' ));
}
So cool! 修改其他方法:
1
2
3
4
5
6
7
8
9
public function edit ( Article $article ) {
return view ( 'articles.edit' , compact ( 'article' ));
}
public function update ( Article $article , Requests\ArticleRequest $request ) {
$article -> update ( $request -> all ());
return redirect ( 'articles' );
}
当然,我们有完全的控制路由模型如何绑定:
1
2
3
4
5
6
7
8
9
public function boot ( Router $router )
{
parent :: boot ( $router );
//如果你需要控制模型的生成,可以这样
$router -> bind ( 'articles' , function ( $id ) {
return \App\Article :: published () -> findOrFail ( $id );
});
}