本章將會繼續講如何構建前臺內容的呈現。我們將調整首頁,顯示有關博客文章評論的信息,并通過將標題內容加入到 URL 中來提升 SEO 效果。
還將在側邊欄添加 2 個常見的博客組件標簽云和最新評論。我們將了解如何為模板引擎進行擴展,以及如何管理網站中靜態資源文件。
顯示首頁評論數
到目前為止首頁會列出最新的文章列表,但并不會列出跟這些文章相關的評論。既然我們已經建立了評論模型,可以獲取文章相關的數據,那么可以回到首頁來呈現文章的評論信息。我們更新一下首頁的模板,打開位于 resources/views/pages/index.blade.php
的文件,根據以下所示調整相應內容:
<footer class="meta">
<p>Comments: {{ $post->comments()->count() }}</p>
<p>Posted by <span class="highlight">{{ $post->author }}</span> at {{ $post->created_at->toDateTimeString() }}</p>
<p>Tags: <span class="highlight">{{ $post->tags }}</span></p>
</footer>
這里 $post->comments()->count()
是通過模型關系返回的結果,count()
即是返回對應關系模型的記錄數。
瀏覽器訪問 http://localhost:8000/
你將會看到每篇的評論數量已經能夠顯示出來了。
構建側邊欄
目前 larablog 的側邊欄看起來有點空。我們將要給這個側邊欄增加兩個常用的部件,標簽云和最新評論。
標簽云
標簽云是顯示文章標簽的一種方式,將常用的標簽加粗顯示。要達到這個目的,我們需要獲取所有日志文章的標簽。
讓我們在 Post
類中新增方法來實現。更新 app/Post.php
,添加如下方法:
public static function getTags()
{
$blogTags = Post::lists('tags');
$tags = [];
foreach ($blogTags as $blogTag) {
$tags = array_merge(explode(',', $blogTag), $tags);
}
return array_map(function ($item){ return trim($item); }, $tags);
return $tags;
}
public static function getTagweights($tags)
{
$tagWeights = [];
if (empty($tags)) {
return $tagWeights;
}
foreach($tags as $tag) {
$tagWeights[$tag] = isset($tagWeights[$tag]) ? $tagWeights[$tag] + 1 : 1;
}
// Shuffle the tags
uksort($tagWeights, function() {
return rand() > rand();
});
$max = max($tagWeights);
// Max of 5 weights
$multiplier = ($max > 5) ? 5 / $max : 1;
foreach ($tagWeights as &$tag) {
$tag = ceil($tag * $multiplier);
}
return $tagWeights;
}
標簽是已逗號分隔的方式存儲在數據庫中,我們需要一個方式將它們分離并形成一個數組。這在 getTags()
方法中實現了。然后 getTagWeights()
方法可以使用這個數組來統計標簽的權重根據他們在數組中的熱門程度計算展示值。標簽經過隨機排序以顯示在頁面上。
現在有能力產生標簽云了,我們需要將它顯示出來。我們建立一個視圖組件用于呈現標簽云。
視圖組件就是在視圖被渲染前,會被調用的閉包或類方法。如果你想在每次渲染某些視圖時綁定數據,視圖組件可以幫你把這樣的程序邏輯都組織到同一個地方。
更多內容請參考 視圖組件
新建一個服務提供者 app/Providers/ComposerServiceProvider.php
,我們通過它增加相應的視圖組件。其內容為:
<?php
namespace App\Providers;
use App\Post;
use Illuminate\Support\ServiceProvider;
class ComposerServiceProvider extends ServiceProvider
{
/**
* 在容器內注冊所有綁定。
*
* @return void
*/
public function boot()
{
view()->composer('*.sidebar', function ($view) {
$view->with('tags', Post::getTagweights(Post::getTags()));
});
}
/**
* 注冊服務提供者。
*
* @return void
*/
public function register()
{
//
}
}
接下來,我們要將這個新建的服務提供者加入到在 config/app.php
配置文件內的 providers 數組中。
'providers' => [
// ...
App\Providers\ComposerServiceProvider::class,
],
在 ComposerServiceProvider
中你會注意到,視圖的 composer
方法可以接受 *
作為通配符,這里是對 *.sidebar
引入了側邊欄的視圖附加相應的視圖內容??梢钥吹?,我們給相應的視圖附加了 tags
數據。
打開側邊欄的視圖 resources/views/partials/sidebar.blade.php
,將內容修改為以下所示:
@section('sidebar')
<section class="section">
<header>
<h3>Tag Cloud</h3>
</header>
<p class="tags">
@forelse($tags as $tag => $weight)
<span class="weight-{{ $weight }}">{{ $tag }}</span>
@empty
<p>There are no tags</p>
@endforelse
</p>
</section>
@show
這個模板不難理解,將標簽數據循環輸出,根據標簽的權重顯示不同的粗細樣式。
最后我們給標簽云添加相應的樣式,新建樣式表 public/css/sidebar.css
,內容如下:
.sidebar .section { margin-bottom: 20px; }
.sidebar h3 { line-height: 1.2em; font-size: 20px; margin-bottom: 10px; font-weight: normal; background: #eee; padding: 5px; }
.sidebar p { line-height: 1.5em; margin-bottom: 20px; }
.sidebar ul { list-style: none }
.sidebar ul li { line-height: 1.5em }
.sidebar .small { font-size: 12px; }
.sidebar .comment p { margin-bottom: 5px; }
.sidebar .comment { margin-bottom: 10px; padding-bottom: 10px; }
.sidebar .tags { font-weight: bold; }
.sidebar .tags span { color: #000; font-size: 12px; }
.sidebar .tags .weight-1 { font-size: 12px; }
.sidebar .tags .weight-2 { font-size: 15px; }
.sidebar .tags .weight-3 { font-size: 18px; }
.sidebar .tags .weight-4 { font-size: 21px; }
.sidebar .tags .weight-5 { font-size: 24px; }
我們找到應用的基礎布局模板 resources/views/layouts/app.blade.php
將側邊欄的樣式引入進來:
@section('stylesheets')
<link href="{{ asset('css/screen.css') }}" type="text/css" rel="stylesheet" />
<link href="{{ asset('css/blog.css') }}" type="text/css" rel="stylesheet" />
<link href="{{ asset('css/sidebar.css') }}" type="text/css" rel="stylesheet" />
@show
如果你現在刷新頁面查看網站將看到標簽云可以正確顯示在你們面前。它根據不同的標簽的權重來顯示為相應的大小,你可以試試通過數據庫修改文章中的標簽數據來看看是否有相應的變化。
最近評論
現在標簽云已經添加到了側邊欄,接下來我們要將最近評論加入其中。
在 Comment
類,文件 app/Comment.php
中加入以下代碼:
public static function getLatest($limit = 10)
{
return Comment::orderBy('id', 'DESC')->take($limit)->get();
}
然后,我們和標簽云的做法一樣,來增加視圖組件,打開 app/Providers/ComposerServiceProvider.php
,修改 boot
方法:
<?php
namespace App\Providers;
use App\Post;
use App\Comment;
use Illuminate\Support\ServiceProvider;
class ComposerServiceProvider extends ServiceProvider
{
/**
* 在容器內注冊所有綁定。
*
* @return void
*/
public function boot()
{
view()->composer('*.sidebar', function ($view) {
$view->with('tags', Post::getTagweights(Post::getTags()));
$view->with('latestComments', Comment::getLatest());
});
}
/**
* 注冊服務提供者。
*
* @return void
*/
public function register()
{
//
}
}
最后我們需要再次修改視圖模板,加入最新評論的顯示代碼。打開文件 resources/views/partials/sidebar.blade.php
,修改為如下內容:
@section('sidebar')
<section class="section">
<header>
<h3>Tag Cloud</h3>
</header>
<p class="tags">
@forelse($tags as $tag => $weight)
<span class="weight-{{ $weight }}">{{ $tag }}</span>
@empty
<p>There are no tags</p>
@endforelse
</p>
</section>
<section class="section">
<header>
<h3>Latest Comments</h3>
</header>
@forelse($latestComments as $latestComment)
<article class="comment">
<header>
<p class="small"><span class="highlight">{{ $latestComment->user }}</span> commented on
<a href="/posts/{{ $latestComment->post->id }}#comment-{{ $latestComment->id }}">
{{ $latestComment->post->title }}
</a>
[<em>{{ $latestComment->created_at->toDateTimeString('Y-m-d h:iA') }}</time></em>]
</p>
</header>
<p>{{ $latestComment->comment }}</p>
</p>
</article>
@empty
<p>There are no recent comments</p>
@endforelse
</section>
@show
至此,如果你打開瀏覽器刷新頁面,你將會看到最近的評論位于標簽云的下方,也能正確的顯示出來了。
模板引擎擴展
到目前為止,我們一直以標準的日期格式顯示博客的評論日期。一個更好的方法將是顯示評論被發布多久,如 3 小時前發布。我們可以向評論模型中添加一個方法來達到目的,但是我們在別的模型也可能要用到這個功能,因此我們通過擴充模版引擎來實現會更好。Laravel 的模板引擎 Blade 提供擴展接口可以讓我們定義自己的模板命令。
我們將創建一個新的模板標記,可以使用如下:
@datetime($comment->created_at)
添加擴展
打開文件 app/Providers/AppServiceProvider.php
,編輯內容如下所示:
<?php
namespace App\Providers;
use Illuminate\Support\ServiceProvider;
class AppServiceProvider extends ServiceProvider
{
/**
* Bootstrap any application services.
*
* @return void
*/
public function boot()
{
\Blade::directive('datetime', function($expression) {
return "<?php echo with{$expression}->diffForHumans(); ?>";
});
}
/**
* Register any application services.
*
* @return void
*/
public function register()
{
//
}
}
我們通過自定義命令,使用 directive
方法注冊命令。當 Blade 編譯器遇到該命令時,它將會帶參數調用提供的回調函數。with
輔助函數會簡單地返回指定的對象或值,并允許使用便利的鏈式調用。最后此命令生成的 PHP 會是:
<?php echo with($var)->diffForHumans(); ?>
更多詳細內容可以閱讀文檔 擴充 Blade
更新視圖
現在我們可以更新側邊欄上評論的顯示時間,使用剛建立的模板命令來讓時間更為直觀。打開文件 resources/views/partials/sidebar.blade.php
,將輸出時間信息的標記部分更改更改為如下所示:
[<em>@datetime($latestComment->created_at)</time></em>]
如果你現在訪問首頁,刷新頁面,你可以看到最新評論已經可以更佳直觀的顯示評論發布的時間。
讓我們也來更新一下文章的評論列表的視圖,打開位于 resources/views/comments/index.blade.php
,將其內容更新為如下所示:
@forelse($comments as $i => $comment)
<article class="comment {{ $i % 2 == 0 ? 'odd' : 'even' }}" id="comment-{{ $comment->id }}">
<header>
<p><span class="highlight">{{ $comment->user }}</span> commented {{ $comment->created_at->format('l, F j, Y') }}</p>
</header>
<p>{{ $comment->comment }}</p>
</article>
@empty
<p>There are no comments for this post. Be the first to comment...</p>
@endforelse()
自此我們通過擴展模板引擎讓評論的發布時間顯示更為直觀友好了。
格式化 URL
目前每個博客文章的網址只是通過編號表示來顯示,雖然從功能的角度來完全可以接受,但它對 SEO 并不好。例如 url http://localhost:8000/1
并沒有提供關于博客內容的任何信息,像 http://localhost:8000/a-day-with-laravel
則會好得多。為了達到這個目的,我們將把這個博客標題格式化并將其作為這個 URL 的一部分。標題將刪除所有非 ASCII 字符,并用 -
替換它們。
更新路由
首先,我們打開路由配置文件 app/Http/routes.php
,增加新的文章詳情頁的路由配置,在 routes.php
中增加如下配置:
Route::get('{id}/{slug}', 'PostsController@show');
格式化
緊接著我們打開 Post
類,文件位于 app/Post.php
新增如下方法:
public static function slugify($text)
{
// replace non letter or digits by -
$text = preg_replace('#[^\\pL\d]+#u', '-', $text);
// trim
$text = trim($text, '-');
// transliterate
if (function_exists('iconv'))
{
$text = iconv('utf-8', 'us-ascii//TRANSLIT', $text);
}
// lowercase
$text = strtolower($text);
// remove unwanted characters
$text = preg_replace('#[^-\w]+#', '', $text);
if (empty($text))
{
return 'n-a';
}
return $text;
}
模型和數據遷移
由于我們要通過格式化的標題也能訪問文章信息,所以我們要儲相應的格式化信息,那么我們就需要給文章表增加一個新的字段 slug
。
我們通過一下命令建立一個新的遷移腳本,目的是向 posts
表中添加這個新的字段:
php artisan make:migration add_slug_to_posts_table --table=posts
然后打開遷移文件 xxxx_xx_xx_xxxxxx_add_slug_to_posts_table.php
,更改其內容為如下所示:
<?php
use Illuminate\Database\Schema\Blueprint;
use Illuminate\Database\Migrations\Migration;
class AddSlugToPostsTable extends Migration
{
/**
* Run the migrations.
*
* @return void
*/
public function up()
{
Schema::table('posts', function (Blueprint $table) {
$table->string('slug');
});
}
/**
* Reverse the migrations.
*
* @return void
*/
public function down()
{
Schema::table('posts', function (Blueprint $table) {
$table->dropColumn('slug');
});
}
}
讓我們執行腳本遷移讓改動立即生效:
php artisan migrate
為了讓設置文章標題時能自動添加格式化的 slug
信息,我們可以給模型 Post
定義一個屬性修改器,打開 app/Post.php
,添加如下方法:
public function setTitleAttribute($value)
{
$this->attributes['title'] = $value;
$this->attributes['slug'] = self::slugify($value);
}
我們定義了 title
的屬性修改器,會當我們嘗試在模型上設置 title
的值時自動調用此修改器。
我們利用這個屬性修改器來自動設置 slug
的值。
接下來,我們通過數據庫客戶端訪問數據庫,將 posts
和 comments
表清空(truncate 操作)。
然后重新執行我們之前的數據填充操作:
php artisan db:seed
這樣我們的我們重新生成了 Post
數據,可以看到數據庫中每條文章的 slug
字段都已經自動生成好了。
更新視圖中的路由信息
接下來,我們要對首頁的文章和側邊欄的文章鏈接進行修改,使用我們的格式化 URL 來替換原來的鏈接地址。
打開 resources/views/pages/index.blade.php
,找到博客文章鏈接的位置修改為如下內容:
<header>
<h2><a href="/{{ $post->id }}/{{ $post->slug }}">{{ $post->title }}</a></h2>
</header>
緊接著我們打開側邊欄的視圖文件 resources/views/partials/sidebar.blade.php
,將文章鏈接修改為如下所示:
<header>
<p class="small"><span class="highlight">{{ $latestComment->user }}</span> commented on
<a href="/{{ $latestComment->post->id }}/{{ $latestComment->post->slug }}#comment-{{ $latestComment->id }}">
{{ $latestComment->post->title }}
</a>
[<em>@datetime($latestComment->created_at)</time></em>]
</p>
</header>
最后我們還需要評論控制器 CommentsController
作出一些調整,目的是讓用戶發表評論后跳轉到相應的評論所在位置。
打開 app/Http/Controllers/CommentsController.php
,修改 store
方法為如下所示:
public function store(CommentRequest $request, $postId)
{
$post = Post::findOrFail($postId);
$comment = new Comment;
$comment->user = $request->get('user');
$comment->comment = $request->get('comment');
$comment->post()->associate($post);
$comment->save();
return redirect("/{$post->id}/{$post->slug}#comment-{$comment->id}");
}
現在你可以刷新瀏覽器,訪問首頁來點擊博客文章看看我們是否將格式化的標題信息加入了到了 URL 當中,同時我們在發布完評論之后,也會立即定位到所在評論信息。
資源管理
在我們的項目開發中涉及到一些靜態資源的管理,比如 css
,js
的管理。這時我們可以利用到 Laravel Elixir。
Laravel Elixir 是官方推薦的靜態資源管理工具,此工具合理的定義項目的開發流程,尤其針對前端開發,解決了很多通用問題,如;Sass 編譯器,靜態資源文件的版本與緩存清除等。
它提供了簡潔流暢的 API,讓你能夠在你的 Laravel 應用程序中定義基本的 Gulp 任務。Elixir 支持許多常見的 CSS 與 JavaScrtip 預處理器,甚至包含了測試工具。
安裝及配置
在開始使用 Elixir 之前,你必須先確定你的機器上有安裝 Node.js
。
Node 可以通過下面的鏈接下載:
https://nodejs.org/en/download/
Mac 系統的用戶建議通過 brew install node
進行安裝。
安裝完成后,可以通過下面命令查看:
node -v
接著,你需要全局安裝 Gulp 的 NPM 擴展包:
npm install --global gulp
最后的步驟就是安裝 Elixir,進入你項目目錄,你會發現根目錄有個名為 package.json 的文件。想像它就如同你的 composer.json 文件,只不過它定義的是 Node 的依賴擴展包,而不是 PHP 的。
編輯 package.json
文件,將內容修改為如下所示:
{
"private": true,
"devDependencies": {
"gulp": "^3.8.8"
},
"dependencies": {
"laravel-elixir": "^4.0.0"
}
}
執行下面的命令安裝依賴包:
npm install
樣式表
我們將位于 public/css
目錄下的 screen.css
,blog.css
,sidebar.css
文件移動到 resources/assets/css
下。
接下來我們做的是將多個 CSS 樣式合并成單個的文件,我們打開項目目錄下的 gulpfile.js
定義 Elixir 任務:
elixir(function(mix) {
mix.styles([
'screen.css',
'blog.css',
'sidebar.css'
]);
});
styles
方法的默認讀取路徑為resources/assets/css
目錄,而生成的 CSS 會被放置于public/css/all.css
。
可以通過傳遞第二個參數至 styles 方法,將生成的文件輸出至指定的位置:
mix.styles(['normalize.css','main.css'], 'public/assets/css');
在項目目錄下執行 gulp
來執行 Elixir 任務,我們可以看到我們生成了預期的 public/css/all.css
文件。
打開我們的視圖布局模板文件 resources/views/layouts/app.blade.php
,可以修改 stylesheets
部分的內容,使用我們合并后的 CSS 文件:
@section('stylesheets')
<link href="{{ asset('css/all.css') }}" type="text/css" rel="stylesheet" />
@show
JS
至于 Javascript,雖然我們這里沒有涉及到 JS 的內容,不過如果你之后也需要對 JS 有合并的需求也可以這么添加 Elixir 任務:
elixir(function(mix) {
mix.scripts([
'jquery.js',
'app.js'
]);
});
scripts
方法假設所有的路徑都相對于resources/assets/js
目錄,且默認會將生成的 JavaScript 放置于public/js/all.js
。
壓縮
當你應用發布到生產環境時,你可以通過下面的命令來壓縮所有 CSS 及 JavaScript:
// 運行所有任務并壓縮所有 CSS 及 JavaScript...
gulp --production
壓縮這些靜態資源的好處最明顯的就是縮小其文件大小提高傳輸相應速度。
其它
這里我們只是使用了 Elixir 來做簡單的合并和壓縮,它的功能不止這些,Elixir 支持許多常見的 CSS 與 JavaScrtip 預處理器,甚至包含了測試工具。使用鏈式調用,Elixir 還能讓你流暢地定義開發流程。這非常適合前端的開發流程化,是個不可多得好工具。
更多內容還請關注相應的文檔內容 Laravel Elixir。
總結
通過這個部分的實踐,我們又接觸到了 Laravel 的許多新內容,包括 Laravel 視圖組件以及靜態資源文件的管理。我么還對主頁進行了改進,并在側邊欄添加了一些組件。
在下一章我們繼續探討測試相關的內容。我們將使用 PHPUnit 進行單元和功能測試。我們還會編寫模擬 Web 請求的功能測試,填寫表單和點擊鏈接,然后檢查返回的響應。