Laracasts 教程总结:Laravel 5 菜鸟入门(上)

1. 使用 Composer 快速创建项目

本节介绍如何使用 Composer 来创建 Laravel 项目。

Composer 是 PHP 的一个依赖管理工具。它以项目为单位进行管理,允许你申明项目所依赖的代码库,它会在你的项目中为你安装他们。Mac 下的安装(其他平台安装):

$ curl -sS https://getcomposer.org/installer | php

安装后,使用 require 命令增加新的依赖包:

$ mkdir learncomposer
$ cd learncomposer
$ php composer.phar require phpspec/phpspec

为了便于使用,可以把 composer.phar 添加到 PATH 目录中:

$ mv composer.phar /usr/local/bin/composer

刚才的命令就可以简化为:

$ composer phpspec/phpspec

命令执行完,Composer 都干了啥呢?首先创建了 composer.json,将依赖添加进来,composer.json,包括了项目依赖以及其他元数据:

{
    "require": {
        "phpspec/phpspec": "^3.1"
    }
}

其次,会搜索可用的 phpspec/phpspec 包将其安装到 vendor 目录下的 phpspec/phpspec 下面,而使用 phpspect 所需要的其他库也会自动被安装。装好之后,也可以在终端执行:

$ vendor/bin/phpspec  desc Markdown
Specification for Markdown created in /Users/zen/composer/spec/MarkdownSpec.php.

了解了 Composer 的基本使用之后,我们利用它来创建一个 Laravel 项目,create-project 命令可以从现有的包中创建一个新的项目:

$composer create-project laravel/laravel=5.0.* learnlaravel5

本教程使用的 Laravel 版本为 5.0,需要手动指定,否则会自动下载最新版本 5.3。安装成功之后,只需要指定项目的 public 为根目录即可运行网站:

$ cd learnlaravel5/
$ php -S localhost:8000 -t public/

这里使用的是 PHP 提供的内置服务器,也可以用 Laravel 提供的更为简单的命令行:

$ cd learnlaravel5/
$ php artisan serve

打开浏览器,输入 localhost:8000,则会看到 Laravel 5 的字眼,代表项目初始化成功。

2. 快速了解路由、控制器及视图

本节介绍 Laravel 的最基本流程,即 路由 → 控制器 → 视图,同时实现一个超级简单的联系我功能。

首先来快速了解一下打开网站之后出现的「欢迎界面」的流程是怎样的?首先,来看看路由,路由可以简单的理解来为 URL 指派任务。先来看看路由文件:

// /app/Http/routes.php
<?php

Route::get('/', 'WelcomeController@index');
Route::get('home', 'HomeController@index');

Route::controllers([
	'auth' => 'Auth\AuthController',
	'password' => 'Auth\PasswordController',
]);

先来看看第一行路由。意思是说当访问根目录时候,向后台发送了一条请求,该请求在路由里面进行匹配,将其分配给指定的控制器(WelcomeController)的某个方法(这里是 index)来处理该请求,从这里可以看出,控制器就是用来处理后台业务逻辑的,具体的方法如下:

// /app/Http/Controllers/WelcomeController.php
public function index()
	{	
		return view('welcome');
	}

index 方法里面调用了 view 方法,将返回视图文件 welcom,用户看到的就是经过应用程序渲染过的 HTML 页面了:

// /resources/views/welcome.blade.php
<html>
	<head>
		<title>Laravel</title>
		
		<link href='//fonts.googleapis.com/css?family=Lato:100' rel='stylesheet' type='text/css'>
	</head>
	<body>
		<div class="container">
			<div class="content">
				<div class="title">Laravel 5</div>
				<div class="quote">{{ Inspiring::quote() }}</div>
			</div>
		</div>
	</body>
</html>

为了熟练掌握,动手模仿一个「联系我」的功能吧。首先在路由中指派任务:

