Skip to content

3. 开始

Govern Fu edited this page May 24, 2019 · 15 revisions

初始化

自动加载

默认情况下,模块类不会自动加载。您可以使用自动加载模块 psr-4。例如:

{
  "autoload": {
    "psr-4": {
      "App\\": "app/",
      "Modules\\": "modules/"
    }
  }
}

提示:不要忘记 composer dump-autoload 之后运行。

单元测试

code phpunit.xml
<?xml version="1.0" encoding="UTF-8"?>
<phpunit backupGlobals="false"
         backupStaticAttributes="false"
         bootstrap="vendor/autoload.php"
         colors="true"
         convertErrorsToExceptions="true"
         convertNoticesToExceptions="true"
         convertWarningsToExceptions="true"
         processIsolation="false"
         stopOnFailure="false">
    <testsuites>
        <testsuite name="Feature">
            <directory suffix="Test.php">./modules/**/Tests/</directory>
        </testsuite>
        <testsuite name="Unit">
            <directory suffix="Test.php">./modules/**/Tests/</directory>
        </testsuite>
    </testsuites>
    <filter>
        <whitelist processUncoveredFilesFromWhitelist="false">
            <directory suffix=".php">./modules</directory>
            <exclude>
                <directory suffix=".php">./modules/**/Routes</directory>
                <directory suffix=".php">./modules/**/Resources</directory>
                <directory suffix=".php">./modules/**/Tests</directory>
                <directory suffix=".php">./modules/**/Config</directory>
            </exclude>
        </whitelist>
    </filter>
    <php>
        <env name="APP_ENV" value="testing"/>
        <env name="BCRYPT_ROUNDS" value="4"/>
        <env name="CACHE_DRIVER" value="array"/>
        <env name="QUEUE_CONNECTION" value="sync"/>
    </php>
</phpunit>

更新模块

php artisan module:update

创建模块

php artisan module:make Demo

创建模型、迁移文件

php artisan module:make-model Example -m Example

你也可以将其分开创建

php artisan module:make-model Example Example
php artisan module:make-migration create_examples_table Example

创建填充文件

php artisan module:make-seed ExampleTableSeeder Example

创建仓库、演示者、转化器

php artisan module:make-repository ExampleRepository -pr Example

你也可以将其分开创建

php artisan module:make-repository ExampleRepository Example
php artisan module:make-presenter ExamplePresenter Example
php artisan module:make-resource ExampleTransformer Example

创建服务

php artisan module:make-service ExampleService Example

创建控制器

php artisan module:make-api ExampleController Example

创建请求规则

php artisan module:make-request ExampleStoreRequest Example
php artisan module:make-request ExampleUpdateRequest Example

创建单元测试

php artisan module:make-test ExampleTest Example

开始

迁移数据库

通过迁移文件在数据库中添加数据字段:

code modules/Example/Database/Migrations/*_create_examples_table.php
<?php

use Illuminate\Support\Facades\Schema;
use Illuminate\Database\Schema\Blueprint;
use Illuminate\Database\Migrations\Migration;

class CreateExamplesTable extends Migration
{
    /**
     * Run the migrations.
     *
     * @return void
     */
    public function up()
    {
        Schema::create('examples', function (Blueprint $table) {
            $table->bigIncrements('id');

            $table->string('name')->unique()->comment('键名');
            $table->string('value')->comment('键值');

            $table->timestamps();
        });
    }

    /**
     * Reverse the migrations.
     *
     * @return void
     */
    public function down()
    {
        Schema::dropIfExists('examples');
    }
}
php artisan module:migrate

定义模型可填充字段

在模型文件中添加可填充字段:

code modules/Example/Entities/Example.php
<?php

namespace Modules\Example\Entities;

use Illuminate\Database\Eloquent\Model;
use Modules\Core\Traits\CamelMutatorTrait;

class Example extends Model
{
    use CamelMutatorTrait;

    protected $fillable = [
        'name',
        'value',
    ];
}

填充数据

通过填充文件在数据库中添加数据:

code modules/Example/Database/Seeders/ExampleTableSeederTableSeeder.php
<?php

namespace Modules\Example\Database\Seeders;

use Illuminate\Database\Seeder;
use Illuminate\Database\Eloquent\Model;
use Modules\Example\Entities\Example;

class ExampleTableSeederTableSeeder extends Seeder
{
    /**
     * Run the database seeds.
     *
     * @return void
     */
    public function run()
    {
        Example::truncate();
        Example::create([
            'name' => 'demo',
            'value' => 'hello world',
        ]);
    }
}
code modules/Demo/Database/Seeders/DemoDatabaseSeeder.php
<?php

namespace Modules\Example\Database\Seeders;

use Illuminate\Database\Seeder;
use Illuminate\Database\Eloquent\Model;

class ExampleDatabaseSeeder extends Seeder
{
    /**
     * Run the database seeds.
     *
     * @return void
     */
    public function run()
    {
        Model::unguard();

        $this->call(ExampleTableSeederTableSeeder::class);
    }
}
php artisan module:seed

数据转化

在转化器中添加输出字段转化:

