定制 ahmedebead/lara-crud 二次开发

按需修改功能、优化性能、对接业务系统,提供一站式技术支持

邮箱:yvsm@zunyunkeji.com | QQ:316430983 | 微信:yvsm316

ahmedebead/lara-crud

Composer 安装命令:

composer require ahmedebead/lara-crud

包简介

A comprehensive Laravel package to generate CRUD operations with advanced features for APIs and web applications.

README 文档

README

Lara-CRUD Banner

Lara-CRUD

A Laravel package that generates complete CRUD scaffolding and provides a layered service architecture out of the box.

Requires: PHP 8.2+, Laravel 10–13, spatie/laravel-query-builder ^5|^6|^7

composer require ahmedebead/lara-crud

Table of Contents

  1. How it works
  2. Generate a module
  3. What gets generated
  4. Layer by layer
  5. Hook system
  6. Configuration reference
  7. Artisan commands
  8. Contributing / License

How it works

Every generated module follows the same layered flow:

HTTP Request → Controller → Service → Repository → Model
Layer Responsibility
Controller Resolves request class, delegates to service, returns JSON
Service Business logic, wraps results in BaseResponse
Repository Data access via Spatie QueryBuilder
Model Eloquent model with allowed filters/includes/sorts

Generate a module

php artisan lara-crud:go

The wizard asks for the table name and generates everything. Publish the config and stubs if you want to customise the defaults:

php artisan vendor:publish --provider="Ahmed3bead\LaraCrud\LaraCrudServiceProvider"

Config lands at config/lara_crud.php. Stubs land at resources/stubs/vendor/lara-crud/.

What gets generated

app/{MainContainer}/{ModuleName}/
├── Controllers/       ProductsController.php
├── Models/            Product.php
├── Repositories/      ProductsRepository.php
├── Services/          ProductsService.php
├── Requests/          CreateProductRequest.php  UpdateProductRequest.php  ...
├── Resources/         ProductShowResource.php   ProductListResource.php
├── DTOs/              ProductDTO.php
├── Mappers/           ProductDTOMapper.php
├── Filters/           ProductFilter.php
├── Policies/          ProductPolicy.php
├── Events/            ProductCreated.php  ...
├── Notifications/
├── Scopes/
└── Traits/

Layer by layer

Model

BaseModel (integer PK) · BaseUuidModel · BaseUlidModel

Override these static methods to control filtering and sorting:

class Product extends BaseModel
{
    protected $fillable = ['name', 'price', 'category_id'];

    public static function getAllowedFilters(): array
    {
        return ['name', 'category_id', AllowedFilter::scope('active')];
    }

    public static function getAllowedIncludes(): array
    {
        return ['category', 'tags'];
    }

    // Sort direction is passed in by the repository — no request() calls here
    public static function getDefaultSort(bool $sortAsc = false): string
    {
        return $sortAsc ? 'created_at' : '-created_at';
    }
}

Repository

BaseRepository provides all common data access methods. Inject it; do not call it directly from controllers.

// Available out of the box:
$repo->paginate($requestQuery, $perPage);
$repo->all();
$repo->find($id);                          // findOrFail
$repo->create(array $data);
$repo->update($model, array $data);
$repo->delete($model);
$repo->count(array $filters = []);
$repo->exists(array $filters);
$repo->findMany(array $ids);
$repo->createMany(array $records);         // bulk insert
$repo->findWhere(array $conditions);
$repo->firstWhere(array $conditions);
$repo->minimalListWithFilter(with: [], where: [], limit: 250);

Extend it to add module-specific queries:

class ProductsRepository extends BaseRepository
{
    public function __construct()
    {
        parent::__construct(new Product, new ProductSelector);
    }

    public function findByCategory(int $categoryId): Collection
    {
        return $this->getModel()->where('category_id', $categoryId)->get();
    }
}

Service — Resource mode (default)

The generated service uses Laravel API Resources by default. ShowResource wraps single items; ListResource wraps collections.

class ProductsService extends BaseService
{
    protected string $resourceClass     = ProductShowResource::class;
    protected string $listResourceClass = ProductListResource::class;

    public function __construct(ProductsRepository $repository)
    {
        parent::__construct($repository);
    }
}

BaseService provides these methods automatically:

Method HTTP verb Wraps with
paginate($request) GET /index $listResourceClass::collection()
all() GET ?getAllRecords $listResourceClass::collection()
show($id) GET /show new $resourceClass()
create($data) POST new $resourceClass()
update($data, $id) PUT/PATCH new $resourceClass()
delete($id) DELETE 204 message

Service — Mapper mode

Replace the resource properties with a DTOMapper if you prefer manual transformation:

class ProductsService extends BaseService
{
    public function __construct(
        ProductsRepository $repository,
        ProductDTOMapper   $mapper
    ) {
        parent::__construct($repository, $mapper);
    }
}