// /app/Http/routes.php
Route::get('contact','WelcomeController@contact’);

当访问 /contact 时,我们需要能够调用 WelcomeControllercontact 方法。

// /app/Http/Controllers/WelcomeController.php
public function contact()
	{
		// 等价于 return view('pages/contact');
		return view('pages.contact');
	}

最后就来创建 pages 目录以及 contact 视图文件吧:

// /resources/views/pages/contact.blade.php
<!DOCTYPE html>
<html lang="en">
<head>
	<meta charset="UTF-8">
	<title>Document</title>
</head>
<body>
	<p>联系我:180XXXXXXX</p>
</body>
</html>

访问 /contact 即可,简单的实现了一个联系我的功能。

3. 数据传递

本节要实现的是「关于我」的功能,在上一节的基础上,增加了创建控制器、传递数据给视图的知识点。同时,也介绍了命令行工具的使用,调试模式的开启。

首先依旧是路由:

// /app/Http/routes.php
<?
Route::get('/about','PagesController@about');

这次调用的是 PagesController,由于还没有创建,访问 /about 自然就会报错,报什么错呢?取决于是否开启调试模式。

  • 开启调试模式:Class App\Http\Controllers\PagesController does not exist
  • 不开启调试模式:Whoops, looks like something went wrong

因此,在开发的时候,可以选择开启调试模式,才能看到具体的错误在哪,而在项目上线的时候,要关掉调试模式。那么如何开启和关闭呢?首先重命名项目目录下的 .env.example

$ cd learnlaravel5/
$ mv .env.example  .env

然后编辑 .env 里面的 APP_DEBUG 即可,TRUE 代表开启, False 代表关闭,我们选择开启调试模式。

既然没有 PageController,那么就需要创建,一种方法是手动创建 php 文件,这样做比较繁琐,因此,Laravel 提供了命令行方式快速创建。首先来了解 Laravel 的命令行工具都有哪些指令可以用:

$ php artisan

该命令可以查看都有哪些命令可用,其中就有一条 make:controller,我们可以通过 help 来进一步查看 make:controller 的用法:

$ php artisan help make:controller

该命令将会显示控制器的具体用法:

Usage:
 make:controller [--plain] name

Arguments:
 name                  The name of the class

Options:
 --plain               Generate an empty controller class.
... 省略

最常用的创建方式有两种,默认的话是创建带有各种资源方法的控制器,如果要创建空白的控制器,则需要使用 --plain 参数,根据使用说明,就可以创建一个控制器了:

$ php artisan make:controller PagesController --plain
Controller created successfully.

控制器创建好了之后,就可以去定义 about 方法了:

//  /app/Http/Controllers/PagesController.php
public function about ()
{
	return view('pages.about');
}

最后是视图:

// `/resources/views/pages/about.blade.php`
<!DOCTYPE html>
<html lang="en">
<head>
	<meta charset="UTF-8">
	<title>Document</title>
</head>
<body>
	<h1>关于我:张三</h1>
</body>
</html>

到这步,和上一节没什么两样,所以增加点难度,我们希望通过变量传递的方式来传递「张三」这个名字,可以使用 with 方法实现:

//  /app/Http/Controllers/PagesController.php
public function about ()
	{
		$name = "张三";
		return view('pages.about')->with('name',$name);
	}

HTML 页面也要嵌入 PHP 代码来输出变量:

// `/resources/views/pages/about.blade.php`
<h1>About:<?php echo $name; ?></h1>

虽然可以使用嵌入 PHP 脚本的方式实现,不过 Laravel 提供了更为方便而且安全的方式:

  • {{ 变量名 }} : 转义输出
  • {!! 变量名 !!} : 原生输出,比如图片、链接、js 代码等

这两者有什么区别呢?比如对于 $name = '<script>alert(123)</script>'; ,使用 {{ $name }} 将输出 <script>alert(123)</script>,而使用 {!! $name !!} 则会弹出 alert 窗口。所以,可以改成:

<h1>About:{{ $name }}</h1>

这里传递的是单个变量,也可以传递多个变量:

// /app/Http/Controllers/PagesController.php
public function about ()
	{
		return view('pages.about')->with(array(
			'firstname' => '三',
			'lastname' => '张',
			));
	}

// 或者这样写
public function about ()
	{	
		$data = array(
			'firstname' => '三',
			'lastname' => '张',
			);
		return view('pages.about',$data);
	}
	
// about.blade.php
<h1>About:{{ $firstname }} {{ $lastname}}</h1>

比数组更为灵活的方式是使用 PHP 的函数 compact,该函数创建一个包含变量名和它们的值的数组,用这个可以方便的添加变量哦:

public function about ()
	{	
		$firstname = '三';
		$lastname = '张';
		$age = 23;
		return view('pages.about',compact('firstname','lastname','age'));
	}

最后,把之前的 contact 功能也转移到 PagesController 里吧,大家自行完成。

4. 使用 Blade

本节介绍 Laravel 的模板功能,通过 Laravel 提供的指令,可以创建通用模板、根据需要显示变量等。

之前所用的视图都比较简单,都是单一的 HTML 页面及嵌入简单的变量,实际上,Larave 的视图功能不仅仅如此。首先,about.blade.php 以及 contact.blade.php,这两个文件都有重复的内容,比如头文件,可以将其提取变成通用布局,避免重复编写代码。创建一个新的模板:

// /resources/views/app.blade.php
<!DOCTYPE html>
<html lang="en">
<head>
	<meta charset="UTF-8">
	<title>Hello Laravel 5</title>
	<link rel="stylesheet" href="http://cdn.bootcss.com/bootstrap/3.3.0/css/bootstrap.min.css">
	<script src="http://cdn.bootcss.com/jquery/1.11.1/jquery.min.js"></script>
	<script src="http://cdn.bootcss.com/bootstrap/3.3.0/js/bootstrap.min.js"></script>
</head>
<body>
	<div class="container">
		@yield('content')
	</div>
</body>
</html>

该例子中,@yield 充当占位符,这意味着,我们的 aboutcontact 页面只需要继承该页面,都可以定义各自的 content 片段的内容,以此来满足不同的显示:

// /resources/views/pages/about.blade.php
@extends('app')

@section('content')
	<h1>About:{{ $firstname }} {{ $lastname}}</h1>
@stop
// /resources/views/pages/contact.blade.php
@extends('app')

@section('content')
	<p>联系我</p>
@stop

该例子中,@extends 指令用于指定继承的模板,同时用 @section@stop 来显示自己的 content 的内容。比如访问 /about,实际上就变成了:

<!DOCTYPE html>
<html lang="en">
<head>
	<meta charset="UTF-8">
	<title>Hello Laravel 5</title>
	<link rel="stylesheet" href="http://cdn.bootcss.com/bootstrap/3.3.0/css/bootstrap.min.css">
	<script src="http://cdn.bootcss.com/jquery/1.11.1/jquery.min.js"></script>
	<script src="http://cdn.bootcss.com/bootstrap/3.3.0/js/bootstrap.min.js"></script>
</head>
<body>
	<div class="container">
		<h1>About:{{ $firstname }} {{ $lastname}}</h1>
	</div>
</body>
</html>

当然也可以在布局文件中使用多个 @yield 来满足不同页面的需求:

// /resources/views/app.blade.php
<body>
	<div class="container">
		@yield('content')
	</div>

	@yield('footer')
</body>
// /resources/views/pages/contact.blade.php
@extends('app')

@section('content')
	<p>联系我</p>
@stop

@section('footer')
	<script>alert("about footer")</script>
@stop

Laravel 提供的模板指令不单单只有这几个,还有很多,这里简单的列举几个:

@if:基本的条件判断,假如 $firstname 的值为 Jack 则输出 .. 否则输出 ...

// /resources/views/pages/about.blade.php
@extends('app')

@section('content')
	@if ($firstname == 'Jack')
		<h1>About:{{ $firstname }} {{ $lastname}}</h1>
	@else
		<h1>无名氏</h1>
	@endif
@stop

@unless: 等价于 if(! condition),即条件不满足时候,执行操作:

// /resources/views/pages/about.blade.php
@extends('app')

@section('content')
	@unless ($firstname == 'Jack')
		<h1>About:{{ $firstname }} {{ $lastname}}</h1>
	@endunless
@stop

@foreach: 遍历指令,与 PHP 的 foreach 类似

// /resources/views/pages/about.blade.php
@extends('app')

@section('content')
	@if( count($data) )
		<h1>Data List:</h1>
		<ul>
			@foreach ($data as $item)
				<li>{{ $item }}</li>
			@endforeach
		</ul>
	@endif	
@stop

// /app/Http/Controllers/PagesController.php
public function about ()
	{	
		$data = array(
			'firstname' => '三',
			'lastname' => '张',
			'age' => 99
			);
		return view('pages.about',compact('data'));
	}

5. 数据库配置与迁移

本节开始逐步实现一个简单的博客功能,会接连用到 Laravel 的不同特性,先从数据库开始,重点介绍数据库的配置,以及如何通过迁移创建表,或者给表添加行,或者进行回滚操作。

首先是数据库的配置,Laravel 的配置文件保存在 config 目录下面,例如 config/database.php 保存了数据库的配置信息:

'mysql' => [
	'driver'    => 'mysql',
	'host'      => env('DB_HOST', 'localhost'),
	'database'  => env('DB_DATABASE', 'forge'),
	'username'  => env('DB_USERNAME', 'forge'),
	'password'  => env('DB_PASSWORD', ''),
	...
],

其中,host 等几个变量使用 env 方法来获取,当然也可以直接写成 'host' => 'localhost', 的形式,不过这样做并不利于版本的控制,因此通常在 .env 目录下面配置这几个变量。所以只需要在 .env 下面配置好数据库的相关信息就可以了。

DB_HOST=127.0.0.1
DB_DATABASE=learnlara5
DB_USERNAME=root
DB_PASSWORD=

那么我们如何对数据库的表进行操作呢?使用迁移即可。迁移任务究竟是什么?可以简单的理解为对数据库的版本控制。通过迁移,团队可以修改和共享数据库模式。迁移文件包含了两个基本方法,up() 方法用来创建表、字段、索引等,而 down() 方法则是 up() 方法的反操作,回滚时调用该方法。

Laravel 默认就有两个迁移:

  • /database/migrations/2014_10_12_000000_create_users_table.php
  • /database/migrations/2014_10_12_100000_create_password_resets_table.php

首先执行迁移,要详细了解迁移命令用法的可以使用上节介绍的 help 指令:

php artisan migrate
Migration table created successfully.
Migrated: 2014_10_12_000000_create_users_table
Migrated: 2014_10_12_100000_create_password_resets_table

数据库会生成三张表 migrationspassword_resets 以及 users,其中,migratsions 表不需要去管,是 Laravel 用来记录表的迁移或者回滚的。如果想要撤回刚才生成表,可以使用回滚操作:

$ php artisan migrate:rollback
Rolled back: 2014_10_12_100000_create_password_resets_table
Rolled back: 2014_10_12_000000_create_users_table

接下来创建一个新的迁移,用于创建 article 表:

$ php artisan make:migration create_articles_table --create=articles
Created Migration: 2016_11_22_112714_create_articles_table

如果指定了 --create 参数,代表要建立数据表 articles,生成的文件如下:

<?php

use Illuminate\Database\Schema\Blueprint;
use Illuminate\Database\Migrations\Migration;

class CreateArticlesTable extends Migration {

	public function up()
	{
		Schema::create('articles', function(Blueprint $table)
		{
			$table->increments('id');
			$table->timestamps();
		});
	}

	public function down()
	{
		Schema::drop('articles');
	}

}

当然,如果不指定,也是可以的,不过就要自己添加方法了,在这里,显然指定创建的表更方便些:

// php artisan make:migration create_articles_table
<?php

use Illuminate\Database\Schema\Blueprint;
use Illuminate\Database\Migrations\Migration;

class CreateArticlesTable extends Migration {

	public function up()
	{
		//
	}


	public function down()
	{
		//
	}

}

接着为其添加几个字段:

public function up()
{
	Schema::create('articles', function(Blueprint $table)
	{
		$table->increments('id');
		$table->string('title');
		$table->text('body');
		$table->timestamp('published_at');
		$table->timestamps();
	});
}

然后执行迁移 php artisan migrate 即可,相当于如下 SQL:

CREATE TABLE IF NOT EXISTS `articles` (
`id` int(10) unsigned NOT NULL,
  `title` varchar(255) COLLATE utf8_unicode_ci NOT NULL,
  `body` text COLLATE utf8_unicode_ci NOT NULL,
  `published_at` timestamp NOT NULL DEFAULT '0000-00-00 00:00:00',
  `created_at` timestamp NOT NULL DEFAULT '0000-00-00 00:00:00',
  `updated_at` timestamp NOT NULL DEFAULT '0000-00-00 00:00:00'
) ENGINE=InnoDB DEFAULT CHARSET=utf8 COLLATE=utf8_unicode_ci;


ALTER TABLE `articles`
 ADD PRIMARY KEY (`id`);

ALTER TABLE `articles`
MODIFY `id` int(10) unsigned NOT NULL AUTO_INCREMENT;

现在,我们想为 articles 表添加一个字段怎么办?可以新建一个迁移,并通过 --table 指定已有的表:

php artisan make:migration add_excerpt_to_articles_table --table=articles
Created Migration: 2016_11_22_113810_add_excerpt_to_articles_table

编辑迁移文件,然后执行迁移即可

	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');
		});
	}