code modules/Example/Transformers/ExampleTransformer.php
<?php

namespace Modules\Example\Transformers;

use Modules\Core\Abstracts\TransformerAbstract;

/**
 * Class ExampleTransformer
 *
 * @package Modules\Example\Transformers
 */
class ExampleTransformer extends TransformerAbstract
{
    /**
     * Transform the Example entity.
     *
     * @param \Modules\Example\Entities\Example $attribute
     *
     * @return array
     */
    public function fields($attribute)
    {
        return [
            'id' => (int) $attribute->id,

            'name' => (string) $attribute->name,
            'value' => (string) $attribute->value
        ];
    }
}

可搜索字段

在仓库实现中定义可搜索字段:

code modules/Example/Repositories/ExampleRepositoryEloquent.php
<?php

namespace Modules\Example\Repositories;

use Modules\Example\Entities\Example;
use Modules\Example\Presenters\ExamplePresenter;
use Prettus\Repository\Eloquent\BaseRepository;

class ExampleRepositoryEloquent extends BaseRepository implements ExampleRepository
{
    protected $fieldSearchable = [
        'name' => 'like',
    ];

    /**
     * Specify Model
     *
     * @return string
     */
    public function model()
    {
        return Example::class;
    }

    /**
     * Specify Presenter
     *
     * @return mixed
     */
    public function presenter()
    {
        return ExamplePresenter::class;
    }
}

可请求字段

在请求条件中定义可请求字段:

code modules/Example/Http/Requests/ExampleStoreRequest.php
<?php

namespace Modules\Example\Http\Requests;

use Modules\Core\Http\Requests\Request;

class ExampleStoreRequest extends Request
{
    /**
     * Get the validation rules that apply to the request.
     *
     * @return array
     */
    public function rules()
    {
        return [
            'name' => 'required|unique:examples',
            'value' => 'required',
        ];
    }
}
code modules/Example/Http/Requests/ExampleUpdateRequest.php
<?php

namespace Modules\Example\Http\Requests;

use Modules\Core\Http\Requests\Request;

class ExampleUpdateRequest extends Request
{
    /**
     * Get the validation rules that apply to the request.
     *
     * @return array
     */
    public function rules()
    {
        return [
            'value' => 'required',
        ];
    }
}

服务接口

code modules/Example/Services/ExampleService.php
<?php

namespace Modules\Example\Services;

use Modules\Core\Supports\Response;

interface ExampleService
{
    public function index(): Response;

    public function store(array $attributes): Response;

    public function show(int $id): Response;

    public function update(int $id, array $attributes): Response;

    public function destroy(int $id): Response;
}

服务实现

code modules/Example/Services/ExampleServiceI.php
<?php

namespace Modules\Example\Services;

use Modules\Core\Supports\Response;
use Modules\Example\Repositories\ExampleRepository;

class ExampleServiceI implements ExampleService
{
    /**
     * @var \Modules\Example\Repositories\ExampleRepository
     */
    private $exampleRepository;

    public function __construct(ExampleRepository $dataRepository)
    {
        $this->exampleRepository = $dataRepository;
    }

    public function index(): Response
    {
        return Response::handleOk($this->exampleRepository->all());
    }

    public function store(array $attributes): Response
    {
        return Response::handleCreated($this->exampleRepository->create($attributes));
    }

    public function show(int $id): Response
    {
        return Response::handleOk($this->exampleRepository->find($id));
    }

    public function update(int $id, array $attributes): Response
    {
        return Response::handleResetContent($this->exampleRepository->update($attributes, $id));
    }

    public function destroy(int $id): Response
    {
        return Response::handleNoContent($this->exampleRepository->delete($id));
    }
}

控制器接口

code modules/Example/Http/Controllers/Api/V1/ExampleController.php
<?php

namespace Modules\Example\Http\Controllers\Api\V1;

use Modules\Core\Supports\Response;
use Modules\Core\Supports\ResponsibilityChain;
use Modules\Example\Http\Requests\ExampleStoreRequest;
use Modules\Example\Http\Requests\ExampleUpdateRequest;
use Modules\Example\Services\ExampleService;

interface ExampleController
{
    public function index(ResponsibilityChain $responsibilityChain, ExampleService $exampleService): Response;

    public function store(
        ResponsibilityChain $responsibilityChain,
        ExampleService $exampleService,
        ExampleStoreRequest $exampleStoreRequest
    ): Response;

    public function show(ResponsibilityChain $responsibilityChain, ExampleService $exampleService, int $id): Response;

    public function update(
        ResponsibilityChain $responsibilityChain,
        ExampleService $exampleService,
        int $id,
        ExampleUpdateRequest $exampleUpdateRequest
    ): Response;

    public function destroy(
        ResponsibilityChain $responsibilityChain,
        ExampleService $exampleService,
        int $id
    ): Response;
}

控制器实现

code modules/Example/Http/Controllers/Api/V1/ExampleControllerI.php
<?php

namespace Modules\Example\Http\Controllers\Api\V1;

