Menu

85.锁定话题(二)

本节说明

  • 对应视频教程第 85 小节:An Administrator May Lock Any Thread: Part 2

本节内容

本节我们继续锁定话题功能的开发。首先我们添加一个功能测试:
forum\tests\Feature\LockThreadsTest.php

    .
    .
    /** @test */
    public function non_administrator_may_not_lock_threads()
    {
        $this->signIn();

        $thread = create('App\Thread',[
            'user_id' => auth()->id()
        ]);

        $this->patch($thread->path(),[
            'locked' => true
        ])->assertStatus(403);

        $this->assertFalse(!! $thread->fresh()->locked);
    }

    /** @test */
    public function once_locked_thread_may_not_receive_new_replies()
    {
        .
        .
    }
}

这个测试是确保非管理员角色的用户无法锁定话题。在之前的章节中,我们设定了用户 NoNo1 是管理员。我们本节继续这么设定,保持简单:
forum\app\User.php

    .
    .
    public function confirm()
    {
        $this->confirmed = true;
        $this->confirmation_token = null;

        $this->save();
    }

    public function isAdmin()
    {
        return in_array($this->name,['NoNo1']);
    }
    .
    .

为了让测试通过,我们来添加patch路由:
forum\routes\web.php

.
.
Route::get('threads/{channel}/{thread}','ThreadsController@show');
Route::patch('threads/{channel}/{thread}','ThreadsController@update')->name('threads.update');
.
.

添加update()方法:
forum\app\Http\Controllers\ThreadsController.php

    .
    .
    public function update()
    {
        if (request()->has('locked')) {
            if(! auth()->user()->isAdmin()) {
                return response('',403);
            }
        }
    }

    public function destroy($channel,Thread $thread)
    {
        .
        .
    }
    .
    .

运行测试:
file
我们继续添加功能测试:
forum\tests\Feature\LockThreadsTest.php

    .
    .
    public function non_administrator_may_not_lock_threads()
    {
        .
        .
    }

    /** @test */
    public function administrators_can_lock_threads()
    {
        $this->signIn(factory('App\User')->states('administrator')->create());

        $thread = create('App\Thread',['user_id' => auth()->id()]);

        $this->patch($thread->path(),[
            'locked' => true
        ]);

        $this->assertTrue(!! $thread->fresh()->locked);
    }
    .
    .

在上面测试中,我们让管理员登录,为了更具可读性,我们使用了states('administrator')。我们修改模型工厂文件,添加states('administrator')声明:
forum\database\factories\ModelFactory.php

.
.
$factory->state(App\User::class,'unconfirmed',function () {
   return [
      'confirmed' => false
   ];
});

$factory->state(App\User::class,'administrator',function () {
    return [
        'name' => 'NoNo1'
    ];
});
.
.

我们还需要修改控制器,当登录用户为管理员时进行更新locked字段:
forum\app\Http\Controllers\ThreadsController.php

    .
    .
    public function update($channelId,Thread $thread)
    {
        if (request()->has('locked')) {
            if(! auth()->user()->isAdmin()) {
                return response('',403);
            }

            $thread->lock();
        }
    }
    .
    .

运行测试:
file
接下来我们来做点重构。我们知道,很多的动作只有管理员才能触发,所以我们把验证用户是否是管理员的逻辑封装成中间件,在需要验证的控制器中开启此中间件即可。首先我们新建中间件:

$ php artisan make:middleware Administrator

forum\app\Http\Middleware\Administrator.php

<?php

namespace App\Http\Middleware;

use Closure;

class Administrator
{
    public function handle($request, Closure $next)
    {
        if(auth()->check() && auth()->user()->isAdmin()) {
            return $next($request);
        }

        abort(403,'You do not have permission to do so.');
    }
}

然后我们注册中间件:
forum\app\Http\Kernel.php

<?php

namespace App\Http;

use App\Http\Middleware\Administrator;
use App\Http\Middleware\RedirectIfEmailNotConfirmed;
use Illuminate\Foundation\Http\Kernel as HttpKernel;

class Kernel extends HttpKernel
{
    .
    .
    protected $routeMiddleware = [
        'auth' => \Illuminate\Auth\Middleware\Authenticate::class,
        'auth.basic' => \Illuminate\Auth\Middleware\AuthenticateWithBasicAuth::class,
        'bindings' => \Illuminate\Routing\Middleware\SubstituteBindings::class,
        'can' => \Illuminate\Auth\Middleware\Authorize::class,
        'guest' => \App\Http\Middleware\RedirectIfAuthenticated::class,
        'throttle' => \Illuminate\Routing\Middleware\ThrottleRequests::class,
        'must-be-confirmed' => RedirectIfEmailNotConfirmed::class,
        'admin' => Administrator::class
    ];
}

接着我们来把锁定话题的逻辑放到单独的控制器中。因为在锁定话题时,我们实际上只需更新locked字段即可。如果放到话题控制器的update方法中,那会与更新话题的逻辑混杂,所以我们抽取成单独的控制器。首先需要修改路由:
forum\routes\web.php

.
.
Route::get('threads','ThreadsController@index')->name('threads');
Route::get('threads/create','ThreadsController@create');
Route::get('threads/{channel}/{thread}','ThreadsController@show');
Route::delete('threads/{channel}/{thread}','ThreadsController@destroy');
Route::post('threads','ThreadsController@store')->middleware('must-be-confirmed');
Route::get('threads/{channel}','ThreadsController@index');

Route::post('locked-threads/{thread}','LockedThreadsController@store')->name('locked-threads.store')->middleware('admin');
.
.

我们移除了本节之前定义的路由,并且为新定义的路由应用了中间件:admin。然后我们来新建控制器:

$ php artisan make:controller LockedThreadsController

将锁定话题的逻辑移到store()方法中:
forum\app\Http\Controllers\LockedThreadsController.php

<?php

namespace App\Http\Controllers;

use App\Thread;

class LockedThreadsController extends Controller
{
    public function store(Thread $thread)
    {
        $thread->lock();
    }
}

最后,我们需要更新我们的测试:
forum\tests\Feature\LockThreadsTest.php

<?php

namespace Tests\Feature;

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

class LockThreadsTest extends TestCase
{
    use DatabaseMigrations;

    /** @test */
    public function non_administrator_may_not_lock_threads()
    {
        // 开启
        $this->withExceptionHandling();

        $this->signIn();

        $thread = create('App\Thread',[
            'user_id' => auth()->id()
        ]);

        // 更改
        $this->post(route('locked-threads.store',$thread))->assertStatus(403);

        $this->assertFalse(!! $thread->fresh()->locked);
    }

    /** @test */
    public function administrators_can_lock_threads()
    {
        $this->signIn(factory('App\User')->states('administrator')->create());

        $thread = create('App\Thread',['user_id' => auth()->id()]);

        // 更改
        $this->post(route('locked-threads.store',$thread));

        $this->assertTrue(!! $thread->fresh()->locked);
    }

    /** @test */
    public function once_locked_thread_may_not_receive_new_replies()
    {
        $this->signIn();

        $thread = create('App\Thread');

        $thread->lock();

        $this->post($thread->path() . '/replies',[
            'body' => 'Foobar',
            'user_id' => auth()->id()
        ])->assertStatus(422);
    }
}

我们运行测试:
file

本文章首发在 Laravel China 社区
上一篇 下一篇
讨论数量: 0
发起讨论


暂无话题~