Laravel大型項目系列教程(三)之發表文章

Laravel大型項目系列教程(三)之發表文章

1、前言

上一節教程中完成了用戶管理,這節教程將大概完成發表Markdown格式文章並展現的功能。php


2、Let's go

1.數據庫遷移

文章模塊中咱們須要創建articlestags以及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.phplaravel

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

2.建立Article和Tag模型

建立ArticleTag模型:服務器

$ php artisan generate:model article
$ php artisan generate:model tag

先在User.php中增長:app

public function articles()
{
    return $this->hasMany('Article');
}

一個用戶會有多篇文章。composer

修改Article.phpide

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">&times;</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.jsonrequire中增長:

"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

相關文章
相關標籤/搜索