The mapper must extend BaseDTOMapper and implement fromModel(). The base class handles fromCollection(), fromArray(), and fromPaginator() automatically.

Service — custom transformation

Override getResourceByType() for full control:

public function getResourceByType(string $type, $data = null): mixed
{
    return match($type) {
        'list'  => ProductListResource::collection($data),
        default => new ProductShowResource($data),
    };
}

Controller

BaseController resolves the request class from $requestMap, validates it, and calls the service. You never write try-catch boilerplate — Laravel's exception handler takes care of it.

class ProductsController extends BaseController
{
    protected array $requestMap = [
        'index'  => IndexProductRequest::class,
        'create' => CreateProductRequest::class,
        'update' => UpdateProductRequest::class,
        'delete' => DeleteProductRequest::class,
        'show'   => ShowProductRequest::class,
    ];

    public function __construct(ProductsService $service)
    {
        parent::__construct($service);
    }
}

If a key is missing from $requestMap a descriptive RuntimeException is thrown immediately.

Request

BaseRequest defaults to authorize(): bool { return true; }. Override it per request class to enforce policies:

class CreateProductRequest extends BaseRequest
{
    public function authorize(): bool
    {
        return $this->user()->can('create', Product::class);
    }

    public function rules(): array
    {
        return [
            'name'  => 'required|string|max:255',
            'price' => 'required|numeric|min:0',
        ];
    }
}

Response envelope

Every service method returns a BaseResponse. The controller's tryAndResponse() converts it to JSON.

{
    "status_code": 200,
    "data": { ... },
    "extra_data": {},
    "meta": {
        "currentPage": 1,
        "lastPage": 5,
        "perPage": 20,
        "total": 98
    },
    "errors": {},
    "message": "",
    "source": "OPs"
}

The debug block (query log) is never included unless you opt in via config — it will never leak to production responses:

// config/lara_crud.php
'expose_debug_in_response' => env('LARA_CRUD_EXPOSE_DEBUG', false),

Hook system

Hooks let you attach code to any service method lifecycle without touching the method itself.

Execution flow

Before hooks (sync)   → validation, authorization, rate-limit checks
       ↓
Core service method   → the actual create/update/delete/show/paginate
       ↓
After hooks           → notifications, audit log, cache invalidation
       ↓
Error hooks           → alerting, rollback side-effects

Creating a hook

php artisan lara-crud:hook ProductAuditHook
class ProductAuditHook extends BaseHookJob
{
    public function handle(HookContext $context): void
    {
        $model = $context->getModelFromResult();

        ActivityLog::record(
            user:    $context->user,
            action:  $context->method,
            subject: $model,
        );
    }

    public function shouldExecute(HookContext $context): bool
    {
        return $context->isAfter() && $context->isSuccessful();
    }
}

Registering hooks in a service

Override registerHooks() in your service:

class ProductsService extends BaseService
{
    protected string $resourceClass     = ProductShowResource::class;
    protected string $listResourceClass = ProductListResource::class;

    public function __construct(ProductsRepository $repository)
    {
        parent::__construct($repository);
    }

    protected function registerHooks(): void
    {
        parent::registerHooks();

        // Runs synchronously before every create
        $this->addServiceSyncHook('before', 'create', ValidateSkuHook::class);

        // Queued after create/update/delete — does not slow down the response
        $this->addServiceQueuedHook('after', 'create', SendProductCreatedEmailHook::class);
        $this->addServiceQueuedHook('after', 'update', InvalidateProductCacheHook::class);
        $this->addServiceQueuedHook('after', 'delete', InvalidateProductCacheHook::class);
    }
}

Hook execution strategies

Method Behavior
addServiceSyncHook Immediate, blocks the request
addServiceQueuedHook Dispatched to a queue
addServiceDelayedHook Queued with a delay (seconds)
addServiceBatchedHook Accumulated into a batch job

Extension points in BaseService

Override these empty methods instead of registering hooks manually when you want category-level grouping:

// Runs when hooks.default_service_hooks.global = true
protected function registerGlobalServiceHooks(): void
{
    $this->addServiceSyncHook('before', 'create', AuthorizationHook::class);
}

// Runs when hooks.default_service_hooks.crud = true
protected function registerCrudHooks(): void
{
    $this->addServiceQueuedHook('after', 'create', NotifyAdminHook::class);
}

// Runs when hooks.default_service_hooks.performance = true
protected function registerPerformanceHooks(): void {}

// Runs when hooks.default_service_hooks.caching = true
protected function registerCachingHooks(): void {}

HookContext reference

HookContext is passed to every hook's handle() method:

$context->method            // 'create', 'update', 'delete', 'show', 'paginate', ...
$context->phase             // 'before' | 'after' | 'error'
$context->data              // raw input passed to the service method
$context->parameters        // extra parameters array
$context->result            // the BaseResponse (after phase only)
$context->service           // the service instance
$context->user              // Auth::user()

