Overview
The BaseService class handles all business logic for REST operations. It sits between your controller and model, providing:
- CRUD operations with validation
- Dynamic filtering via the
oper parameter
- Eager loading with field selection
- Pagination (standard and cursor-based)
- Hierarchical queries for tree data
- Automatic caching (optional)
- Export helpers for Excel and PDF
Service Setup
Create a service for each model by extending BaseService:
use Ronu\RestGenericClass\Core\Services\BaseService;
use App\Models\Product;
class ProductService extends BaseService
{
public function __construct()
{
parent::__construct(Product::class);
}
}
That’s it! Your service now has all REST capabilities.
Core Methods
list_all()
Retrieves a collection of records with optional filtering, relations, and pagination.
public function list_all($params, $toJson = true): mixed
Query parameters from the request:
select — Column selection
relations — Eager load relations
oper — Filters (and/or conditions)
orderby — Sorting
pagination — Page settings
hierarchy — Hierarchical mode
_nested — Apply relation filters to eager loads
If true, returns ['data' => $results]. If false, returns raw array.
Example:
$params = [
'select' => ['id', 'name', 'price'],
'relations' => ['category:id,name'],
'oper' => ['and' => ['status|=|active', 'price|>=|100']],
'orderby' => [['price' => 'desc']],
'pagination' => ['page' => 1, 'pageSize' => 25]
];
$result = $service->list_all($params);
get_one()
Retrieves a single record matching the query.
public function get_one($params, $toJson = true): mixed
Same parameters as list_all(), but returns the first matching record:
$params = [
'oper' => ['and' => ['sku|=|ABC123']],
'relations' => ['category', 'reviews']
];
$product = $service->get_one($params);
// Returns: ['data' => Product]
create()
Creates one or more records.
public function create(array $params): array
Single record:
$result = $service->create([
'name' => 'Wireless Mouse',
'price' => 29.99,
'stock' => 100,
'category_id' => 5
]);
// Returns: ['success' => true, 'model' => [...]]
Batch creation:
$result = $service->create([
'product' => [
['name' => 'Item 1', 'price' => 10],
['name' => 'Item 2', 'price' => 20]
]
]);
// Returns: ['success' => true, ['model' => ...], ['model' => ...]]
The key name (product) must match your model’s const MODEL value.
update()
Updates an existing record.
public function update(array $attributes, $id, $validate = false): array
Fields to update (only provided fields are changed).
The primary key value or custom field key if fieldKeyUpdate is set.
If true, runs model validation before saving.
Example:
$result = $service->update(
['price' => 24.99, 'stock' => 85],
$productId
);
// Returns: ['success' => true, 'model' => [...]]
Partial updates are supported — only send the fields you want to change:
// Only update stock, leave price unchanged
$service->update(['stock' => 50], $productId);
destroy()
Deletes a record by ID.
public function destroy($id): array
$result = $service->destroy($productId);
// Returns: ['success' => true, 'model' => Product]
Supports soft deletes if your model uses SoftDeletes trait.
Query Building with process_query()
The process_query() method is the heart of the service layer. It transforms REST parameters into an Eloquent query.
From BaseService.php:350-398:
public function process_query($params, $query): Builder
{
// 1. Apply legacy equality filters (attr/eq)
if (isset($params["attr"])) {
$query = $this->eq_attr($query, $params['attr']);
}
// 2. Parse oper parameter
$oper = $params['oper'] ?? null;
if (is_string($oper)) {
$decoded = json_decode($oper, true);
if (json_last_error() === JSON_ERROR_NONE) {
$oper = $decoded;
}
}
// 3. Apply oper tree (includes whereHas for relations)
if (!empty($oper)) {
$query = $this->applyOperTree($query, $oper, 'and', $this->modelClass);
}
// 4. Eager load relations with field selection
if (isset($params['relations'])) {
$query = $this->relations(
$query,
$params['relations'],
$params['_nested'] ? $oper : null
);
}
// 5. Select clause for main model
if (isset($params['select'])) {
$query = $query->select($params['select']);
} else {
$query = $query->select($this->modelClass->getTable() . '.*');
}
// 6. Order by
if (isset($params['orderby'])) {
$query = $this->order_by($query, $params['orderby']);
}
return $query;
}
Order of Execution
- Legacy filters (
attr) — Simple equality conditions
- Dynamic filters (
oper) — Complex filtering tree with whereHas for relations
- Eager loading (
relations) — Loads related data (optionally filtered if _nested=true)
- Column selection (
select) — Limits returned columns
- Sorting (
orderby) — Applies ORDER BY clauses
- Pagination — Applied by
process_all() after process_query()
Filtering System
The service uses HasDynamicFilter trait for powerful query building.
Filter Operators
From HasDynamicFilter.php:35-57:
'=', '!=', '<>', '<', '>', '<=', '>=',
'like', 'not like', 'ilike', 'not ilike',
'in', 'not in',
'between', 'not between',
'date', 'not date',
'null', 'not null',
'exists', 'not exists',
'regexp', 'not regexp'
Conditions use pipe-separated format:
Example filters:
{
"oper": {
"and": [
"status|=|active",
"price|>=|100",
"category_id|in|1,2,3",
"created_at|date|2024-01-01",
"name|like|%wireless%"
]
}
}
See Dynamic Filtering for complete documentation.
Relation Loading
The relations() method handles eager loading with optional field selection.
Basic syntax:
{
"relations": ["category", "reviews"]
}
With field selection:
{
"relations": [
"category:id,name",
"reviews:id,rating,comment"
]
}
Nested relations:
{
"relations": [
"category.parent:id,name",
"reviews.user:id,name"
]
}
See Relation Loading for advanced usage.
Caching
BaseService includes built-in caching for read operations.
Configuration
From config/rest-generic-class.php:
'cache' => [
'enabled' => env('REST_CACHE_ENABLED', false),
'store' => env('REST_CACHE_STORE', 'redis'),
'ttl' => (int)env('REST_CACHE_TTL', 60),
'ttl_by_method' => [
'list_all' => 60,
'get_one' => 30,
],
'cacheable_methods' => ['list_all', 'get_one'],
'vary' => [
'headers' => ['Accept-Language', 'X-Tenant-Id'],
],
]
Cache Keys
Cache keys are deterministic and include:
- Operation name (
list_all, get_one)
- Model class
- Query parameters (
select, oper, relations, etc.)
- User ID (from
auth()->id())
- Vary headers (locale, tenant, etc.)
- Cache version (for invalidation)
From BaseService.php:1127-1151:
private function buildCacheKey(string $operation, array $params): string
{
$request = request();
$route = $request?->route();
$headersToVary = config('rest-generic-class.cache.vary.headers', []);
$varyHeaders = [];
foreach ($headersToVary as $header) {
$varyHeaders[$header] = $request?->header($header);
}
$fingerprint = [
'op' => $operation,
'model' => get_class($this->modelClass),
'route' => $route?->getName() ?? $request?->path() ?? 'cli',
'method' => $request?->method(),
'query' => $request?->query(),
'headers' => $varyHeaders,
'user' => auth()->id(),
'params' => $params,
'version' => $this->getCacheVersion(),
];
return $this->cachePrefix . ':' . sha1(json_encode($fingerprint));
}
Cache Invalidation
The cache version bumps automatically after write operations (create, update, destroy), invalidating all cached queries for that model.
Per-Request Control
{
"cache": false,
"oper": {"and": ["status|=|active"]}
}
Or set custom TTL:
{
"cache_ttl": 300,
"relations": ["category"]
}
Export Helpers
Export features require optional packages:composer require maatwebsite/excel barryvdh/laravel-dompdf
exportExcel()
public function exportExcel($params)
$params = [
'select' => ['id', 'name', 'email', 'created_at'],
'columns' => ['name', 'email'], // Columns to export
'oper' => ['and' => ['active|=|1']],
'filename' => 'active-users.xlsx'
];
return $service->exportExcel($params);
exportPdf()
public function exportPdf($params)
$params = [
'columns' => ['name', 'email', 'created_at'],
'oper' => ['and' => ['active|=|1']],
'template' => 'reports.users', // Blade view
'filename' => 'users-report.pdf'
];
return $service->exportPdf($params);
The Blade template receives:
$data — Array of records
$columns — Column names to display
$model — Model instance
$params — Original parameters
Complete Example
use Ronu\RestGenericClass\Core\Services\BaseService;
use App\Models\Product;
class ProductService extends BaseService
{
public function __construct()
{
parent::__construct(Product::class);
}
// Custom method example
public function getTopRated(int $limit = 10)
{
$params = [
'select' => ['id', 'name', 'price', 'rating'],
'relations' => ['category:id,name'],
'oper' => ['and' => ['rating|>=|4.5']],
'orderby' => [['rating' => 'desc']],
'pagination' => ['page' => 1, 'pageSize' => $limit]
];
return $this->list_all($params);
}
// Override validation
public function create(array $params): array
{
// Custom business logic before creation
if (isset($params['sku'])) {
$params['sku'] = strtoupper($params['sku']);
}
return parent::create($params);
}
}
Next Steps