11.根据频道来筛选话题

未匹配的标注

本节说明#

  • 对应视频第 11 小节:A User Can Filter Threads By Channel

本节内容#

上一节中我们引入了 Channel 的概念:一个 Thread 属于一个 Channel,一个 Channel 拥有多个 Thread。现在我们来实现根据 Channel 筛选 Thread 的功能。
首先新建测试:
forum\tests\Feature\ReadThreadsTest.php

.
.
/** @test */
public function a_user_can_filter_threads_according_to_a_channel()
{
    $channel = create('App\Channel');
    $threadInChannel = create('App\Thread',['channel_id' => $channel->id]);
    $threadNotInChannel = create('App\Thread');

    $this->get('/threads/' . $channel->slug)
        ->assertSee($threadInChannel->title)
        ->assertDontSee($threadNotInChannel->title);
}
.
.

在测试用例中,我们新建了一个 Channel 两个 Thread,其中一个 Threadchannel_id 是我们新建 Channelid。我们的测试是,当我们通过该 Channle 来筛选 Thread,我们希望看到与该 Channel 相关的 Thread,并且不看到与该 Channel 无关的 Thread
运行测试:
file
让我们来修复它:
forum\routes\web.php

.
.
Route::get('/home', 'HomeController@index')->name('home');
Route::get('threads/{channel}','ThreadsController@index');  -->修改此处路由
Route::get('threads/create','ThreadsController@create');
Route::get('threads/{channel}/{thread}','ThreadsController@show');
Route::post('threads','ThreadsController@store');
Route::post('/threads/{channel}/{thread}/replies','RepliesController@store');
.
.

forum\app\Http\Controllers\ThreadsController.php

.
.
public function index($channelSlug = null)
{
    if($channelSlug){
        $channelId = Channel::where('slug',$channelSlug)->first()->id;

        $threads = Thread::where('channel_id',$channelId)->latest()->get();
    }else{
        $threads = Thread::latest()->get();
    }

    return view('threads.index',compact('threads'));
}
.
.

可以看到,我们在控制器中的代码是很粗糙的。我们这么做的原因是为了先让测试通过,稍后进行修改。现在来运行测试:

$ APP_ENV=testing phpunit --filter a_user_can_filter_threads_according_to_a_channel

测试通过:
file
现在我们的测试已经通过,接下来我们来完善刚刚编写的代码:

public function index(Channel $channel)
{
    if($channel->exists){
        $threads = $channel->threads()->latest()->get();
    }else{
        $threads = Thread::latest()->get();
    }

    return view('threads.index',compact('threads'));
}

注意到我们使用了 threads() 来获取 $threads,但模型关联尚未建立,需要先编写单元测试,这也是 TDD 的开发理念。

我们来编写单元测试:
forum\tests\Unit\ChannelTest.php

<?php

namespace Tests\Unit;

use Tests\TestCase;
use Illuminate\Foundation\Testing\DatabaseMigrations;

class ChannelTest extends TestCase
{
    use DatabaseMigrations;

    /** @test */
    public function a_channel_consists_of_threads()
    {
        $channel = create('App\Channel');
        $thread = create('App\Thread',['channel_id' => $channel->id]);

        $this->assertTrue($channel->threads->contains($thread));
    }
}

运行一下测试:
file
因为此时尚未建立模型关联,因此 $thread->threads 获取的对象为 null,所以就抛出了上面的异常。我们来建立模型关联:
forum\app\Channel.php

<?php

namespace App;

use Illuminate\Database\Eloquent\Model;

class Channel extends Model
{
    public function threads()
    {
        return $this->hasMany(Thread::class);
    }
}

运行测试:
file
测试已经 OK ,但是我们现在还有一个问题要注意:如果我们访问 http://forum.test/threads/{channel} 这样的路由,我们是无法访问的:
file
注意看一下我们的路由定义:

Route::get('threads/{channel}','ThreadsController@index');

我们知道,以上的路由符合 Laravel隐性路由模型绑定 原则。但是 {channel} 路由片段默认对应的是 id 字段,而我们需要对应的是 slug 字段。所以我们需要重写 getRouteKeyName() 方法:
forum\app\Channel.php

<?php

namespace App;

use Illuminate\Database\Eloquent\Model;

class Channel extends Model
{
    public function getRouteKeyName()
    {
        return 'slug';
    }
    .
    .
}

运行一下全部的测试:

$ APP_ENV=testing phpunit

file
先看一下失败的 guests_may_not_create_threads 测试:
forum\tests\Feature\CreateThreadsTest.php

.
.
/** @test */
public function guests_may_not_create_threads()
{
    $this->withExceptionHandling();

    $this->get('/threads/create')
        ->assertRedirect('/login');

    $this->post('/threads')
        ->assertRedirect('/login');
}
.
.

注意看这一行 $this->get('/threads/create')。我们声明过 /threads/{channel} 这样的路由,用来筛选该 channel 下的所有相关 thread,在 /threads/create 这样的 url 中,Laravel 会认为 create 是一个 channel。我们需要调整路由声明的顺序:
forum\routes\web.php

.
.
Route::get('/home', 'HomeController@index')->name('home');
Route::get('threads','ThreadsController@index');   -->在之前的课程中误删除了此路由,因此测试时报了一个错误
Route::get('threads/create','ThreadsController@create');
Route::get('threads/{channel}/{thread}','ThreadsController@show');
Route::post('threads','ThreadsController@store');
Route::get('threads/{channel}','ThreadsController@index');  -->将此路由放在后面声明
Route::post('/threads/{channel}/{thread}/replies','RepliesController@store');
.

再次运行测试,即可成功通过:
file
现在我们已经可以根据 channel 来筛选 thread 了,让我们在页面展示出来:
forum\resources\views\layouts\app.blade.php

.
.
<div class="collapse navbar-collapse" id="app-navbar-collapse">
    <!-- Left Side Of Navbar -->
    <ul class="nav navbar-nav">
        <li><a href="/threads">All Threads</a></li>

        <li class="dropdown">
            <a href="#" class="dropdown-toggle" data-toggle="dropdown" aria-hidden="true"
               aria-expanded="false">Channels <span class="caret"></span> </a>

            <ul class="dropdown-menu">
                @foreach(\App\Channel::all() as $channel)
                    <li><a href="/threads/{{ $channel->slug }}">{{ $channel->name }}</a> </li>
                @endforeach
            </ul>
        </li>
    </ul>
    .
    .

注:获取所有 channel 的代码稍后需要完善,目前的比较粗糙

看一下效果:

file

本文章首发在 LearnKu.com 网站上。

上一篇 下一篇
《L04 微信小程序从零到发布》
从小程序个人账户申请开始,带你一步步进行开发一个微信小程序,直到提交微信控制台上线发布。
《L01 基础入门》
我们将带你从零开发一个项目并部署到线上,本课程教授 Web 开发中专业、实用的技能,如 Git 工作流、Laravel Mix 前端工作流等。
讨论数量: 0
发起讨论 查看所有版本


暂无话题~