重要: 这一章已经更新到支持Laravel5.2了。这是一个beta公开测试版。让我们知道你的喜好。我们定期更新所有的章节来修改错误和bug。
第四章: 搭建一个博客应用程序
到目前为止,我们已经使用了很多Laravel特性来搭建我们的应用程序。在本章中,我们将会搭建一个博客应用。通过这项目,我们将会学习Laravel的认证,seeding,本地化,中间件和其他很多能够帮助我们深刻认识Laravel的特性
对我们来说,最好是先考虑下怎么让我们的博客应用工作。
开始之前我们需要什么东西呢?
假设你已经执行了我们在之前章节中提供的指令,也已经创建一个票务系统。我们将会使用之前的应用作为我们的模板。
我们将会搭建什么?
我们简单的博客应用将会有一下特性:
用户可以注册和登陆。
管理员可以写文章。
用户可评论文章。
有一个管理员控制面板来管理用户,文章(增加,更新,删除)。
权限和角色系统。
管理员可以 创建/删除/编辑 分类。
让我们开始吧!
搭建一个用户注册页
从Laravel 5开始,实现权限系统变得很简单,Laravel已经为我们准备了大多数事情。
在本节中,你将会学习怎么创建一个简单的注册页。
首先,我们需要创建一些路由。打开 routes.php 并且添加如下路由:
Route::get('users/register', 'Auth\AuthController@getRegister');
Route::post('users/register', 'Auth\AuthController@postRegister');
第一个路由将会提供 注册表单。 第二个路由器将会提供 提交表单。 所有的路由将会被 AuthController中的action( getRegister 和 postRegister)处理。
Laravel默认已经帮我们创建了 AuthorController。你可以在 Auth 文件夹下找到它。
打开文件,看一下里面究竟是什么鬼:
protected function validator(array $data)
{
return Validator::make($data, [
'name' => 'required|max:255',
'email' => 'required|email|max:255|unique:users',
'password' => 'required|confirmed|min:6',
]);
}
正如你所看到的,这里有 三个字段:name,email和password.
当用户访问 users/register, 这个AuthController将会渲染一个包含了注册表单的视图。
很不幸的是Laravel没有为我们创建一个注册视图,我们必须手动创建他。这个注册视图将会被放置在resources/views/auth/register.blade.php里。
以下是注册视图里的代码:
@extends('master')
@section('name', 'Register')
@section('content')
<div class="container col-md-6 col-md-offset-3">
<div class="well well bs-component">
<form class="form-horizontal" method="post">
@foreach ($errors->all() as $error)
<p class="alert alert-danger">{{ $error }}</p>
@endforeach
{!! csrf_field() !!}
<fieldset>
<legend>Register an account</legend>
<div class="form-group">
<label for="name" class="col-lg-2 control-label">Name</label>
<div class="col-lg-10">
<input type="text" class="form-control" id="name" placeholder="Name" name="name" value="{{ old('name') }}">
</div>
</div>
<div class="form-group">
<label for="email" class="col-lg-2 control-label">Email</label>
<div class="col-lg-10">
<input type="email" class="form-control" id="email" placeholder="Email" name="email" value="{{ old('email') }}">
</div>
</div>
<div class="form-group">
<label for="password" class="col-lg-2 control-label">Password</label>
<div class="col-lg-10">
<input type="password" class="form-control" name="password">
</div>
</div>
<div class="form-group">
<label for="password" class="col-lg-2 control-label">Confirm password</label>
<div class="col-lg-10">
<input type="password" class="form-control" name="password_confirmation">
</div>
</div>
<div class="form-group">
<div class="col-lg-10 col-lg-offset-2">
<button type="reset" class="btn btn-default">Cancel</button>
<button type="submit" class="btn btn-primary">Submit</button>
</div>
</div>
</fieldset>
</form>
</div>
</div>
@endsection
你已经在之前的章节中创建了很多表单,因此我猜你很清楚这文件中内容。
这里有一个细节,使用这一行代码来生成一个新的CSRF(跨站请求伪造)令牌:
<input type="hidden" name="_token" value="{!! csrf_token() !!}">
你可以轻松地使用这个很管用的函数来生成令牌!
{!! csrf_field() !!}
你也会意识到这里有一个新的 old() 方法。当表单验证失败,用户请求将会被重定向到表单处。我们使用这个方法来显示用户之前的输入内容,因此他们不必重新填写表单的全部内容。
现在,访问 http://homestead.app/users/register, 你将会看到一个很棒的用户注册表单。
填写所有字段,点击提交!你就已经注册一个新用户了!
检查你的数据库,你应该会看到:
Laravel默认将会自动定位到 /home 地址。当你访问 http://homestead.app/home时,报了如下错误:
NotFoundHttpException in RouteCollection.php line 161:
这就意味着你的 routes.php 文件没有 home 路由:
Route::get('home', 'PagesController@home');
你可以通过在你的应用中添加 home 路由 来更改这个错误,或者忽略它。下一节你将会学习如何重定向用户到其他页面。
创建登出功能
当用户注册一个新账号,Laravel将会验证注册表单。如果验证规则通过了,Laravel将会保存数据到数据库,记录用户登陆状态然后重定向到我们应用的主页。
要重定向用户到其他地方,打开AuthController.php文件,找到:
use AuthenticatesAndRegistersUsers;
在下面添加如下代码:
protected $redirectPath = '/yourPath';
你将会注意到当你访问注册页时,Laravel将会自动将你重定向到主页,因为你已经登陆了。
注意: 在Laravel 5.2, redirectPath∗∗变量被改成了∗∗ redirectTo.
到目前为止我们还没有 登出 功能,但是不要担心,这个实现起来很简单。
打开 shared/navbar.blade.php 视图,找到:
<li><a href="/users/register">Register</a></li>
<li><a href="/users/login">Login</a></li>
用下面的代码来替换它们:
@if (Auth::check())
<li><a href="/users/logout">Logout</a></li>
@else
<li><a href="/users/register">Register</a></li>
<li><a href="/users/login">Login</a></li>
@endif
要检查用户是否登陆了,我们可以使用 Auth::check() 方法。 在上面的代码中,如果用户已经登陆了,我们将会显示一个登出链接。
要让这个链接有生效,打开routes.php, 添加如下代码:
Route::get('users/logout', 'Auth\AuthController@getLogout');
如果你正在使用 Laravel 5.2, 打开你的 AuthController, 更新你的 构造器 (又名 构造函数) ,如下:
public function __construct()
{
$this->middleware('guest', ['except' => ['logout', 'getLogout']]);
}
正如你所看到的,当用户访问 users/logout 链接,我们将会 调用AuthController的getLogout 动作来将用户登出。
你自己试试这个功能!现在可以登出了!
创建一个登陆页
现在来创建我们的登陆表单吧。和之前一样,我们将会在 users/login 定义两个不同的动作:
Route::get('users/login', 'Auth\AuthController@getLogin');
Route::post('users/login', 'Auth\AuthController@postLogin');
这个 GET 路由将会显示登陆表单,POST 路由将会提交处理表单。
你应该猜得到吧,现在你该创建一个login视图。这个视图将会被放置在 views/auth/login.blade.php中.
@extends('master')
@section('name', 'Login')
@section('content')
<div class="container col-md-6 col-md-offset-3">
<div class="well well bs-component">
<form class="form-horizontal" method="post">
@foreach ($errors->all() as $error)
<p class="alert alert-danger">{{ $error }}</p>
@endforeach
{!! csrf_field() !!}
<fieldset>
<legend>Login</legend>
<div class="form-group">
<label for="email" class="col-lg-2 control-label">Email</label>
<div class="col-lg-10">
<input type="email" class="form-control" id="email" name="email" value="{{ old('email') }}">
</div>
</div>
<div class="form-group">
<label for="password" class="col-lg-2 control-label">Password</label>
<div class="col-lg-10">
<input type="password" class="form-control" name="password">
</div>
</div>
<div class="checkbox">
<label>
<input type="checkbox" name="remember" > Remember Me?
</label>
</div>
<div class="form-group">
<div class="col-lg-10 col-lg-offset-2">
<button type="submit" class="btn btn-primary">Login</button>
</div>
</div>
</fieldset>
</form>
</div>
</div>
@endsection
我们的登陆表单很简单,只需要两个字段:email和password.用户必须输入正确的email和password才能登陆。
Laravel提供了记住我功能。我们可以通过创建一个checkbox轻松的实现这个功能。
注意: checkbox的名字应该是 “remember”.
完成。现在跳到http://homestead.app/users/login 试着用你的email和密码登陆!
如果你登陆时候报了错误信息,也许是如下错误:
Laravel默认使用 auth/login 映射到验证用户功能,如果用户输入了错误的凭据将会被重定向到这个路由上来。然而,我们使用 users/login 路由,因此我们需要让Laravel知道打开 AuthController 类,修改 loginPath 属性:
protected $loginPath = '/users/login';
现在将会一切顺利。
给你的应用添加认证限制
Laravel 5.1.4 介绍了一个新特性:
“认证限制”。这个特性用来限制试图登陆你的应用。如果用户试图登陆多次,他们在一分钟内将会被限制不能登陆了。
注意:如果你正在使用 Laravel 5.1.4 或者更新的版本,这个特性已经实现了。你可以跳过这几步。话说回来,这也是了解它是如何工作的好机会。
当我们使用AuthController类来验证,你可以通过以下步骤轻松的整合这个特性:
首先,确认你应用的版本为 5.1.4 或者更新版。你可以通过 vagrant ssh连接到你的虚拟机,进入你的Laravel根目录,运行以下命令来更新你的应用:
composer update
一旦更新后,打开AuthController.php文件找到:
use App\Http\Controllers\Controller;
在下面添加:
use Illuminate\Foundation\Auth\ThrottlesLogins;
找到:
use AuthenticatesAndRegistersUsers;
修改为:
use AuthenticatesAndRegistersUsers, ThrottlesLogins;
搞定!你已经实现这个限制登陆特性了。
现在,让我们试着使用错误的凭证登陆:
当你试着登陆好几次后,你将会看到如下错误信息:
搭建一个管理员区域
如果我们的应用有一个管理员模块和前端模块,这将会有很多路由。我们需要一个组织这些路由的方法。
此外,我们希望只允许管理员能够访问我们的管理模块。幸运的是,Laravel帮我能够轻松地实现这些功能。
让我们打开 routes.php 文件并且添加:
Route::group(array('prefix' => 'admin', 'namespace' => 'Admin', 'middleware' => 'auth'), function () {
Route::get('users', 'UsersController@index');
});
通过使用 Route::group 我们可以将相关的路由分组到一起还可以为它们提供特殊的规则。
'prefix' => 'admin'
我们使用 prefix 属性 给每个路由组的路由添加 URI (admin)前缀。在这种情况下,当我们前往http://homestead.app/admin或者任何包含admin前缀的路由,Laravel将会认为我们想要访问admin模块
Laravel 5.1版本使用PSR-2 自动加载标准,这是一种编码风格。你的应用程序中的控制器,模型和其他类必须包含在命名空间中。
什么是命名空间?根据PHP官方文档: “命名空间是用来解决两个问题–当创建可复用的代码片段比如类和函数时,遇到的库和应用的作者冲突问题。”。 简单的说,你可以把命名空间看成人名中的姓。当很多人同名时,我们将会使用他们的姓来区分他们。
要命名空间一个类,我们可以使用 namespace 关键词在每个文件的顶部声明命名空间。举个栗子,打开AuthController.php类,你将会看到如下代码:
<?php
namespace App\Http\Controllers\Auth;
你可以从这里了解更多关于命名空间的信息:
http://php.net/manual/en/language.namespaces.rationale.php
正如你所看到的。我之前为我们的路由定义了一个名为 Admin的命名空间:
'namespace' => 'Admin'
如果我们有一个如下拥有命名空间的类:
<?php
namespace App\Http\Controllers\Admin;
Laravel将会准确地知道我们想加载哪个类和去哪里找到这个类。
Laravel 5也有一个叫做 HTTP Middleware 的新特性。一般我们都是使用它来过滤http请求的。比如,我用:
'middleware' => 'auth'
这意味着我想为这个路由组使用 auth middeware。只有经过认证的用户才能访问这些路由。接下来我们将会学习更多关于Middleware的知识。
列出所有用户
现在我们已经有了一个管理员控制面板的路由组了。让我们列出所有的用户从而让我们可以更简单的查看和管理他们。
正如你所知道的,我们在这里已经定义了一个路由:
Route::get('users', 'UsersController@index');
我们还没有 UsersController控制器,让我们使用 PHP Artisan 来创建他吧:
php artisan make:controller Admin/UsersController
这次,我们的代码有一点点不同。在每个控制器名字前面都有一个 /Admin前缀。Laravel很聪明。当我们的代码想这样,它将会自动创建一个名为 Admin 的文件夹,然后将 UserController文件放在Admin文件夹下。不仅如此,我们的控制器也已经被自动加上了命名空间了!
此时,我觉得你已经知道怎么列出所有的用户了。基本上,这流程和我们在第三章中所列出所有票的动作很相似。
首先,我们得告诉Laravel我们想使用 User模型:
找到:
use App\Http\Controllers\Controller;
在下面添加:
use App\User;
将 index() 动作更新如下:
public function index()
{
$users = User::all();
return view('backend.users.index', compact('users'));
}
你应该猜到了吧,我们将会把所有的管理视图放在 backend 目录中。
接下来我们创建一个新的名为index.blade.php的视图来显示给所有的用户。让我们创建一个新的users文件夹,然后把上面的index视图放到该目录中。
因此这个index视图将会被放置在views/backend/users/index.blade.php文件中.
代码如下:
@extends('master')
@section('title', 'All users')
@section('content')
<div class="container col-md-8 col-md-offset-2">
<div class="panel panel-default">
<div class="panel-heading">
<h2> All users </h2>
</div>
@if (session('status'))
<div class="alert alert-success">
{{ session('status') }}
</div>
@endif
@if ($users->isEmpty())
<p> There is no user.</p>
@else
<table class="table">
<thead>
<tr>
<th>ID</th>
<th>Name</th>
<th>Email</th>
<th>Joined at</th>
</tr>
</thead>
<tbody>
@foreach($users as $user)
<tr>
<td>{!! $user->id !!}</td>
<td>
<a href="#">{!! $user->name !!} </a>
</td>
<td>{!! $user->email !!}</td>
<td>{!! $user->created_at !!}</td>
</tr>
@endforeach
</tbody>
</table>
@endif
</div>
</div>
@endsection
现在,确认已经登陆了我们的应用,然后访问http://homestead.app/admin/users
酷毙了!我们可以预览所有的用户了!
如果你还没有登陆就访问这个页面,将会看到如下错误:
Laravel将会将你重定向到http://homestead.app/auth/login来让你登陆。
不要担心,这是因为我们的Middleware起了作用。下一节我们将会学习如何使用Middleware来解决这个bug。
关于Middleware
Laravel5最好的新特性之一是 Middleware(又叫做 HTTP Middleware)。 想象一下在所有的请求和回复之间都有一层膜,这层膜可以在请求/回复处理之前操作所有的请求和返回适当的回复。我们把这层膜就叫做:Middleware(中间件)。
从这里可以了解一下它:
http://laravel.com/docs/master/middleware
我们可以使用中间件做很多事情。比如,中间件可以帮助我们验证用户,记录数据用以分析,添加CSRF保护,等等。
你可以在 app/Http/Middleware 文件夹下找到所有的中间件。打开这个文件夹,你将会看到是个中间件:
Authenticated中间件:这个中间件是用来回复验证用户的。如果用户没有登陆,他们将会被重定向到登陆页。
EncryptCookies中间件:用来加密应用的cookies.
RedirectIfAuthenticated中间件:如果用户没有认证则重定向到另一个页面。
VerifyCsrfToken中间件:用来管理CSRF令牌。
我们之前在管理员路由组中使用过Authenticated中间件。让我们打开它吧:
public function handle($request, Closure $next)
{
if ($this->auth->guest()) {
if ($request->ajax()) {
return response('Unauthorized.', 401);
} else {
return redirect()->guest('auth/login');
}
}
return $next($request);
}
正如你所看到的,这个Authenticated中间件只是一个类。我们使用handle方法来处理所有请求和定义请求过滤器。
如果用户还未登陆,中间件默认将会自动重定向用户到auth/login URI.
return redirect()->guest('auth/login');
然而,我们使用users/login路由来访问我们的登陆页。 这就是为什么Laravel不明白auth/login路由映射到哪,它将会抛出一个错误。
为了解决这个bug,我们修改如下行:
return redirect()->guest('users/login');
当你使用错误凭据登陆时,你将会被重定向到users/login路由。
现在宝宝们希望使用一个名为Manager的中间件,并且确认只有管理员们才能访问管理模块哦。
创建一个新的中间件
创建一个新的中间件很简单,只需要运行一下下面的Artisan命令:
php artisan make:middleware Manager
一个名为Manager的新中间件将会被创建。你可以在Middleware目录中找到。
<?php
namespace App\Http\Middleware;
use Closure;
class Manager
{
public function handle($request, Closure $next)
{
return $next($request);
}
}
还需要做一步,我们需要注册Manager中间件。请打开Kernel.php 文件,可以在Http目录中找到。
protected $middleware = [
\Illuminate\Foundation\Http\Middleware\CheckForMaintenanceMode::class,
\App\Http\Middleware\EncryptCookies::class,
\Illuminate\Cookie\Middleware\AddQueuedCookiesToResponse::class,
\Illuminate\Session\Middleware\StartSession::class,
\Illuminate\View\Middleware\ShareErrorsFromSession::class,
\App\Http\Middleware\VerifyCsrfToken::class,
];
protected $routeMiddleware = [
'auth' => \App\Http\Middleware\Authenticate::class,
'auth.basic' => \Illuminate\Auth\Middleware\AuthenticateWithBasicAuth::class,
'guest' => \App\Http\Middleware\RedirectIfAuthenticated::class,
];
这里有两个属性: middleware∗∗和∗∗ routeMiddleware.
如果你想为每个路由启用一个中间件,你可以在$middleware属性中追加它。比如,你可以如下一样添加Manager类:
protected $middleware = [
\Illuminate\Foundation\Http\Middleware\CheckForMaintenanceMode::class,
\App\Http\Middleware\EncryptCookies::class,
\Illuminate\Cookie\Middleware\AddQueuedCookiesToResponse::class,
\Illuminate\Session\Middleware\StartSession::class,
\Illuminate\View\Middleware\ShareErrorsFromSession::class,
\App\Http\Middleware\VerifyCsrfToken::class,
\App\Http\Middleware\Manager::class,
];
然而,我们只是想往我们的管理路由组中添加Manager中间件。 因此,我们所需要做的在$routeMiddleware属性中添加Manager中间件。
protected $routeMiddleware = [
'auth' => \App\Http\Middleware\Authenticate::class,
'auth.basic' => \Illuminate\Auth\Middleware\AuthenticateWithBasicAuth::class,
'guest' => \App\Http\Middleware\RedirectIfAuthenticated::class,
'manager' => \App\Http\Middleware\Manager::class,
];
我们可以使用manager来作为Manager中间件的简称。
备注::不要在 middleware∗∗属性中添加Manager中间件。只需要在∗∗ routeMiddleware属性中添加这个中间件,因为我们只希望在管理模块的路由组中使用它。
接下来,打开routes.php文件修改管理路由组来启用Manager中间件:
Route::group(array('prefix' => 'admin', 'namespace' => 'Admin', 'middleware' => 'auth'), function () {
Route::get('users', 'UsersController@index');
});
改成如下:
Route::group(array('prefix' => 'admin', 'namespace' => 'Admin', 'middleware' => 'manager'), function () {
Route::get('users', 'UsersController@index');
});
打完收工! 现在你对中间件的知识有了坚实的基础了。时刻记住你可以使用中间件干很多事情哦!
尽管我们的Manager中间件可以正常地工作,但是我们并没有看到有什么不同。原因呢,就是我们还木有创建任何请求过滤器。
如果我们想要限制别人访问管理模块,我们需要给我们的用户添加角色和权限。幸运的是,Laravel有一个灰常灰常受欢迎的包可以帮助我们轻松地实现这个特性,这个高大上的特性就是:Entrust.
使用Entrust给我们的应用添加角色和权限管理
超级多的Laravel开发者都在使用Entrust来给他们的Laravel应用添加基于角色的权限管理功能。点我查看更多知识哦:
https://github.com/Zizaco/entrust
给我们的Laravel5.2安装Entrust包
注意: 本节将向你展示如何使用Laravel5.2安装Entrust.如果你使用的是5.0或者5.1版本,那就可以跳过这节。
官方Entrust包目前还不支持Laravel5.2。我们必须安装Entrust一个不同的分支来使它正常工作。当官方Entrust包被更新了,你可以使用官方版本(详情查看下一小节)。
要给Laravel5.2安装Entrust,在composer.json文件中找到require小节,添加如下代码:
"zizaco/entrust": "dev-laravel-5-2@dev"
你的composer.json文件看起来将会是这样的:
"require": {
"php": ">=5.5.9",
"laravel/framework": "5.2.*",
"laravelcollective/html": "5.2.*",
"zizaco/entrust": "dev-laravel-5-2@dev"
},
再添加如下代码:
"repositories": [
{
"type": "vcs",
"url": "https://github.com/hiendv/entrust"
}
],
接下来,运行下composer update命令来安装Entrust.
现在打开config/app.php文件并找到providers数组,添加:
'Zizaco\Entrust\EntrustServiceProvider',
然后再找到aliases数组, 添加:
'Entrust' => 'Zizaco\Entrust\EntrustFacade',
如果你打算使用中间件 (要求Laravel5.1版本以上), 找到app/Http/Kernel.php文件中的routeMiddleware并加上:
'role' => Zizaco\Entrust\Middleware\EntrustRole::class,
'permission' => Zizaco\Entrust\Middleware\EntrustPermission::class,
'ability' => Zizaco\Entrust\Middleware\EntrustAbility::class,
运行以下命令来创建entrust.php文件,这个文件躲在config 目录里呢。
php artisan vendor:publish
你可以在这个文件自定义表名和模型的命名空间哦。
Entrus需要一些数据库表才能正常工作哦,使用以下命令生成数据库迁徙文件:
php artisan entrust:migration
当你要求确认时,选择yes选项。
运行如下命令创建Entrust需要用到的表:
php artisan migrate
现在你已经有四张表了:
roles: 存储角色记录
permissions: 存储权限记录
role_user: 存储角色和用户之间的关系
permission_role: 存储角色和权限之间的关系
接下来,我们需要创建两个模型: Role 和 Permission.
创建一个名为Role.php的新文件,将之放到app目录下。
<?php namespace App;
use Zizaco\Entrust\EntrustRole;
class Role extends EntrustRole
{
}
创建一个名叫Permission.php的新文件,将之放到app文件夹里。
<?php namespace App;
use Zizaco\Entrust\EntrustPermission;
class Permission extends EntrustPermission
{
}
最后,打开app/User.php文件并修改它:
<?php
namespace App;
use Illuminate\Foundation\Auth\User as Authenticatable;
class User extends Authenticatable
{
修改后变成如下:
<?php
namespace App;
use Illuminate\Auth\Authenticatable;
use Illuminate\Database\Eloquent\Model;
use Illuminate\Auth\Passwords\CanResetPassword;
use Illuminate\Contracts\Auth\Authenticatable as AuthenticatableContract;
use Illuminate\Contracts\Auth\CanResetPassword as CanResetPasswordContract;
use Zizaco\Entrust\Traits\EntrustUserTrait;
class User extends Model implements AuthenticatableContract, CanResetPasswordContract
{
use Authenticatable, CanResetPassword;
use EntrustUserTrait;
让我们来看看这行代码:
use EntrustUserTrait;
这是Entrust特性。当你把在User模型中添加Entrust特性时,你可以使用如下方法:roles(),hasRole( name)∗∗,∗∗can( permission), 和‘能力’( roles, permissions, $options).
你应该有类型于如下的内容:
<?php
namespace App;
use Illuminate\Auth\Authenticatable;
use Illuminate\Database\Eloquent\Model;
use Illuminate\Auth\Passwords\CanResetPassword;
use Illuminate\Contracts\Auth\Authenticatable as AuthenticatableContract;
use Illuminate\Contracts\Auth\CanResetPassword as CanResetPasswordContract;
use Zizaco\Entrust\Traits\EntrustUserTrait;
class User extends Model implements AuthenticatableContract, CanResetPasswordContract
{
use Authenticatable, CanResetPassword;
use EntrustUserTrait;
/**
* The database table used by the model.
*
* @var string
*/
protected $table = 'users';
当你添加一个新文件,你需要运行如下命令来重建class map:
composer dump-autoload
干的漂亮!你已经给Laravel5.2安装上Entrust包了!
创建Entrust角色
一旦安装好Entrust包后,我们可以创建用户角色了。加上我们有俩角色: 管理者 和 会员.
作为练习,我们创建一个简单的创建角色表单:
首先,编辑routes.php文件再更新管理模块路由组如下:
Route::group(array('prefix' => 'admin', 'namespace' => 'Admin', 'middleware' => 'manager'), function () {
Route::get('users', [ 'as' => 'admin.user.index', 'uses' => 'UsersController@index']);
Route::get('roles', 'RolesController@index');
Route::get('roles/create', 'RolesController@create');
Route::post('roles/create', 'RolesController@store');
});
我们定义了供我们访问和创建角色的路由。
故技重施,使用如下命令创建RolesController控制器:
php artisan make:controller Admin/RolesController
打开RolesController.php文件,更新create action 如下:
public function create()
{
return view('backend.roles.create');
}
创建一个新roles文件夹,将之放到views/backend目录下。然后在创建一个叫做create的视图:
@extends('master')
@section('title', 'Create A New Role')
@section('content')
<div class="container col-md-8 col-md-offset-2">
<div class="well well bs-component">
<form class="form-horizontal" method="post">
@foreach ($errors->all() as $error)
<p class="alert alert-danger">{{ $error }}</p>
@endforeach
@if (session('status'))
<div class="alert alert-success">
{{ session('status') }}
</div>
@endif
<input type="hidden" name="_token" value="{!! csrf_token() !!}">
<fieldset>
<legend>Create a new role</legend>
<div class="form-group">
<label for="name" class="col-lg-2 control-label">Name</label>
<div class="col-lg-10">
<input type="text" class="form-control" id="name" name="name">
</div>
</div>
<div class="form-group">
<label for="display_name" class="col-lg-2 control-label">Display Name</label>
<div class="col-lg-10">
<input type="display_name" class="form-control" id="display_name" name="display_name">
</div>
</div>
<div class="form-group">
<label for="description" class="col-lg-2 control-label">Description</label>
<div class="col-lg-10">
<textarea class="form-control" rows="3" id="description" name="description"></textarea>
</div>
</div>
<div class="form-group">
<div class="col-lg-10 col-lg-offset-2">
<button type="reset" class="btn btn-default">Cancel</button>
<button type="submit" class="btn btn-primary">Submit</button>
</div>
</div>
</fieldset>
</form>
</div>
</div>
@endsection
访问http://homestead.app/admin/roles/create就可以看到我们应该有一个很棒的角色控制表单了。
接下来,让我们更新RolesController中的store action用来存储数据。
public function store(RoleFormRequest $request)
{
$role = new Role(array(
'name' => $request->get('name'),
'display_name' => $request->get('display_name'),
'description' => $request->get('description')
));
$role->save();
return redirect('/admin/roles/create')->with('status', 'A new role has been created!');
}
这里我们使用RoleFormRequest来验证表单,但是我们至今还没有请求文件。先创建它吧:
php artisan make:request RoleFormRequest
打开并找到:
public function authorize()
{
return false;
}
更改为:
public function authorize()
{
return true;
}
这里就是验证规则:
public function rules()
{
return [
'name' => 'required',
];
}
这个Display Name和Description字段是可选的。我们只需要用户输入角色的名字(Name字段).
Role和RoleFormRequest,修改代码如下:
use App\Role;
use Illuminate\Http\Request;
use App\Http\Requests;
use App\Http\Controllers\Controller;
use App\Http\Requests\RoleFormRequest;
最后,打开Role.php添加:
class Role extends EntrustRole
{
protected $fillable = ['name', 'display_name', 'description'];
}
$fillable属性使列可分配赋值。
现在,跳往http://homestead.app/admdin/roles/create然后创建连个角色:manager和member.
为了预览所有的角色,更新RolesController文件中的index action如下:
public function index()
{
$roles = Role::all();
return view('backend.roles.index', compact('roles'));
}
再在views/backend/roles/index.blade.php文件中创建一个新的index视图:
@extends('master')
@section('title', 'All roles')
@section('content')
<div class="container col-md-8 col-md-offset-2">
<div class="panel panel-default">
<div class="panel-heading">
<h2> All roles </h2>
</div>
@if (session('status'))
<div class="alert alert-success">
{{ session('status') }}
</div>
@endif
@if ($roles->isEmpty())
<p> There is no role.</p>
@else
<table class="table">
<thead>
<tr>
<th>Name</th>
<th>Display Name</th>
<th>Description</th>
</tr>
</thead>
<tbody>
@foreach($roles as $role)
<tr>
<td>{!! $role->name !!}</td>
<td>{!! $role->display_name !!}</td>
<td>{!! $role->description !!}</td>
</tr>
@endforeach
</tbody>
</table>
@endif
</div>
</div>
@endsection
跳到http://homestead.app/admin/roles. 你就应该可以看到角色列表了。
给用户分配角色
在本节中,我们将会学习如何编辑用户和给他们分配角色。
让我们从给我们的管理路由组添加如下路由开始吧:
Route::get('users/{id?}/edit', 'UsersController@edit');
Route::post('users/{id?}/edit','UsersController@update');
接下来,打开users/index视图然后找到:
<a href="#">{!! $user->name !!} </a>
更新链接为:
<a href="{!! action('Admin\UsersController@edit', $user->id) !!}">{!! $user->name !!} </a>
打开UsersController, 更新edit action:
public function edit($id)
{
$user = User::whereId($id)->firstOrFail();
$roles = Role::all();
$selectedRoles = $user->roles->lists('id')->toArray();
return view('backend.users.edit', compact('user', 'roles', 'selectedRoles'));
}
我们需要做的就是使用用户ID找到一个正确的用户,再列出所有的角色供用户选择。
$selectedRoles是一个包含了用户目前所属角色的数组。
正如你所看到的,我们在这里使用Role模型。不要忘了在UsersController顶部添加如下代码:
use App\Role;
以下是users/edit视图:
@extends('master')
@section('name', 'Edit a user')
@section('content')
<div class="container col-md-6 col-md-offset-3">
<div class="well well bs-component">
<form class="form-horizontal" method="post">
@foreach ($errors->all() as $error)
<p class="alert alert-danger">{{ $error }}</p>
@endforeach
@if (session('status'))
<div class="alert alert-success">
{{ session('status') }}
</div>
@endif
{!! csrf_field() !!}
<fieldset>
<legend>Edit user</legend>
<div class="form-group">
<label for="name" class="col-lg-2 control-label">Name</label>
<div class="col-lg-10">
<input type="text" class="form-control" id="name" placeholder="Name" name="name"
value="{{ $user->name }}">
</div>
</div>
<div class="form-group">
<label for="email" class="col-lg-2 control-label">Email</label>
<div class="col-lg-10">
<input type="email" class="form-control" id="email" placeholder="Email" name="email"
value="{{ $user->email }}">
</div>
</div>
<div class="form-group">
<label for="select" class="col-lg-2 control-label">Role</label>
<div class="col-lg-10">
<select class="form-control" id="role" name="role[]" multiple>
@foreach($roles as $role)
<option value="{!! $role->id !!}" @if(in_array($role->id, $selectedRoles))
selected="selected" @endif >{!! $role->display_name !!}
</option>
@endforeach
</select>
</div>
</div>
<div class="form-group">
<label for="password" class="col-lg-2 control-label">Password</label>
<div class="col-lg-10">
<input type="password" class="form-control" name="password">
</div>
</div>
<div class="form-group">
<label for="password" class="col-lg-2 control-label">Confirm password</label>
<div class="col-lg-10">
<input type="password" class="form-control" name="password_confirmation">
</div>
</div>
<div class="form-group">
<div class="col-lg-10 col-lg-offset-2">
<button type="reset" class="btn btn-default">Cancel</button>
<button type="submit" class="btn btn-primary">Submit</button>
</div>
</div>
</fieldset>
</form>
</div>
</div>
@endsection
让我们来看看这段代码吧:
<select class="form-control" id="role" name="role[]" multiple>
@foreach($roles as $role)
<option value="{!! $role->id !!}" @if(in_array($role->id, $selectedRoles)) selected="selected" @endif >
{!! $role->display_name !!}
</option>
@endforeach
</select>
这个视图将会给我们展示一个多选框。我们使用@foreach来遍历$roles和显示所有的可选项。
为了展示已选项,我们使用in_array函数来检查selectedRoles数组是否包含了role的id。
保存更改,然后跳转到http://homestead.app/admin/users。点击 你想要编辑的那个用户的名字。
如果你现在点击Submit按钮,不会有任何反应的。我们必须编辑UsersController中的**update**action来保存数据到我们的数据库中:
public function update($id, UserEditFormRequest $request)
{
$user = User::whereId($id)->firstOrFail();
$user->name = $request->get('name');
$user->email = $request->get('email');
$password = $request->get('password');
if($password != "") {
$user->password = Hash::make($password);
}
$user->save();
$user->saveRoles($request->get('role'));
return redirect(action('Admin\UsersController@edit', $user->id))->with('status', 'The user has been updated!');
}
我们使用用户ID找到用户然后使用$user->save()方法保存更改到数据库中。
注意我们是怎么处理密码的:
$password = $request->get('password');
if($password != "") {
$user->password = Hash::make($password);
}
首先要确认下密码是否为空。只有当用户输入了新密码时我们才保存它,使用如下代码:
$user->password = Hash::make($password);
这段代码将会创建一个已被hash的密码。在将密码保存到数据库之前,记住一定要先使用Hash::make()方法将他hash好。
想了解更多,请访问:
http://laravel.com/docs/master/hashing#introduction
我们也需要包含Hash facade和UserEditFormRequest. 找到:
class UsersController extends Controller
在其上添加如下代码:
use App\Http\Requests\UserEditFormRequest;
use Illuminate\Support\Facades\Hash;
你可能发现这里有一个新的saveRoles()方法:
$user->saveRoles($request->get('role'));
很不幸的是,Entrust没有任何方法可以自动同步(联系和拆分)多个角色。因此,我们必须创建一个新的saveRoles方法来处理这个场景:
打开User.php模型,添加:
public function saveRoles($roles)
{
if(!empty($roles))
{
$this->roles()->sync($roles);
} else {
$this->roles()->detach();
}
}
首先,这个方法将会检索$role数组,就是那个包含了角色ID的数组,然后将合适的角色和用户联系起来。如果这里并没有角色,它将会从用户那里拆分出角色来。
和往常一样,我们使用以下命令生成UserEditFormRequest:
php artisan make:request UserEditFormRequest
确保将return false改成了return true. 以下是验证规则:
<?php
namespace App\Http\Requests;
use App\Http\Requests\Request;
class UserEditFormRequest extends Request
{
public function authorize()
{
return true;
}
public function rules()
{
return [
'name' => 'required',
'email'=> 'required',
'role'=> 'required',
'password'=>'alpha_num|min:6|confirmed',
'password_confirmation'=>'alpha_num|min:6',
];
}
}
棒棒哒!现在试试给用户分配一个角色呗!
限制用户访问管理用户模块
很明显,我们并不希望任何人都可以随便地访问管理模块,除了我们的管理员。 打开我们的Manager中间件:
public function handle($request, Closure $next)
{
return $next($request);
}
正如你所看到的,这里并没有过滤器,任何人都可以访问我们的管理模块。
有很多种方法限制访问,但是最最简单的就是使用Auth facade:
<?php
namespace App\Http\Middleware;
use Closure;
use Illuminate\Support\Facades\Auth;
class Manager
{
public function handle($request, Closure $next)
{
if(!Auth::check()) {
return redirect('users/login');
} else {
$user = Auth::user();
if($user->hasRole('manager'))
{
return $next($request);
} else {
return redirect('/home');
}
}
}
}
让我们把上面的代码一行一行的看下来。
首先,我们告诉Laravel我们想要使用Auth facade:
use Illuminate\Support\Facades\Auth;
然后我们使用Auth:check()方法来检查用户是否已经登陆了。如果没有,就将用户重定向到登陆页。
if(!Auth::check()) {
return redirect('users/login');
} else {
否则,我们使用Auth::user()方法来检索已经认证通过的用户。通过这个方法,我们可以检查用户是否是管理员角色。如果不是,用户将会被重定向到主页。
$user = Auth::user();
if($user->hasRole('manager'))
{
return $next($request);
} else {
return redirect('/home');
}
现在,试试访问管理模块来测试一下我们的管理员中间件。如果你没登陆或者并不是管理员,你将访问不了管理模块路由组的任何一个路由。
创建一个后台管理控制面板页
由于之前我们没有创建后台主页,当我们访问http://homestead.app/admin, 这里将会报一个错:
为了可以轻松地访问到管理模块,现在让我们创建一个后台主页。
更新我们的routes.php文件,在后台管理路由组中添加如下路由:
Route::get('/', 'PagesController@home');
接下来,创建Admin/PagesController:
php artisan make:controller Admin/PagesController
删除所有默认的actions,然后更新Admin/PagesController如下:
<?php
namespace App\Http\Controllers\Admin;
use Illuminate\Http\Request;
use App\Http\Requests;
use App\Http\Controllers\Controller;
class PagesController extends Controller
{
public function home()
{
return view('backend.home');
}
}
最后在backend文件夹下创建一个新的home视图(home.blade.php):
@extends('master')
@section('title', 'Admin Control Panel')
@section('content')
<div class="container">
<div class="row banner">
<div class="col-md-12">
<div class="list-group">
<div class="list-group-item">
<div class="row-action-primary">
<i class="mdi-social-person"></i>
</div>
<div class="row-content">
<div class="action-secondary"><i class="mdi-social-info"></i></div>
<h4 class="list-group-item-heading">Manage User</h4>
<a href="/admin/users" class="btn btn-default btn-raised">All Users</a>
</div>
</div>
<div class="list-group-separator"></div>
<div class="list-group-item">
<div class="row-action-primary">
<i class="mdi-social-group"></i>
</div>
<div class="row-content">
<div class="action-secondary"><i class="mdi-material-info"></i></div>
<h4 class="list-group-item-heading">Manage Roles</h4>
<a href="/admin/roles" class="btn btn-default btn-raised">All Roles</a>
<a href="/admin/roles/create" class="btn btn-primary btn-raised">Create A Role</a>
</div>
</div>
<div class="list-group-separator"></div>
<div class="list-group-item">
<div class="row-action-primary">
<i class="mdi-editor-border-color"></i>
</div>
<div class="row-content">
<div class="action-secondary"><i class="mdi-material-info"></i></div>
<h4 class="list-group-item-heading">Manage Posts</h4>
<a href="/admin/posts" class="btn btn-default btn-raised">All Posts</a>
<a href="/admin/posts/create" class="btn btn-primary btn-raised">Create A Post</a>
</div>
</div>
<div class="list-group-separator"></div>
</div>
</div>
</div>
</div>
@endsection
我在这里添加了几个按钮来访问其他后台页面。你可以随便改成任意你喜欢的布局。
还有一件事,可不可以在我们导航栏添加后台模块的链接让访问后台模块变得更简单呢? 打开shared/navbar视图然后找到:
@if (Auth::check())
<li><a href="/users/logout">Logout</a></li>
@else
更改为:
@if (Auth::check())
@if(Auth::user()->hasRole('manager'))
<li><a href="/admin">Admin</a></li>
@endif
<li><a href="/users/logout">Logout</a></li>
@else
我们也可以在视图中使用hasRole方法来检验用户是否是管理员。
创建一个新的文章
万事俱备只欠东风,在本节中我们将会搭建一个表单来创建博客的文章。
实际上,这个流程和我们之前创建用户、票、角色的流程很相似。
开始之前,让我们创建一个新的post模型和他的数据库迁徙文件:
php artisan make:model Post -m
与此同时你可以在后面添加-m选项来生成文章的数据库迁徙文件。
打开migrations文件夹下的timestamp_create_posts_table.php文件,更新up方法如下:
public function up()
{
Schema::create('posts', function (Blueprint $table) {
$table->increments('id');
$table->string('title', 255);
$table->text('content');
$table->string('slug')->nullable();
$table->tinyInteger('status')->default(1);
$table->integer('user_id');
$table->timestamps();
});
}
不要忘记运行migrate命令来向数据库中添加新的posts表和它的列:
php artisan migrate
打开routes.php文件,在后台路由组中添加如下路由:
Route::get('posts', 'PostsController@index');
Route::get('posts/create', 'PostsController@create');
Route::post('posts/create', 'PostsController@store');
Route::get('posts/{id?}/edit', 'PostsController@edit');
Route::post('posts/{id?}/edit','PostsController@update');
运行以下命令来创建一个新的Admin/PostsController:
php artisan make:controller Admin/PostsController
打开Admin/PostsController, 更新create action如下:
public function create()
{
return view('backend.posts.create');
}
在backend目录下创建一个新posts文件夹。把新的create 视图放在这里面:
@extends('master')
@section('title', 'Create A New Post')
@section('content')
<div class="container col-md-8 col-md-offset-2">
<div class="well well bs-component">
<form class="form-horizontal" method="post">
@foreach ($errors->all() as $error)
<p class="alert alert-danger">{{ $error }}</p>
@endforeach
@if (session('status'))
<div class="alert alert-success">
{{ session('status') }}
</div>
@endif
<input type="hidden" name="_token" value="{!! csrf_token() !!}">
<fieldset>
<legend>Create a new post</legend>
<div class="form-group">
<label for="title" class="col-lg-2 control-label">Title</label>
<div class="col-lg-10">
<input type="text" class="form-control" id="title" placeholder="Title" name="title">
</div>
</div>
<div class="form-group">
<label for="content" class="col-lg-2 control-label">Content</label>
<div class="col-lg-10">
<textarea class="form-control" rows="3" id="content" name="content"></textarea>
</div>
</div>
<div class="form-group">
<div class="col-lg-10 col-lg-offset-2">
<button type="reset" class="btn btn-default">Cancel</button>
<button type="submit" class="btn btn-primary">Submit</button>
</div>
</div>
</fieldset>
</form>
</div>
</div>
@endsection
当我们访问http://homestead.app/admin/posts/create, 我们可以看到一个新文章的创建表单。
然而,我们需要创建文章类别。文章类别可以允许用户为他们创建的文章选择合适的分类。
运行以下命令来创建新的Category模型和它的数据库迁徙文件:
php artisan make:model Category -m
接下来打开timestamp_create_categories_table.php更新up方法如下:
public function up()
{
Schema::create('categories', function (Blueprint $table) {
$table->increments('id');
$table->string('name', 255);
$table->timestamps();
});
}
以上代码将会创建一个名为name的新列来存储分类的名字。
创建一个多对多的关系
正如你所知道的,一篇文章会从属很多分类,一种分类下也会有很多文章。这就是所谓的多对多关系。
多对多关系需要一个中间表(又名枢纽表)才能起作用。这个表有两列分别保存这两个关联模型的外键。比如,role_user表是一个枢纽表,是由users_id和role_id组成的。
这个文章和分类表的枢纽表应该叫做category_post表 (按字母排序)。当我们使用Entrust时,role_user表将会自动创建,但是我们必须手动创建category_post表。
运行以下Artisan命令生成category_post数据库迁徙文件:
php artisan make:migration create_category_post_table --create=category_post
接下来打开新的timestamp_create_category_post_table 数据库迁徙文件并且修改up方法:
public function up()
{
Schema::create('category_post', function (Blueprint $table) {
$table->increments('id')->unsigned();
$table->integer('post_id')->unsigned()->index();
$table->integer('category_id')->unsigned()->index();
$table->timestamps();
$table->foreign('category_id')
->references('id')->on('categories')
->onUpdate('cascade')
->onDelete('cascade');
$table->foreign('post_id')
->references('id')->on('posts')
->onUpdate('cascade')
->onDelete('cascade');
});
}
保存更改,然后运行migrate命令:
php artisan migrate
检查你的数据库,确保你已经拥有所有的表: posts, categories和category_post.
另外,你可以使用Laravel 5 Extended Generators包来更快的生成枢纽表:
https://github.com/laracasts/Laravel-5-Generators-Extended
太棒了!现在打开我们的Post模型然后修改内容如下:
<?php
namespace App;
use Illuminate\Database\Eloquent\Model;
class Post extends Model
{
protected $guarded = ['id'];
public function categories()
{
return $this->belongsToMany('App\Category')->withTimestamps();
}
}
我们使用$guarded属性来给每一列分配名字,除了ID列以外(其他都是可分配的)。
belongsToMany方法被用来告知Laravel这模型是一个多对多的关系。
打开我们的Category模型然后修改内容如下:
<?php
namespace App;
use Illuminate\Database\Eloquent\Model;
class Category extends Model
{
protected $guarded = ['id'];
public function posts()
{
return $this->belongsToMany('App\Post')->withTimestamps();
}
}
你已经成功地定义了一个多对多的关系。
创建和预览分类
让我们创建一个可以用来创建分类的表单吧。打开routes.php,添加如下路由至后台路由组中:
Route::get('categories', 'CategoriesController@index');
Route::get('categories/create', 'CategoriesController@create');
Route::post('categories/create', 'CategoriesController@store');
运行以下命令生成一个新的CategoriesController:
php artisan make:controller Admin/CategoriesController
打开刚刚新创建的CategoriesController并修改其中的create action:
public function create()
{
return view('backend.categories.create');
}
和平常一样,在backend文件夹中创建一个categories 文件夹。将一个新的create视图放到categories目录中:
@extends('master')
@section('title', 'Create A New Category')
@section('content')
<div class="container col-md-8 col-md-offset-2">
<div class="well well bs-component">
<form class="form-horizontal" method="post">
@foreach ($errors->all() as $error)
<p class="alert alert-danger">{{ $error }}</p>
@endforeach
@if (session('status'))
<div class="alert alert-success">
{{ session('status') }}
</div>
@endif
<input type="hidden" name="_token" value="{!! csrf_token() !!}">
<fieldset>
<legend>Create a new category</legend>
<div class="form-group">
<label for="name" class="col-lg-2 control-label">Name</label>
<div class="col-lg-10">
<input type="text" class="form-control" id="name" name="name">
</div>
</div>
<div class="form-group">
<div class="col-lg-10 col-lg-offset-2">
<button type="reset" class="btn btn-default">Cancel</button>
<button type="submit" class="btn btn-primary">Submit</button>
</div>
</div>
</fieldset>
</form>
</div>
</div>
@endsection
访问http://homestead.app/admin/categories/create, 你应该可以看到一个创建新分类的表单:
干得漂亮!现在再次打开CategoriesController, 修改store action如下:
public function store(CategoryFormRequest $request)
{
$category = new Category(array(
'name' => $request->get('name'),
));
$category->save();
return redirect('/admin/categories/create')->with('status', 'A new category has been created!');
}
告诉Laravel你想使用CategoryFormRequest和Category 模型:
use App\Category;
use App\Http\Requests\CategoryFormRequest;
运行以下命令生成一个新的CategoryFormRequest文件:
php artisan make:request CategoryFormRequest
修改authorize方法和rules方法,如下:
public function authorize()
{
return true;
}
public function rules()
{
return [
'name'=> 'required|min:3',
];
}
保存更改,再次访问分类表单。创建一些新的分类(新闻,Laravel, 公告):
要预览所有的分类,再次打开CategoriesController,修改index action:
public function index()
{
$categories = Category::all();
return view('backend.categories.index', compact('categories'));
}
创建一个新index视图并将它放在categories文件夹中:
@extends('master')
@section('title', 'All categories')
@section('content')
<div class="container col-md-8 col-md-offset-2">
<div class="panel panel-default">
<div class="panel-heading">
<h2> All categories </h2>
</div>
@if (session('status'))
<div class="alert alert-success">
{{ session('status') }}
</div>
@endif
@if ($categories->isEmpty())
<p> There is no category.</p>
@else
<table class="table">
<tbody>
@foreach($categories as $category)
<tr>
<td>{!! $category->name !!}</td>
</tr>
@endforeach
</tbody>
</table>
@endif
</div>
</div>
@endsection
现在你可以在http://homestead.app/admin/categories中预览所有分分类了。
让我们修改后台主页(控制面板)来包含分类链接。打开backend/home视图:
找到(文件末尾):
<a href="/admin/posts/create" class="btn btn-primary btn-raised">Create A Post</a>
</div>
</div>
<div class="list-group-separator"></div>
在下面追加如下内容:
<div class="list-group-item">
<div class="row-action-primary">
<i class="mdi-file-folder"></i>
</div>
<div class="row-content">
<div class="action-secondary"><i class="mdi-material-info"></i></div>
<h4 class="list-group-item-heading">Manage Categories</h4>
<a href="/admin/categories" class="btn btn-default btn-raised">All Categories</a>
<a href="/admin/categories/create" class="btn btn-primary btn-raised">New Category</a>
</div>
</div>
<div class="list-group-separator"></div>
我们新后台主页就变成这样了:
创建文章的时候选择分类
现在我们可以在创建新文章的时候选择分类了。
打开PostsController并找到:
class PostsController extends Controller
在上面添加:
use App\Category;
use App\Post;
use Illuminate\Support\Str;
更改create action如下:
public function create()
{
$categories = Category::all();
return view('backend.posts.create', compact('categories'));
}
接下来,打开posts/create视图找到:
<div class="form-group">
<label for="content" class="col-lg-2 control-label">Content</label>
<div class="col-lg-10">
<textarea class="form-control" rows="3" id="content" name="content"></textarea>
</div>
</div>
添加如下代码,允许用户选择分类:
<div class="form-group">
<label for="categories" class="col-lg-2 control-label">Categories</label>
<div class="col-lg-10">
<select class="form-control" id="category" name="categories[]" multiple>
@foreach($categories as $category)
<option value="{!! $category->id !!}">
{!! $category->name !!}
</option>
@endforeach
</select>
</div>
</div>
现在我们有了一个漂亮的用来创建文章的新表单!
现在让我们创建一个PostFormRequest:
php artisan make:request PostFormRequest
修改PostFormRequest中的authorize方法和rules方法 如下:
public function authorize()
{
return true;
}
public function rules()
{
return [
'title' => 'required',
'content'=> 'required',
'categories' => 'required',
];
}
最后,再次打开PostController找到:
class PostsController extends Controller
添加如下代码以便在这个控制器中使用PostFormRequest:
use App\Http\Requests\PostFormRequest;
接下来,更新store action:
public function store(PostFormRequest $request)
{
$post= new Post(array(
'title' => $request->get('title'),
'content' => $request->get('content'),
'slug' => Str::slug($request->get('title'), '-'),
));
$post->save();
$post->categories()->sync($request->get('categories'));
return redirect('/admin/posts/create')->with('status', 'The post has been created!');
}
要创建文章的块,我们可以使用Str::slug方法来从给定的文章标题自动生成一个友好的块。
在使用$post->save保存文章的数据到数据库之后,我们可以使用:
$post->categories()->sync($request->get('categories'));
将已选的分类和文章联系起来。
现在试着创建一个新的文章咯。
要要要切克闹!你已经创建了你博客的第一篇文章!
预览和编辑文章
既然你知道怎么创建一篇文章,那么接下来就是创建页面来预览和编辑他们吧。
显示所有的文章
作为提醒,我们在之前的小节中已经设置了这个路由了:
Route::get('posts', 'PostsController@index');
我们可以修改PostController中的index action如下:
public function index()
{
$posts = Post::all();
return view('backend.posts.index', compact('posts'));
}
接着在backend/posts/index.blade.php中创建一个新的index视图:
@extends('master')
@section('title', 'All posts')
@section('content')
<div class="container col-md-8 col-md-offset-2">
<div class="panel panel-default">
<div class="panel-heading">
<h2> All posts </h2>
</div>
@if (session('status'))
<div class="alert alert-success">
{{ session('status') }}
</div>
@endif
@if ($posts->isEmpty())
<p> There is no post.</p>
@else
<table class="table">
<thead>
<tr>
<th>ID</th>
<th>Title</th>
<th>Slug</th>
<th>Created At</th>
<th>Updated At</th>
</tr>
</thead>
<tbody>
@foreach($posts as $post)
<tr>
<td>{!! $post->id !!}</td>
<td>
<a href="#">{!! $post->title !!} </a>
</td>
<td>{!! $post->slug !!}</td>
<td>{!! $post->created_at !!}</td>
<td>{!! $post->updated_at !!}</td>
</tr>
@endforeach
</tbody>
</table>
@endif
</div>
</div>
@endsection
前往http://homestead.app/admin/posts预览所有的文章。
编辑文章
我们也早已定义了这些路由:
Route::get('posts/{id?}/edit', 'PostsController@edit');
Route::post('posts/{id?}/edit','PostsController@update');
先修改edit action来显示文章的编辑表单:
public function edit($id)
{
$post = Post::whereId($id)->firstOrFail();
$categories = Category::all();
$selectedCategories = $post->categories->lists('id')->toArray();;
return view('backend.posts.edit', compact('post', 'categories', 'selectedCategories'));
}
在backend/posts/edit.blade.php中创建一个新edit视图:
@extends('master')
@section('title', 'Edit A Post')
@section('content')
<div class="container col-md-8 col-md-offset-2">
<div class="well well bs-component">
<form class="form-horizontal" method="post">
@foreach ($errors->all() as $error)
<p class="alert alert-danger">{{ $error }}</p>
@endforeach
@if (session('status'))
<div class="alert alert-success">
{{ session('status') }}
</div>
@endif
<input type="hidden" name="_token" value="{!! csrf_token() !!}">
<fieldset>
<legend>Edit a post</legend>
<div class="form-group">
<label for="title" class="col-lg-2 control-label">Title</label>
<div class="col-lg-10">
<input type="text" class="form-control" id="title" placeholder="Title" name="title" value="{{ $post->title }}">
</div>
</div>
<div class="form-group">
<label for="content" class="col-lg-2 control-label">Content</label>
<div class="col-lg-10">
<textarea class="form-control" rows="3" id="content" name="content">{!! $post->content !!}</textarea>
</div>
</div>
<div class="form-group">
<label for="select" class="col-lg-2 control-label">Categories</label>
<div class="col-lg-10">
<select class="form-control" id="categories" name="categories[]" multiple>
@foreach($categories as $category)
<option value="{!! $category->id !!}" @if(in_array($category->id, $selectedCategories))
selected="selected" @endif >{!! $category->name !!}
</option>
@endforeach
</select>
</div>
</div>
<div class="form-group">
<div class="col-lg-10 col-lg-offset-2">
<button type="reset" class="btn btn-default">Cancel</button>
<button type="submit" class="btn btn-primary">Submit</button>
</div>
</div>
</fieldset>
</form>
</div>
</div>
@endsection
创建表单后,你需要修改posts/index视图给文章添加链接。
找到:
<a href="#">{!! $post->title !!} </a>
改成:
<a href="{!! action('Admin\PostsController@edit', $post->id) !!}">{!! $post->title !!} </a>
跳往http://homestead.app/admin/posts并点击文章标题,你应该能够看到一个文章编辑表单了:
和往常一样,运行以下命令生成创建新PostEditFormRequest:
php artisan make:request PostEditFormRequest
修改authorize方法和rules方法如下:
public function authorize()
{
return true;
}
public function rules()
{
return [
'title' => 'required',
'content'=> 'required',
'categories' => 'required',
];
}
保存文件,打开PostsController并找到:
class PostsController extends Controller
在上面添加:
use App\Http\Requests\PostEditFormRequest;
最后,修改update方法来保存更改到我们的数据库:
public function update($id, PostEditFormRequest $request)
{
$post = Post::whereId($id)->firstOrFail();
$post->title = $request->get('title');
$post->content = $request->get('content');
$post->slug = Str::slug($request->get('title'), '-');
$post->save();
$post->categories()->sync($request->get('categories'));
return redirect(action('Admin\PostsController@edit', $post->id))->with('status', 'The post has been updated!');
}
为了替代创建新的saveCategories方法(和saveRoles方法类似), 我们可以如下一样同步分类:
$post->categories()->sync($request->get('categories'));
现在试试编辑文章并且提交更改。
干得漂亮!一旦提交后,你应该刷新一篇文章来看看!
显示所有的博客文章
在本节中,我们将会创建一个列出所有博客文章的页面。这个页面是公共的,每个人都可以访问。
添加如下代码到routes.php中来注册博客路由:
Route::get('/blog', 'BlogController@index');
我们应该创建一个BlogController:
php artisan make:controller BlogController
添加如下代码到BlogController中:
找到:
class BlogController extends Controller
告诉Laravel你想在这个控制器中使用Post模型。添加如下代码:
use App\Post;
现在,更改index action:
public function index()
{
$posts = Post::all();
return view('blog.index', compact('posts'));
}
在views/blog目录下创建一个新index视图。views/blog/index.blade.php文件内容如下:
@extends('master')
@section('title', 'Blog')
@section('content')
<div class="container col-md-8 col-md-offset-2">
@if (session('status'))
<div class="alert alert-success">
{{ session('status') }}
</div>
@endif
@if ($posts->isEmpty())
<p> There is no post.</p>
@else
@foreach ($posts as $post)
<div class="panel panel-default">
<div class="panel-heading">{!! $post->title !!}</div>
<div class="panel-body">
{!! mb_substr($post->content,0,500) !!}
</div>
</div>
@endforeach
@endif
</div>
@endsection
我们使用mb_substr(多字节字符串)函数来只显示文章的500个字符。
如果你想了解函数的具体信息,访问:
http://php.net/manual/en/function.mb-substr.php
我们应该给我们的导航栏添加一个blog链接使访问博客页时更快。 打开shared/navbar.blade.php找到:
<li><a href="/about">About</a></li>
在上面添加:
<li><a href="/blog">Blog</a></li>
回到你的浏览器并访问你的博客页。
现在,每个人都可以看到你的博客文章了!
显示单条博客文章
当我们点击文章的标题时,我们可以看到文章的全部内容。
打开routes.php,注册这条路由:
Route::get('/blog/{slug?}', 'BlogController@show');
在更改show方法之前,让我们思考一下如何实现博客文章评论的特性。
注意:我假设你已经创建了一个Comment模型。如果没有,请你阅读第3章来了解如何实现评论特性。
通常,我们必须创建不同的评论模型(比如PostComment)来列出文章的评论。这意味着我们有两行:comments 和 postcomments. 非常不幸的,通过定义多态关系,仅仅通过单个comments表我们就可以存储票的评论和文章的评论!
使用多态关系
你可以通过这里来了解更多的关系:
http://laravel.com/docs/master/eloquent-relationships#many-to-many-polymorphic-relations
为了搭建这个关系,我们的comments表必须有两个列: post_id和post_type. post_id列包含了票或者文章的ID,而post_type包含了自己模型的名字(App\Ticket或App\Post).
到目前为止,comments表只有post_id列还没有post_type列。让我们创建一个数据库迁徙来添加这列到我们的comments表中吧:
php artisan make:migration add_post_type_to_comments_table --table=comments
以上命令将会创建一个叫做timestamp_add_post_type_to_comments_table.php的文件在你的migrations目录中。
打开并修改文件如下:
public function up()
{
if(Schema::hasColumn('comments', 'post_type')) {
} else {
Schema::table('comments', function (Blueprint $table) {
$table->string('post_type')->nullable();
});
}
}
public function down()
{
Schema::table('comments', function (Blueprint $table) {
$table->dropColumn('post_type');
});
}
我检查一下comments表是否已经有了post_type列。如果没有,我们添加post_type列到表中。
不要忘了运行这条migrate命令哦:
php artisan migrate
到此我们comments表已经有post_type列了。
现在该搭建多态关系了。打开Comment模型,更新如下:
<?php
namespace App;
use Illuminate\Database\Eloquent\Model;
class Comment extends Model
{
protected $guarded = ['id'];
public function post()
{
return $this->morphTo();
}
}
post()方法将会获取已拥有的模型。
打开Ticket模型,更新comments()方法如下:
<?php
namespace App;
use Illuminate\Database\Eloquent\Model;
class Ticket extends Model
{
protected $fillable = ['title', 'content', 'slug', 'status', 'user_id'];
public function comments()
{
return $this->morphMany('App\Comment', 'post');
}
}
打开Post模型,更新代码如下:
<?php
namespace App;
use Illuminate\Database\Eloquent\Model;
class Post extends Model
{
protected $guarded = ['id'];
public function categories()
{
return $this->belongsToMany('App\Category')->withTimestamps();
}
public function comments()
{
return $this->morphMany('App\Comment', 'post');
}
}
comments()方法将会获取所有文章的评论。
打完收工!你已经定义了多态关系了。
现在,打开BlogController并更新show方法:
public function show($slug)
{
$post = Post::whereSlug($slug)->firstOrFail();
$comments = $post->comments()->get();
return view('blog.show', compact('post', 'comments'));
}
以下是blog/show视图:
@extends('master')
@section('title', 'View a post')
@section('content')
<div class="container col-md-8 col-md-offset-2">
<div class="well well bs-component">
<div class="content">
<h2 class="header">{!! $post->title !!}</h2>
<p> {!! $post->content !!} </p>
</div>
<div class="clearfix"></div>
</div>
@foreach($comments as $comment)
<div class="well well bs-component">
<div class="content">
{!! $comment->content !!}
</div>
</div>
@endforeach
<div class="well well bs-component">
<form class="form-horizontal" method="post" action="/comment">
@foreach($errors->all() as $error)
<p class="alert alert-danger">{{ $error }}</p>
@endforeach
@if(session('status'))
<div class="alert alert-success">
{{ session('status') }}
</div>
@endif
<input type="hidden" name="_token" value="{!! csrf_token() !!}">
<input type="hidden" name="post_id" value="{!! $post->id !!}">
<input type="hidden" name="post_type" value="App\Post">
<fieldset>
<legend>Comment</legend>
<div class="form-group">
<div class="col-lg-12">
<textarea class="form-control" rows="3" id="content" name="content"></textarea>
</div>
</div>
<div class="form-group">
<div class="col-lg-10 col-lg-offset-2">
<button type="reset" class="btn btn-default">Cancel</button>
<button type="submit" class="btn btn-primary">Post</button>
</div>
</div>
</fieldset>
</form>
</div>
</div>
@endsection
注意,我们添加了一个新的名为post_type隐藏表单字段。因为这是文章的评论,这个字段的值是:“App\Post” (类名).
打开CommentsController找到:
$comment = new Comment(array(
'post_id' => $request->get('post_id'),
'content' => $request->get('content'),
));
添加post_type来保存它的值到数据库中:
$comment = new Comment(array(
'post_id' => $request->get('post_id'),
'content' => $request->get('content'),
'post_type' => $request->get('post_type')
));
最后,打开ticket/show视图找到:
<input type="hidden" name="post_id" value="{!! $ticket->id !!}">
在下面添加如下代码:
<input type="hidden" name="post_type" value="App\Ticket">
我们添加一个新的名为post_type隐藏字段,它将会拥有“App\Ticket”的值,为了让Laravel知道这个视图里的这个字段是数据 Ticket模型的。
还有一件事需要做,打开blog/index视图找到:
<div class="panel-heading">{!! $post->title !!}</div>
修改为:
<div class="panel-heading"><a href="{!! action('BlogController@show', $post->slug) !!}">{!! $post->title !!}</a></div>
现在,跳到你的浏览器然后预览一篇文章。再发表一个评论试试。
让我们检查一下数据库来看看是怎么工作的:
正如你说看到的,尽管comments已经有了一个同样的post_id (比如都是2), 但是他们的post_type值却不一样哦。ORM使用post_type列来确定哪个模型和comments关联起来。
这个理解起来有一丢丢的混乱。然而,如果你知道怎么使用多态关系,你将会受益匪浅呀。
Seeding我们的数据库
相比手动创建测试数据,我们可以使用Laravel的Seeder类来给我们的数据库“种植”数据。在本节中,我们将会学习如何使用Seeder来给我们的用户创建样本用户。
要创建种植器,运行以下命令:
php artisan make:seeder UserTableSeeder
这个命令将会创建一个名为UserTableSeeder的类,将会放置在database/seeds目录中。
public function run()
{
DB::table('users')->insert([
[
'name' => 'Nathan',
'email' => str_random(12).'@email.com',
'password' => bcrypt('yourPassword'),
'created_at' => new DateTime,
'updated_at' => new DateTime,
],
[
'name' => 'David',
'email' => str_random(12).'@email.com',
'password' => bcrypt('yourPassword'),
'created_at' => new DateTime,
'updated_at' => new DateTime,
],
[
'name' => 'Lisa',
'email' => str_random(12).'@email.com',
'password' => bcrypt('yourPassword'),
'created_at' => new DateTime,
'updated_at' => new DateTime,
],
]);
}
在run方法中,我们使用数据库查询构造器来创建假的用户数据。 记住,我们也可以从JSON和CSV文件加载数据。
不仅可以使用Hash facade来hash你的密码,你也可以使用bcrypt()帮助函数来实现这个功能。
点击这里了解更多关于数据库查询构造器 :
http://laravel.com/docs/master/queries
你可以试试一个叫Faker的非常流行的PHP包,可以高效地帮助你伪造数据。
https://github.com/fzaninotto/Faker
写完seeder类之后,打开database/seeds/DatabaseSeeder.php.
public function run()
{
Model::unguard();
// $this->call('UserTableSeeder');
Model::reguard();
}
通常,我们可以创建很多种植器类来种植不同的假数据。比如UserTableSeeder是用来创建假用户的,PostTableSeeder是用来创建假的文章等等。我们使用DatabaseSeeder文件来控制种植器类的顺序。 在这种情况下,我们只有一个UserTableSeeder类,将该类内容修改如下:
public function run()
{
Model::unguard();
$this->call('UserTableSeeder');
Model::reguard();
}
要种植数据,运行一下Artisan命令:
php artisan db:seed
这个将会执行UserTableSeed中的run方法并插入数据到我们的数据库中。
现在检查一下你的数据库或者访问http://homestead.app/admin/users, 这里有了很多新用户:
种植很有用。你可以试着用这个新特性来创建文章,票和其他数据用以测试你的应用。
本地化
你可以使用Laravel本地化的特性将字符串翻译成多种语言。
所有语言文件全部被放置在resources/lang文件夹中。
系统默认的话,这里有一个en (English) 文件夹,它包含了英文字符。如果你想支持其他语言,创建一个和en同级的新文件夹。注意哦,所有的语言目录都应该按照ISO 639-1 Code标准来命令。
阅读更多关于ISO 639-1 Code的信息:
http://www.loc.gov/standards/iso639-2/php/code_list.php
打开en/passwords.php文件:
<?php
return [
'password' => 'Passwords must be at least six characters and match the confirmation.',
'user' => "We can't find a user with that e-mail address.",
'token' => 'This password reset token is invalid.',
'sent' => 'We have e-mailed your password reset link!',
'reset' => 'Your password has been reset!',
];
正如你所看到的,一个语言文件只返回包含翻译字符串的数组。
你可以通过修改如下参数来更改默认语言:
'locale' => 'en',
这个参数可以在config/app.php配置文件中找到。
让我们创建一个新语言文件来翻译我们的应用吧!
前往resources/lang/en文件夹,创建一个名为main.php的新文件,编辑内容如下:
<?php
return [
'subtitle' => 'Fastest way to learn Laravel',
];
接下来打开home视图(views/home.blade.php文件)找到:
<h3 class="text-center margin-top-100 editContent">Building Practical Applications</h3>
更改为:
<h3 class="text-center margin-top-100 editContent">{!! trans('main.subtitle') !!}</h3>
回到你的浏览器:
trans帮助函数被用来从语言文件检索行:
{!! trans('main.subtitle') !!}
main是语言文件的名字(main.php). subtitle是语言行的键名。
正如你所注意到的,我们也使用本地化来管理字符串。比如,将你应用所有的字符串放到main.php文件中;当你想要编辑一个字符串,你可以轻松的找到它。
可不要忘了阅读文档来了解更多关于本地化的知识:
http://laravel.com/docs/master/localization
第四章总结
哎呦,不错哦!你已经成功搭建一个完整的博客应用了!
我们的应用目前还不完美,但是你就把它当做以后创建大应用的练手项目呗。
在本章的结束,让我们温习一下我们学习的内容吧:
你已经知道了怎么验证用户和给你的应用添加限制登陆功能。
现在你更了解路由和路由组了。
使用中间件,你可以高效地处理请求/响应。
很多Laravel应用正在使用Entrust,这是管理角色和权限的最好的包之一。在本书中我们只是用了“角色”特性。试着创建更多的权限来使你的应用更安全。
刚开始理解多对多关系和多态关系确实有点困难,但是之后你会受益匪浅的。
现在你能够种植你的数据库了!种植系唔系很简单?
Laravel本地化特性很简单,但是却灰常有用、强大。试着使用这个特性来管理你应用所有的字符串。
记住一点,这只是开始,我们要学的东西还有很多。
要始终相信有一天你终将完成一个很棒的应用!好好享受这过程咯!
注意: 我将会更新所有的章节来修正错误包括代码和语法。当然更多小节的内容将会被添加进来。如果你乐意给我们反馈,报告bugs,或者有任何疑问,请邮件联系support@learninglaravel.net.多谢多谢!