6. Eloquent 入门

本节介绍如何与数据库进行交互,一些常用的方法,希望能够在练习中学会。

每个数据表都有一个与之相对应的模型,用于和数据表交互。本节将帮助你快速上手这些交互操作。首先,为 articles 表创建一个交互 Model

$ php artisan make:model Article

这样,我们就创建了一个 Article 模型,通过操作该模型就可以与数据库进行交互了。如何快速上手?可以直接在控制台里面尝试使用这些方法。

首先可以开启 tinker ,进行交互式编程。

$ php artisan tinker

然后,就可以在练习中学会如何操作数据库了,比如:

实例化一个 model,并添加一些数据,这里用到了 Carbon 类提供的操作的时间的方法:

>>> $article = new App\Article();
=> <App\Article #0000000060c3016a000000016bd293a3> {}
>>>  $article->title = "标题1"
=> "标题1"
>>> $article->body ="文章内容1"
=> "文章内容1"
>>> $article->published_at = Carbon\Carbon::now();
=> <Carbon\Carbon #0000000060c30169000000016bd2980b> {
       date: "2016-11-30 06:25:06.000000",
       timezone_type: 3,
       timezone: "UTC"
   }

可以方便的将刚才的实例转化成各种需要的格式,使用 toArray()toJson()

>>> $article
=> <App\Article #0000000060c3016a000000016bd293a3> {
       title: "标题1",
       body: "文章内容1",
       published_at: <Carbon\Carbon #0000000060c30169000000016bd2980b> {
           date: "2016-11-30 06:25:06.000000",
           timezone_type: 3,
           timezone: "UTC"
       }
   }
