Laravel大型项目系列教程(三)之发表文章
Laravel大型项目系列教程(三)之发表文章
一、前言
上一节教程中完成了用户管理,这节教程将大概完成发表Markdown格式文章并展示的功能。
二、Let's go
1.数据库迁移
文章模块中我们需要建立articles
、tags
以及article_tag
表,每篇文章会有一到多个标签,每个标签会有一到多篇文章,创建迁移文件:
$ php artisan migrate:make create_articles_table --create=articles
$ php artisan migrate:make create_tags_table --create=tags
$ php artisan migrate:make create_article_tag_table --create=article_tag
修改*_create_articles_table.php
:
Schema::create('articles', function(Blueprint $table)
{
$table->increments('id');
$table->string('title');
$table->string('summary')->nullable();
$table->text('content');
$table->text('resolved_content');
$table->integer('user_id');
$table->softDeletes();
$table->timestamps();
});
修改*_create_tags_table.php
:
Schema::create('tags', function(Blueprint $table)
{
$table->increments('id');
$table->string('name')->unique();
$table->integer('count')->default(0);
$table->softDeletes();
$table->timestamps();
});
修改*_create_article_tag_table.php
:
Schema::create('article_tag', function(Blueprint $table)
{
$table->increments('id');
$table->integer('article_id');
$table->integer('tag_id');
});
执行迁移:
$ php artisan migrate
2.创建Article和Tag模型
创建Article
和Tag
模型:
$ php artisan generate:model article
$ php artisan generate:model tag
先在User.php
中增加:
public function articles()
{
return $this->hasMany('Article');
}
一个用户会有多篇文章。
修改Article.php
:
use Illuminate\Database\Eloquent\SoftDeletingTrait; class Article extends \Eloquent { use SoftDeletingTrait; protected $fillable = ['title', 'content']; public function tags()
{
return $this->belongsToMany('Tag');
} public function user()
{
return $this->belongsTo('User');
}
}
一篇文章会有多个标签并属于一个用户。
修改Tag.php
:
use Illuminate\Database\Eloquent\SoftDeletingTrait; class Tag extends \Eloquent { use SoftDeletingTrait; protected $fillable = ['name']; public function articles()
{
return $this->belongsToMany('Article');
}
}
一个标签会有多篇文章。
上面用到了软删除,belongsToMany
用于多对多关联。
3.发表文章视图
首先在导航条nav.blade.php
中添加一个发表文章的选项:
<li><a href="{{ URL::to('article/create') }}"><span class="am-icon-edit"></span> Publish Article</a></li>
这时候登录会发现多了一个选项:
下面创建视图:
$ php artisan generate:view articles.create
修改articles/create.blade.php
:
@extends('_layouts.default') @section('main')
<div class="am-g am-g-fixed">
<div class="am-u-sm-12">
<h1>Publish Article</h1>
<hr/>
@if ($errors->has())
<div class="am-alert am-alert-danger" data-am-alert>
<p>{{ $errors->first() }}</p>
</div>
@endif
{{ Form::open(array('url' => 'article', 'class' => 'am-form')) }}
<div class="am-form-group">
<label for="title">Title</label>
<input id="title" name="title" type="text" value="{{ Input::old('title') }}"/>
</div>
<div class="am-form-group">
<label for="content">Content</label>
<textarea id="content" name="content" rows="20">{{ Input::old('content') }}</textarea>
<p class="am-form-help">
<button id="preview" type="button" class="am-btn am-btn-xs am-btn-primary"><span class="am-icon-eye"></span> Preview</button>
</p>
</div>
<div class="am-form-group">
<label for="tags">Tags</label>
<input id="tags" name="tags" type="text" value="{{ Input::old('tags') }}"/>
<p class="am-form-help">Separate multiple tags with a comma ","</p>
</div>
<p><button type="submit" class="am-btn am-btn-success"><span class="am-icon-send"></span> Publish</button></p>
{{ Form::close() }}
</div>
</div> <div class="am-popup" id="preview-popup">
<div class="am-popup-inner">
<div class="am-popup-hd">
<h4 class="am-popup-title"></h4>
<span data-am-modal-close
class="am-close">×</span>
</div>
<div class="am-popup-bd">
</div>
</div>
</div>
<script>
$(function() {
$('#preview').on('click', function() {
$('.am-popup-title').text($('#title').val());
$.post('preview', {'content': $('#content').val()}, function(data, status) {
$('.am-popup-bd').html(data);
});
$('#preview-popup').modal();
});
});
</script>
@stop
开始上一节中我们发现routes.php
中的代码已经很零乱,那是因为我们把业务逻辑写在了这个文件中,对于文章模块我们把业务逻辑写在控制器中,首先创建一个文章控制器:
$ php artisan generate:controller ArticleController
我们会发现在app\controllers
下多了一个ArticleController.php
文件,它是一个资源控制器,在routes.php
中增加:
Route::resource('article', 'ArticleController');
对应路由如下:
现在在ArticleController.php
中增加过滤器并修改create
方法:
public function __construct()
{
$this->beforeFilter('auth', array('only' => array('create', 'store', 'edit', 'update', 'destroy')));
} public function create()
{
return View::make('articles.create');
}
这时在登录后点击Publish Article
选项后,会出现发表文章的页面:
4.文章预览
这里我们将使用Markdown
格式来编写文章,同时需要提供一个预览功能,先需要安装Markdown
解析插件,在composer.json
的require
中增加:
"maxhoffmann/parsedown-laravel": "dev-master"
然后composer update
安装,在config/app.php
中增加:
'providers' => array(
...
'MaxHoffmann\Parsedown\ParsedownServiceProvider'
), 'aliases' => array(
...
'Markdown' => 'MaxHoffmann\Parsedown\ParsedownFacade',
),
安装完后,在ArticleController.php
中增加:
public function preview() {
return Markdown::parse(Input::get('content'));
}
在routes.php
中增加:
Route::post('article/preview', array('before' => 'auth', 'uses' => 'ArticleController@preview'));
切记要添加在
Route::resource('article', 'ArticleController');
前面。
现在来试试我们的预览功能吧,点击Preview
按钮:
出现预览界面:
5.添加文章
下面就要向数据库添加文章了,在ArticleController.php
中修改:
public function store()
{
$rules = [
'title' => 'required|max:100',
'content' => 'required',
'tags' => array('required', 'regex:/^\w+$|^(\w+,)+\w+$/'),
];
$validator = Validator::make(Input::all(), $rules);
if ($validator->passes()) {
$article = Article::create(Input::only('title', 'content'));
$article->user_id = Auth::id();
$resolved_content = Markdown::parse(Input::get('content'));
$article->resolved_content = $resolved_content;
$tags = explode(',', Input::get('tags'));
if (str_contains($resolved_content, '<p>')) {
$start = strpos($resolved_content, '<p>');
$length = strpos($resolved_content, '</p>') - $start - 3;
$article->summary = substr($resolved_content, $start + 3, $length);
} else if (str_contains($resolved_content, '</h')) {
$start = strpos($resolved_content, '<h');
$length = strpos($resolved_content, '</h') - $start - 4;
$article->summary = substr($resolved_content, $start + 4, $length);
}
$article->save();
foreach ($tags as $tagName) {
$tag = Tag::whereName($tagName)->first();
if (!$tag) {
$tag = Tag::create(array('name' => $tagName));
}
$tag->count++;
$article->tags()->save($tag);
}
return Redirect::route('article.show', $article->id);
} else {
return Redirect::route('article.create')->withInput()->withErrors($validator);
}
} public function show($id)
{
return View::make('articles.show')->with('article', Article::find($id));
}
上面代码实现了保存文章和显示文章的业务逻辑,保存文章时验证tags
用了regex
正则表达式来验证标签是否用,
号分隔,有没有发现Article
模型中有一个resolved_content
字段,这个字段来保存解析后的内容,这样只需要在保存的时候解析一次,显示文章页面就显示resolved_content
字段的内容,不需要再解析一次,这是空间换时间的做法,看个人喜好了,如果想要更好的体验,可以只在前台页面解析,保存的时候把前台解析的内容保存到resolved_content
字段,前台解析Markdown有一个很好的工具StackEdit。
现在就差个显示文章的视图了,创建:
$ php artisan generate:view articles.show
修改articles/show.blade.php
:
@extends('_layouts.default') @section('main')
<article class="am-article">
<div class="am-g am-g-fixed">
<div class="am-u-sm-12">
<br/>
<div class="am-article-hd">
<h1 class="am-article-title">{{{ $article->title }}}</h1>
<p class="am-article-meta">Author: <a style="cursor: pointer;">{{{ $article->user->nickname }}}</a> Datetime: {{ $article->updated_at }}</p>
</div>
<div class="am-article-bd">
<blockquote>
Tags:
@foreach ($article->tags as $tag)
<a class="am-badge am-badge-success am-radius">{{ $tag->name }}</a>
@endforeach
</blockquote>
</p>
<p>{{ $article->resolved_content }}</p>
</div>
<br/>
</div>
</div>
</article>
@stop
完成之后看看效果吧,先编辑文章:
发布后跳转到显示文章页面:
6.小结
这节教程使用了控制器,完成了发布文章并展示的功能,但还是有很多瑕疵,在代码方面,现在你可以重构下User
和验证登录的路由,把它们都变成控制器,在用户体验上你可以把发布文章编辑内容时由服务器端解析改成客户端解析,推荐StackEdit,下一节教程将完成网站首页和用户主页展示文章列表和标签,并且让用户能够删除和修改文章。
本文详细出处http://www.shiyanlou.com/courses/123
Laravel大型项目系列教程(三)之发表文章的更多相关文章
- Laravel大型项目系列教程(一)
Laravel大型项目系列教程(一) 一.课程概述 1.课程介绍 本教程将使用Laravel完成一个多用户的博客系统,大概会包含如下内容: 路由管理. 用户管理,如用户注册.修改信息.锁定用户等. 文 ...
- Laravel大型项目系列教程(二)之用户管理
Laravel大型项目系列教程(二) 一.前言 本节教程将大概实现用户的注册.修改个人信息.管理用户功能. 二.Let's go 1.创建用户注册视图 $ php artisan generate:v ...
- Laravel大型项目系列教程(四)显示文章列表和用户修改文章
小编心语:不知不觉已经第四部分了,非常感谢很多人给小编提的意见,改了很多bug,希望以后能继续帮小编找找茬~小编也不希望误导大家~这一节,主要讲的 是如何显示文章列表和让用户修改文章,小编预告一下(一 ...
- Laravel大型项目系列教程(五)之文章和标签管理
一.前言 本节教程将大概完成文章和标签管理以及标签关联. 二.Let's go 1.文章管理 首先创建管理后台文章列表视图: $ php artisan generate:view admin.art ...
- ABP框架搭建项目系列教程基础版完结篇
返回总目录<一步一步使用ABP框架搭建正式项目系列教程> 经过前面十二篇的基础教程,现在终于该做个总结了. 回顾 第一篇,我们建议新手朋友们先通过ABP官网的启动模板生成解决方案,因为这样 ...
- Android Studio系列教程三--快捷键
Android Studio系列教程三--快捷键 2014 年 12 月 09 日 DevTools 本文为个人原创,欢迎转载,但请务必在明显位置注明出处!http://stormzhang.com/ ...
- webpack4 系列教程(三): 多页面解决方案--提取公共代码
这节课讲解webpack4打包多页面应用过程中的提取公共代码部分.相比于webpack3,4.0版本用optimization.splitChunks配置替换了3.0版本的CommonsChunkPl ...
- WPF系列教程——(三)使用Win10 Edge浏览器内核 - 简书
原文:WPF系列教程--(三)使用Win10 Edge浏览器内核 - 简书 在需要显示一些 H5网站的时候自带的WebBrowser总是显示不了,WebBrowser使用的是IE内核,许多H5新特性都 ...
- CRL快速开发框架系列教程三(更新数据)
本系列目录 CRL快速开发框架系列教程一(Code First数据表不需再关心) CRL快速开发框架系列教程二(基于Lambda表达式查询) CRL快速开发框架系列教程三(更新数据) CRL快速开发框 ...
随机推荐
- Disque:Redis之父新开源的分布式内存作业队列
Disque是Redis之父Salvatore Sanfilippo新开源的一个分布式内存消息代理.它适应于"Redis作为作业队列"的场景,但采用了一种专用.独立.可扩展且具有容 ...
- Unity AssetBundle爬坑手记
这篇文章从AssetBundle的打包,使用,管理以及内存占用各个方面进行了比较全面的分析,对AssetBundle使用过程中的一些坑进行填补指引以及喷! AssetBundle是Unity推荐的 ...
- .Net 4.5可执行程序OutOfMemory
原创文章转载请注明出处:@协思, http://zeeman.cnblogs.com 产线上新部署的服务,发生几次无故停止的情况,通过系统事件看到是这样: 这个服务缓存了大量的数据,内存占用比 ...
- iOS中数据库应用基础
iOS 数据库入门 一.数据库简介 1.什么是数据库? 数据库(Database) 是按照数据结构来组织,存储和管理数据的仓库 数据库可以分为2大种类 关系型数据库(主流) PC端 Oracle My ...
- Gulp探究折腾之路(I)
前言: gulp是前端开发过程中对代码进行构建的工具,是自动化项目的构建利器:她不仅能对网站资源进行优化,而且在开发过程中很多重复的任务能够使用正确的工具自动完成:使用她,我们不仅可以很愉快的编写代码 ...
- Atitit rss没落以及替代品在线阅读器
Atitit rss没落以及替代品在线阅读器 1.1. 对RSS的疯狂追逐,在2005年达到了一个高峰.1 1.2. Rss的问题,支持支rss,不支持url1 1.3. ,博客受到社交网络的冲击.s ...
- Android线程管理之AsyncTask异步任务
前言: 前面几篇文章主要学习了线程以及线程池的创建与使用,今天来学习一下AsyncTask异步任务,学习下AsyncTask到底解决了什么问题?然而它有什么弊端?正所谓知己知彼百战百胜嘛! 线程管理相 ...
- AHCI: Failed to attach drive to Port1 (VERR_GENERAL_FAILURE).
在mac操作系统下,安装VirtualBoxVm虚拟机,虚拟机里面安装wind7操作系统.在启动虚拟机的时候报错:AHCI: Failed to attach drive to Port1 (VERR ...
- JavaScript:正则表达式 前瞻
正向前瞻:用来捕获出现在特定字符之前的字符,只有当字符后面跟着某个特定字符才去捕获它.(?=) 负向前瞻:它用匹配只有当字符后面不跟着某个特定字符时才去匹配它.(?!) 在执行前瞻和负向前瞻之类的运算 ...
- react+redux教程(六)redux服务端渲染流程
今天,我们要讲解的是react+redux服务端渲染.个人认为,react击败angular的真正“杀手锏”就是服务端渲染.我们为什么要实现服务端渲染,主要是为了SEO. 例子 例子仍然是官方的计数器 ...