上一節教程中完成了用戶管理,這節教程將大概完成發表Markdown格式文章並展現的功能。php
文章模塊中咱們須要創建articles
、tags
以及article_tag
表,每篇文章會有一到多個標籤,每一個標籤會有一到多篇文章,建立遷移文件:html
$ 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
:laravel
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'); });
執行遷移:json
$ php artisan migrate
建立Article
和Tag
模型:服務器
$ php artisan generate:model article $ php artisan generate:model tag
先在User.php
中增長:app
public function articles() { return $this->hasMany('Article'); }
一個用戶會有多篇文章。composer
修改Article.php
:ide
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
用於多對多關聯。
首先在導航條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
選項後,會出現發表文章的頁面:
這裏咱們將使用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
按鈕:
出現預覽界面:
下面就要向數據庫添加文章了,在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
完成以後看看效果吧,先編輯文章:
發佈後跳轉到顯示文章頁面:
這節教程使用了控制器,完成了發佈文章並展現的功能,但仍是有不少瑕疵,在代碼方面,如今你能夠重構下User
和驗證登陸的路由,把它們都變成控制器,在用戶體驗上你能夠把發佈文章編輯內容時由服務器端解析改爲客戶端解析,推薦StackEdit,下一節教程將完成網站首頁和用戶主頁展現文章列表和標籤,而且讓用戶可以刪除和修改文章。