>>> $article->toArray()
=> [
       "title"        => "标题1",
       "body"         => "文章内容1",
       "published_at" => <Carbon\Carbon #0000000060c30169000000016bd2980b> {
           date: "2016-11-30 06:25:06.000000",
           timezone_type: 3,
           timezone: "UTC"
       }
   ]
>>> $article->toJson()

将刚才添加的数据存入数据库,用到的 save()

>>> $article->save()
=> true
>>> $article
=> <App\Article #0000000060c3016a000000016bd293a3> {
       title: "标题1",
       body: "文章内容1",
       published_at: <Carbon\Carbon #0000000060c30169000000016bd2980b> {
           date: "2016-11-30 06:25:06.000000",
           timezone_type: 3,
           timezone: "UTC"
       },
       updated_at: "2016-11-30 06:27:54",
       created_at: "2016-11-30 06:27:54",
       id: 1
   }

save() 方法也可以用于更新表数据:

>>> $article->body = "更新内容1"
=> "更新内容1"
>>> $article->title = '更新标题1'
=> "更新标题1"
>>> $article->save()
=> true
>>> $article
=> <App\Article #0000000060c3016a000000016bd293a3> {
       title: "更新标题1",
       body: "更新内容1",
       published_at: <Carbon\Carbon #0000000060c30169000000016bd2980b> {
           date: "2016-11-30 06:25:06.000000",
           timezone_type: 3,
           timezone: "UTC"
       },
       updated_at: "2016-11-30 06:32:41",
       created_at: "2016-11-30 06:27:54",
       id: 1
   }