use Illuminate\Routing\Controller;
use Modules\Core\Supports\Response;
use Modules\Core\Supports\ResponsibilityChain;
use Modules\Example\Http\Requests\ExampleStoreRequest;
use Modules\Example\Http\Requests\ExampleUpdateRequest;
use Modules\Example\Services\ExampleService;

class ExampleControllerI extends Controller implements ExampleController
{
    public function index(ResponsibilityChain $responsibilityChain, ExampleService $exampleService): Response
    {
        $step1 = function () use ($exampleService) {
            return $exampleService->index();
        };

        return $responsibilityChain->append($step1)->handle();
    }

    public function store(
        ResponsibilityChain $responsibilityChain,
        ExampleService $exampleService,
        ExampleStoreRequest $exampleStoreRequest
    ): Response {
        $attributes = $exampleStoreRequest->validated();

        $step1 = function () use ($exampleService, $attributes) {
            return $exampleService->store($attributes);
        };

        return $responsibilityChain->append($step1)->handle();
    }

    public function show(ResponsibilityChain $responsibilityChain, ExampleService $exampleService, int $id): Response
    {
        $step1 = function () use ($exampleService, $id) {
            return $exampleService->show($id);
        };

        return $responsibilityChain->append($step1)->handle();
    }

    public function update(
        ResponsibilityChain $responsibilityChain,
        ExampleService $exampleService,
        int $id,
        ExampleUpdateRequest $exampleUpdateRequest
    ): Response {
        $attributes = $exampleUpdateRequest->validated();

        $step1 = function () use ($exampleService, $id, $attributes) {
            return $exampleService->update($id, $attributes);
        };

        return $responsibilityChain->append($step1)->handle();
    }

    public function destroy(ResponsibilityChain $responsibilityChain, ExampleService $exampleService, int $id): Response
    {
        $step1 = function () use ($exampleService, $id) {
            return $exampleService->destroy($id);
        };

        return $responsibilityChain->append($step1)->handle();
    }
}

添加路由

code modules/Example/Routes/api.php
<?php

/*
|--------------------------------------------------------------------------
| API Routes
|--------------------------------------------------------------------------
|
| Here is where you can register API routes for your application. These
| routes are loaded by the RouteServiceProvider within a group which
| is assigned the "api" middleware group. Enjoy building your API!
|
*/

Route::get('/example', 'ExampleController@index');

Route::group(['prefix' => 'v1', 'namespace' => 'Api\V1'], function () {
    Route::group(['prefix' => 'examples'], function () {
        Route::get('/', 'ExampleController@index');
        Route::post('/', 'ExampleController@store');
        Route::get('/{id}', 'ExampleController@show')->where('id', '\d+');
        Route::put('/{id}', 'ExampleController@update')->where('id', '\d+');
        Route::delete('/{id}', 'ExampleController@destroy')->where('id', '\d+');
    });
});

添加单元测试

code modules/Example/Tests/ExampleTest.php
<?php

namespace Modules\Example\Tests;

use Illuminate\Contracts\Console\Kernel;
use Illuminate\Support\Str;
use Modules\Core\Enums\StatusCodeEnum;
use Modules\Example\Database\Seeders\ExampleTableSeederTableSeeder;
use Illuminate\Foundation\Testing\TestCase;
use Illuminate\Foundation\Testing\RefreshDatabase;

class ExampleTest extends TestCase
{
    use RefreshDatabase;

    private $baseUri = 'api/v1/';
    private $resource = 'examples';
    private $uri;

    /**
     * Creates the application.
     *
     * @return \Illuminate\Foundation\Application
     */
    public function createApplication()
    {
        $app = require __DIR__.'/../../../bootstrap/app.php';

        $app->make(Kernel::class)->bootstrap();

        return $app;
    }

    public function __construct()
    {
        $this->uri = $this->baseUri.Str::plural($this->resource);

        parent::__construct();
    }

    public function testIndex()
    {
        $this->get("{$this->uri}")->assertStatus(StatusCodeEnum::HTTP_OK);
    }

    public function testStore()
    {
        $this->post("{$this->uri}", ['name' => 'demo', 'value' => 'hello world'])
             ->assertStatus(StatusCodeEnum::HTTP_CREATED);
    }

    public function testShow()
    {
        $this->seed(ExampleTableSeederTableSeeder::class);

        $this->get("{$this->uri}/1")->assertStatus(StatusCodeEnum::HTTP_OK);
    }

    public function testUpdate()
    {
        $this->seed(ExampleTableSeederTableSeeder::class);

        $this->put("{$this->uri}/1", ['value' => 'Hello World'])->assertStatus(StatusCodeEnum::HTTP_RESET_CONTENT);
    }

    public function testDestroy()
    {
        $this->seed(ExampleTableSeederTableSeeder::class);

        $this->delete("{$this->uri}/1")->assertStatus(StatusCodeEnum::HTTP_NO_CONTENT);
    }
}
./vendor/bin/phpunit
PHPUnit 7.5.11 by Sebastian Bergmann and contributors.

.....                                                               5 / 5 (100%)

Time: 683 ms, Memory: 26.00 MB

OK (5 tests, 5 assertions)