gustavosantarosa/service-basics-extension
A lightweight Laravel extension for service-basics that adds reusable service-layer scaffolding, helping you organize business logic into clean, testable service classes with minimal boilerplate. A simple starting point for consistent service patterns in projects already using the base package.
This package gives you an opinionated base Service + Controller layer for Eloquent resources: a BaseService for query/business logic and a controller base that wires up index/show (and soft-delete handling via a "Trash" trait). Version 2.1.0 is mostly an internal reorganization into traits, so the fastest way to get productive is to read the traits rather than the base classes.
Install:
composer require quantum-tecnology/service-basics-extension
Confirm the exact Composer name on Packagist/the repo README — the GitHub name (Quantum-Tecnology/Service-Basics-Extension) is not always the package name.
Look first at:
src/Traits/ — this is where the real behavior now lives (Search, Sort, Scopes, Trash, and the renamed filter/include trait).BaseService — now a thin shell that composes those traits.index/show — also abstracted into traits in 2.1.0.Publish any config (if the package ships one) and skim it for searchable/sortable/include whitelists:
php artisan vendor:publish --provider="QuantumTecnology\ServiceBasicsExtension\ServiceBasicsServiceProvider"
First use case — a read-only listing endpoint:
class UserService extends BaseService
{
protected string $model = User::class;
protected array $searchable = ['name', 'email'];
protected array $sortable = ['name', 'created_at'];
protected array $allowedIncludes = ['posts', 'roles'];
}
class UserController extends BaseController
{
public function __construct(UserService $service)
{
$this->service = $service;
}
}
Then hit GET /users?search=ana&sort=-created_at&include=roles. That request-driven behavior is the package's main selling point and the thing to verify first in your app.
Service-per-resource, controller delegates. Keep controllers nearly empty. Put validation-adjacent filters and query shaping in the service; the base controller's index/show traits will call into it. Override index/show only when you need a different response shape:
public function index(Request $request)
{
$paginator = $this->service->index($request);
return UserResource::collection($paginator);
}
Request-driven listing. The new Scopes trait is designed to apply search/sort/filter automatically from the request. Treat this as the default listing pipeline, and layer your own scopes alongside it:
// In your model
public function scopeActive($query): void
{
$query->where('active', true);
}
// In the service, combine automatic + explicit
$this->service->index($request)->through(fn ($u) => ...);
Compose traits explicitly for custom flows. Because 2.1.0 split logic into traits, you can now use only what you need — e.g. a "report" service that sorts but never exposes includes:
class ReportService extends BaseService
{
use Search, Sort; // deliberately not Trash / FilterInclude
}
Soft deletes via the Trash trait. Model the expected REST surface: DELETE /users/{id} (soft), GET /users/trash or ?trashed=1 (listing), PATCH /users/{id}/restore, DELETE /users/{id}/force. Verify the exact route/query convention your version exposes — the changelog only says the trait was extracted, not that the surface changed.
Includes / eager loading. Use the renamed filter/include trait for ?include=posts,roles with an allow-list on the service. This is the right place to prevent arbitrary relationship loading.
Testing. Feature-test the query-string contract — search, sort direction, pagination, includes, trash — because most of 2.1.0 is a refactor and you want a regression net before you upgrade.
2.1.0 is a refactor release — assume breakage in subclasses. Base service methods moved into traits, and the filter/include trait was renamed. If you extended BaseService and overrode methods, or referenced the old trait name directly, grep for it and update. Run composer dump-autoload and your test suite immediately after upgrading.
Trait method collisions. With Search, Sort, Scopes and Trash all in play, identical method names are plausible. Resolve explicitly:
use Search, Sort {
Search::apply insteadof Sort;
Sort::apply as applySort;
}
If you see "trait method has not been applied" or a method silently resolving to the wrong trait, this is why.
Auto-scopes are an injection surface. Request-driven sorting that maps directly to orderBy($request->sort) allows ordering by non-indexed or sensitive columns. Always configure a sortable whitelist and reject unknown keys; same for searchable columns and includes.
include without a whitelist is an N+1 machine. Even with an allow-list, eager-load counts can explode on large pages. Cap page size and consider withCount for counts instead of loading relations.
phpCSFixer was added in 2.1.0. It affects the package's own source, not your application code. Don't run the package's fixer config against your app/ directory — it will reformat your codebase and produce a noisy diff.
Debugging the index pipeline. When a listing returns unexpected results, isolate the stage: log the query before/after search, then sort, then scopes, then includes (->toSql() at each point). The trait split makes this easier than the old monolithic service — use it.
Extension points to prefer: model scopes for reusable constraints, service properties for whitelists, resource classes for output shape. Avoid overriding the base index/show unless you need a structurally different response; the base traits are the intended seam.
Upgrade check: diff 2.0.5 → 2.1.0 and look specifically for renamed traits/methods. The changelog flags the rename but not a deprecation alias, so anything still referencing the old trait name will fail at class-load time.
How can I help you explore Laravel packages today?