保存后,我们就可以取出数据库的数据了,使用 all() 方法可以取出全部数据,同时也可以使用刚才的 toArray() 转化数据格式:

>>> App\Article::all()->toArray();
=> [
       [
           "id"           => 1,
           "title"        => "标题1",
           "body"         => "文章内容1",
           "published_at" => "2016-11-30 06:25:06",
           "created_at"   => "2016-11-30 06:27:54",
           "updated_at"   => "2016-11-30 06:27:54",
           "excerpt"      => null
       ]
   ]

也可以根据 ID 来寻找某条记录:find()

>>> $article = App\Article::find(1)
=> <App\Article #0000000060c3017f000000016bd293a3> {
       id: 1,
       title: "更新标题1",
       body: "更新内容1",
       published_at: "2016-11-30 06:25:06",
       created_at: "2016-11-30 06:27:54",
       updated_at: "2016-11-30 06:32:41",
       excerpt: null
   }

不根据 ID 的话,也可以自己定义条件来获取符合条件的所有记录:where()

>>> $article = App\Article::where('body','更新内容1')->get();
=> <Illuminate\Database\Eloquent\Collection #0000000060c30182000000016bd293a3> [
       <App\Article #0000000060c30186000000016bd293a3> {
           id: 1,
           title: "更新标题1",
           body: "更新内容1",
           published_at: "2016-11-30 06:25:06",
           created_at: "2016-11-30 06:27:54",
           updated_at: "2016-11-30 06:32:41",
           excerpt: null
       }
   ]

也可以获取符合条件的第一条记录: first()

>>> $article = App\Article::where('body','更新内容1')->first();

刚才只是单条数据的操作,Laravel 也可以批量赋值创建:

>>> $article = App\Article::create(['title'=>'另一篇标题','body'=>'另一篇内容','published_at'=>Carbon\Carbon::now()]);
Illuminate\Database\Eloquent\MassAssignmentException with message 'title'

Laravel 默认是不允许这样做的,需要将允许批量创建的字段添加到 $fillable 属性中。

// Article.php
<?php namespace App;

use Illuminate\Database\Eloquent\Model;

class Article extends Model {

	protected $fillable = [
		'title',
		'body',
		'published_at',
	];

}

然后,重新启动 tinker 并执行刚才的命令:

