axyr/laravel-tractor
Composer 安装命令:
composer require axyr/laravel-tractor
包简介
Opiniated Laravel CRUD generator for a Module/Repository/Filter based CRUD setup.
README 文档
README
Scaffold a Laravel module structure for JSON API's.
Introduction
Laravel Tractor is another module scaffolder for generating the basic PHP classes commonly used for JSON API's. The scaffolder is mainly targetted on stand alone API's without any Blade, Livewire or any other frontend library.
After installing, you can generate all required files for a working CRUD based module:
php artisan tractor:generate Post
Above command will generate a basic module structure with all the files needed for a model based JSON API.
The module will have boilerplate tests that tests all the permission authorization, database operations and filters.
📂 app
📂 app-modules
┗ 📂 Posts
┗ 📂 src
┗ 📂 Factories
┗ 📄 PostFactory.php
┗ 📂 Filters
┗ 📄 PostFilter.php
┗ 📂 Http
┗ 📂 Controllers
┗ 📄 PostController.php
┗ 📂 Requests
┗ 📄 PostRequest.php
┗ 📂 Resources
┗ 📄 PostResource.php
┗ 📂 Models
┗ 📄 Post.php
┗ 📂 Policies
┗ 📄 PostPolicy.php
┗ 📂 Repositories
┗ 📄 PostRepository.php
┗ 📂 Seeders
┗ 📄 PostSeeder.php
┗ 📂 tests
┗ 📂 Filters
┗ 📄 PostFilterTest.php
┗ 📂 Http
┗ 📂 Controllers
┗ 📄 PostControllerAuthorizationTest.php
┗ 📄 PostControllerTest.php
┗ 📄 composer.json
┗ 📄 routes.php
📂 database
┗ 📂 migrations
┗ 📄 2024_08_16_135340_create_posts_table.php
Examples
A full example of a generater module can be found here:
TODO
We generate the bare minimum, but workable and testable code, some examples:
Model
<?php namespace App\Modules\Posts\Models; use Axyr\CrudGenerator\Filters\Traits\FiltersRecords; use Illuminate\Database\Eloquent\Model; use Illuminate\Support\Carbon; /** * @property int $id * * @property Carbon $created_at * @property Carbon $updated_at */ class Post extends Model { use FiltersRecords; protected $guarded = ['id']; }
Controller
<?php namespace Modules\Posts\Http\Controllers; use Illuminate\Routing\Controller; use Modules\Posts\Models\Post; use Modules\Posts\Repositories\PostRepository; use Modules\Posts\Http\Requests\PostRequest; use Modules\Posts\Http\Resources\PostResource; use Illuminate\Foundation\Auth\Access\AuthorizesRequests; use Illuminate\Http\Request; use Illuminate\Http\Resources\Json\ResourceCollection; use Illuminate\Http\Response; class PostController extends Controller { use AuthorizesRequests; public function __construct(protected PostRepository $repository) { $this->authorizeResource(Post::class); } public function index(Request $request): ResourceCollection { $posts = $this->repository->setRequest($request)->paginate(); return PostResource::collection($posts)->preserveQuery(); } public function store(PostRequest $request): PostResource { $post = Post::query()->create($request->validated()); return new PostResource($post); } public function show(Post $post): PostResource { return new PostResource($post); } public function update(PostRequest $request, Post $post): PostResource { $post->update($request->validated()); return new PostResource($post); } public function destroy(Post $post): Response { $post->delete(); return response()->noContent(); } }
PermissionSeeder
For every resource we create a seeder that stores a permission for each Controller action:
<?php namespace App\Modules\Posts\Seeders; use Illuminate\Database\Eloquent\Model; use Illuminate\Database\Seeder; use Spatie\Permission\Models\Permission; use Spatie\Permission\Models\Role; use Spatie\Permission\PermissionRegistrar; class PostPermissionSeeder extends Seeder { private null|Model|Role $defaultRole; public function run(): void { app()[PermissionRegistrar::class]->forgetCachedPermissions(); $this->createDefaultRole(); $this->createPermissions(); } private function createDefaultRole(): void { $defaultRoleName = config('crudgenerator.default_role_name'); $defaultGuardName = config('crudgenerator.default_guard_name'); $this->defaultRole = Role::query()->firstOrCreate(['name' => $defaultRoleName], ['guard_name' => $defaultGuardName]); $permission = Permission::query()->firstOrCreate(['name' => $defaultRoleName]); $this->defaultRole->givePermissionTo($permission); } private function createPermissions(): void { $viewAny = Permission::query()->firstOrCreate(['name' => 'post.viewAny']); $view = Permission::query()->firstOrCreate(['name' => 'post.view']); $create = Permission::query()->firstOrCreate(['name' => 'post.create']); $update = Permission::query()->firstOrCreate(['name' => 'post.update']); $delete = Permission::query()->firstOrCreate(['name' => 'post.delete']); $this->defaultRole->givePermissionTo($viewAny, $view, $create, $update, $delete); } }
ControllerAuthorizationTest
The ControllerAuthorizationTest test every permission for an allowed user and a restricted user. In this way we are sure every Controller actions can be finegrained assigned to any future role.
<?php namespace App\Modules\Posts\Tests\Http\Controllers; use App\Models\User; use App\Modules\Posts\Factories\PostFactory; use App\Modules\Posts\Models\Post; use App\Modules\Posts\Seeders\PostPermissionSeeder; use Database\Factories\UserFactory; use Illuminate\Foundation\Testing\RefreshDatabase; use PHPUnit\Framework\Attributes\DataProvider; use Spatie\Permission\Models\Role; use Symfony\Component\HttpFoundation\Response; use Tests\TestCase; class PostControllerAuthorizationTest extends TestCase { use RefreshDatabase; public function userWithRole(string $roleName, array $attributes = []): User { (new PostPermissionSeeder)->run(); $defaultGuardName = config('crudgenerator.default_guard_name'); $role = Role::query()->firstOrCreate(['name' => $roleName], ['guard_name' => $defaultGuardName]); return UserFactory::new()->create($attributes)->assignRole($role); } #[DataProvider('roleDataProvider')] public function testListPostAuthorization(string $role, bool $allow): void { $user = $this->userWithRole($role); $response = $this->actingAs($user)->get('posts'); $this->assertEquals($allow, $user->can('viewAny', Post::class)); $this->assertEquals($allow, $response->getStatusCode() !== Response::HTTP_FORBIDDEN, $response->getStatusCode()); } #[DataProvider('roleDataProvider')] public function testCreatePostAuthorization(string $role, bool $allow): void { $user = $this->userWithRole($role); $response = $this->actingAs($user)->post('posts'); $this->assertEquals($allow, $user->can('create', Post::class)); $this->assertEquals($allow, $response->getStatusCode() !== Response::HTTP_FORBIDDEN, $response->getStatusCode()); } #[DataProvider('roleDataProvider')] public function testUpdatePostAuthorization(string $role, bool $allow): void { $user = $this->userWithRole($role); $post = PostFactory::new()->create(); $response = $this->actingAs($user)->patch("posts/{$post->id}"); $this->assertEquals($allow, $user->can('update', $post)); $this->assertEquals($allow, $response->getStatusCode() !== Response::HTTP_FORBIDDEN, $response->getStatusCode()); } #[DataProvider('roleDataProvider')] public function testShowPostAuthorization(string $role, bool $allow): void { $user = $this->userWithRole($role); $post = PostFactory::new()->create(); $response = $this->actingAs($user)->get("posts/{$post->id}"); $this->assertEquals($allow, $user->can('view', $post)); $this->assertEquals($allow, $response->getStatusCode() !== Response::HTTP_FORBIDDEN, $response->getStatusCode()); } #[DataProvider('roleDataProvider')] public function testDeletePostAuthorization(string $role, bool $allow): void { $user = $this->userWithRole($role); $post = PostFactory::new()->create(); $response = $this->actingAs($user)->delete("posts/{$post->id}"); $this->assertEquals($allow, $user->can('delete', $post)); $this->assertEquals($allow, $response->getStatusCode() !== Response::HTTP_FORBIDDEN, $response->getStatusCode()); } public static function roleDataProvider(): array { return [ 'default role with permissions is allowed' => [ 'role' => 'admin', 'allow' => true, ], 'custom role without permissions is not allowed' => [ 'role' => 'not-allowed', 'allow' => false, ], ]; } }
Quick start
Run the composer install command from the terminal:
composer require axyr/laravel-tractor
Add this snippet to the "extra" key in your composer file:
"extra": { "merge-plugin": { "merge-extra": true, "merge-extra-deep": true, "include": [ "app-modules/*/composer.json" ] } }
The include the package test, add the testsuite to the phpunit.xml file:
<testsuite name="Modules"> <directory>./app-modules/*/tests</directory> </testsuite>
This will enable the autoload of modules in a custom directory outside app.
Then you can generate a Module for a Model resource:
php artisan tractor:generate Post
After that you can run the tests:
php artisan test
From here, you can start extending the module with your specific business cases.
axyr/laravel-tractor 适用场景与选型建议
axyr/laravel-tractor 是一款 基于 PHP 开发的 Composer 扩展包,目前已累计 1.32k 次下载、GitHub Stars 达 5, 最近一次更新时间为 2024 年 10 月 13 日, 在 PHP 生态内属于活跃度较高的组件。
它主要适用于以下技术方向: 「json」 「api」 「module」 「boilerplate」 「crud」 「laravel」 等业务场景。在实际项目中,围绕这些方向常见需要落地的问题包括:接口对接、性能调优、并发安全、与既有框架(Laravel / ThinkPHP / Yii / Webman 等)的兼容适配,以及生产环境的日志埋点与稳定性保障。
我们在过去多个企业项目中使用过 axyr/laravel-tractor 或与其功能相近的方案,如果你在选型或落地过程中遇到问题,例如 版本兼容、二次改造、私有化封装、与内部系统对接、生产 BUG 排查,欢迎联系我们协助评估。
基于 axyr/laravel-tractor 在你已有业务上做功能扩展、字段裁剪、UI 适配、与内部账号 / 权限 / 日志系统的深度对接。
线上偶发问题、内存泄漏、慢查询、并发异常等排查修复;针对高流量场景做缓存、队列、索引层面的调优。
承接完整的项目从需求 → 设计 → 开发 → 上线 → 长期运维;也可按月提供技术保姆服务。
与 axyr/laravel-tractor 相关的其它包
同方向 / 同关键字的高下载量 PHP Composer 包推荐,方便对比选型:
Kinikit - PHP Application development framework MVC component
ext-json wrapper with sane defaults
A package to cast json fields, each sub-keys is castable
A PSR-7 compatible library for making CRUD API endpoints
swagger-php - Generate interactive documentation for your RESTful API using phpdoc annotations
Get wordpress nav menus and share the main site menus with the child sites.
统计信息
- 总下载量: 1.32k
- 月度下载量: 0
- 日度下载量: 0
- 收藏数: 5
- 点击次数: 10
- 依赖项目数: 0
- 推荐数: 0
其他信息
- 授权协议: MIT
- 更新时间: 2024-10-13
