timwassenburg/laravel-service-generator
Laravel Service Generator adds an artisan command to quickly scaffold service classes, keeping business logic out of controllers. Customize stubs, generate services with dependencies, and follow a clean, testable architecture. A simple way to organize Laravel apps as they grow.
Install the package via Composer; Laravel's package auto-discovery registers the service provider:
composer require timwassenburg/laravel-service-generator
Publish the config so you can control where services land and what they are named:
php artisan vendor:publish --tag=service-generator-config
# if the tag differs in your version:
php artisan vendor:publish --provider="TimWassenburg\ServiceGenerator\ServiceGeneratorServiceProvider"
Confirm the command is registered before relying on it:
php artisan list | grep -i service
Generate your first service:
php artisan make:service UserService
You should get app/Services/UserService.php (or whatever path/namespace the config declares). Open the generated file and the published config/service-generator.php side by side — those two files are the whole package. There is no runtime component, no facade, no container magic; everything the package does is produce a PHP file from a stub.
First use case: pull one fat controller action apart. Move the orchestration (fetch → transform → persist → notify) into the generated service, leave the controller with validation and a single call, and inject the service into the controller constructor. That single refactor is the best way to judge whether the package's defaults match your project's conventions.
Controller → Service → Repository. The generated class is an empty shell, so establish the shape yourself:
namespace App\Services;
class UserService
{
public function __construct(private UserRepository $users) {}
public function register(array $data): User
{
return DB::transaction(function () use ($data) {
$user = $this->users->create($data);
event(new UserRegistered($user));
return $user;
});
}
}
Inject it in the controller via constructor promotion and keep the action to a few lines.
Domain subfolders. If the generator supports nested names, php artisan make:service Billing/InvoiceService keeps large apps navigable. If it does not, either adjust path per generation or fall back to make:class for anything non-flat — do not fight the tool.
Interfaces are your job. The package generates a concrete class only. The standard pattern is to hand-write UserServiceInterface (or extract it later), then bind it in a service provider:
$this->app->bind(UserServiceInterface::class, UserService::class);
Type-hint the interface in controllers and jobs, never the concrete class, or you lose the ability to swap implementations in tests.
Custom stub for team conventions. Publish the stub, then bake in your house style — a Logger trait, a protected array $dependencies, a standard docblock, whatever your review checklist demands. This is the highest-leverage feature of the package: every new service starts compliant instead of being fixed in code review.
Batch scaffolding. When introducing the pattern to an existing codebase, generate all services in one pass (make:service OrderService, make:service PaymentService, …), commit the empty shells, then fill them in over several PRs. Reviewers see structure before logic.
Testing. Because services are plain classes with constructor dependencies, instantiate them directly in unit tests with mocked collaborators. No app() resolution, no HTTP layer — this is the payoff of the pattern.
Service suffix and you also pass UserService, you may end up with UserServiceService. Pick one convention: either always omit the suffix from the argument or set the config suffix to an empty string.namespace and path config values must agree. Setting path to app/Domain/Services while leaving namespace as App\Services produces a class that Composer cannot autoload. Change both together, and run composer dump-autoload after pointing the path outside app/.php artisan make:service --help after install rather than assuming flags like --force or --interface exist.make:service. If php artisan list shows a different signature than the README, another provider won the registration — check config/app.php providers and discovery order.php artisan config:clear && composer dump-autoload. If the file lands in the wrong place, the published config was not picked up (env-specific config caching is a common culprit).php artisan make:class Services/Foo is equivalent and adds no dependency. Adopt this package when you want the directory, the namespace, and the stub standardized across a team — otherwise it is ceremony.How can I help you explore Laravel packages today?