$ php artisan tinker
Psy Shell v0.4.1 (PHP 5.6.22 — cli) by Justin Hileman
>>> $article = App\Article::create(['title'=>'另一篇标题','body'=>'另一篇内容','published_at'=>Carbon\Carbon::now()]);
=> <App\Article #0000000001dffeec0000000159ffa4c5> {
       title: "另一篇标题",
       body: "另一篇内容",
       published_at: <Carbon\Carbon #0000000001dffeef0000000159ffaf6d> {
           date: "2016-11-30 06:46:59.000000",
           timezone_type: 3,
           timezone: "UTC"
       },
       updated_at: "2016-11-30 06:46:59",
       created_at: "2016-11-30 06:46:59",
       id: 3
   }

除了 save() 外,update() 方法也可以用于更新表的数据:

>>> $article = App\Article::find(1)
=> <App\Article #0000000001dffee20000000159ffa4c5> {
       id: 1,
       title: "更新标题1",
       body: "更新内容1",
       published_at: "2016-11-30 06:25:06",
       created_at: "2016-11-30 06:27:54",
       updated_at: "2016-11-30 06:32:41",
       excerpt: null
   }
>>> $article->update(['body'=>'更新2']);

7. 基本流程(文章显示功能)

之前已经学过了路由→控制器→视图的开发流程,这节主要把上节学习的模型加进来,然后实现博客文章的显示功能。

首先是文章列表的显示功能,从基本的路由开始:

// /app/Http/routes.php
Route::get('articles','ArticlesController@index');

接着创建控制器:

$ php artisan make:controller ArticlesController --plain

定义 index() 方法,用到了上一节的 all() 方法,记得在开头引入命名空间。

// /app/Http/Controllers/ArticlesController.php
<?php namespace App\Http\Controllers;

use App\Article;
use App\Http\Controllers\Controller;
use App\Http\Requests;
use Illuminate\Http\Request;

class ArticlesController extends Controller {

	public function index()
	{
		$articles = Article::all();
		return view('articles.index',compact('articles'));
	}
}

最后,创建视图,用到了之前提到的 @foreach 模板指令:

// /resources/views/articles/index.blade.php
@extends('app')

@section('content')
	<h1>文章列表</h1>
	<hr>
	@foreach ($articles as $article)
		<article>
			<h2> {{ $article->title }} </h2>
			<div class="body">{{ $article->body }}</div>
		</article>
	@endforeach
@stop

接着是显示某篇具体文章,首先是路由:

// /app/Http/routes.php
Route::get('articles/{id}','ArticlesController@show');

{id} 相当于给这个位置的值起一个名字,比如你输入 articles/4,那么后台接收的 $id 变量的值就是 4,然后后台的 show() 方法里面再根据这个值来查找文章:

// /app/Http/Controllers/ArticlesController.php
public function show( $id )	
{
	$article = Article::find($id);

	return $article;
}

但是,如果用户输入 articles/foo,后台接收的 $id 的值就变成 foo 了,数据库就可能找不到该信息。即使用户输入的 id 是整数的,后台也可能不存在对应数据,因此这里要增加一条判断:

// /app/Http/Controllers/ArticlesController.php
public function show( $id )	
{
	$article = Article::find($id);
	if ( is_null($article)) {
		abort(404);
	}
	return $article;
}

比手工判断更为常用的是使用 Laravel 提供的 findOrFail() 方法:

// /app/Http/Controllers/ArticlesController.php
public function show( $id )	
{
	$article = Article::findOrFail($id);
	return view('articles.show',compact('article'));
}

然后是视图:

// /resources/views/articles/show.blade.php
@extends('app')

@section('content')
	<h1> {{$article->title}} </h1>
	<hr>
	<article>
		{{ $article->body }}
	</article>
@stop

最后,文章列表里面应该还需要有这样一个功能:列表里面点击某篇文章,自动跳转到对应的文章。最土的办法,就是直接添加 url 地址:

// /resources/views/articles/index.blade.php
@extends('app')

@section('content')
	<h1>文章列表</h1>
	<hr>
	@foreach ($articles as $article)
		<article>
			<a href="/articles/{{ $article->id }}">
				<h2> {{ $article->title }} </h2>
			</a>		
			<div class="body">{{ $article->body }}</div>
		</article>
	@endforeach
@stop

更为方便的,可以使用 Laravel 提供的辅助方法 action() 或者 url()

action() 函数根据控制器生成对应的 url,而且可以省略 App\Http\Controller 前缀。因此我们还需要传入 id 参数,所以将其放到第二个参数里:

<a href="{{ action('ArticlesController@show',[$article->id]) }}">
	<h2> {{ $article->title }} </h2>
