johnlui / learn-laravel-5 Goto Github PK
View Code? Open in Web Editor NEWLaravel 5 系列入门教程
Home Page: https://github.com/johnlui/Learn-Laravel-5/issues
Laravel 5 系列入门教程
Home Page: https://github.com/johnlui/Learn-Laravel-5/issues
johnlui,你好,看了你的入门教程,收获很大,教程里写的思路也很好,很适合开发者学习,但是有一个小小的疑问,如果有比较负责的业务逻辑的话,比如需要多个Model
的数据进行拼装然后返回给客户端(API
的形式),就像java
中可能会建立一个Service
层处理这些逻辑,在Laravel
中有什么好的建议?
没有数据库文件啊。谢谢
FPM下,Laravel性能一直被诟病,虽有精简的Lumen,但依然会有每次加载文件、初始化资源的开销。
基于Swoole Http Server常驻内存后(替代FPM),可节省这部分开销,同时还可以利用Swoole的很多特效,例如协程、异步IO。
本文是本系列教程的完结篇,我们将一起给 Article 加入评论功能,让游客在前台页面可以查看、提交、回复评论,并完成后台评论管理功能,可以删除、编辑评论。Article 和评论将使用 Laravel Eloquent 提供的“一对多关系”功能大大简化模型间关系的复杂度。最终,我们将得到一个个人博客系统的雏形,并布置一个大作业,供大家实战练习。
本篇文章中我将会使用一些 Laravel 的高级功能,这些高级功能对新手理解系统是不利的,但熟手使用这些功能可以大幅提升开发效率。
前面我们已经说过,Laravel Eloquent ORM 是 Laravel 中最强大的部分,也是 Laravel 能如此流行最重要的原因。中文文档在:https://d.laravel-china.org/docs/5.5/eloquent
learnlaravel5/app/Article.php
就是一个最简单的 Eloquent Model 类:
<?php
namespace App;
use Illuminate\Database\Eloquent\Model;
class Article extends Model
{
//
}
若想进一步了解 Eloquent 推荐阅读系列文章:深入理解 Laravel Eloquent
我们需要新建一个表专门用来存放每一条评论,每一条评论都属于某一篇文章。评论之间的层级关系比较复杂,本文为入门教程,主要是为了带领大家体验模型间关系,就不再做过多的规划了,将“回复别人的评论”暂定为简单的在评论内容前面增加 @johnlui 这样的字符串。
创建名为 Comment 的 Model 类,并顺便创建附带的 migration,在 learnlaravel5 目录下运行命令:
php artisan make:model Comment -m
这样一次性建立了 Comment 类和 learnlaravel5/database/migrations/2017_11_11_151823_create_comments_table.php
两个文件。填充该文件的 up 方法,给 comments
表增加字段:
public function up()
{
Schema::create('comments', function (Blueprint $table) {
$table->increments('id');
$table->string('nickname');
$table->string('email')->nullable();
$table->string('website')->nullable();
$table->text('content')->nullable();
$table->integer('article_id');
$table->timestamps();
});
}
之后运行命令:
php artisan migrate
去数据库里瞧瞧,comments 表已经躺在那儿啦。
在 Article 模型中增加一对多关系的函数:
<?php
namespace App;
use Illuminate\Database\Eloquent\Model;
class Article extends Model
{
public function hasManyComments()
{
return $this->hasMany('App\Comment', 'article_id', 'id');
}
}
搞定啦!Eloquent 中模型间关系就是这么简单!
模型间关系中文文档:http://laravel-china.org/docs/5.1/eloquent-relationships
扩展阅读:深入理解 Laravel Eloquent(三)——模型间关系(关联)
让我们修改前台的视图文件,想办法把评论功能加进去。
运行命令:
php artisan make:controller ArticleController
增加路由:
Route::get('article/{id}', 'ArticleController@show');
此处的 {id} 指代任意字符串,在我们的规划中,此字段为文章 ID,为数字,但是本行路由却会尝试匹配所有请求,所以当你遇到了奇怪的路由调用的方法跟你想象的不一样时,记得检查路由顺序。路由匹配方式为前置匹配:任何一条路由规则匹配成功,会立刻返回结果,后面的路由便没有了响应的机会。
给 ArticleController 增加 show 函数:
public function show($id)
{
return view('article/show')->withArticle(Article::with('hasManyComments')->find($id));
}
别忘了在顶部引入 Model 类,否则会报类找不到的错误:
....
use App\Article;
class ArticleController extends Controller
{
....
新建 learnlaravel5/resources/views/article/show.blade.php
文件:
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="utf-8">
<meta http-equiv="X-UA-Compatible" content="IE=edge">
<meta name="viewport" content="width=device-width, initial-scale=1">
<title>Learn Laravel 5</title>
<link href="//cdn.bootcss.com/bootstrap/3.3.7/css/bootstrap.min.css" rel="stylesheet">
<script src="//cdn.bootcss.com/jquery/2.2.4/jquery.min.js"></script>
<script src="//cdn.bootcss.com/bootstrap/3.3.7/js/bootstrap.min.js"></script>
</head>
<div id="content" style="padding: 50px;">
<h4>
<a href="/"><< 返回首页</a>
</h4>
<h1 style="text-align: center; margin-top: 50px;">{{ $article->title }}</h1>
<hr>
<div id="date" style="text-align: right;">
{{ $article->updated_at }}
</div>
<div id="content" style="margin: 20px;">
<p>
{{ $article->body }}
</p>
</div>
<div id="comments" style="margin-top: 50px;">
@if (count($errors) > 0)
<div class="alert alert-danger">
<strong>操作失败</strong> 输入不符合要求<br><br>
{!! implode('<br>', $errors->all()) !!}
</div>
@endif
<div id="new">
<form action="{{ url('comment') }}" method="POST">
{!! csrf_field() !!}
<input type="hidden" name="article_id" value="{{ $article->id }}">
<div class="form-group">
<label>Nickname</label>
<input type="text" name="nickname" class="form-control" style="width: 300px;" required="required">
</div>
<div class="form-group">
<label>Email address</label>
<input type="email" name="email" class="form-control" style="width: 300px;">
</div>
<div class="form-group">
<label>Home page</label>
<input type="text" name="website" class="form-control" style="width: 300px;">
</div>
<div class="form-group">
<label>Content</label>
<textarea name="content" id="newFormContent" class="form-control" rows="10" required="required"></textarea>
</div>
<button type="submit" class="btn btn-lg btn-success col-lg-12">Submit</button>
</form>
</div>
<script>
function reply(a) {
var nickname = a.parentNode.parentNode.firstChild.nextSibling.getAttribute('data');
var textArea = document.getElementById('newFormContent');
textArea.innerHTML = '@'+nickname+' ';
}
</script>
<div class="conmments" style="margin-top: 100px;">
@foreach ($article->hasManyComments as $comment)
<div class="one" style="border-top: solid 20px #efefef; padding: 5px 20px;">
<div class="nickname" data="{{ $comment->nickname }}">
@if ($comment->website)
<a href="{{ $comment->website }}">
<h3>{{ $comment->nickname }}</h3>
</a>
@else
<h3>{{ $comment->nickname }}</h3>
@endif
<h6>{{ $comment->created_at }}</h6>
</div>
<div class="content">
<p style="padding: 20px;">
{{ $comment->content }}
</p>
</div>
<div class="reply" style="text-align: right; padding: 5px;">
<a href="#new" onclick="reply(this);">回复</a>
</div>
</div>
@endforeach
</div>
</div>
</div>
</body>
</html>
我们需要创建一个 CommentsController 控制器,并增加一条“存储评论”的路由。运行命令:
php artisan make:controller CommentController
控制器创建成功,接下来我们增加一条路由:
Route::post('comment', 'CommentController@store');
给这个类增加 store 函数:
public function store(Request $request)
{
if (Comment::create($request->all())) {
return redirect()->back();
} else {
return redirect()->back()->withInput()->withErrors('评论发表失败!');
}
}
此处 Comment 类请自己引入。
我们采用批量赋值方法来减少存储评论的代码,批量赋值中文文档。
给 Comment 类增加 $fillable 成员变量:
protected $fillable = ['nickname', 'email', 'website', 'content', 'article_id'];
前台文章展示页:
提交几条评论之后:
恭喜你,前台评论功能构建完成!
评论跟 Article 一样,是一种可以管理的资源列表。2015 版教程的最后,我风风火火地罗列了一堆又一堆的代码,其实对还没入门的人几乎没用。在此,我将这个功能作为大作业布置给大家。大作业嘛,当然是没有标准答案的,但有效果图:
在做这个大作业的过程中,你将会反复地回头去看前面的教程,反复地阅读中文文档,会仔细阅读我的代码,等你完成大作业的时候,Laravel 就真正入门啦~~
本教程示例代码见:https://github.com/johnlui/Learn-Laravel-5
在任何地方卡住,最快的办法就是去看示例代码。
本文基于 Laravel 5.2 版本,无奈 5.2 的中文文档还没有跟上,大家勉强看一下 5.1 的吧:
你应该懂得 PHP 网站运行的基础知识,并且有了一个完善的开发环境。跟随本教程走完一遍,你将会得到一个基础的包含登录、后台编辑、前台评论的简单 blog 系统。
许多人被拦在了学习 Laravel 的第一步:安装。并不是因为安装有多复杂,而是因为【众所周知的原因】。在此我推荐一个 composer 全量**镜像:http://pkg.phpcomposer.com/ 。启用 Composer 镜像服务作为本教程的第一项小作业请自行完成哦。
镜像配置完成后,在终端(Terminal 或 CMD)里切换到你想要放置该网站的目录下(如 C:\wwwroot、/Library/WebServer/Documents/、/var/www/html、/etc/nginx/html 等),运行命令:
composer create-project laravel/laravel learnlaravel5 5.2.31
然后,稍等片刻,当前目录下就会出现一个叫 learnlaravel5 的文件夹,安装完成啦~
为了尽可能地减缓学习曲线,推荐宝宝们使用 PHP 内置 web 服务器驱动我们的网站。运行以下命令:
cd learnlaravel5/public
php -S 0.0.0.0:1024
这时候访问 http://127.0.0.1:1024
就是这个样子的:
我在本地 hosts 中绑定了 fuck.io 到 127.0.0.1,所以截图中我的域名是 fuck.io 而不是 127.0.0.1,其实他们是完全等价的。
这时候你可能要问了:为什么本宝宝的页面是一片空白?请使用开发者工具查看网络请求,只要是 200 状态就说明运行成功了,空白是因为这个页面引用了 Google Fonts,你懂的~
至于为什么选择 1024 端口?因为他是 *UNIX 系统动态端口的开始,是我们不需要 root 权限就可以随意监听的数值最小的端口。
另外,建议不熟悉 PHP 运行环境搭建的宝宝们不要轻易尝试使用 Apache 或 Nginx 驱动 Laravel,特别是在开启了 SELinux 的 Linux 系统上跑。关于 Laravel 在 Linux 上部署的大坑,本宝宝可能要单写一篇长文分享给宝宝们。
Laravel 利用 PHP5.4 的新特性 trait 内置了非常完善好用的简单用户登录注册功能,适合一些不需要复杂用户权限管理的系统,例如公司内部用的简单管理系统。
激活这个功能非常容易,运行以下命令:
php artisan make:auth
访问 http://fuck.io:1024/login
,如果你本地已经科学上网,那就能看到以下页面:
如果你不能科学上网,就把 learnlaravel5/resources/views/layouts/app.blade.php
中的 https://fonts.googleapis.com
换成 http://fonts.useso.com
就好啦~
cdnjs.cloudflare.com 可能会短暂抽风,不要灰心,那只是大墙在抖动,一会儿就好了。
接下来我们要连接数据库了,请自行准备好 MySQL 服务哦。
不出意外的话,learnlaravel5 目录下已经有了一个 .env 文件,如果没有,可以复制一份 .env.example 文件重命名成 .env,修改下面几行的值:
DB_HOST=127.0.0.1
DB_PORT=3306
DB_DATABASE=laravel5
DB_USERNAME=root
DB_PASSWORD=password
推荐新建一个名为 laravel5 的数据库,并且使用 root 账户直接操作,降低学习数据库的成本。
数据库配置好之后,在登录界面填写任意邮箱和密码,点击 Login,你应该会得到以下画面:
它说 users 表不存在呀,接下来我们将见识 Laravel 另外一个实用特性。
运行命令:
php artisan migrate
我们得到了如下结果:
数据库迁移成功!赶快打开 http://fuck.io:1024/home 注册一个用户试试吧~
下图是本宝宝注册了一个 username 为 1 用户:
打开 learnlaravel5/database/migrations/2014_10_12_000000_create_users_table.php
文件,你肯定能一眼看出它的作用:用 PHP 描述数据库构造,并且使用命令行一次性部署所有数据库结构。
Eloquent 是 Laravel 的 ORM,是 Laravel 系统中最强大的地方,没有之一。当初 Laravel 作者在开发第一版的时候花了整整三分之一的时间才搞出来 Eloquent。当然,“欲练此功,必先自宫”,Eloquent 也是 Laravel 中最慢的地方,迄今无法解决。(路由、自动载入、配置分散、视图引发的性能问题都通过缓存几乎彻底解决了)
当然,我们还是要承袭第一版教程中对 Eloquent ORM 的描述:鹅妹子英!
如果你想深入地了解 Eloquent,可以阅读系列文章:深入理解 Laravel Eloquent(一)——基本概念及用法
Eloquent 是 Laravel 内置的 ORM 系统,我们的 Model 类将继承自 Eloquent 提供的 Model 类,然后,就天生具备了数十个异常强大的函数,从此想干啥事儿都是一行代码就搞定。
我们使用 Artisan 工具新建 Model 类及其附属的 Migration 和 Seeder(数据填充)类。
运行以下命令:
php artisan make:model Article
去看看你的 app 目录,下面是不是多了一个 Article.php 文件?那就是 Artisan 帮我们生成的 Model 文件:
<?php
namespace App;
use Illuminate\Database\Eloquent\Model;
class Article extends Model
{
//
}
如此简洁有力的代码,隐藏了背后极高的难度和巨大的复杂度,让我们闭上眼睛,静静地感受 Laravel 的优雅吧 (~ ̄▽ ̄)~
$article = Article::find(2);
echo $article->title;
$article = Article::where('title', '我是标题')->first();
echo $article->id;
$articles = Article::all(); // 此处得到的 $articles 是一个对象集合,可以在后面加上 '->toArray()' 变成多维数组。
foreach ($articles as $article) {
echo $article->title;
}
$articles = Article::where('id', '>', 10)->where('id', '<', 20)->get();
foreach ($articles as $article) {
echo $article->title;
}
$articles = Article::where('id', '>', 10)->where('id', '<', 20)->orderBy('updated_at', 'desc')->get();
foreach ($articles as $article) {
echo $article->title;
}
接下来我们生成对应 Article 这个 Model 的 Migration 和 Seeder。
在 learnlaravel5 目录下运行命令:
php artisan make:migration create_article_table
成功之后打开 learnlaravel5/database/migrations
,你会发现有一个名为 2*****_create_article_table 的文件被创建了。我们修改他的 up 函数为:
public function up()
{
Schema::create('articles', function(Blueprint $table)
{
$table->increments('id');
$table->string('title');
$table->text('body')->nullable();
$table->integer('user_id');
$table->timestamps();
});
}
这几行代码描述的是 Article 对应的数据库中那张表的结构。Laravel 默认 Model 对应的表名是这个英文单词的复数形式,在这里,就是 articles。接下来让我们把 PHP 代码变成真实的 MySQL 中的数据表,运行命令:
php artisan migrate
执行成功后,articles 表已经出现在数据库里了:
上图中的软件叫 Sequel Pro,是一个开源的 MAC 下 MySQL GUI 管理工具,超好用,推荐给用 MAC 的同学。
Seeder 是我们接触到的一个新概念,字面意思为播种机。Seeder 解决的是我们在开发 web 应用的时候,需要手动向数据库中填入假数据的繁琐低效问题。
运行以下命令创建 Seeder 文件:
php artisan make:seeder ArticleSeeder
我们会发现 learnlaravel5/database/seeds
里多了一个文件 ArticleSeeder.php
,修改此文件中的 run 函数为:
public function run()
{
DB::table('articles')->delete();
for ($i=0; $i < 10; $i++) {
\App\Article::create([
'title' => 'Title '.$i,
'body' => 'Body '.$i,
'user_id' => 1,
]);
}
}
上面代码中的 \App\Article
为命名空间绝对引用。如果你对命名空间还不熟悉,可以读一下 《PHP 命名空间 解惑》,很容易理解的。
接下来我们把 ArticleSeeder 注册到系统内。修改 learnlaravel5/database/seeds/DatabaseSeeder.php
中的 run 函数为:
public function run()
{
$this->call(ArticleSeeder::class);
}
由于 database 目录没有像 app 目录那样被 composer 注册为 psr-4 自动加载,采用的是 psr-0 classmap 方式,所以我们还需要运行以下命令把 ArticleSeeder.php
加入自动加载系统,避免找不到类的错误:
composer dump-autoload
然后执行 seed:
php artisan db:seed
你应该得到如下结果:
这时候刷新一下数据库中的 articles 表,会发现已经被插入了 10 行假数据:
下一步:2016 版 Laravel 系列入门教程(二)【最适合**人的 Laravel 教程】
在目标目录下运行
composer create-project laravel/laravel testProject
生成的项目会有出现某些文件缺失的情况,比如
/app/http/ 目录下缺少routes.php文件,缺少Requests文件夹。
本篇文章中,我将跟大家一起实现 Article 的新增、编辑和删除功能,仔细解读每一段代码,相信本篇文章看完,你就能够 get Laravel 使用之道。
资源控制器是 Laravel 内部的一种功能强大的约定,它约定了一系列对某一种资源进行“增删改查”操作的路由配置,让我们不再需要对每一项需要管理的资源都写 N 行重复形式的路由。中文文档见:https://d.laravel-china.org/docs/5.5/controllers#resource-controllers
我们只需要写一行简单的路由:
Route::resource('photo', 'PhotoController');
就可以得到下面 7 条路由配置:
左边是 HTTP 方法,中间是 URL 路径,右边是 控制器中对应的函数,只要某个请求符合这七行中某一行的要求,那么这条请求就会触发第三列的 function。这是 Laravel 对于 RESTful 的规范,它不仅仅帮我们省去了几行路由配置代码,更是如何合理规划 URL 的指路明灯,相信你会从中学到很多。
下面我们正式开始一项一项地实现 Article 的新增、编辑、删除功能:
将当前路由配置中的 Route::get('article', 'ArticleController@index');
改成 Route::resource('articles', 'ArticleController');
,哦了。注意,article 单数变成了复数。
由于从单数变成了复数,后台首页及文章列表页的视图文件里的链接也需要修改。
learnlaravel5/resources/views/admin/home.blade.php
中的 {{ url('admin/article') }}
为 {{ url('admin/articles') }}
。learnlaravel5/resources/views/admin/article/index.blade.php
中的 {{ url('admin/article/create') }}
为 {{ url('admin/articles/create') }}
;修改 {{ url('admin/article/'.$article->id.'/edit') }}
为 {{ url('admin/articles/'.$article->id.'/edit') }}
;修改 {{ url('admin/article/'.$article->id) }}
为 {{ url('admin/articles/'.$article->id) }}
。新增一篇文章需要两个动作:第一步,获取“新增Article”的页面;第二步,提交数据到后端,插入一篇文章到数据库。我们使用下图中红框内的两条路由规则来实现这两步操作:
第一个红框里告诉我们应该使用 /admin/articles/create
对应“新增Article”的页面,浏览器使用 GET 方法从服务器获取,对应的是 ArticleController
中的 create()
方法,下面我们手动新建这个方法:
public function create()
{
return view('admin/article/create');
}
新增视图文件 learnlaravel5/resources/views/admin/article/create.blade.php
:
@extends('layouts.app')
@section('content')
<div class="container">
<div class="row">
<div class="col-md-10 col-md-offset-1">
<div class="panel panel-default">
<div class="panel-heading">新增一篇文章</div>
<div class="panel-body">
@if (count($errors) > 0)
<div class="alert alert-danger">
<strong>新增失败</strong> 输入不符合要求<br><br>
{!! implode('<br>', $errors->all()) !!}
</div>
@endif
<form action="{{ url('admin/articles') }}" method="POST">
{!! csrf_field() !!}
<input type="text" name="title" class="form-control" required="required" placeholder="请输入标题">
<br>
<textarea name="body" rows="10" class="form-control" required="required" placeholder="请输入内容"></textarea>
<br>
<button class="btn btn-lg btn-info">新增文章</button>
</form>
</div>
</div>
</div>
</div>
</div>
@endsection
点击文章管理页面最上面的“新增”按钮,你将得到以下页面:
上文中我使用 return view('admin/article/create');
返回了视图文件。
view()
方法是 Laravel 中一个全局的方法,用于调用视图文件,他接受一个字符串参数,并会按照这个参数去调取对应的路由,这很容易理解。实际上 'admin/article/create'
跟 'admin.article.create'
是等价的,而且看起来后者更加优雅,不过我个人更推荐前者。代码优雅是好事儿,不过本质上代码是写给人看的,一切提高代码理解成本的行为都是不可取的。
“新增Article”的页面已经展示出来,下一步就是提交数据到后端了,理解提交数据,要从 HTML 表单开始。
视图文件中有一个表单:
<form action="{{ url('admin/articles') }}" method="POST">
{!! csrf_field() !!}
<input type="text" name="title" class="form-control" required="required" placeholder="请输入标题">
<br>
<textarea name="body" rows="10" class="form-control" required="required" placeholder="请输入内容"></textarea>
<br>
<button class="btn btn-lg btn-info">新增文章</button>
</form>
这是一个非常普通的 HTML form(表单),只有两点需要我们费点心思去理解。
第一,表单的 action。form 是 HTML 规范,在点击了表单中的提交按钮后,浏览器会使用 method 属性的值(GET、POST等)将某些数据组装好发送给 action 的值(URL),这里我们动态生成了一个 URL 作为 action,并且指定了表单提交需要使用 POST 方法。
第二,csrf_field。这是 Laravel 中内置的应对 CSRF 攻击的防范措施,任何 POST PUT PATCH 请求都会被检测是否提交了 CSRF 字段。对应的代码为 learnlaravel5/app/Http/Kernel.php
里的 $middlewareGroups
属性里的 \App\Http\Middleware\VerifyCsrfToken::class
值。
{!! csrf_field() !!}
实际上会生成一个隐藏的 input:<input type="hidden" name="_token" value="GYZ8OHDAbZICMcEvcTiS82qlZs2XrELklpEl159S">
这一行也可以这么写:
<input type="hidden" name="_token" value="{{ csrf_token() }}">
如果你的系统有很多的 Ajax,而你又不想降低安全性,这里的 csrf_token() 函数将会给你巨大的帮助。
我们在页面中随便填入一些数据,点击提交按钮,这条请求会被分配到那里呢?
第二个红框告诉我们,应该向 admin/articles
以 POST 方法提交表单,其对应的是 store()
方法。现在我们新建 store 方法:
public function store(Request $request)
{
$this->validate($request, [
'title' => 'required|unique:articles|max:255',
'body' => 'required',
]);
$article = new Article;
$article->title = $request->get('title');
$article->body = $request->get('body');
$article->user_id = $request->user()->id;
if ($article->save()) {
return redirect('admin/articles');
} else {
return redirect()->back()->withInput()->withErrors('保存失败!');
}
}
填入数据:
点击按钮,页面跳转到“文章管理”页,将此页面拉到最底部:
恭喜你,文章新增功能完成!
下面我已注释的形式细细解析每一段代码的作用:
public function store(Request $request) // Laravel 的依赖注入系统会自动初始化我们需要的 Request 类
{
// 数据验证
$this->validate($request, [
'title' => 'required|unique:articles|max:255', // 必填、在 articles 表中唯一、最大长度 255
'body' => 'required', // 必填
]);
// 通过 Article Model 插入一条数据进 articles 表
$article = new Article; // 初始化 Article 对象
$article->title = $request->get('title'); // 将 POST 提交过了的 title 字段的值赋给 article 的 title 属性
$article->body = $request->get('body'); // 同上
$article->user_id = $request->user()->id; // 获取当前 Auth 系统中注册的用户,并将其 id 赋给 article 的 user_id 属性
// 将数据保存到数据库,通过判断保存结果,控制页面进行不同跳转
if ($article->save()) {
return redirect('admin/articles'); // 保存成功,跳转到 文章管理 页
} else {
// 保存失败,跳回来路页面,保留用户的输入,并给出提示
return redirect()->back()->withInput()->withErrors('保存失败!');
}
}
这两行路由配置可以满足我们的需求:
上面一行:展示“编辑某一篇文章”的表单;下面一行:上传数据并到数据库更新这篇文章。
这个就当做第二个小作业留给你们,尝试自己去构建吧这里面还有个小坑,参考我的代码就可以迅速地解决呦
删除某个资源跟新增、编辑相比最大的不同就是运行方式的不同:删除按钮看起来是一个独立的按钮,其实它是一个完整的表单,只不过只有这一个按钮暴露在页面上:
<form action="{{ url('admin/articles/'.$article->id) }}" method="POST" style="display: inline;">
{{ method_field('DELETE') }}
{{ csrf_field() }}
<button type="submit" class="btn btn-danger">删除</button>
</form>
大家可能注意到了这句代码 {{ method_field('DELETE') }}
,这是什么意思呢?这是 Laravel 特有的请求处理系统的特殊约定。虽然 DELETE 方法在 RFC2616 中是可以携带 body 的(甚至 GET 方法都是可以携带的),但是由于历史的原因,不少 web server 软件都将 DELETE 方法和 GET 方法视作不可携带 body 的方法,有些 web server 软件会丢弃 body,有些干脆直接认为请求不合法拒绝接收。所以在这里,Laravel 的请求处理系统要求所有非 GET 和 POST 的请求全部通过 POST 请求来执行,再将真正的方法使用 _method 表单字段携带给后端。上面小作业中的小坑便是这个,PUT/PATCH 请求也要通过 POST 来执行。
在控制器中增加删除文章对应的是 destroy 方法:
public function destroy($id)
{
Article::find($id)->delete();
return redirect()->back()->withInput()->withErrors('删除成功!');
}
点击删除按钮,检验效果:
恭喜你,文章新增、编辑、删除功能构建成功!
下一步:2017 版 Laravel 系列入门教程(五)【最适合**人的 Laravel 教程】
本教程示例代码见:https://github.com/johnlui/Learn-Laravel-5
在任何地方卡住,最快的办法就是去看示例代码。
本篇文章中,我将跟宝宝们一起学习 Laravel 框架最重要的部分——路由系统。
如果你读过旧的基于 Laravel 5.0 版本的此教程,你会发现那篇文章里大书特书的 Auth 系统构建已经被 Laravel 捎带手给解决了。在更早的 Laravel 4 版本的教程中,实际上我是通过让大家自己动手构建高难度的 Auth 系统来提高短期学习曲线的斜率,以便宝宝们能更快地感受到 Laravel 运行的原理。但是很遗憾,现在的 Auth 系统实在是太强大了,执行几句命令就激活了这个功能,新手其实还是云里雾里。为了弥补这个缺憾,本宝宝决定赤膊上阵,手刃路由系统,直接给大家展示 Laravel 是如何组织 MVC 架构,来控制网站运行的。
路由系统是所有 PHP 框架的核心,路由承载的是 URL 到代码片段的映射,不同的框架所附带的路由系统是这个框架本质最真实的写照,一丝不挂,一览无余。Laravel 路由中文文档:http://laravel-china.org/docs/5.1/routing
Laravel 5 之后就把路由放到了 learnlaravel5/app/Http/routes.php
这个位置,我们先看一下目前路由中仅存的几行代码:
Route::get('/', function () {
return view('welcome');
});
Route::auth();
Route::get('/home', 'HomeController@index');
中间的一行代码 Route::auth();
就是 Auth 系统自动注入的路由配置,我们不用深究,我们的注意力主要集中头三行和最后一行代码上。
本宝宝一直认为 Laravel 5 除了性能大幅提升之外相对于 4 最大的进步就在于新的命名空间规划:更清晰,更合理,更有利于新手。
Laravel 4 时代,大量的代码都运行在根命名空间下,路由、Controller、Model 等等。看起来这么做可以少写几行枯燥的 use xxxx;
,实则是对于命名空间的误使用,而且对于新手学习命名空间是有毒的。
Laravel 5 全面引入了 psr-4 命名空间标准:命名空间和实际文件所在的文件夹层级一致,文件夹首字母大写即为此文件的约定命名空间。举个小栗子:learnlaravel5/app/Http/Controllers/HomeController.php
的绝对类名为:\App\Http\Controllers\HomeController
,learnlaravel5/app/User.php
的绝对类名为:\App\User
。
“绝对类名”是本宝宝自创的:在启用了命名空间的系统中,子命名空间下的类有一个全局都可以直接访问的名称,这个名称就是该类的命名空间全称。虽然命名空间在“实用主义”的 PHP 语言里看起来十分古怪,不过他也还是 PHP 嘛,依然遵循 PHP 的运行原理和哲学。同理,Laravel 无论多么强大,他都是 PHP 代码写成的,所以当你苦于 Laravel 没有提供某个你需要的功能时,不要惊慌不要着急,just write the code in the PHP way。
psr-4 的官方英文文档在这里:http://www.php-fig.org/psr/psr-4/
命名空间其实没什么特别难的地方,我曾经写过一篇文章专门扒光命名空间的秘密:《PHP 命名空间 解惑
》
路由文件中前三行即为闭包路由:
Route::get('/', function () {
return view('welcome');
});
闭包路由使用闭包作为此条请求的响应代码,方便灵活,很多简单操作直接在闭包里解决即可。例如“输出服务器当前时间”:
Route::get('now', function () {
return date("Y-m-d H:i:s");
});
如果你想得到北京时间,请在 learnlaravel5/config/app.php
第 55 行左右把 timezone 设置为上海:
'timezone' => 'Asia/Shanghai',
这时候访问 http://fuck.io:1024/now 可以得到如下结果:
闭包路由虽然灵活强大,不过大多数场景下我们还是需要回归到 MVC 架构的:
Route::get('/home', 'HomeController@index');
这行路由代码的意**必大家都能猜到一二了:当以 GET 方法访问 http://fuck.io:1024/home
的时候,调用 HomeController 控制器中的 index 方法(函数)。同理,你可以使用 Route::post('/home', 'HomeController@indexPost');
响应 POST 方法的请求。
Laravel 的路由跟所有 PHP 框架的路由一样,都是用的最简单直接的 PHP 方式来调用控制器中的方法的:使用字符串初始化类得到对象,调用对象的指定方法,返回结果。下面我简单罗列几步对 Laravel 路由调用过程的探测,感兴趣的话可以自己研究。
learnlaravel5/app/Providers/RouteServiceProvider.php
全局搜索 routes.php
,我们找到了这个文件。此文件最后的 mapWebRoutes 方法,给所有的路由统一加进了一个路由组,定义了一个命名空间和一个中间件:
protected function mapWebRoutes(Router $router)
{
$router->group([
'namespace' => $this->namespace, 'middleware' => 'web',
], function ($router) {
require app_path('Http/routes.php');
});
}
顺着这个函数往上看,你会发现命名空间定义的地方:
protected $namespace = 'App\Http\Controllers';
之后命名空间、类、方法是如何传递的呢?
learnlaravel5/vendor/laravel/framework/src/Illuminate/Routing/ControllerDispatcher.php
经过简单的追踪,我们找到了这个文件。让我们在 dispatch 方法中增加一行 var_dump($controller);
,刷新就可以看到页面上如下的输出:
这就是我们要调用的控制器类的“绝对类名”。
Laravel 使用了完整的面向对象程序架构,对控制器的调用进行了超多层封装,所以最简单地探测方式其实是手动抛出错误,这样就可以看到完整的调用栈:
在 HomeController 的 index 方法里的 return 之前增加一行 throw new \Exception("我故意的", 1);
,刷新页面,你将看到以下画面:
我们可以看到,是 learnlaravel5/vendor/laravel/framework/src/Illuminate/Routing/Controller.php
第 80 行最终驱动起了 HomeController:
public function callAction($method, $parameters)
{
return call_user_func_array([$this, $method], $parameters);
}
具体的细节不再详解,宝宝如果感兴趣的话,把这些方法一个一个地都看一遍吧,相信对于你理解 Laravel 运行原理很有帮助。其实 PHP 跟字符串结合的紧密程度已经紧逼 js 和 JSON 了。结尾分享一个小彩蛋:这个laravel路由怎么写?
下一步:2016 版 Laravel 系列入门教程(三)【最适合**人的 Laravel 教程】
部署完成报500错误 哪里可以看详细错误呢?
在删除article之后,应该删除对应的评论,这里涉及到comments与article两张表,我在Admin\ArticleController下添加了如下代码:
Comment::where('article_id', '=', $id)->delete();
涉及两张相关联的表,有更好的删除方法吗?
#6 __
教程不错,推荐在我的github
在本篇文章中,我们将尝试构建一个带后台的简单博客系统。我们将会使用到 路由、MVC、Eloquent ORM 和 blade 视图系统。
我们在教程一中已经新建了一个继承自 Eloquent Model 类的 Article 类,使用 migration 建立了数据表并使用 seeder 填入了测试数据。我们的博客系统暂时将只管理这一种资源:后台需要使用账号密码登录,进入后台之后,可以新增、修改、删除文章;前台显示文章列表,并在点击标题之后显示出文章全文。
下面我们正式开始。
前台的搭建是最简单的,我先带大家找找感觉。
删掉
Route::get('/', function () {
return date("Y-m-d H:i:s");
});
将Route::get('/home', 'HomeController@index')->name('home');
改为 Route::get('/', 'HomeController@index')->name('home');
,现在我们系统的首页就落到了 App\Http\Controllers\HomeController
类的 index
方法上了。
将 learnlaravel5/app/Http/Controllers/HomeController.php
的 index 函数我们之前加的 Exception 那行代码删除,就只剩一行代码了:return view('home');
,这个很好理解,返回名叫 home 的视图给用户。这个视图文件在哪里呢?在 learnlaravel5/resources/views/home.blade.php
,blade 是 Laravel 视图引擎的名字,会对视图文件进行加工。
blade 引擎会对视图文件进行预处理,帮我们简化一些重复性很高的 echo、foreach 等 PHP 代码。blade 还提供了一个灵活强大的视图组织系统。打开 home.blade.php
:
@extends('layouts.app')
@section('content')
<div class="container">
<div class="row">
<div class="col-md-8 col-md-offset-2">
<div class="panel panel-default">
<div class="panel-heading">Dashboard</div>
<div class="panel-body">
@if (session('status'))
<div class="alert alert-success">
{{ session('status') }}
</div>
@endif
You are logged in!
</div>
</div>
</div>
</div>
</div>
@endsection
这表示此视图的基视图是 learnlaravel5/resources/views/layouts/app.blade.php
。这个函数还隐含了一个小知识:在使用名称查找视图的时候,可以使用 . 来代替 / 或 \。
这两个标识符之间的代码,会被放到基视图的 @yield('content')
中进行输出。
首先删除 learnlaravel5/vendor/laravel/framework/src/Illuminate/Routing/ControllerDispatcher.php
中 dispatch
函数里我们加的的 var_dump 代码,否则会出现奇怪的页面。
访问 http://fuck.io:1024 ,不出意外的话,你会看到这个页面:
为什么需要登录呢?怎么去掉这个强制登录呢?删掉 HomeController 中的构造函数即可:
public function __construct()
{
$this->middleware('auth');
}
这个函数会在控制器类生成对象后第一时间自动载入一个名为 auth 的中间件,正是这一步导致了首页需要登录。删除构造函数之后,重新访问 http://fuck.io:1024 ,页面应该就会直接出来了。这里要注意两点:① 一定要重新访问,不要刷新,因为此时页面的 url 其实是 http://fuck.io:1024/login ② 这个页面跟之前的欢迎页虽然看起来一毛一样,但其实文字是不同的,注意仔细观察哦。
既然 Controller - View 的架构已经运行,下一步就是引入 Model 了。Laravel 中向视图传数据非常简单:
public function index()
{
return view('home')->withArticles(\App\Article::all());
}
2016 版教程里很多人看到这段代码都十分不解,这里解释一下:
绝对命名空间
方式对 Article 类的调用。$articles
的变量,这段代码等价于 ->with('articles', \App\Article::all())
。->withFooBar(100)
等价于 ->with('foo_bar', 100)
,即驼峰变量会被完全转换为蛇形变量。修改视图文件 learnlaravel5/resources/views/home.blade.php
的代码为:
@extends('layouts.app')
@section('content')
<div id="title" style="text-align: center;">
<h1>Learn Laravel 5</h1>
<div style="padding: 5px; font-size: 16px;">Learn Laravel 5</div>
</div>
<hr>
<div id="content">
<ul>
@foreach ($articles as $article)
<li style="margin: 50px 0;">
<div class="title">
<a href="{{ url('article/'.$article->id) }}">
<h4>{{ $article->title }}</h4>
</a>
</div>
<div class="body">
<p>{{ $article->body }}</p>
</div>
</li>
@endforeach
</ul>
</div>
@endsection
刷新,得到:
如果看到以上页面,恭喜你,Laravel 初体验成功!
前台页面是不应该有顶部的菜单栏的,特别是还有注册、登录之类的按钮。接下来我们修改视图内容为:
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="utf-8">
<meta http-equiv="X-UA-Compatible" content="IE=edge">
<meta name="viewport" content="width=device-width, initial-scale=1">
<title>Learn Laravel 5</title>
<link href="//cdn.bootcss.com/bootstrap/3.3.7/css/bootstrap.min.css" rel="stylesheet">
<script src="//cdn.bootcss.com/jquery/2.2.4/jquery.min.js"></script>
<script src="//cdn.bootcss.com/bootstrap/3.3.7/js/bootstrap.min.js"></script>
</head>
<div id="title" style="text-align: center;">
<h1>Learn Laravel 5</h1>
<div style="padding: 5px; font-size: 16px;">Learn Laravel 5</div>
</div>
<hr>
<div id="content">
<ul>
@foreach ($articles as $article)
<li style="margin: 50px 0;">
<div class="title">
<a href="{{ url('article/'.$article->id) }}">
<h4>{{ $article->title }}</h4>
</a>
</div>
<div class="body">
<p>{{ $article->body }}</p>
</div>
</li>
@endforeach
</ul>
</div>
</body>
</html>
此视图文件变成了一个独立视图,不再有基视图,并且将 jQuery 和 BootStrap 替换为了国内的 CDN,更快更稳定了。
同理我们修改 learnlaravel5/resources/views/layouts/app.blade.php
:
① 删除 <script src="{{ asset('js/app.js') }}"></script>
② 替换 <link href="{{ asset('css/app.css') }}" rel="stylesheet">
为
<link href="//cdn.bootcss.com/bootstrap/3.3.7/css/bootstrap.min.css" rel="stylesheet">
<script src="//cdn.bootcss.com/jquery/2.2.4/jquery.min.js"></script>
<script src="//cdn.bootcss.com/bootstrap/3.3.7/js/bootstrap.min.js"></script>
接下来我们来着手搭建后台。
我们使用 Artisan 工具来生成控制器文件及代码:
php artisan make:controller Admin/HomeController
成功之后,我们就可以看到 artisan 帮我们建立的文件夹及控制器文件了:
我们要使用路由组来将后台页面置于“需要登录才能访问”的中间件下,以保证安全。在 web.php 里增加下面三行:
Route::group(['middleware' => 'auth', 'namespace' => 'Admin', 'prefix' => 'admin'], function() {
Route::get('/', 'HomeController@index');
});
上一篇文章中我们已经接触到了路由组,这是 Laravel 的另一个伟大创造。路由组可以给组内路由一次性增加 命名空间、uri 前缀、域名限定、中间件 等属性,并且可以多级嵌套,异常强大。路由组中文文档在此:https://d.laravel-china.org/docs/5.5/routing#route-groups
上面的三行代码的功能简单概括就是:访问这个页面必须先登录,若已经登录,则将 http://fuck.io:1024/admin
指向 App\Http\Controllers\Admin\HomeController
的 index 方法。其中需要登录由 middleware
定义,/admin
由 prefix
定义,Admin
由 namespace
定义,HomeController
是实际的类名。
在新生成的 learnlaravel5/app/Http/Controllers/Admin/HomeController.php
文件中增加一个 function:
public function index()
{
return view('admin/home');
}
在 learnlaravel5/resources/views/
目录下新建一个名为 admin 的文件夹,在 admin 内新建一个名为 home.blade.php
的文件,填入代码:
@extends('layouts.app')
@section('content')
<div class="container">
<div class="row">
<div class="col-md-10 col-md-offset-1">
<div class="panel panel-default">
<div class="panel-heading">Learn Laravel 5 后台</div>
<div class="panel-body">
<a href="{{ url('admin/article') }}" class="btn btn-lg btn-success col-xs-12">管理文章</a>
</div>
</div>
</div>
</div>
</div>
@endsection
修改 learnlaravel5/app/Http/Controllers/Auth/LoginController.php
中相应的代码为:
protected $redirectTo = '/admin';
访问 http://fuck.io:1024/admin ,它会跳转到登陆界面,输入邮箱和密码之后,你应该会看到如下页面:
恭喜你,后台首页搭建完成!下面我们开始构建 Article 的后台管理功能。
让我们先尝试点一下 “管理文章”按钮,不出意外你将得到一个报错:
这是 Laravel 5.5 刚刚引入的新策略:404 错误不再报告详细信息,而是展示一个友好的 404 页面。
404 错误是访问了系统没有监听的路由导致的。下面我们要添加针对 http://fuck.io:1024/admin/article
的路由:
Route::group(['middleware' => 'auth', 'namespace' => 'Admin', 'prefix' => 'admin'], function() {
Route::get('/', 'HomeController@index');
Route::get('article', 'ArticleController@index');
});
刷新,出现详细报错信息了:
很多新手看到这个报错直接就慌了:什么鬼?全是英文看不懂呀。然后在文章下面把完整的错误栈全部粘贴出来。老实说我第一次见到 Laravel 报这个错也是完全没耐心去读,不过我还是复制了最明显的那句话“Class App\Http\Controllers\Admin\ArticleController does not exist”去 Google 了一下,从此我就再也没搜索过它了。
如果你遇到了奇怪的报错,不要慌,稳住,Google 一下,我们能赢。
上图中的报错是控制器不存在。我们使用 Artisan 来新建控制器:
php artisan make:controller Admin/ArticleController
刷新,错误又变了:
index 方法不存在。让我们新增 index 方法:
public function index()
{
return view('admin/article/index')->withArticles(Article::all());
}
上面我们已经新建过视图,现在应该已经轻车熟路了。在 learnlaravel5/resources/views/admin
下新建 article 文件夹,在文件夹内新建一个 index.blade.php 文件,内容如下:
@extends('layouts.app')
@section('content')
<div class="container">
<div class="row">
<div class="col-md-10 col-md-offset-1">
<div class="panel panel-default">
<div class="panel-heading">文章管理</div>
<div class="panel-body">
@if (count($errors) > 0)
<div class="alert alert-danger">
{!! implode('<br>', $errors->all()) !!}
</div>
@endif
<a href="{{ url('admin/article/create') }}" class="btn btn-lg btn-primary">新增</a>
@foreach ($articles as $article)
<hr>
<div class="article">
<h4>{{ $article->title }}</h4>
<div class="content">
<p>
{{ $article->body }}
</p>
</div>
</div>
<a href="{{ url('admin/article/'.$article->id.'/edit') }}" class="btn btn-success">编辑</a>
<form action="{{ url('admin/article/'.$article->id) }}" method="POST" style="display: inline;">
{{ method_field('DELETE') }}
{{ csrf_field() }}
<button type="submit" class="btn btn-danger">删除</button>
</form>
@endforeach
</div>
</div>
</div>
</div>
</div>
@endsection
刷新,错误又变了:
Article 类不存在?原因很简单:Article 类和当前控制器类不在一个命名空间路径下,不能直接调用。解决办法就是主动导入 \App\Article
类:
<?php
namespace App\Http\Controllers\Admin;
use Illuminate\Http\Request;
use App\Http\Controllers\Controller;
use App\Article;
class ArticleController extends Controller
{
....
如果你还不熟悉命名空间,请阅读《PHP 命名空间 解惑》。
再次刷新,你应该能看到如下画面:
如果你没到这个画面也不用担心,根据错误提示去 Google 吧,一定能解决的。
这三个功能我将在下一篇教程与大家分享,这是 2015 版 Laravel 教程做的不够好的地方,其实这里才是最应该掰开揉碎仔细讲解的地方。
下一步: 2017 版 Laravel 系列入门教程(四)【最适合**人的 Laravel 教程】
本教程示例代码见:https://github.com/johnlui/Learn-Laravel-5
在任何地方卡住,最快的办法就是去看示例代码。
本文是本系列教程的完结篇,我们将一起给 Article 加入评论功能,让游客在前台页面可以查看、提交、回复评论,并完成后台评论管理功能,可以删除、编辑评论。Article 和评论将使用 Laravel Eloquent 提供的“一对多关系”功能大大简化模型见关系的开发复杂度。最终,我们将得到一个个人博客系统的雏形,并布置一个大作业,供大家实战练习。
本篇文章中我将会使用一些 Laravel 的高级功能,这些高级功能对新手理解系统是不利的,但却可以进一步提高熟手的开发效率。
前面我们已经说过,Laravel Eloquent ORM 是 Laravel 中最强大的部分,也是 Laravel 能如此流行最重要的原因。中文文档在:http://laravel-china.org/docs/5.1/eloquent
learnlaravel5/app/Article.php
就是一个最简单的 Eloquent Model 类:
<?php
namespace App;
use Illuminate\Database\Eloquent\Model;
class Article extends Model
{
//
}
若想进一步了解 Eloquent,推荐阅读系列文章:深入理解 Laravel Eloquent
我们需要新建一个表专门用来存放数据,每条评论都属于某一篇文章。评论之间的层级关系比较复杂,本文为入门教程,主要是为了带领大家体验模型间关系,就不在做过多的规划,将“回复别人的评论”暂定为简单的在评论内容前面增加 @john 这样的字符串。
创建名为 Comment 的 Model 类,并顺便创建附带的 migration,在 learnlaravel5 目录下运行命令:
php artisan make:model Comment -m
这样一次性建立了 Comment 类和 2016_06_03_220325_create_comments_table 两个文件。修改 migration 文件的 up 方法为:
public function up()
{
Schema::create('comments', function (Blueprint $table) {
$table->increments('id');
$table->string('nickname');
$table->string('email')->nullable();
$table->string('website')->nullable();
$table->text('content')->nullable();
$table->integer('article_id');
$table->timestamps();
});
}
之后运行命令:
php artisan migrate
去数据库里瞧瞧,comments 表已经躺在那儿啦。
在 Article 模型中增加一对多关系的函数:
<?php
namespace App;
use Illuminate\Database\Eloquent\Model;
class Article extends Model
{
public function hasManyComments()
{
return $this->hasMany('App\Comment', 'article_id', 'id');
}
}
搞定啦!Eloquent 中模型间关系就是这么简单!
模型间关系中文文档:http://laravel-china.org/docs/5.1/eloquent-relationships
扩展阅读:深入理解 Laravel Eloquent(三)——模型间关系(关联)
让我们修改前台的视图文件,想办法把评论功能加入进去。
运行命令:
php artisan make:controller ArticleController
增加路由:
Route::get('article/{id}', 'ArticleController@show');
此处的 {id} 指代任意字符串,在我们的规划中,此字段为文章 ID,为数字,但是本行路由却会尝试匹配所有请求,所以当你遇到了奇怪的路由调用的方法跟你想象的不一样时,记得检查路由顺序。路由匹配方式为前置匹配:任何一条路由规则匹配成功,会立刻返回结果,后面的路由便没有了机会。
给 ArticleController 增加 show 函数:
public function show($id)
{
return view('article/show')->withArticle(Article::with('hasManyComments')->find($id));
}
新建 learnlaravel5/resources/views/article/show.blade.php
文件:
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="utf-8">
<meta http-equiv="X-UA-Compatible" content="IE=edge">
<meta name="viewport" content="width=device-width, initial-scale=1">
<title>Learn Laravel 5</title>
<link href="//cdn.bootcss.com/bootstrap/3.2.0/css/bootstrap.min.css" rel="stylesheet">
<script src="//cdn.bootcss.com/jquery/1.11.1/jquery.min.js"></script>
<script src="//cdn.bootcss.com/bootstrap/3.2.0/js/bootstrap.min.js"></script>
</head>
<div id="content" style="padding: 50px;">
<h4>
<a href="/"><< 返回首页</a>
</h4>
<h1 style="text-align: center; margin-top: 50px;">{{ $article->title }}</h1>
<hr>
<div id="date" style="text-align: right;">
{{ $article->updated_at }}
</div>
<div id="content" style="margin: 20px;">
<p>
{{ $article->body }}
</p>
</div>
<div id="comments" style="margin-top: 50px;">
@if (count($errors) > 0)
<div class="alert alert-danger">
<strong>操作失败</strong> 输入不符合要求<br><br>
{!! implode('<br>', $errors->all()) !!}
</div>
@endif
<div id="new">
<form action="{{ url('comment') }}" method="POST">
{!! csrf_field() !!}
<input type="hidden" name="article_id" value="{{ $article->id }}">
<div class="form-group">
<label>Nickname</label>
<input type="text" name="nickname" class="form-control" style="width: 300px;" required="required">
</div>
<div class="form-group">
<label>Email address</label>
<input type="email" name="email" class="form-control" style="width: 300px;">
</div>
<div class="form-group">
<label>Home page</label>
<input type="text" name="website" class="form-control" style="width: 300px;">
</div>
<div class="form-group">
<label>Content</label>
<textarea name="content" id="newFormContent" class="form-control" rows="10" required="required"></textarea>
</div>
<button type="submit" class="btn btn-lg btn-success col-lg-12">Submit</button>
</form>
</div>
<script>
function reply(a) {
var nickname = a.parentNode.parentNode.firstChild.nextSibling.getAttribute('data');
var textArea = document.getElementById('newFormContent');
textArea.innerHTML = '@'+nickname+' ';
}
</script>
<div class="conmments" style="margin-top: 100px;">
@foreach ($article->hasManyComments as $comment)
<div class="one" style="border-top: solid 20px #efefef; padding: 5px 20px;">
<div class="nickname" data="{{ $comment->nickname }}">
@if ($comment->website)
<a href="{{ $comment->website }}">
<h3>{{ $comment->nickname }}</h3>
</a>
@else
<h3>{{ $comment->nickname }}</h3>
@endif
<h6>{{ $comment->created_at }}</h6>
</div>
<div class="content">
<p style="padding: 20px;">
{{ $comment->content }}
</p>
</div>
<div class="reply" style="text-align: right; padding: 5px;">
<a href="#new" onclick="reply(this);">回复</a>
</div>
</div>
@endforeach
</div>
</div>
</div>
</body>
</html>
我们需要创建一个 CommentsController 控制器,并增加一条“存储评论”的路由。运行命令:
php artisan make:controller CommentController
控制器创建成功,接下来我们增加一条路由:
Route::post('comment', 'CommentController@store');
给这个类增加 store 函数:
public function store(Request $request)
{
if (Comment::create($request->all())) {
return redirect()->back();
} else {
return redirect()->back()->withInput()->withErrors('评论发表失败!');
}
}
我们采用批量赋值方法来减少存储评论的代码,批量赋值中文文档。
给 Comment 类增加 $fillable 成员变量:
protected $fillable = ['nickname', 'email', 'website', 'content', 'article_id'];
前台文章展示页:
提交几条评论之后:
恭喜你,前台评论功能构建完成!
评论跟 Article 一样,是一种可以管理的资源列表。2015 版教程的最后,我风风火火地罗列了一堆又一堆的代码,其实对读者宝宝们几乎没有作用。在此,我将这个功能作为大作业布置给大家。大作业嘛,当然是没有标准答案的,不过我还是提供效果图给宝宝们:
在做这个大作业的过程中,你将会反复地回头去看前面的教程,反复地阅读中文文档,会仔细阅读我的代码,等你完成大作业的时候,Laravel 5 就真正入门啦~~
本教程示例代码见:https://github.com/johnlui/Learn-Laravel-5
在任何地方卡住,最快的办法就是去看示例代码。
本篇文章中,我将跟大家一起实现 Article 的新增、编辑和删除功能,仔细解读每一段代码,相信本篇文章看完,你就能够 get Laravel 使用之道。
资源控制器是 Laravel 内部的一种功能强大的约定,它约定了一系列对某一种资源进行“增删改查”操作的路由配置,让我们不再需要对每一项需要管理的资源都写 N 行重复形式的路由。中文文档见:http://www.golaravel.com/laravel/docs/5.1/controllers/#restful-resource-controllers
我们只需要写一行简单的路由:
Route::resource('photo', 'PhotoController');
就可以得到下面 7 条路由配置:
左边是 HTTP 方法,中间是 URL 路径,右边是 控制器中对应的函数,只要某个请求符合这七行中某一行的要求,那么这条请求就会触发最后一列的方法。这是 Laravel 对于 RESTful 的规范,它不仅仅帮我们省去了几行路由配置代码,更是如何合理规划 URL 的指路明灯,相信你会从中学到很多。
下面我们正式开始一项一项地实现 Article 的新增、编辑、删除功能:
将当前路由配置中的 Route::get('article', 'ArticleController@index');
改成 Route::resource('article', 'ArticleController');
,哦了。
新增一篇文章需要两个动作:第一步,获取“新增Article”的页面;第二步,提交数据到后端,插入一篇文章到数据库。
第二行路由可以满足我们的需求:
那下一步就明了了,在 ArticleController 中新增 create 方法,放回一个可以输入文章的页面:
ArticleController 中:
public function create()
{
return view('admin/article/create');
}
新增视图文件 learnlaravel5/resources/views/admin/article/create.blade.php
:
@extends('layouts.app')
@section('content')
<div class="container">
<div class="row">
<div class="col-md-10 col-md-offset-1">
<div class="panel panel-default">
<div class="panel-heading">新增一篇文章</div>
<div class="panel-body">
@if (count($errors) > 0)
<div class="alert alert-danger">
<strong>新增失败</strong> 输入不符合要求<br><br>
{!! implode('<br>', $errors->all()) !!}
</div>
@endif
<form action="{{ url('admin/article') }}" method="POST">
{!! csrf_field() !!}
<input type="text" name="title" class="form-control" required="required" placeholder="请输入标题">
<br>
<textarea name="body" rows="10" class="form-control" required="required" placeholder="请输入内容"></textarea>
<br>
<button class="btn btn-lg btn-info">新增文章</button>
</form>
</div>
</div>
</div>
</div>
</div>
@endsection
点击文章管理页面最上面的“新增”按钮,你将得到以下页面:
上文中我使用 return view('admin/article/create');
返回了视图文件。
view()
方法是 Laravel 中一个全局的方法,用于调用视图文件,他接受一个字符串参数,并会按照这个参数去调取对应的路由,这很容易理解。实际上 'admin/article/create'
跟 'admin.article.create'
是等价的,而且看起来后者更加优雅,不过本宝宝十分推荐前者。代码优雅是好事儿,不过本质上代码是写给人看的,一切提高代码理解成本的行为都是错误的。
“新增Article”的页面已经展示出来,下一步就是提交数据到后端了,理解提交数据,要从表单开始。
视图文件中有一个表单:
<form action="{{ url('admin/article') }}" method="POST">
{!! csrf_field() !!}
<input type="text" name="title" class="form-control" required="required" placeholder="请输入标题">
<br>
<textarea name="body" rows="10" class="form-control" required="required" placeholder="请输入内容"></textarea>
<br>
<button class="btn btn-lg btn-info">新增文章</button>
</form>
这是一个非常普通的 form 表单,只有两点需要我们费点心思去理解。
第一,表单的 action。form 是 HTML 规范,在点击了表单中的提交按钮后,浏览器会使用 method 方法将某些数据组装好发送给 action,这里我们动态生成了一个 URL 作为 action,并且指定了表单提交需要使用 POST 方法。
第二,csrf_field。这是 Laravel 中内置的防止应对 CSRF 攻击的防范措施,任何 POST PUT PATCH 请求都会被检测是否提交了 CSRF 字段。
{!! csrf_field() !!}
实际上会生成一个隐藏的 input:
<input type="hidden" name="_token" value="GYZ8OHDAbZICMcEvcTiS82qlZs2XrELklpEl159S">
这一行也可以这么写:
<input type="hidden" name="_token" value="{{ csrf_token() }}">
如果你的系统有很多的 Ajax,而你又不想降低安全性,这里的 csrf_token() 函数将会给你巨大的帮助。
我们在页面中填入了一些数据,点击了提交按钮,这条请求会被分配到那里呢?
第三行解答了我们的问题。好了,新建 store 方法:
public function store(Request $request)
{
$this->validate($request, [
'title' => 'required|unique:articles|max:255',
'body' => 'required',
]);
$article = new Article;
$article->title = $request->get('title');
$article->body = $request->get('body');
$article->user_id = $request->user()->id;
if ($article->save()) {
return redirect('admin/article');
} else {
return redirect()->back()->withInput()->withErrors('保存失败!');
}
}
填入数据:
点击按钮,页面跳转到“文章管理”页,将此页面拉到最底部:
恭喜你,文章新增成功!
下面我已注释的形式细细解析每一段代码的作用:
public function store(Request $request) // Laravel 的依赖注入系统会自动初始化我们需要的 Request 类
{
// 数据验证
$this->validate($request, [
'title' => 'required|unique:articles|max:255', // 必填、在 articles 表中唯一、最大长度 255
'body' => 'required', // 必填
]);
// 通过 Article Model 插入一条数据进 articles 表
$article = new Article; // 初始化 Article 对象
$article->title = $request->get('title'); // 将 POST 提交过了的 title 字段的值赋给 article 的 title 属性
$article->body = $request->get('body'); // 同上
$article->user_id = $request->user()->id; // 获取当前 Auth 系统中注册的用户,并将其 id 赋给 article 的 user_id 属性
// 将数据保存到数据库,通过判断保存结果,控制页面进行不同跳转
if ($article->save()) {
return redirect('admin/article'); // 保存成功,跳转到 文章管理 页
} else {
// 保存失败,跳回来路页面,保留用户的输入,并给出提示
return redirect()->back()->withInput()->withErrors('保存失败!');
}
}
这两行路由配置可以满足我们的需求:
上面一行:展示“编辑某一篇文章”的页面;下面一行:上传数据更新这篇文章。
这个就当做第二个小作业留给你们,尝试自己去构建吧这里面还有个小坑,参考我的代码就可以迅速地解决呦
删除某个资源跟新增、编辑相比最大的不同就是运行方式的不同:删除按钮看起来是一个独立的按钮,其实它是一个完整的表单,只不过只有这一个按钮暴露在页面上:
<form action="{{ url('admin/article/'.$article->id) }}" method="POST" style="display: inline;">
{{ method_field('DELETE') }}
{{ csrf_field() }}
<button type="submit" class="btn btn-danger">删除</button>
</form>
大家可能注意到了这句代码 {{ method_field('DELETE') }}
,这是什么意思呢?这是 Laravel 特有的请求处理系统的特殊约定。虽然 DELETE 方法在 RFC2616 中是可以携带 body 的(甚至 GET 方法都是可以携带的),但是由于历史的原因,不少 web server 软件都将 DELETE 方法和 GET 方法视作不可携带 body 的方法,有些 web server 软件会丢弃 body,有些干脆直接认为请求不合法拒绝接收。所以在这里,Laravel 的请求处理系统要求所有非 GET 和 POST 的请求全部通过 POST 请求来执行,再将真正的方法使用 _method 表单字段携带给后端代码。上面小作业中的小坑便是这个,PUT/PATCH 请求也要通过 POST 来执行。
另外,这里还有一些小问题:PUT/PATCH 请求通过 POST 方法执行仅限于 form 提交,对 Ajax 请求目前来看是无效的,看来我要去给 Laravel 提交一个 patch 了。🐳
在控制器中增加删除文章对应的是 destroy 方法:
public function destroy($id)
{
Article::find($id)->delete();
return redirect()->back()->withInput()->withErrors('删除成功!');
}
点击删除按钮,检验效果:
恭喜你,文章新增、编辑、删除功能构建成功!
下一步:2016 版 Laravel 系列入门教程(五)【最适合**人的 Laravel 教程】
本篇文章中,我将跟大家一起体验 Laravel 框架最重要的部分——路由系统。
如果你读过 2015 版的教程,你会发现那篇文章里大书特书的 Auth 系统构建已经被 Laravel 捎带手给解决了。在更早的 2014(Laravel 4)版教程中,实际上我是通过让大家自己手动构建高难度的 Auth 系统来提高短期学习曲线的斜率,以便大家能更快地感受到 Laravel 运行的原理。但是很遗憾,现在的 Auth 系统实在是太强大了,执行几句命令就激活了这个功能,新手其实还是云里雾里。为了弥补这个缺憾,我决定赤膊上阵,手刃路由系统,直接给大家展示 Laravel 是如何组织 MVC 架构而控制网站运行的。
路由系统是所有 PHP 框架的核心,路由承载的是 URL 到代码片段的映射,不同的框架所附带的路由系统是这个框架本质最真实的写照,一丝不挂,一览无余。Laravel 路由中文文档:http://laravel-china.org/docs/5.5/routing
Laravel 5.3 之后就把路由放到了 learnlaravel5/routes
文件夹中,一共有四个文件。
我们先看一下web.php
中仅存的几行代码:
Route::get('/', function () {
return view('welcome');
});
Auth::routes();
Route::get('/home', 'HomeController@index')->name('home');
中间的一行代码 Auth::routes();
就是 Auth 系统自动注入的路由配置,我们不用深究,我们的注意力主要集中头三行和最后一行代码上。
我一直认为 Laravel 5 除了性能大幅提升之外相对于 4 最大的进步就在于新的命名空间规划:更清晰,更合理,更有利于新手。
Laravel 4 时代,大量的代码都运行在根命名空间下,路由、Controller、Model 等等。看起来这么做可以少写几行枯燥的 use xxxx;
,实则是对于命名空间的误使用,而且对于新手学习命名空间是有毒的。
Laravel 5 全面引入了 psr-4 命名空间标准:命名空间和实际文件所在的文件夹层级一致,文件夹首字母大写,并自动成为此文件的约定命名空间。举个小栗子:learnlaravel5/app/Http/Controllers/HomeController.php
的绝对类名为:\App\Http\Controllers\HomeController
,learnlaravel5/app/User.php
的绝对类名为:\App\User
。(实际上 psr-4 是自动加载标准,用在这里故称其为命名空间标准。)
“绝对类名”是我自创的:在启用了命名空间的系统中,子命名空间下的类有一个全局都可以直接访问的名称,这个名称就是该类的命名空间全称。虽然命名空间在“实用主义”的 PHP 语言里看起来十分古怪,不过他也还是 PHP 嘛,依然遵循 PHP 的运行原理和哲学。同理,Laravel 无论多么强大,他都是 PHP 代码写成的,所以当你苦于 Laravel 没有提供某个你需要的功能时,不要惊慌不要着急,just write your PHP code。
psr-4 的官方英文文档在这里:http://www.php-fig.org/psr/psr-4/
命名空间其实没什么特别难的地方,我曾经写过一篇文章专门扒光命名空间的秘密:《PHP 命名空间 解惑
》
路由文件中前三行即为闭包路由:
Route::get('/', function () {
return view('welcome');
});
闭包路由使用闭包作为此条请求的响应代码,方便灵活,很多简单操作直接在闭包里解决即可。例如“输出服务器当前时间”:
Route::get('now', function () {
return date("Y-m-d H:i:s");
});
如果你想得到北京时间,请在 learnlaravel5/config/app.php
第 68 行左右把 timezone 设置为上海:
'timezone' => 'Asia/Shanghai',
这时候访问 http://fuck.io:1024/now 可以得到如下结果:
闭包路由虽然灵活强大,不过大多数场景下我们还是需要回归到 MVC 架构的:
Route::get('/home', 'HomeController@index')->name('home');
这行路由代码的意**必大家都能猜到一二了:当以 GET 方法访问 http://fuck.io:1024/home
的时候,调用 HomeController 控制器中的 index 方法(函数)。同理,你可以使用 Route::post('/home', 'HomeController@indexPost');
响应 POST 方法的请求。最后的->name()
不是必须的,感兴趣可以自己了解。
Laravel 的路由跟所有 PHP 框架的路由一样,都是用的最简单直接的 PHP 方式来调用控制器中的方法的:使用字符串初始化类得到对象,调用对象的指定方法,返回结果。下面我简单罗列几步对 Laravel 路由调用过程的探测,感兴趣的话可以自己研究。
learnlaravel5/app/Providers/RouteServiceProvider.php
全局搜索 routes.php
,我们找到了这个文件。此文件最后的 mapWebRoutes 方法,给所有的路由统一加进了一个路由组,定义了一个命名空间和一个中间件:
protected function mapWebRoutes()
{
Route::middleware('web')
->namespace($this->namespace)
->group(base_path('routes/web.php'));
}
顺着这个函数往上看,你会发现命名空间定义的地方:
protected $namespace = 'App\Http\Controllers';
之后命名空间、类、方法是如何传递的呢?
learnlaravel5/vendor/laravel/framework/src/Illuminate/Routing/ControllerDispatcher.php
经过简单的追踪,我们找到了这个文件。让我们在 dispatch 方法中增加一行 var_dump($controller);
,刷新http://fuck.io:1024/home
就可以看到页面上如下的输出:
开头的App\Http\Controllers\HomeController
就是我们要调用的控制器类的“绝对类名”,注意这里是不带\
根命名空间符号的。
Laravel 使用了完整的面向对象程序架构,对控制器的调用进行了超多层封装,所以最简单地探测方式其实是手动抛出错误,这样就可以看到完整的调用栈:
在 HomeController 的 index 方法里的 return 之前增加一行 throw new \Exception("我故意的", 1);
,刷新页面,你将看到以下画面:
我们可以看到,是 learnlaravel5/vendor/laravel/framework/src/Illuminate/Routing/Controller.php
第 54 行最终驱动起了 HomeController:
public function callAction($method, $parameters)
{
return call_user_func_array([$this, $method], $parameters);
}
具体的细节不再详解,大家如果感兴趣的话,把这些方法一个一个地都看一遍吧,相信对于你理解 Laravel 运行原理很有帮助。其实 PHP 跟字符串结合的紧密程度已经紧逼 js 和 JSON 了。结尾分享一个小彩蛋:这个laravel路由怎么写?
下一步:2017 版 Laravel 系列入门教程(三)【最适合**人的 Laravel 教程】
本教程示例代码见:https://github.com/johnlui/Learn-Laravel-5
在任何地方卡住,最快的办法就是去看示例代码。
在本篇文章中,我们将尝试构建一个带后台的简单博客系统。我们将会使用到 路由、MVC、Eloquent ORM 和 blade 视图系统。
我们在教程一中已经新建了一个 Eloquent 的 Model 类 Article,使用 migration 建立了数据表并使用 seeder 填入了测试数据。我们的博客系统暂时将只管理这一种资源:后台需要使用账号密码登录,进入后台之后,可以新增、修改、删除文章;前台显示文章列表,并在点击标题之后显示出文章全文。
下面我们正式开始。
前台的搭建是最简单的,我先带领大家找找感觉。
删掉
Route::get('/', function () {
return view('welcome');
});
将 /home 那一行修改为 Route::get('/', 'HomeController@index');
,现在我们系统的首页就落到了 App\Http\Controllers\HomeController 类的 index 方法上了。
learnlaravel5/app/Http/Controllers/HomeController.php
的 index 函数只有一行代码:return view('home');
,这个很好理解,返回名字叫 home 的视图给用户。这个视图文件在哪里呢?在 learnlaravel5/resources/views/home.blade.php
,blade 是 Laravel 视图系统的名字。
blade 会对视图文件进行预处理,帮我们简化一些重复性很高的 echo、foreach 等 PHP 代码。blade 还提供了一个灵活强大的视图组织系统。打开 home.blade.php :
@extends('layouts.app')
@section('content')
<div class="container">
<div class="row">
<div class="col-md-10 col-md-offset-1">
<div class="panel panel-default">
<div class="panel-heading">Dashboard</div>
<div class="panel-body">
You are logged in!
</div>
</div>
</div>
</div>
</div>
@endsection
这表示此视图的基视图是 learnlaravel5/resources/views/layouts/app.blade.php
。这个函数还隐含了一个小知识:在使用名称查找视图的时候,可以使用 . 来代替 / 或 \。
这两个标识符之间的代码,会被放到基视图的 @yield('content')
中进行输出。
访问 http://fuck.io:1024 ,不出意外的话,你会看到这个页面:
为什么需要登录呢?怎么去掉这个强制登录呢?删掉 HomeController 中的构造函数即可:
public function __construct()
{
$this->middleware('auth');
}
这个函数会在控制器类初始化的时候自动载入一个名为 auth 的中间件,正式这一步导致了首页需要登录。删除构造函数之后,重新访问 http://fuck.io:1024 ,页面应该就会直接出来了。这里要注意两点:① 一定要重新访问,不要刷新,因为此时页面的 url 其实是 http://fuck.io:1024/login ② 这个页面跟之前的欢迎页虽然看起来一毛一样,但其实文字是不同的,注意仔细观察哦。
既然 Controller - View 的架构已经运行,下一步就是引入 Model 了。Laravel 中向视图传数据非常简单:
public function index()
{
return view('home')->withArticles(\App\Article::all());
}
修改视图文件 learnlaravel5/resources/views/home.blade.php
的代码为:
@extends('layouts.app')
@section('content')
<div id="title" style="text-align: center;">
<h1>Learn Laravel 5</h1>
<div style="padding: 5px; font-size: 16px;">Learn Laravel 5</div>
</div>
<hr>
<div id="content">
<ul>
@foreach ($articles as $article)
<li style="margin: 50px 0;">
<div class="title">
<a href="{{ url('article/'.$article->id) }}">
<h4>{{ $article->title }}</h4>
</a>
</div>
<div class="body">
<p>{{ $article->body }}</p>
</div>
</li>
@endforeach
</ul>
</div>
@endsection
刷新
如果你得到以上页面,恭喜你,Laravel 初体验成功!
前台页面是不应该有顶部的菜单栏的,特别是还有注册、登录之类的按钮。修改视图文件为:
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="utf-8">
<meta http-equiv="X-UA-Compatible" content="IE=edge">
<meta name="viewport" content="width=device-width, initial-scale=1">
<title>Learn Laravel 5</title>
<link href="//cdn.bootcss.com/bootstrap/3.2.0/css/bootstrap.min.css" rel="stylesheet">
<script src="//cdn.bootcss.com/jquery/1.11.1/jquery.min.js"></script>
<script src="//cdn.bootcss.com/bootstrap/3.2.0/js/bootstrap.min.js"></script>
</head>
<div id="title" style="text-align: center;">
<h1>Learn Laravel 5</h1>
<div style="padding: 5px; font-size: 16px;">Learn Laravel 5</div>
</div>
<hr>
<div id="content">
<ul>
@foreach ($articles as $article)
<li style="margin: 50px 0;">
<div class="title">
<a href="{{ url('article/'.$article->id) }}">
<h4>{{ $article->title }}</h4>
</a>
</div>
<div class="body">
<p>{{ $article->body }}</p>
</div>
</li>
@endforeach
</ul>
</div>
</body>
</html>
此视图文件变成了一个独立视图,不再有基视图,并且将 jQuery 和 BootStrap 替换为了国内的 CDN,更快更稳定了。
同理我们修改 learnlaravel5/resources/views/layouts/app.blade.php
为如下代码:
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="utf-8">
<meta http-equiv="X-UA-Compatible" content="IE=edge">
<meta name="viewport" content="width=device-width, initial-scale=1">
<title>Laravel</title>
<link href="//cdn.bootcss.com/bootstrap/3.2.0/css/bootstrap.min.css" rel="stylesheet">
<script src="//cdn.bootcss.com/jquery/1.11.1/jquery.min.js"></script>
<script src="//cdn.bootcss.com/bootstrap/3.2.0/js/bootstrap.min.js"></script>
</head>
<body id="app-layout">
<nav class="navbar navbar-default navbar-static-top">
<div class="container">
<div class="navbar-header">
<!-- Collapsed Hamburger -->
<button type="button" class="navbar-toggle collapsed" data-toggle="collapse" data-target="#app-navbar-collapse">
<span class="sr-only">Toggle Navigation</span>
<span class="icon-bar"></span>
<span class="icon-bar"></span>
<span class="icon-bar"></span>
</button>
<!-- Branding Image -->
<a class="navbar-brand" href="{{ url('/') }}">
Laravel
</a>
</div>
<div class="collapse navbar-collapse" id="app-navbar-collapse">
<!-- Left Side Of Navbar -->
<ul class="nav navbar-nav">
<li><a href="{{ url('/home') }}">Home</a></li>
</ul>
<!-- Right Side Of Navbar -->
<ul class="nav navbar-nav navbar-right">
<!-- Authentication Links -->
@if (Auth::guest())
<li><a href="{{ url('/login') }}">Login</a></li>
<li><a href="{{ url('/register') }}">Register</a></li>
@else
<li class="dropdown">
<a href="#" class="dropdown-toggle" data-toggle="dropdown" role="button" aria-expanded="false">
{{ Auth::user()->name }} <span class="caret"></span>
</a>
<ul class="dropdown-menu" role="menu">
<li><a href="{{ url('/logout') }}"><i class="fa fa-btn fa-sign-out"></i>Logout</a></li>
</ul>
</li>
@endif
</ul>
</div>
</div>
</nav>
@yield('content')
</body>
</html>
接下来我们来着手搭建后台。
我们使用 Artisan 工具来生成控制器文件:
php artisan make:controller Admin/HomeController
成功之后,我们就可以看到 artisan 帮我们建立的文件夹及控制器文件了:
我们要使用路由组来将后台页面置于“需要登录才能访问”的中间件下,以保证安全:
Route::group(['middleware' => 'auth', 'namespace' => 'Admin', 'prefix' => 'admin'], function() {
Route::get('/', 'HomeController@index');
});
上一篇文章中我们已经接触到了路由组,这是 Laravel 的另一个伟大创造。路由组可以给组内路由一次性增加 命名空间、uri 前缀、域名限定、中间件 等属性,并且可以多级嵌套,异常强大。路由组中文文档在此:http://laravel-china.org/docs/5.1/routing#route-groups
上面的三行代码的功能简单概括就是:访问这个页面必须先登录,若已经登录,则将 http://fuck.io:1024/admin
指向 App\Http\Controllers\Admin\HomeController 的 index 方法。其中需要登录由 middleware 定义, /admin 由 prefix 定义,Admin 由 namespace 定义,HomeController 是实际的类名。
public function index()
{
return view('admin/home');
}
在 learnlaravel5/resources/views/
目录下新建一个名为 admin 的文件夹,在 admin 内新建一个名为 home.blade.php 的文件,填入代码:
@extends('layouts.app')
@section('content')
<div class="container">
<div class="row">
<div class="col-md-10 col-md-offset-1">
<div class="panel panel-default">
<div class="panel-heading">Dashboard</div>
<div class="panel-body">
<a href="{{ url('admin/article') }}" class="btn btn-lg btn-success col-xs-12">管理文章</a>
</div>
</div>
</div>
</div>
</div>
@endsection
修改 learnlaravel5/app/Http/Controllers/Auth/AuthController.php
中的相应代码为:
protected $redirectTo = 'admin';
访问 http://fuck.io:1024/admin ,它会跳转到登陆界面,输入邮箱和密码之后,你应该会看到如下页面:
恭喜你,后台首页搭建完成!下面我们开始构建 Article 的后台管理功能。
让我们先尝试点一下 “管理文章”按钮,不出意外你将得到一个 404 的报错:
很多新手看到这个报错直接就慌了:什么鬼?全是英文看不懂呀。然后在文章下面把完整的错误栈全部粘贴出来。老实说我第一次见到 Laravel 报这个错也是完全没耐心去读,不过我还是复制了最明显的那个词“NotFoundHttpException”去 Google 了一下,从此我就再也没搜索过它了。
我为我的浮躁感到羞愧。那句话说的太对了:大多数人的努力程度之低,完全没有到拼天赋的程度。愿本教程的读者都做“少数人”。
404 错误是访问了系统没有监听的路由导致的。下面我们要添加针对 http://fuck.io:1024/admin/article
的路由:
Route::group(['middleware' => 'auth', 'namespace' => 'Admin', 'prefix' => 'admin'], function() {
Route::get('/', 'HomeController@index');
Route::get('article', 'ArticleController@index');
});
刷新,错误变了:
上图中的报错是控制器不存在。我们使用 Artisan 来新建控制器:
php artisan make:controller Admin/ArticleController
刷新,错误又变了:
index 方法不存在。让我们新增 index 方法:
public function index()
{
return view('admin/article/index')->withArticles(Article::all());
}
上面我们已经新建过视图,现在应该已经轻车熟路了。在 learnlaravel5/resources/views/admin
下新建 article 文件夹,在文件夹内新建一个 index.blade.php 文件,内容如下:
@extends('layouts.app')
@section('content')
<div class="container">
<div class="row">
<div class="col-md-10 col-md-offset-1">
<div class="panel panel-default">
<div class="panel-heading">文章管理</div>
<div class="panel-body">
@if (count($errors) > 0)
<div class="alert alert-danger">
{!! implode('<br>', $errors->all()) !!}
</div>
@endif
<a href="{{ url('admin/article/create') }}" class="btn btn-lg btn-primary">新增</a>
@foreach ($articles as $article)
<hr>
<div class="article">
<h4>{{ $article->title }}</h4>
<div class="content">
<p>
{{ $article->body }}
</p>
</div>
</div>
<a href="{{ url('admin/article/'.$article->id.'/edit') }}" class="btn btn-success">编辑</a>
<form action="{{ url('admin/article/'.$article->id) }}" method="POST" style="display: inline;">
{{ method_field('DELETE') }}
{{ csrf_field() }}
<button type="submit" class="btn btn-danger">删除</button>
</form>
@endforeach
</div>
</div>
</div>
</div>
</div>
@endsection
刷新,错误又变了:
Article 类不存在?原因很简单:Article 类和当前控制器类不在一个命名空间路径下,不能直接调用。解决办法就是主动导入 \App\Article 类:
... ...
use App\Http\Requests;
use App\Http\Controllers\Controller;
use App\Article;
class ArticleController extends Controller
{
... ...
如果你还不熟悉命名空间,可以参考《PHP 命名空间 解惑》。
再次刷新,你应该能看到如下画面:
如果你没到这个画面也不用担心,根据他的错误提示去 Google 吧,一定能解决的。
这三个功能我将在下一篇教程与大家分享,这是 2015 版 Laravel 教程做的不够好的地方,其实这里才是最应该掰开揉碎仔细讲解的地方。
下一步:2016 版 Laravel 系列入门教程(四)【最适合**人的 Laravel 教程】
本教程示例代码见:https://github.com/johnlui/Learn-Laravel-5
在任何地方卡住,最快的办法就是去看示例代码。
热烈庆祝 Laravel 5.5 LTS 发布! 实际上 Laravel 上一个 LTS 选择 5.1 是非常不明智的,因为 5.2 增加了许许多多优秀的特性。现在好了,大家都用最新的长期支持版本 5.5 吧!
你应该懂得 PHP 网站运行的基础知识,并且拥有一个完善的开发环境。跟随本教程走完一遍,你将会得到一个基础的包含登录、后台编辑、前台评论的简单 blog 系统。
许多人被拦在了学习 Laravel 的第一步:安装。并不是因为安装有多复杂,而是因为【众所周知的原因】。在此我推荐一个 composer 全量**镜像:http://pkg.phpcomposer.com 。启用 Composer 镜像服务作为本教程的第一项小作业请自行完成哦。
镜像配置完成后,在终端(Terminal 或 CMD)里切换到你想要放置该网站的目录下(如 C:\wwwroot、/Library/WebServer/Documents/、/var/www/html、/etc/nginx/html 等),运行命令:
composer create-project laravel/laravel learnlaravel5 ^5.5
然后,稍等片刻,当前目录下就会出现一个叫 learnlaravel5 的文件夹,安装完成啦~
为了尽可能地减缓学习曲线,推荐大家使用 PHP 内置 web server 驱动我们的网站。运行以下命令:
cd learnlaravel5/public
php -S 0.0.0.0:1024
这时候访问 http://127.0.0.1:1024
就是这个样子的:
我在本地 hosts 中绑定了 fuck.io 到 127.0.0.1,所以截图中我的域名是 fuck.io 而不是 127.0.0.1,其实他们是完全等价的。跟过去几年比,有一件大快人心的大好事,fonts.googleapis.com
网站已经转移到了墙内,不会再“白屏”了。
至于为什么选择 1024 端口?因为他是 *UNIX 系统动态端口的开始,是我们不需要 root 权限就可以随意监听的数值最小的端口。
另外,如果你不熟悉 PHP 运行环境搭建的话不要轻易尝试使用 Apache 或 Nginx 驱动 Laravel,特别是在开启了 SELinux 的 Linux 系统上跑这俩货。关于 Laravel 在 Linux 上部署的大坑,我可能要单写一篇长文分享给宝宝们。(说了一年了也没见写呀)
Laravel 利用 PHP5.4 的新特性 trait 内置了非常完善好用的简单用户登录注册功能,适合一些不需要复杂用户权限管理的系统,例如公司内部用的简单管理系统。
激活这个功能非常容易,运行以下命令:
php artisan make:auth
访问 http://fuck.io:1024/login
,看到以下页面:
接下来我们要连接数据库了,请自行准备好 MySQL 服务哦。
不出意外的话,learnlaravel5 目录下已经有了一个 .env 文件,如果没有,可以复制一份 .env.example 文件重命名成 .env,修改下面几行的值:
DB_HOST=127.0.0.1
DB_PORT=3306
DB_DATABASE=laravel5
DB_USERNAME=root
DB_PASSWORD=password
推荐新建一个名为 laravel5 的数据库(编码设置为 utf8mb4),并且使用 root 账户直接操作,降低学习数据库的成本。
数据库配置好之后,在登录界面填写任意邮箱和密码,点击 Login,你应该会得到以下画面:
它说 users 表不存在,接下来我们将见识 Laravel 另外一个实用特性。
tip:如果你下载了我的示例代码,默认是跑不起来的,还需要生成 .env,运行 composer update 填充 vendor 文件夹等操作,如果部署在 Linux 上,用的 Apache 或 Nginx,还需要设置文件夹权限:
chmod 777 -R storage bootstrap/cache
运行命令:
php artisan migrate
我们得到了如下结果:
» php artisan migrate
Migration table created successfully.
Migrating: 2014_10_12_000000_create_users_table
Migrated: 2014_10_12_000000_create_users_table
Migrating: 2014_10_12_100000_create_password_resets_table
Migrated: 2014_10_12_100000_create_password_resets_table
数据库迁移成功!赶快打开 http://fuck.io:1024/home 点击右上角的注册按钮注册一个用户试试吧~
下图是本宝宝注册了一个 username 为 1 用户:
打开 learnlaravel5/database/migrations/2014_10_12_000000_create_users_table.php
文件,你肯定能一眼看出它的作用:用 PHP 描述数据库构造,并且使用命令行一次性部署所有数据库结构。
Eloquent 是 Laravel 的 ORM,是 Laravel 系统中最强大的地方,没有之一。当初 Laravel 作者在开发第一版的时候花了整整三分之一的时间才搞出来 Eloquent。当然,“欲练此功,必先自宫”,Eloquent 也是 Laravel 中最慢的地方,迄今无法解决。(路由、自动载入、配置分散、视图引发的性能问题都通过缓存几乎彻底解决了,Composer Autoload 巨量的性能消耗也被 PHP7 手起刀落解决了)
当然,我们还是要承袭第一版教程中对 Eloquent ORM 的描述:鹅妹子英!
如果你想深入地了解 Eloquent,可以阅读系列文章:深入理解 Laravel Eloquent(一)——基本概念及用法
Eloquent 是 Laravel 内置的 ORM 系统,我们的 Model 类将继承自 Eloquent 提供的 Model 类,然后,就天生具备了数十个异常强大的函数,从此想干啥事儿都是一行代码就搞定。
经过了三年多的大规模使用,我发现 Eloquent 另辟蹊径采用和 Java 技术完全不同的思路解决了多人开发耦合过重的问题:数据库相关操作全部用一句话解决,Model 中不写共用函数,大幅降低了 bug 几率。什么?你说性能?软件工程研究的对象是人,性能的优先级比代码格式规范都低好嘛。我时刻谨记:代码是写给人看的,只是恰好能运行。
我们使用 Artisan 工具新建 Model 类及其附属的 Migration 和 Seeder(数据填充)类。
运行以下命令:
php artisan make:model Article
去看看你的 app 目录,下面是不是多了一个 Article.php 文件?那就是 Artisan 帮我们生成的 Model 文件:
<?php
namespace App;
use Illuminate\Database\Eloquent\Model;
class Article extends Model
{
//
}
如此简洁有力的代码,隐藏了背后极高的难度和巨大的复杂度,让我们闭上眼睛,静静地感受 Laravel 的优雅吧 (~ ̄▽ ̄)~
$article = Article::find(2);
echo $article->title;
$article = Article::where('title', '我是标题')->first();
echo $article->id;
$articles = Article::all(); // 此处得到的 $articles 是一个对象集合,可以在后面加上 '->toArray()' 变成多维数组。
foreach ($articles as $article) {
echo $article->title;
}
$articles = Article::where('id', '>', 10)->where('id', '<', 20)->get();
foreach ($articles as $article) {
echo $article->title;
}
$articles = Article::where('id', '>', 10)->where('id', '<', 20)->orderBy('updated_at', 'desc')->get();
foreach ($articles as $article) {
echo $article->title;
}
接下来我们生成对应 Article 这个 Model 的 Migration 和 Seeder。
在 learnlaravel5 目录下运行命令:
php artisan make:migration create_articles_table
成功之后打开 learnlaravel5/database/migrations
,你会发现有一个名为 2*****_create_articles_table 的文件被创建了。我们修改他的 up 函数为:
public function up()
{
Schema::create('articles', function (Blueprint $table)
{
$table->increments('id');
$table->string('title');
$table->text('body')->nullable();
$table->integer('user_id');
$table->timestamps();
});
}
这几行代码描述的是 Article 对应的数据库中那张表的结构。Laravel Model 默认的表名是这个英文单词的复数形式,在这里,就是 articles。接下来让我们把 PHP 代码变成真实的 MySQL 中的数据表,运行命令:
php artisan migrate
执行成功后,articles 表已经出现在数据库里了:
上图中的软件叫 Sequel Pro,是一个开源的 MAC 下 MySQL GUI 管理工具,超好用,推荐给用 MAC 的同学。
Seeder 是我们接触到的一个新概念,字面意思为播种机。Seeder 解决的是我们在开发 web 应用的时候,需要手动向数据库中填入假数据的繁琐低效问题。
运行以下命令创建 Seeder 文件:
php artisan make:seeder ArticleSeeder
我们会发现 learnlaravel5/database/seeds
里多了一个文件 ArticleSeeder.php
,修改此文件中的 run 函数为:
public function run()
{
DB::table('articles')->delete();
for ($i=0; $i < 10; $i++) {
\App\Article::create([
'title' => 'Title '.$i,
'body' => 'Body '.$i,
'user_id' => 1,
]);
}
}
上面代码中的 \App\Article
为命名空间绝对引用。如果你对命名空间还不熟悉,可以读一下 《PHP 命名空间 解惑》,很容易理解的。
接下来我们把 ArticleSeeder 注册到系统内。修改 learnlaravel5/database/seeds/DatabaseSeeder.php
中的 run 函数为:
public function run()
{
$this->call(ArticleSeeder::class);
}
由于 database 目录没有像 app 目录那样被 composer 注册为 psr-4 自动加载,采用的是 psr-0 classmap 方式,所以我们还需要运行以下命令把 ArticleSeeder.php
加入自动加载系统,避免找不到类的错误:
composer dump-autoload
然后执行 seed:
php artisan db:seed
你应该得到如下结果:
» php artisan db:seed
Seeding: ArticleSeeder
这时候刷新一下数据库中的 articles 表,会发现已经被插入了 10 行假数据:
下一步:2017 版 Laravel 系列入门教程(二)【最适合**人的 Laravel 教程】
Warning: require(/Applications/MAMP/htdocs/Learn-Laravel-5-master/bootstrap/../vendor/autoload.php): failed to open stream: No such file or directory in /Applications/MAMP/htdocs/Learn-Laravel-5-master/bootstrap/autoload.php on line 17
Fatal error: require(): Failed opening required '/Applications/MAMP/htdocs/Learn-Laravel-5-master/bootstrap/../vendor/autoload.php' (include_path='.:/Applications/MAMP/bin/php/php7.0.0/lib/php') in /Applications/MAMP/htdocs/Learn-Laravel-5-master/bootstrap/autoload.php on line 17
A declarative, efficient, and flexible JavaScript library for building user interfaces.
🖖 Vue.js is a progressive, incrementally-adoptable JavaScript framework for building UI on the web.
TypeScript is a superset of JavaScript that compiles to clean JavaScript output.
An Open Source Machine Learning Framework for Everyone
The Web framework for perfectionists with deadlines.
A PHP framework for web artisans
Bring data to life with SVG, Canvas and HTML. 📊📈🎉
JavaScript (JS) is a lightweight interpreted programming language with first-class functions.
Some thing interesting about web. New door for the world.
A server is a program made to process requests and deliver data to clients.
Machine learning is a way of modeling and interpreting data that allows a piece of software to respond intelligently.
Some thing interesting about visualization, use data art
Some thing interesting about game, make everyone happy.
We are working to build community through open source technology. NB: members must have two-factor auth.
Open source projects and samples from Microsoft.
Google ❤️ Open Source for everyone.
Alibaba Open Source for everyone
Data-Driven Documents codes.
China tencent open source team.