kenokokoro / laravel-basetree
构建laravel应用时的基础扩展结构
Requires
- php: ^7.4
- barryvdh/laravel-ide-helper: ^2.6
- illuminate/console: ^8.0
- illuminate/container: ^8.0
- illuminate/contracts: ^8.0
- illuminate/http: ^8.0
- illuminate/support: ^8.0
- yajra/laravel-datatables-oracle: >9
Requires (Dev)
- beyondcode/laravel-dump-server: ^1.0
- fzaninotto/faker: ^1.9.1
- laravel/laravel: ^7.0
- mockery/mockery: ^1.3.1
- nunomaduro/collision: ^4.1
- phpunit/phpunit: ^8.5
Suggests
- kenokokoro/laravel-basetree-logger: Allows you to log incoming requests and responses to database or laravel.log
This package is auto-updated.
Last update: 2024-09-24 14:07:48 UTC
README
支持
安装
composer require kenokokoro/laravel-basetree
将包拉入后,在您的 AppServiceProvider
中注册服务提供者
public function register() { $this->app->register(BaseTree\Providers\BaseTreeServiceProvider::class); }
这就完成了。
描述
基本的多层结构,主要用于RESTful API和Web CRUD资源。包括基础类,通过扩展这些类可以实现单个资源的RESTful API端点和CRUD操作。这强制将代码分离到业务逻辑层和数据访问层,从而得到更干净的代码。
包含
-
通过扩展
BaseTree\Exception\LaravelHandler.php
或BaseTree\Exception\LumenHandler
来处理错误 -
带有必需方法的基础控制器
a.
BaseTree\Controller\Laravel\JsonController.php
用于Laravel框架上的RESTful APIb.
BaseTree\Controller\Lumen\JsonController.php
用于Lumen框架上的RESTful APIc.
BaseTree\Controller\WebController.php
用于Laravel上的基于Web的CRUD操作 -
基础资源,这是一个为特定Model(模型)编写的业务逻辑类
BaseTree\Resources\BaseResource.php
-
用于http或json响应的通用分离类。
-
基本的模型接口,用于每个资源和数据访问层
BaseTree\Models\BaseTreeModel.php
。创建的模型应该实现此模型。 -
数据访问层,它将模型包装在单独的仓库中,以实现更好的结构和使用现有数据库调用。每个仓库都有自己的接口和实现。
-
每个数据库更新都包含在事务中,因此如果您没有收到控制器的响应,并且抛出异常,则数据库中不会持久化任何内容。
-
LaravelTestCase
和LumenTestCase
,它们包含许多用于使用PHPUnit框架进行集成测试的辅助工具。
用法
要求
-
为您的资源创建迁移和模型。注意,模型应该扩展BaseTree模型。
因此,在您的
app\Models
目录中创建Foo.php
namespace App\Models; use Illuminate\Database\Eloquent\Model; use BaseTree\Models\BaseTreeModel; class Foo extends Model implements BaseTreeModel { protected $fillable = ['name', 'description']; }
您必须设置
$fillable
属性才能正常工作。 -
为创建的模型创建数据访问层。(自动创建)
例如,您可以在您的
app
文件夹下创建一个名为DAL
的文件夹,并在其中创建一个与模型名称相同的文件夹,在这种情况下是Foo
因此,在
app/DAL/Foo
中创建FooRepository.php
namespace App\DAL\Foo; use BaseTree\Eloquent\RepositoryInterface; interface FooRepository extends RepositoryInterface { }
现在创建实现新创建的接口的存储库实现。
在
app/DAL/Foo
中创建EloquentFoo.php
namespace App\DAL\Foo; use BaseTree\Eloquent\BaseEloquent; use App\Models\Foo; class EloquentFoo extends BaseEloquent implements FooRepository { public function __construct(Foo $model) { parent::__construct($model); } }
现在在您自定义创建的服务提供者中绑定一切。我会这样做:
在
app/DAL
中创建DalServiceProvider.php
namespace App\DAL; use Illuminate\Support\ServiceProvider; use App\DAL\Foo\FooRepository; use App\DAL\Foo\EloquentFoo; class DalServiceProvider extends ServiceProvider { public function register() { $bindings = [ FooRepository::class => EloquentFoo::class, # Every other repository should be registered here ]; foreach($bindings as $interface => $implementation) { $this->app->bind($interface, $implementation); } } }
然后,在您的
AppServiceProvider
中注册DalServiceProvider
use App\DAL\DalServiceProvider; ... public function register() { $this->app->register(DalServiceProvider::class); }
这样,您就完成了数据访问层结构。您可以在需要时注入接口并重用查询。
-
创建专门负责业务逻辑规则和与数据访问层交互的资源。在您的
app
文件夹中创建一个名为Resources
或BLL
的文件夹,并在其中保存您的模型资源。(自动创建)在
app/BLL
目录下创建文件FooResource.php
namespace App\BLL; use BaseTree\BLL\BaseResource use App\DAL\FooRepository; class FooResource extends BaseResource { public function __construct(FooRepository $repository) { parent::__construct($repository); } }
这样基本上资源就创建完成了,但此包包含一些辅助接口,可以帮助您组织验证、创建和更新。
BaseTree\Resources\Contracts\ResourceValidation
接口将强制您实现storeRules()
、updateRules()
和destroyRules()
。在资源上实现此接口后,对store()
、update()
和destroy()
的请求将进行验证。如果您不需要对某些方法进行验证,只需返回空数组即可。您也可以根据需要单独使用它们(检查BaseTree\Resources\Contracts\ResourceValidation
,所有扩展接口都有自己的方法,如果需要可以单独使用)BaseTree\Resources\Contracts\ResourceCallbacks
接口包含created()
和updated()
方法,它们基本上是在资源创建或更新后的钩子。传递的$dependencyAttributes
值包含除了设置在您的模型上的$fillable
属性之外的所有内容。这很好,因为使用这些值您可以轻松地更新您的关联。如果需要,回调也可以单独使用,就像上面提到的资源验证一样(检查BaseTree\Resources\Contracts\ResourceCallbacks
的扩展接口)$attributes
包含$fillable
属性。 -
现在来配置控制器。每个控制器应该扩展
BaseTree\Controllers\Laravel\JsonController
(用于laravel)和BaseTree\Controllers\Lumen\JsonController
(用于lumen),当使用json时,根据需要扩展BaseTree\Controllers\Laravel\WebController
。(自动创建)在
app/Http/Controllers
(或其它位置)创建FoosController.php
。namespace App\Http\Controllers; use BaseTree\Controllers\Laravel\JsonController; use App\BLL\FooResource; class FoosController extends JsonController { public function __construct(FooResource $resource) { parent::__construct($resource); } }
-
现在您可以准备您的路由
Route::resource('foos', 'FoosController')->except(['edit']);
-
使用 base-tree 的异常处理器。在您的
App\Exceptions\Handler
中执行以下操作namespace App\Exceptions; use BaseTree\Exception\LaravelHandler as BaseTreeHandler; class Handler extends BaseTreeHandler { }
请求 - 响应
请注意,您必须始终将 Accept 头设置为 application/json
。
有了这些,现在您只需创建几个类即可拥有 RESTful API
- 访问
/api/foos&datatable=1
将返回可以用于 jQuery datatables 插件的响应。 - 访问
/api/foos&paginate=1&perPage=10
将返回分页响应,其中包含下一页和上一页的 URL。默认值perPage
为 15。 - 访问
api/foos&constraints[0]=name|=|bar
将返回所有名为 bar 的 foos。您可以使用 PHP 函数http_build_query(['constraints' => ['name|=|bar', 'active|1']])
构建查询字符串。 - 访问
api/foos&fields[0]=Bar
将返回所有 foos,但在响应中包含Bar
关联。
约束值的结构为 columnName|operator|value
。如果您需要根据另一个列添加约束:columnName|operator|`otherColumn`
测试
此包包含数据库测试类,可以使您的数据库所需测试变得更容易。
- 在
phpunit.xml
中设置您的 DB_CONNECTION 为测试<env name="DB_CONNECTION" value="testing"></env>
- 在您的
database.php
配置文件中设置测试连接:在config/database.php
下的connections
添加'testing' => [ 'driver' => 'sqlite', 'database' => ':memory:', ],
这将使BaseTree\Tests\Traits\DatabaseMigrations
能够在测试开始前迁移和填充,在测试完成后回滚。这样您将始终拥有干净的数据库。
测试模型
在 tests/Models
下创建与您的 Foo.php
模型对应的 FooTest.php
。假设您的 Foo
模型有一个 Bar
模型。
namespace Tests\Models; use App\Models\Foo; use App\Models\Bar; use BaseTree\Tests\LaravelDatabaseTestCase; class FooTest extends LaravelDatabaseTestCase { /** @test */ public function a_foo_has_one_bar() { $foo = create(Foo::class); $bar = create(Bar::class); $foo->bar()->save($bar); $this->assertHasOne($foo, $bar, 'bar', ['id' => $bar->id, 'foo_id' => $foo->id]); } }
测试控制器端点
在创建扩展了 DatabaseTestCase
的 FoosController
之后,您可以
use BaseTree\Responses\JsonResponse; use App\Models\Foo; ... /** @test */ public function it_should_fetch_all_foos(): void { $response = $this->jsonGet(route("foos.index")); $response->assertStatus(JsonResponse::HTTP_OK)->assertJsonStructure(['status', 'message', 'data']); } /** @test */ public function it_requires_data_in_order_to_store_foo(): void { $response = $this->jsonPost(route('foos.store')); $response->assertStatus(JsonResponse::HTTP_UNPROCESSABLE_ENTITY)->assertJsonStructure([ 'status', 'message', 'validator' ]); $validator = $response->json()['validator']; $this->assertCount(7, $validator); # Response messages assertions. Third argument is value that laravel will translate without the _ $this->assertFieldRequired($validator, 'name'); $this->assertEmailField($validator, 'user_email', 'user email'); $this->assertPasswordIsConfirmed($validator, 'password'); $this->assertValueIn($validator, 'value_from_enum', 'value from enum'); $this->assertFieldExist($validator, 'id'); $this->assertFieldIsArray($validator, 'array'); $this->assertValueIsUnique($validator, 'unique_column', 'unique column'); } /** @test */ public function foo_can_be_stored(): void { $response = $this->jsonPost(route('foos.store', ['name' => 'Foo Name'])); $response->assertStatus(JsonResponse::HTTP_UNPROCESSABLE_ENTITY)->assertJsonStructure([ 'status', 'message', 'validator' ]); $this->assertCreated(new Foo, ['name' => 'Foo Name']); }
只创建空文件感觉有限
如果您需要额外的功能,您可以始终覆盖父方法。例如,如果您想为只有名称值作为值的资源生成 slug,在您的 app\BLL\FooResource.php
namespace App\BLL; use BaseTree\Resources\BaseResource; use App\DAL\FooRepository; class FooResource extends BaseResource { public function __construct(FooRepository $repository) { parent::__construct($repository); } public function store(array $attributes) { $attributes['slug'] = str_slug($attributes['name']); # Or whatever logic you need here return parent::store($attributes); } }
同样的逻辑适用于控制器和DAL。您需要定制的任何内容都可以进行扩展和覆盖。
Artisan 生成器
-
生成数据访问层。此命令将生成仓库和eloquent实现。不要忘记将其绑定到您的
ServiceProvider
Usage: php artisan basetree:dal [options] Options: --model[=MODEL] Fully qualified model name including the namespace --interface-folder[=INTERFACE-FOLDER] Folder where to create the DAL interface [default: "app/DAL/[model-name]"] --interface-namespace[=INTERFACE-NAMESPACE] Namespace to create the DAL interface under [default: "App\DAL\[model-name]"] --dal-folder[=DAL-FOLDER] Folder where to create the DAL implementation [default: "app/DAL/[model-name]"] --dal-namespace[=DAL-NAMESPACE] Namespace to create the DAL implementation under [default: "App\DAL\[model-name]"]
示例:
php artisan basetree:dal --model=App\\Models\\User
-
生成业务逻辑层。这将生成一个资源,在构造函数中注入了仓库接口
Usage: php artisan basetree:bll [options] Options: --model[=MODEL] Fully qualified model name including namespace --dal-interface[=DAL-INTERFACE] Fully qualified data access layer name including namespace --folder[=FOLDER] Folder where to create the BLL [default: "app/BLL/"] --namespace[=NAMESPACE] Namespace to create the BLL under [default: "App\BLL"]
示例:
php artisan basetree:bll --model=App\\Models\\User --dal-interface=App\\DAL\\User\\UserRepository
-
生成控制器。生成的控制器将在构造函数中注入指定的业务逻辑层
注意:在此阶段,生成器仅创建扩展自
RestfulJsonController
的控制器。您需要手动更改生成控制器以扩展WebController
或手动创建它Usage: php artisan basetree:controller [options] Options: --model-plural[=MODEL-PLURAL] Plural form of the model name. For instance if the model is User, you should send here Users --bll[=BLL] Fully qualified business logic layer name including namespace --folder[=FOLDER] Folder where to create the controller [default: "app/Http/Controllers/Api/"] --namespace[=NAMESPACE] Namespace to create the controller under [default: "App\Http\Controllers\Api"]
示例:
php artisan basetree:controller --model-plural=users -bll=App\\BLL\\UserResource
-
发布docker-compose架构。查看.env.docker-compose.example以获取使docker容器正常运行所需的变量。
Usage: php artisan basetree:boilerplates [options] Options: --docker-compose Publish the docker structure Help: Publish some already predefined environments. --docker-compose: Docker environment for local development (nginx 1.13, php7.1-fpm + composer, npm 3.3, nodejs 6.7, MariaDB 10.3, phpmyadmin 4.7)
示例:
php artisan basetree:boilerplates --docker-compose
所需变量
DOCKER_HOST_UID=1000
# 您的主机用户ID。通过执行echo $UID
或id username
检查它。DOCKER_HOST_GID=1000
# 您的主机组ID。通过执行echo $GID
或id username
检查它。DATABASE_LOCAL_STORAGE=/opt/mariadb/project
# 在您的主机机器上保存数据库文件的位置。这是必需的,因为如果运行docker-compose down -v
,这将销毁数据库中的数据,如果存储未挂载在您的主机机器上。PMA_PORT=81
# PhpMyAdmin的公共暴露端口。如果运行在QA环境中或不需要,您可以将其从docker-compose.yml
文件中删除,或通过运行docker-compose stop phpmyadmin
来删除。NGINX_SERVER_NAME=localhost
# 虚拟主机名称NGINX_PORT=80
# nginx容器的公共暴露端口。为了在域名后不添加端口号,应为80
。例如:如果您的NGINX_PORT=8080
,您将不得不在浏览器中访问它:localhost:8080
。另外,如果您已经有一些服务在监听给定的端口,您将不得不关闭它们。CONTAINER_ROOT=/application
# 在容器内您的项目的名称。DB_ROOT_PASSWORD=root-pass123
# MariaDB根密码QA_HTTP_HOST=
# 如果您正在运行多个docker实例,并且希望将它们全部绑定到80
端口,您将必须在此处指定fastcgi_param HTTP_HOST
,以便您的应用程序重定向到您的代理URL。DB_HOST=mariadb
# 如果您正在使用docker mariadb实例,而不是您已安装的自己的。
设置好所有这些后,您必须运行
docker-compose -f docker-compose.yml -f qa.docker-compose.yml -f dev.docker-compose.yml up -d
并等待构建完成。通过运行docker-compose ps
检查容器状态。您应该看到类似以下的内容Name Command State Ports --------------------------------------------------------------------------------------------------------------------------------------------------------------------- tutorial_app_1 docker-php-entrypoint /sta ... Up 443/tcp, 0.0.0.0:80->80/tcp, 9000/tcp tutorial_mariadb_1 docker-entrypoint.sh mysqld Up 0.0.0.0:3307->3306/tcp tutorial_phpmyadmin_1 /run.sh phpmyadmin Up 0.0.0.0:81->80/tcp
Lumen支持
关于Laravel的相同说明也适用于Lumen,其行为应与Lumen和Laravel相同。Lumen的特殊情况包括
-
Lumen控制器与Laravel控制器不同
namespace App\Http\Controllers; use BaseTree\Controllers\Lumen\JsonController; use App\BLL\FooResource; class FoosController extends JsonController { public function __construct(FooResource $resource) { parent::__construct($resource); } }
-
使用 base-tree 的异常处理器。在您的
App\Exceptions\Handler
中执行以下操作namespace App\Exceptions; use BaseTree\Exception\LumenHandler as BaseTreeHandler; class Handler extends BaseTreeHandler { }
-
数据库需要测试在
tests/Models
中创建FooTest.php
,它与您的Foo.php
模型相对应。假设您的Foo
模型有一个Bar
模型。namespace Tests\Models; use App\Models\Foo; use App\Models\Bar; use BaseTree\Tests\LumenDatabaseTestCase; class FooTest extends LumenDatabaseTestCase { /** @test */ public function a_foo_has_one_bar() { $foo = create(Foo::class); $bar = create(Bar::class); $foo->bar()->save($bar); $this->assertHasOne($foo, $bar, 'bar', ['id' => $bar->id, 'foo_id' => $foo->id]); } }
待办事项
测试一切Artisan生成器- 维基示例和说明
- 包含JWT支持
- 添加Artisan单端点生成器,一次封装所有生成器
测试
make phpunit
执行测试
许可
BaseTree软件包是开源软件,许可协议为MIT许可