</a>	

url() 则是根据给定的路径来生成完整的 URL 地址:

<a href="{{ url('/articles', $article->id) }}">
	<h2> {{ $article->title }} </h2>
</a>	

8. 快速创建表单(文章创建功能)

本节将介绍如何使用第三方插件快速创建表单,以创建文章为例子。同时,也介绍了第三方插件如何在 Laravel 中进行配置使用。

首先是路由:

// /app/Http/routes.php
Route::get('articles/create', 'ArticlesController@create');
Route::get('articles/{id}','ArticlesController@show');

注意,需要把创建文章的路由放在显示文章的路由上面,否则,我们输入 articles/create 的时候,create 会被当成是 articles/{id} 路由里面 id 的值传入到 show 方法中。

接着定义 create() 方法:

// /app/Http/Controllers/ArticlesController.php
public function create()
{	
	return view('articles.create');
}

然后是视图,我们可以手动编写 HTML 表单,也可以使用 PHP 来生成表单。如何做呢?首先安装 illuminate/html 包:

$ cd learnlaravel
$ composer require illuminate/html

该包会被安装到 /vendor/illuminate/html 中,包含了一系列文件,那么如何使用呢?首先需要把 HtmlServiceProvider 的完整引用添加进来:

// /vendor/illuminate/html/HtmlServiceProvider.php
<?php namespace Illuminate\Html;

use Illuminate\Support\ServiceProvider;

class HtmlServiceProvider extends ServiceProvider {

...

添加到哪里呢?config/app.phpproviders 数组里面:

'providers' => [	
		// ... 省略
		'Illuminate\Html\HtmlServiceProvider',
	],

还要将 HtmlFacade 以及 FormFacade 的完整引用添加到 config/app.phpaliases 数组里面,给它们起个别名。

'aliases' => [
	'Form'  	=> 'Illuminate\Html\FormFacade',
	'Html'  	=> 'Illuminate\Html\HtmlFacade',

],

然后就可以使用 FormBuilder 或者 HtmlBuilder 的各种方法了 (文档说明)

// /resources/views/articles/create.blade.php
@extends('app')

@section('content')
	
	<h1>写一篇文章</h1>

	<hr>

	{!! Form::open() !!}
		<div class="form-group">
			{!! Form::label('title','标题:') !!}
			{!! Form::text('title',null, ['class'=> 'form-control'])!!}
		</div>

		<div class="form-group">
			{!! Form::label('body','内容:') !!}
			{!! Form::textarea('body',null, ['class'=> 'form-control'])!!}
		</div>

