ibekzod/microcrud
Composer 安装命令:
composer require ibekzod/microcrud
包简介
CRUD package for Laravel - Service-Repository pattern with caching, queues, and dynamic filtering
README 文档
README
MicroCRUD
MicroCRUD is a comprehensive Laravel package that eliminates boilerplate code and accelerates API development. Build production-ready RESTful APIs in minutes with advanced features like type-aware filtering, intelligent caching, queue support, and automatic validation.
Why MicroCRUD?
Stop writing the same CRUD logic over and over. MicroCRUD provides:
- ⚡ Rapid Development - Create full CRUD APIs with just 3 classes
- 🎯 Type-Aware Filtering - Automatic search filters based on database column types
- 🔍 Advanced Querying - Range filters, dynamic sorting, grouping, soft deletes, pagination
- 📊 Dynamic Grouping - Group by model columns or relations with auto-joins and eager loading
- ✅ Auto Validation - Generate validation rules from database schema
- 💾 Smart Caching - Tag-based cache with automatic invalidation
- 🚀 Queue Support - Background processing for heavy operations
- 🌐 Multi-Database - MySQL, PostgreSQL, SQLite, SQL Server
- 🌍 i18n Ready - Multi-language support out of the box
- 📦 Bulk Operations - Process multiple records efficiently
- 🎨 Highly Extensible - Hooks, events, and customization points
Table of Contents
- Requirements
- Installation
- Quick Start
- Architecture
- Core Concepts
- Features
- API Documentation
- Advanced Usage
- Middleware
- Configuration
- Examples
- Testing
- Contributing
- Changelog
- License
Requirements
| Requirement | Version |
|---|---|
| PHP | ^7.0 | ^8.0 | ^8.1 | ^8.2 | ^8.3 |
| Laravel | 5.2 - 12.x |
Installation
Install the package via Composer:
composer require ibekzod/microcrud
The package will automatically register itself via Laravel's package discovery.
Publish Assets (Optional)
Publish configuration files and translations:
php artisan vendor:publish --provider="Microcrud\MicrocrudServiceProvider"
This will create:
config/microcrud.php- Package configurationconfig/schema.php- Multi-schema database configuration (PostgreSQL)lang/vendor/microcrud/- Translation files (en, ru, uz)
Quick Start
Create a complete CRUD API in 3 steps:
Step 1: Create Your Model
<?php namespace App\Models; use Microcrud\Abstracts\Model; class Product extends Model { protected $fillable = [ 'name', 'description', 'price', 'stock', 'category_id', 'is_active' ]; // Define relationships as usual public function category() { return $this->belongsTo(Category::class); } }
Step 2: Create Your Service
<?php namespace App\Services; use Microcrud\Abstracts\Service; use App\Models\Product; class ProductService extends Service { protected $model = Product::class; // That's it! You now have full CRUD functionality // Optionally enable advanced features: // protected $enableCache = true; // protected $useJob = true; }
Step 3: Create Your Controller
<?php namespace App\Http\Controllers; use Microcrud\Http\CrudController; use App\Services\ProductService; class ProductController extends CrudController { protected $service = ProductService::class; // Optionally override specific methods for custom logic }
Step 4: Register Routes
Option 1: Use Route Macros (Recommended - all POST endpoints):
// routes/api.php // Single resource Route::microcrud('products', ProductController::class); // Multiple resources Route::microcruds([ 'products' => ProductController::class, 'categories' => CategoryController::class, 'orders' => OrderController::class, ]);
This creates 7 POST endpoints:
POST /products/create→ create()POST /products/update→ update()POST /products/show→ show()POST /products/index→ index()POST /products/delete→ delete()POST /products/restore→ restore()POST /products/bulk-action→ bulkAction()
Option 2: RESTful Routes (Standard Laravel):
// routes/api.php Route::apiResource('products', ProductController::class); Route::post('products/{id}/restore', [ProductController::class, 'restore']); Route::post('products/bulk', [ProductController::class, 'bulkAction']);
That's it! You now have a fully functional API with:
- ✅ List with pagination and filtering
- ✅ Create with validation
- ✅ Read single item
- ✅ Update with validation
- ✅ Delete (soft/hard)
- ✅ Restore soft-deleted items
- ✅ Bulk operations
Architecture
MicroCRUD follows the Service-Repository-Controller pattern with a focus on separation of concerns:
┌─────────────────────────────────────────────────────────┐
│ HTTP Request │
└────────────────────┬────────────────────────────────────┘
│
▼
┌─────────────────────────────────────────────────────────┐
│ Controller Layer │
│ • CrudController (Abstract) │
│ • ApiBaseController (Response Formatting) │
│ • Your Controllers extend CrudController │
└────────────────────┬────────────────────────────────────┘
│
▼
┌─────────────────────────────────────────────────────────┐
│ Service Layer │
│ • Service (Abstract) - Business Logic │
│ • Validation, Caching, Transactions │
│ • Query Building, Filtering │
│ • Job Dispatching │
│ • Before/After Hooks │
└────────────────────┬────────────────────────────────────┘
│
├──────────────┬──────────────┬───────┐
▼ ▼ ▼ ▼
┌──────────┐ ┌──────────┐ ┌────────┐ ┌────────┐
│ Model │ │ Cache │ │ Jobs │ │ Events │
│ Layer │ │ Layer │ │ Layer │ │ Layer │
└────┬─────┘ └──────────┘ └────────┘ └────────┘
│
▼
┌──────────┐
│ Database │
└──────────┘
Component Breakdown
| Component | Purpose | File Location |
|---|---|---|
| Model | Eloquent ORM models | Microcrud\Abstracts\Model |
| Service | Business logic & operations | Microcrud\Abstracts\Service |
| Controller | HTTP handling & routing | Microcrud\Http\CrudController |
| Resource | API response transformation | Microcrud\Responses\ItemResource |
| Middleware | Request preprocessing | Microcrud\Middlewares\* |
| Jobs | Background processing | Microcrud\Abstracts\Jobs\* |
| Exceptions | Error handling | Microcrud\Abstracts\Exceptions\* |
Core Concepts
Services
Services contain all business logic. The base Service class provides:
// Core CRUD operations $service->index($data); // List with filters $service->show($id); // Get single item $service->create($data); // Create new item $service->update($id, $data); // Update existing item $service->delete($id); // Delete (soft/hard) $service->restore($id); // Restore soft-deleted // Bulk operations $service->bulkCreate($items); $service->bulkUpdate($items); $service->bulkDelete($ids); $service->bulkRestore($ids); // Query manipulation $service->setQuery($query); $service->getQuery(); $service->applyDynamicFilters($query, $data); // Validation $service->indexRules(); $service->createRules(); $service->updateRules(); // Cache control $service->enableCache(); $service->clearCache();
Controllers
Controllers handle HTTP requests and delegate to services:
class CrudController extends ApiBaseController { // All methods return formatted JSON responses: index() → 200 OK (paginated list) show($id) → 200 OK (single item) create() → 201 Created update($id) → 202 Accepted delete($id) → 202 Accepted restore($id) → 202 Accepted bulkAction() → 202 Accepted }
Resources
Resources transform model data for API responses:
class ItemResource extends JsonResource { // Automatically: // - Formats dates (Y-m-d H:i:s) // - Converts _id fields to nested objects // - Handles relationships public function toArray($request) { return $this->forModel(); } }
Route Macros
MicroCRUD provides convenient Route macros for registering CRUD resources:
// Single resource Route::microcrud('products', ProductController::class); // Multiple resources Route::microcruds([ 'products' => ProductController::class, 'categories' => CategoryController::class, ]);
With Middleware & Prefix:
Route::prefix('v1')->middleware(['auth:api'])->group(function () { Route::microcruds([ 'products' => ProductController::class, 'orders' => OrderController::class, ]); });
Benefits:
- ✅ 75% less code than manual route definitions
- ✅ Consistent pattern across all resources
- ✅ Works with middleware, prefixes, and versioning
- ✅ All POST endpoints (production-tested pattern)
Features
Dynamic Filtering
MicroCRUD automatically detects column types and provides intelligent filtering:
String Columns → LIKE Search
GET /products?search_by_name=laptop # SELECT * FROM products WHERE name LIKE '%laptop%'
Numeric Columns → Exact Match + Range
GET /products?search_by_price=999 # SELECT * FROM products WHERE price = 999 GET /products?search_by_price_min=100&search_by_price_max=500 # SELECT * FROM products WHERE price >= 100 AND price <= 500 GET /products?search_by_stock_min=10 # SELECT * FROM products WHERE stock >= 10
Date Columns → Exact Match + Range
GET /products?search_by_created_at=2025-01-15 # SELECT * FROM products WHERE DATE(created_at) = '2025-01-15' GET /products?search_by_created_at_from=2025-01-01&search_by_created_at_to=2025-01-31 # SELECT * FROM products WHERE DATE(created_at) >= '2025-01-01' # AND DATE(created_at) <= '2025-01-31'
Boolean Columns → Exact Match
GET /products?search_by_is_active=1 # SELECT * FROM products WHERE is_active = 1
Dynamic Sorting
GET /products?order_by_price=asc GET /products?order_by_created_at=desc GET /products?order_by_name=asc&order_by_price=desc
Dynamic Grouping
Group results by model columns or relation columns:
Simple Grouping:
# Group by single column POST /apartments/index { "group_bies": ["object_id"] } # SELECT apartments.* FROM apartments GROUP BY apartments.object_id # Group by multiple columns POST /apartments/index { "group_bies": ["object_id", "block_id"] } # SELECT apartments.* FROM apartments GROUP BY apartments.object_id, apartments.block_id # Group by relation column (automatically joins and eager loads) POST /apartments/index { "group_bies": ["block.manager_id"] } # SELECT apartments.*, blocks.manager_id # FROM apartments # LEFT JOIN blocks ON apartments.block_id = blocks.id # GROUP BY blocks.manager_id
Grouped Pagination (Top N per Group):
# Get first 10 apartments per block (using window functions) POST /apartments/index { "group_bies": { "block_id": { "limit": 10, "order_by": "created_at", "order_direction": "desc" } } } # Uses ROW_NUMBER() OVER (PARTITION BY block_id ORDER BY created_at DESC) # Returns max 10 apartments per block # Get top 5 highest-priced apartments per object POST /apartments/index { "group_bies": { "object_id": { "limit": 5, "order_by": "price", "order_direction": "desc" } } } # Get first 3 apartments per manager (relation-based grouping) POST /apartments/index { "group_bies": { "block.manager_id": { "limit": 3, "search": "John", "order_by": "id" } } }
Mixed Syntax (Simple + Configured):
POST /apartments/index { "group_bies": { "object_id": { "limit": 10, "search": "Building A" }, "status": null, "block_id": null } }
Features:
- ✅ Simple GROUP BY - For aggregations and unique value queries
- ✅ Top N per Group - Get first/last/top N records per group using window functions
- ✅ Hierarchical Responses - Nested parent-child structure with
hierarchical: true - ✅ Relation Support - Group by relation columns (e.g.,
block.manager_id) - ✅ Auto JOIN & Eager Load - Automatically joins and eager loads relations
- ✅ Search within Groups - Filter specific groups with search parameter
- ✅ Nested Relations - Supports multi-level relations (e.g.,
block.manager.department_id) - ✅ Relation Exclusion - Prevent duplicate data with
exclude_relationsparameter - ✅ Validates Everything - Checks columns and relations exist
- ✅ Database Agnostic - Works with MySQL 8+, PostgreSQL, SQL Server
Response Structure:
The response format depends on whether hierarchical is enabled:
1. Flat Grouped Response (hierarchical: false or not set)
Basic (returns first record per group - non-deterministic):
POST /apartments/index { "group_bies": ["object_id"], "page": 1, "limit": 10 }
With Aggregate Selection (deterministic):
Method 1: Top-level parameters
POST /apartments/index { "group_bies": ["object_id"], "group_order_by": "created_at", "group_order_direction": "desc", "page": 1, "limit": 10 }
Method 2: Inline syntax (recommended)
POST /apartments/index { "group_bies": { "object_id": { "order_by_created_at": "desc" } }, "page": 1, "limit": 10 }
Both return newest apartment per object
Or use group_aggregate shortcuts:
{
"group_bies": ["object_id"],
"group_aggregate": "last",
"group_order_by": "id"
}
first: First record (ORDER BY column ASC, get first)last: Last record (ORDER BY column DESC, get first)max: Max value (ORDER BY column DESC, get first)min: Min value (ORDER BY column ASC, get first)
Response (flat list with standard pagination):
{
"pagination": {
"current": 1,
"totalPage": 10,
"totalItem": 100
},
"data": [
{
"id": 45,
"name": "Apartment 1-Z",
"object_id": 1,
"created_at": "2025-01-29 14:20:00",
"object": {
"id": 1,
"name": "Sunrise Apartments"
}
},
{
"id": 89,
"name": "Apartment 2-Y",
"object_id": 2,
"created_at": "2025-01-28 09:15:00",
"object": {
"id": 2,
"name": "Sunset Towers"
}
}
// ... 8 more apartments (newest per unique object_id)
]
}
2. Hierarchical Grouped Response (hierarchical: true)
Basic example:
POST /apartments/index { "group_bies": ["object_id"], "hierarchical": true, "page": 1, "limit": 10 }
With aggregations and inline ordering:
POST /apartments/index { "group_bies": { "block.manager_id": { "aggregations": { "count": true, "sum": ["price"], "avg": ["price"] }, "order_by_sold_at": "desc" } }, "hierarchical": true, "page": 1, "limit": 10 }
Returns nested structure (group → children):
{
"pagination": {
"current": 1,
"totalPage": 5,
"totalItem": 48
},
"data": [
{
"group": {
"id": 1,
"name": "Sunrise Apartments"
},
"data": [
{"id": 1, "name": "Apartment 1-A"},
{"id": 2, "name": "Apartment 1-B"},
{"id": 3, "name": "Apartment 1-C"}
// ALL apartments in this object (object relation auto-excluded)
]
},
{
"group": {
"id": 2,
"name": "Sunset Towers"
},
"data": [
{"id": 15, "name": "Apartment 2-A"}
]
}
// ... 8 more objects (10 groups total)
]
}
Comparison Table:
| Feature | Flat (hierarchical: false) | Hierarchical (hierarchical: true) |
|---|---|---|
| Structure | [{...}, {...}] |
[{group: {...}, data: [...]}, ...] |
| Parent Data | Repeated in each record | Once per group |
| Records per Group | 1 (configurable with group_aggregate) |
ALL |
| Pagination | Paginates individual records | Paginates groups |
| Relations | Included (normal behavior) | Auto-excluded from children |
| Aggregate Selection | ✅ group_aggregate, group_order_by |
N/A |
| Use Case | Standard listing, latest/oldest per group | Parent-child navigation |
| Performance | Good for simple lists | Better for complex hierarchies |
3. With Aggregation - Add selectRaw() in beforeIndex() for COUNT/SUM/AVG/MAX/MIN
4. Top N per Group - Use window functions (see Performance Guide)
5. With Relations - Automatically eager loads relations (no N+1)
// Example: Add aggregation for grouped queries class ApartmentService extends Service { public function beforeIndex() { $data = $this->getData(); $query = $this->getQuery(); if (!empty($data['group_bies'])) { $table = $this->getModelTableName(); $query->selectRaw("COUNT(*) as apartment_count") ->selectRaw("SUM(price) as total_value") ->selectRaw("AVG(price) as avg_price"); } $this->setQuery($query); return parent::beforeIndex(); } }
Example Response (with aggregation):
{
"data": [
{
"object_id": 101,
"apartment_count": 45,
"total_value": 15750000,
"avg_price": 350000
}
]
}
Example Model Setup:
class Apartment extends Model { public function block() { return $this->belongsTo(Block::class); } } class Block extends Model { public function manager() { return $this->belongsTo(User::class, 'manager_id'); } }
Pagination
GET /products?page=2&limit=50 GET /products?is_all=1 # Get all without pagination
Combining Filters
GET /products?search_by_name=laptop &search_by_price_min=500 &search_by_price_max=2000 &search_by_is_active=1 &order_by_price=asc &page=1 &limit=20
Validation System
Validation rules are auto-generated from your database schema:
class ProductService extends Service { protected $model = Product::class; // Override to customize rules public function createRules($rules = [], $replace = false) { return parent::createRules([ 'name' => 'required|string|max:255|unique:products', 'price' => 'required|numeric|min:0', 'stock' => 'required|integer|min:0', 'category_id' => 'required|exists:categories,id', 'is_active' => 'boolean', ], $replace); } public function updateRules($rules = [], $replace = false) { return parent::updateRules([ 'name' => 'sometimes|string|max:255', 'price' => 'sometimes|numeric|min:0', 'stock' => 'sometimes|integer|min:0', ], $replace); } }
Auto-Generated Rules Based on Column Types:
string→sometimes|nullableinteger→sometimes|integernumeric→sometimes|numericdate→sometimes|dateboolean→sometimes|boolean
Plus automatic range validation:
- Integer:
search_by_{column}_min,search_by_{column}_max - Date:
search_by_{column}_from,search_by_{column}_to
Caching
Enable intelligent caching with automatic invalidation:
class ProductService extends Service { protected $model = Product::class; protected $enableCache = true; protected $cacheExpiration = 3600; // seconds // Cache is automatically: // ✓ Created on read operations // ✓ Tagged by model name // ✓ Invalidated on create/update/delete // ✓ Scoped to query parameters }
Manual cache control:
$service->enableCache(); $service->disableCache(); $service->clearCache();
Queue Jobs
Process heavy operations in the background:
class ProductService extends Service { protected $model = Product::class; protected $useJob = true; protected $queueName = 'products'; // Now create/update operations are queued automatically }
Available Jobs:
StoreJob- Background creationUpdateJob- Background updatesDeleteJob- Background deletion
Job Features:
- ✅
ShouldQueue- Async processing - ✅
ShouldBeUnique- Prevent duplicates - ✅ Failed job logging
- ✅ Configurable queue names
Bulk Operations
Process multiple records efficiently:
// Bulk Create POST /products/bulk { "action": "create", "data": [ {"name": "Product 1", "price": 100}, {"name": "Product 2", "price": 200}, {"name": "Product 3", "price": 300} ] } // Bulk Update POST /products/bulk { "action": "update", "data": [ {"id": 1, "price": 150}, {"id": 2, "price": 250} ] } // Bulk Delete POST /products/bulk { "action": "delete", "ids": [1, 2, 3, 4, 5] } // Bulk Restore POST /products/bulk { "action": "restore", "ids": [1, 2, 3] } // Bulk Show POST /products/bulk { "action": "show", "ids": [1, 2, 3] }
Bulk Features:
- Transaction support (all or nothing)
- Queue support for large batches
- Validation for each item
- Progress tracking
Soft Deletes
Full soft delete support with easy restoration:
# Soft delete (default) DELETE /products/1 # Force delete (permanent) DELETE /products/1?force_delete=true # Restore soft-deleted POST /products/1/restore # List with soft-deleted items GET /products?with_trashed=true # List only soft-deleted items GET /products?only_trashed=true
// In your service $service->delete($id); // Soft delete $service->delete($id, true); // Force delete $service->restore($id); // Restore
Hooks & Events
Add custom logic at any point in the lifecycle:
class ProductService extends Service { protected $model = Product::class; // Before hooks (can modify data) public function beforeCreate($data) { $data['sku'] = 'PRD-' . strtoupper(uniqid()); $data['slug'] = Str::slug($data['name']); return $data; } public function beforeUpdate($id, $data) { Log::info("Updating product {$id}", $data); return $data; } // After hooks (receive created/updated item) public function afterCreate($item) { Cache::tags(['products'])->flush(); event(new ProductCreated($item)); return $item; } public function afterUpdate($item) { event(new ProductUpdated($item)); return $item; } public function afterDelete($item) { Storage::deleteDirectory("products/{$item->id}"); return $item; } public function afterRestore($item) { event(new ProductRestored($item)); return $item; } public function afterIndex() { // Called after listing items } }
Available Hooks:
beforeCreate($data)afterCreate($item)beforeUpdate($id, $data)afterUpdate($item)beforeDelete($id)afterDelete($item)beforeRestore($id)afterRestore($item)afterIndex()
API Documentation
Standard Response Format
Success Response (Single Item)
{
"success": true,
"data": {
"id": 1,
"name": "Laptop",
"price": 999.99,
"stock": 15,
"is_active": true,
"created_at": "2025-01-15 10:30:00",
"updated_at": "2025-01-15 10:30:00"
}
}
Success Response (Paginated List)
{
"success": true,
"data": [
{
"id": 1,
"name": "Laptop",
"price": 999.99
},
{
"id": 2,
"name": "Mouse",
"price": 29.99
}
],
"meta": {
"current_page": 1,
"from": 1,
"last_page": 5,
"per_page": 20,
"to": 20,
"total": 100
}
}
Error Response (Validation)
{
"success": false,
"message": "The given data was invalid.",
"errors": {
"name": ["The name field is required."],
"price": ["The price must be at least 0."]
}
}
Error Response (Not Found)
{
"success": false,
"message": "Resource not found"
}
HTTP Status Codes
| Method | Endpoint | Success Status | Description |
|---|---|---|---|
| GET | /products |
200 OK | List products |
| GET | /products/{id} |
200 OK | Get single product |
| POST | /products |
201 Created | Create product |
| PUT | /products/{id} |
202 Accepted | Update product |
| DELETE | /products/{id} |
202 Accepted | Delete product |
| POST | /products/{id}/restore |
202 Accepted | Restore product |
| POST | /products/bulk |
202 Accepted | Bulk operation |
Error Status Codes
| Status Code | Method | Description |
|---|---|---|
| 400 | errorBadRequest() |
Bad Request |
| 401 | errorUnauthorized() |
Unauthorized |
| 403 | errorForbidden() |
Forbidden |
| 404 | errorNotFound() |
Not Found |
| 422 | errorValidation() |
Validation Error |
| 500 | error() |
Internal Server Error |
Advanced Usage
Custom Resources
Create custom transformations for your API responses:
<?php namespace App\Http\Resources; use Microcrud\Responses\ItemResource; class ProductResource extends ItemResource { public function toArray($request) { return [ 'id' => $this->id, 'name' => $this->name, 'description' => $this->description, 'price' => [ 'amount' => $this->price, 'formatted' => '$' . number_format($this->price, 2), 'currency' => 'USD', ], 'stock' => [ 'quantity' => $this->stock, 'status' => $this->stock > 0 ? 'in_stock' : 'out_of_stock', 'low_stock' => $this->stock < 10, ], 'category' => new CategoryResource($this->whenLoaded('category')), 'images' => ImageResource::collection($this->whenLoaded('images')), 'is_active' => (bool) $this->is_active, 'timestamps' => [ 'created' => $this->created_at->toISOString(), 'updated' => $this->updated_at->toISOString(), ], ]; } }
Use in controller:
class ProductController extends CrudController { protected $service = ProductService::class; protected $resource = ProductResource::class; }
Transaction Management
Transactions are enabled by default. Control them per service:
class ProductService extends Service { protected $model = Product::class; protected $useTransaction = true; // default // Or disable for specific operations public function create($data) { $this->setIsTransactionEnabled(false); return parent::create($data); } }
Custom Query Scopes
Add custom query modifications:
class ProductService extends Service { protected $model = Product::class; public function index($data) { // Apply custom query before processing $query = $this->model::query() ->with(['category', 'images']) ->where('is_active', true) ->whereHas('category', function($q) { $q->where('active', true); }); $this->setQuery($query); return parent::index($data); } }
Preventing N+1 Queries
Eager load relationships to prevent N+1 queries:
class ProductService extends Service { protected $model = Product::class; public function index($data) { // Eager load relationships $query = $this->model::with([ 'category', 'images', 'reviews' => function($q) { $q->where('approved', true); } ]); $this->setQuery($query); return parent::index($data); } public function show($id) { $query = $this->model::with(['category', 'images', 'reviews']); $this->setQuery($query); return parent::show($id); } }
Without Global Scopes
Remove global scopes temporarily:
$service->withoutScopes(['ActiveScope'])->index($data); $service->withoutScopes()->index($data); // Remove all scopes
Parent-Child Relationships
For hierarchical data like categories or organizational structures:
use Microcrud\Traits\ParentChildTrait; class Category extends Model { use ParentChildTrait; protected $fillable = ['name', 'parent_id']; }
Usage:
$category = Category::find(1); // Get direct children $children = $category->children; // Get all descendants (recursive) $allDescendants = $category->allChildren; // Get parent $parent = $category->parent; // Get all descendant IDs $ids = $category->getAllDescendantIds(); // [2, 3, 4, 5, ...] // Get full tree from root $tree = Category::getRootWithChildren();
Auto-cascade delete:
$category->delete(); // Automatically deletes all children
HTTP Client Service
Make external API calls with the built-in HTTP client:
use Microcrud\Services\Curl\Services\CurlService; $curl = new CurlService(); // GET request $curl->setUrl('https://api.example.com/users') ->setHeaders([ 'Authorization' => 'Bearer ' . $token, 'Accept' => 'application/json', ]) ->setParams(['page' => 1, 'limit' => 20]); $response = $curl->get(); // POST request $curl->setUrl('https://api.example.com/users') ->setParams([ 'name' => 'John Doe', 'email' => 'john@example.com', ]); $response = $curl->post(); // Other methods $curl->put(); $curl->patch(); $curl->delete();
Middleware
LocaleMiddleware
Automatically sets application locale from Accept-Language header:
// app/Http/Kernel.php protected $routeMiddleware = [ 'locale' => \Microcrud\Middlewares\LocaleMiddleware::class, ]; // routes/api.php Route::middleware(['locale'])->group(function () { Route::apiResource('products', ProductController::class); });
Request:
GET /api/products Accept-Language: ru
Behavior:
- Checks
Accept-Languageheader - Matches against configured locales (
config('microcrud.locales')) - Falls back to
config('microcrud.locale')
TimezoneMiddleware
Sets timezone from Timezone header:
protected $routeMiddleware = [ 'timezone' => \Microcrud\Middlewares\TimezoneMiddleware::class, ]; Route::middleware(['timezone'])->group(function () { // Your routes });
Request:
GET /api/products Timezone: America/New_York
Default: Uses config('microcrud.timezone', 'UTC')
LogHttpRequest
Logs all HTTP requests with URL, headers, and parameters:
protected $routeMiddleware = [ 'log.http' => \Microcrud\Middlewares\LogHttpRequest::class, ]; Route::middleware(['log.http'])->group(function () { // Your routes });
Logs to Laravel's default log channel.
Configuration
config/microcrud.php
<?php return [ /* |-------------------------------------------------------------------------- | Database Connection |-------------------------------------------------------------------------- | | Specify a custom database connection. Leave empty to use default. | */ 'connection' => env('MICROCRUD_DB_CONNECTION', ''), /* |-------------------------------------------------------------------------- | Authorization |-------------------------------------------------------------------------- | | Enable/disable authorization checks in controllers. | */ 'authorize' => env('MICROCRUD_AUTHORIZE', true), /* |-------------------------------------------------------------------------- | Supported Locales |-------------------------------------------------------------------------- | | List of locales supported by your application. | */ 'locales' => ['en', 'ru', 'uz'], /* |-------------------------------------------------------------------------- | Default Locale |-------------------------------------------------------------------------- | | The default locale for your application. | */ 'locale' => env('MICROCRUD_LOCALE', 'en'), /* |-------------------------------------------------------------------------- | Default Timezone |-------------------------------------------------------------------------- | | The default timezone used by TimezoneMiddleware. | */ 'timezone' => env('MICROCRUD_TIMEZONE', 'UTC'), ];
config/schema.php
For PostgreSQL multi-schema support:
<?php return [ 'notification' => env('DB_NOTIFICATION_SCHEMA', 'public'), 'upload' => env('DB_UPLOAD_SCHEMA', 'public'), 'user' => env('DB_USER_SCHEMA', 'public'), // Add your schemas here ];
Environment Variables
Add to your .env:
# MicroCRUD Configuration MICROCRUD_DB_CONNECTION=mysql MICROCRUD_AUTHORIZE=true MICROCRUD_LOCALE=en MICROCRUD_TIMEZONE=UTC # PostgreSQL Schemas (if needed) DB_NOTIFICATION_SCHEMA=notifications DB_UPLOAD_SCHEMA=uploads DB_USER_SCHEMA=users
Examples
Complete E-commerce Product API
// app/Models/Product.php namespace App\Models; use Microcrud\Abstracts\Model; class Product extends Model { use SoftDeletes; protected $fillable = [ 'name', 'slug', 'description', 'price', 'sale_price', 'sku', 'stock', 'category_id', 'brand_id', 'is_active' ]; protected $casts = [ 'price' => 'decimal:2', 'sale_price' => 'decimal:2', 'is_active' => 'boolean', ]; public function category() { return $this->belongsTo(Category::class); } public function brand() { return $this->belongsTo(Brand::class); } public function images() { return $this->hasMany(ProductImage::class); } public function reviews() { return $this->hasMany(Review::class); } } // app/Services/ProductService.php namespace App\Services; use Microcrud\Abstracts\Service; use App\Models\Product; use Illuminate\Support\Str; class ProductService extends Service { protected $model = Product::class; protected $enableCache = true; protected $cacheExpiration = 3600; protected $useTransaction = true; public function beforeCreate($data) { $data['slug'] = Str::slug($data['name']); $data['sku'] = 'PRD-' . strtoupper(uniqid()); return $data; } public function afterCreate($item) { Cache::tags(['products'])->flush(); event(new ProductCreated($item)); return $item; } public function createRules($rules = [], $replace = false) { return parent::createRules([ 'name' => 'required|string|max:255', 'description' => 'nullable|string', 'price' => 'required|numeric|min:0', 'sale_price' => 'nullable|numeric|min:0|lt:price', 'stock' => 'required|integer|min:0', 'category_id' => 'required|exists:categories,id', 'brand_id' => 'nullable|exists:brands,id', 'is_active' => 'boolean', ], $replace); } public function index($data) { $query = $this->model::with(['category', 'brand', 'images']) ->withCount('reviews') ->withAvg('reviews', 'rating'); $this->setQuery($query); return parent::index($data); } } // app/Http/Controllers/ProductController.php namespace App\Http\Controllers; use Microcrud\Http\CrudController; use App\Services\ProductService; use App\Http\Resources\ProductResource; class ProductController extends CrudController { protected $service = ProductService::class; protected $resource = ProductResource::class; } // app/Http/Resources/ProductResource.php namespace App\Http\Resources; use Microcrud\Responses\ItemResource; class ProductResource extends ItemResource { public function toArray($request) { return [ 'id' => $this->id, 'name' => $this->name, 'slug' => $this->slug, 'description' => $this->description, 'pricing' => [ 'regular' => $this->price, 'sale' => $this->sale_price, 'discount' => $this->sale_price ? round((($this->price - $this->sale_price) / $this->price) * 100) : 0, ], 'inventory' => [ 'sku' => $this->sku, 'stock' => $this->stock, 'in_stock' => $this->stock > 0, ], 'category' => new CategoryResource($this->whenLoaded('category')), 'brand' => new BrandResource($this->whenLoaded('brand')), 'images' => ImageResource::collection($this->whenLoaded('images')), 'rating' => [ 'average' => round($this->reviews_avg_rating ?? 0, 1), 'count' => $this->reviews_count ?? 0, ], 'is_active' => (bool) $this->is_active, 'created_at' => $this->created_at->toISOString(), ]; } }
API Usage Examples
# List products with filters curl -X GET "http://api.example.com/products?search_by_name=laptop&search_by_price_min=500&search_by_price_max=2000&order_by_price=asc&page=1&limit=20" # Get single product curl -X GET "http://api.example.com/products/1" # Create product curl -X POST "http://api.example.com/products" \ -H "Content-Type: application/json" \ -d '{ "name": "Gaming Laptop", "description": "High-performance gaming laptop", "price": 1299.99, "stock": 50, "category_id": 5, "is_active": true }' # Update product curl -X PUT "http://api.example.com/products/1" \ -H "Content-Type: application/json" \ -d '{ "price": 1199.99, "sale_price": 999.99 }' # Delete product (soft) curl -X DELETE "http://api.example.com/products/1" # Force delete curl -X DELETE "http://api.example.com/products/1?force_delete=true" # Restore product curl -X POST "http://api.example.com/products/1/restore" # Bulk create curl -X POST "http://api.example.com/products/bulk" \ -H "Content-Type: application/json" \ -d '{ "action": "create", "data": [ {"name": "Product 1", "price": 99.99, "stock": 10, "category_id": 1}, {"name": "Product 2", "price": 199.99, "stock": 5, "category_id": 1} ] }'
Testing
While the package doesn't include a test suite, here's how to test your implementations:
// tests/Feature/ProductApiTest.php namespace Tests\Feature; use Tests\TestCase; use App\Models\Product; use App\Models\Category; class ProductApiTest extends TestCase { public function test_can_list_products() { Product::factory()->count(5)->create(); $response = $this->getJson('/api/products'); $response->assertStatus(200) ->assertJsonStructure([ 'success', 'data' => [ '*' => ['id', 'name', 'price'] ], 'meta' ]); } public function test_can_create_product() { $category = Category::factory()->create(); $data = [ 'name' => 'Test Product', 'price' => 99.99, 'stock' => 10, 'category_id' => $category->id, ]; $response = $this->postJson('/api/products', $data); $response->assertStatus(201) ->assertJson(['success' => true]); $this->assertDatabaseHas('products', ['name' => 'Test Product']); } public function test_can_filter_products_by_price() { Product::factory()->create(['price' => 50]); Product::factory()->create(['price' => 150]); Product::factory()->create(['price' => 250]); $response = $this->getJson('/api/products?search_by_price_min=100&search_by_price_max=200'); $response->assertStatus(200); $this->assertCount(1, $response->json('data')); } }
Contributing
Contributions are welcome! Please follow these steps:
- Fork the repository
- Create a feature branch (
git checkout -b feature/amazing-feature) - Commit your changes (
git commit -m 'Add amazing feature') - Push to the branch (
git push origin feature/amazing-feature) - Open a Pull Request
Development Setup
git clone https://github.com/ibekzod/microcrud.git
cd microcrud
composer install
Coding Standards
- Follow PSR-12 coding standards
- Write descriptive commit messages
- Add tests for new features
- Update documentation
Changelog
[Latest] - 2025-01-30
Added
- ✨ Dynamic Grouping (group_bies) - Group results by model columns or relations with auto-joins and eager loading
- ✨ Grouped Pagination - "Top N per Group" queries using window functions (ROW_NUMBER OVER PARTITION BY)
- ✨ Hierarchical Grouped Responses - Nested parent-child structure with pagination at each level (
hierarchical: true) - ✨ Relation Exclusion - Prevent duplicate relation data in leaf resources (
exclude_relationsparameter) - ✨ Group Aggregations - Calculate COUNT/SUM/AVG/MAX/MIN per group level
- ✨ Route Macros -
Route::microcrud()andRoute::microcruds()for easy resource registration - ✨ Enhanced Exceptions - Rich error context with toArray()/toJson() methods
- ✨ Improved Middlewares - Better security, logging, and validation
- 📚 Comprehensive Documentation - Enhanced code documentation throughout
Improved
- ⚡ Better Error Handling - ValidationException, CreateException, UpdateException, DeleteException, NotFoundException
- 📝 Controller Documentation - Full PHPDoc for all methods
- 🎨 Code Quality - Better structure, logging, and maintainability
- 🔒 Security - Sensitive data filtering in LogHttpRequest middleware
Previous Releases
- Type-aware dynamic search filters - min/max for numeric, from/to for dates
- DeleteJob - Background deletion operations
- Configurable timezone - Via
config('microcrud.timezone') - DYNAMIC search_by_column & order_by_column - Added dynamic filtering
- Bulk actions - Implemented bulk operations
Security
If you discover any security-related issues, please email erkinovbegzod.45@gmail.com instead of using the issue tracker.
Credits
- Author: iBekzod
- Email: erkinovbegzod.45@gmail.com
- Package: ibekzod/microcrud
License
The MIT License (MIT). Please see License File for more information.
Made with ❤️ by iBekzod
ibekzod/microcrud 适用场景与选型建议
ibekzod/microcrud 是一款 基于 PHP 开发的 Composer 扩展包,目前已累计 4.36k 次下载、GitHub Stars 达 2, 最近一次更新时间为 2024 年 09 月 03 日, 在 PHP 生态内属于活跃度较高的组件。
它主要适用于以下技术方向: 「api」 「crud」 「laravel」 「service-repository」 「microcrud」 等业务场景。在实际项目中,围绕这些方向常见需要落地的问题包括:接口对接、性能调优、并发安全、与既有框架(Laravel / ThinkPHP / Yii / Webman 等)的兼容适配,以及生产环境的日志埋点与稳定性保障。
我们在过去多个企业项目中使用过 ibekzod/microcrud 或与其功能相近的方案,如果你在选型或落地过程中遇到问题,例如 版本兼容、二次改造、私有化封装、与内部系统对接、生产 BUG 排查,欢迎联系我们协助评估。
基于 ibekzod/microcrud 在你已有业务上做功能扩展、字段裁剪、UI 适配、与内部账号 / 权限 / 日志系统的深度对接。
线上偶发问题、内存泄漏、慢查询、并发异常等排查修复;针对高流量场景做缓存、队列、索引层面的调优。
承接完整的项目从需求 → 设计 → 开发 → 上线 → 长期运维;也可按月提供技术保姆服务。
与 ibekzod/microcrud 相关的其它包
同方向 / 同关键字的高下载量 PHP Composer 包推荐,方便对比选型:
Gii Generator for double model generation
A PSR-7 compatible library for making CRUD API endpoints
Admin panel generator for Laravel 8 and based on Vuetify Admin, a separate SPA admin framework running on top of REST APIs.
Collection of tools to use the full power of the Enyalius framework
Laravel Admin CRUD Generator
Alfabank REST API integration
统计信息
- 总下载量: 4.36k
- 月度下载量: 0
- 日度下载量: 0
- 收藏数: 2
- 点击次数: 6
- 依赖项目数: 0
- 推荐数: 0
其他信息
- 授权协议: MIT
- 更新时间: 2024-09-03