// Helpers
$context->isBefore()
$context->isAfter()
$context->isSuccessful()    // status 2xx
$context->getModelFromResult()      // extracts Eloquent model from result
$context->getDataFromResult()       // extracts data payload from result
$context->getStatusCode()
$context->getMessage()
$context->getModelAttributes()
$context->getModelChanges()
$context->wasModelRecentlyCreated()

Hook management commands

php artisan lara-crud:hooks list          # list all registered hooks
php artisan lara-crud:hooks stats         # execution statistics
php artisan lara-crud:hooks debug         # debug a specific service
php artisan lara-crud:hooks enable        # enable hooks globally
php artisan lara-crud:hooks disable       # disable hooks globally
php artisan lara-crud:hooks clear         # remove all hooks
php artisan lara-crud:hooks test          # test-fire a hook
php artisan lara-crud:hooks export        # export hook config to JSON

Configuration reference

config/lara_crud.php

'api_version'              => 'V1',
'dto_enabled'              => false,
'api_resource_enabled'     => true,
'primary_key_fields_type'  => 'id',   // 'id' | 'uuid' | 'ulid'
'ui_mode'                  => 'bootstrap', // 'bootstrap' | 'adminlte'
'policies_enabled'         => false,

// Set to true only in local/staging — query logs will appear in every API response
'expose_debug_in_response' => env('LARA_CRUD_EXPOSE_DEBUG', false),

'hooks' => [
    'enabled'          => env('LARA_CRUD_HOOKS_ENABLED', true),
    'debug'            => env('LARA_CRUD_HOOKS_DEBUG', false),
    'queue_connection' => env('LARA_CRUD_QUEUE_CONNECTION', 'default'),
    'batch_queue'      => env('LARA_CRUD_BATCH_QUEUE', 'batch'),

    'default_service_hooks' => [
        'global'      => true,   // registerGlobalServiceHooks()
        'crud'        => true,   // registerCrudHooks()
        'performance' => false,  // registerPerformanceHooks()
        'caching'     => false,  // registerCachingHooks()
    ],
],

'dirs' => [
    'main-container-dir-name' => 'YourApp',
    // ...
],

Artisan commands

Command Purpose
lara-crud:go Interactive CRUD generator wizard
lara-crud:hook {name} Generate a hook class
lara-crud:hooks Manage hooks (list/stats/debug/clear/enable/disable/test/export)
lara-crud:api-controller Generate API controller only
lara-crud:model Generate model only
lara-crud:test Generate unit test
lara-crud:export-table Export table schema to JSON

Contributing / License

Contributions are welcome. Please open an issue or pull request on GitHub.

This package is open-sourced software licensed under the MIT license.

ahmedebead/lara-crud 适用场景与选型建议

ahmedebead/lara-crud 是一款 基于 PHP 开发的 Composer 扩展包,目前已累计 3.98k 次下载、GitHub Stars 达 2, 最近一次更新时间为 2024 年 09 月 07 日, 在 PHP 生态内属于活跃度较高的组件。

它主要适用于以下技术方向: 「generator」 「api」 「repository」 「service」 「crud」 「scaffold」 等业务场景。在实际项目中,围绕这些方向常见需要落地的问题包括:接口对接、性能调优、并发安全、与既有框架(Laravel / ThinkPHP / Yii / Webman 等)的兼容适配,以及生产环境的日志埋点与稳定性保障。

我们在过去多个企业项目中使用过 ahmedebead/lara-crud 或与其功能相近的方案,如果你在选型或落地过程中遇到问题,例如 版本兼容、二次改造、私有化封装、与内部系统对接、生产 BUG 排查,欢迎联系我们协助评估。

围绕 ahmedebead/lara-crud 我们能提供哪些服务?
定制开发 / 二次开发

基于 ahmedebead/lara-crud 在你已有业务上做功能扩展、字段裁剪、UI 适配、与内部账号 / 权限 / 日志系统的深度对接。

BUG 修复 & 性能优化

线上偶发问题、内存泄漏、慢查询、并发异常等排查修复;针对高流量场景做缓存、队列、索引层面的调优。

项目外包 & 长期维护

承接完整的项目从需求 → 设计 → 开发 → 上线 → 长期运维;也可按月提供技术保姆服务。

yvsm@zunyunkeji.com QQ:316430983 微信:yvsm316 西安尊云信息科技 · 专注 PHP / Go / 分布式系统研发

统计信息

  • 总下载量: 3.98k
  • 月度下载量: 0
  • 日度下载量: 0
  • 收藏数: 2
  • 点击次数: 6
  • 依赖项目数: 0
  • 推荐数: 0

GitHub 信息

  • Stars: 2
  • Watchers: 1
  • Forks: 0
  • 开发语言: PHP

其他信息

  • 授权协议: MIT
  • 更新时间: 2024-09-07