		<div class="form-group">
			{!! Form::submit('新增文章',['class'=>'btn btn-primary form-control']) !!}
		</div>
	{!! Form::close() !!}

@stop

Form::open()Form::close() 用于开始和结束一个表单,生成的 HTML 如下:

<form method="POST" action="http://localhost:8000/articles/create" accept-charset="UTF-8"><input name="_token" type="hidden" value="NZJR1zXS10JW55BwKssKuRX9KHr5HHu7JubuzTVD">

其他的,可以自行对照文档,创建了表单供用户填写之后,还需要对提交进行处理:

Route::post('articles', 'ArticlesController@store');

不过从刚才的 HTML 代码可以看出,默认提交的地址的却是 articles/create,所以我们需要定义提交的的地址,用到的是之前学过的方法:

// {!! Form::open() !!} 改为
{!! Form::open(['url' => 'articles']) !!}
// 或者改为
{!! Form::open(['action' => 'ArticlesController@store']) !!}

再看看提交的表单的提交地址,变成了我们想要的:

<form method="POST" action="http://localhost:8000/articles/create" accept-charset="UTF-8"><input name="_token" type="hidden" value="NZJR1zXS10JW55BwKssKuRX9KHr5HHu7JubuzTVD">

然后我们要在 store() 方面里面获取表单的数据,将其存入数据库。获取数据很简单,使用 Laravel 的 Request。我们刚才已经学过了如何添加插件,我们可以再次看看 aliase 数组:

'aliases' => [

	'Request'   => 'Illuminate\Support\Facades\Request',

	],

默认已经添加了 Request 门面,所以可以像刚才创建表格一样使用 Request::all() 等方法了。在开头添加use Request 即可:


// use Illuminate\Http\Request; // 注释该行
use Request;
use Carbon\Carbon;

public function store()
	{

		$input = Request::all(); // 获取全部提交数据
		$input['published_at'] = Carbon::now();
		Article::create($input);
		return redirect('articles'); 
	}

注意,这里的时间只是简单的后台直接添加,下一讲将完善这一功能。文章创建成功之后,跳转到文章列表,我们发现,列表的文章不是按照最新发表的文章顺序显示的,我们希望完善下这个功能,直接添加排序字段即可:

public function index()
	{	
		$articles = Article::order_by('published_at','desc')->get();
		return view('articles.index',compact('articles'));
	}

不过,Laravel 提供了更为简单的实现: latest()

public function index()
	{	
		$articles = Article::latest('published_at')->get();
		return view('articles.index',compact('articles'));
	}

9. 修改器与范围查询

本节是对 Eloquent 功能的进一步介绍,主要围绕着修改器与范围查询两方面进行介绍,以博客时间的显示与保存功能为例子。

首先,在提交按钮之前添加一个时间控件:

// /resources/views/articles/create.blade.php
 <div class="form-group">
    {!! Form::label('body','内容:') !!}
    {!! Form::textarea('body',null, ['class'=> 'form-control'])!!}
</div>

<div class="form-group">
    {!! Form::label('published_at','发表时间:') !!}
    {!! Form::input('date','published_at',date('Y-m-d'), ['class'=> 'form-control'])!!}
</div>

<div class="form-group">
    {!! Form::submit('新增文章',['class'=>'btn btn-primary form-control']) !!}
</div>

这里用 PHP 自带的 date 方法传入默认值。store 方法稍加修改:

public function store()
{

	$input = Request::all(); // 获取全部提交数据
	Article::create($input);
	return redirect('articles'); 
}

看起来好像没啥问题,如果仔细查看数据,发现最新添加的数据的 publihed_at 字段的时间里面只能精确到天,和 Carbon 生成的时间不同:

$ php artisan tinker
>>> App\Article::latest('published_at')->get();
...
published_at: "2016-12-09 09:06:37",  // Carbon 生成的
published_at: "2016-12-09 00:00:00",  // 刚才创建的控件生成的

显然,使用 Carbon 生成的时间更加准确。我们将使用 Laravel 的修改器功能来实现,修改器可以用来方便的获取或者转换属性,只需要在对应的 Model 下定义即可:

// /app/Article.php

use Carbon\Carbon;
// 命名要求 set+ 属性名 + Attribute
public function setPublishedAtAttribute( $date )	
{
	$this->attributes['published_at'] = Carbon::createFromFormat('Y-m-d',$date);
}

这样,每次保存,时间会被自动转换成对应的格式。

还有一个问题需要考虑,就是创建文章的时候,可以选择未来的时期,那么这些文章就不应该出现在列表中。但是我们显示文章列表的时候,默认显示的却是所有文章,所以可以在显示文章列表时进行简单的筛选:

public function index()
{	

	$articles = Article::latest('published_at')->where('published_at','<=',Carbon::now())->get();
	return view('articles.index',compact('articles'));
}

这样做虽然没问题,但是比较繁琐,而且其他地方也需要进行时间筛选的话,就需要重复编写代码。因此,我们使用 Eloquent 的范围查询来统一解决该问题。首先定义一个范围查询:

// /app/Article.php

// 命名方式:scope + 自定义的方法名
public function scopePublished($query)
{
	$query->where('published_at','<=',Carbon::now());
}

定义之后,就可以方便的使用了:

public function index()
{	

	$articles = Article::latest('published_at')->published()->get();
	return view('articles.index',compact('articles'));
}

还有一个小问题,我们在获取某篇文章的发表时间和创建时间时,发现得到的数据格式不相同:

>>> App\Article::first()->published_at
=> "2016-12-06 06:52:00"
>>> App\Article::first()->created_at
=> <Carbon\Carbon #000000006f0caebe000000010094438f> {
       date: "2016-12-06 06:52:00.000000",
       timezone_type: 3,
       timezone: "UTC"
   }

可以看到,数据表的默认字段是 Carbon 的实例,一个只是普通的时间段。Carbon 实例有啥方便之处了,可以看看以下操作就知道了:

>>> App\Article::first()->created_at->month
=> 12
>>> App\Article::first()->created_at->addDays(8)
=> <Carbon\Carbon #000000006f0caeb7000000010094438f> {
       date: "2016-12-14 06:52:00.000000",
       timezone_type: 3,
       timezone: "UTC"
   }

因此,我们希望 published_at 字段读取的时候也是 Carbon 实例,怎么做呢,在 Model 里面添加即可:

// /app/Article.php
protected $dates = ['published_at'];

ihuangmx has written 2 articles

该作者很低调,还没有自我介绍~

积分:257 等级:P4 职业:未设置 城市:未设置

8 条回复

登录后才能进行评论,立即登录?