Weave Code
Weaver
Helps Laravel developers discover, compare, and choose open-source packages. See popularity, security, maintainers, and scores at a glance to make better decisions.
Feedback
Share your thoughts, report bugs, or suggest improvements.
Subject
Message

Service Laravel Package

smorken/service

A lightweight base service class for Laravel that gives you a clean place to put business logic outside controllers and models. Extend the provided Service, add your methods, and keep controllers thin while sharing reusable, testable operations across your app.

View on GitHub
Deep Wiki
Context7

Getting Started

1. Verify it's installable. With zero stars and no dependents, first confirm the package is actually reachable. If it isn't on Packagist, add the Bitbucket repo as a VCS source in your root composer.json:

{
    "repositories": [
        { "type": "vcs", "url": "https://bitbucket.org/scott_morken/laravel-service-package.git" }
    ],
    "require": {
        "smorken/service": "dev-master"
    }
}

Bitbucket repos are usually private — you'll need an app password / SSH key locally, and COMPOSER_AUTH (or an SSH agent) in CI.

2. Read these four things before writing any code, in this order:

File Why
composer.json PSR-4 namespace, extra.laravel.providers (auto-discovery), PHP/Laravel constraints
src/*ServiceProvider.php What gets bound, singletons vs. bindings, config publishing
The base service class (usually src/Service.php) The actual contract you're extending — constructor signature matters most
config/*.php Any class-map / driver / option the package resolves from config

The whole package is small; spend ten minutes reading src/ end to end rather than guessing from the README.

3. Register (if not auto-discovered). Older Laravel or a missing extra.laravel block means manual registration in config/app.php:

'providers' => [
    Smorken\Service\ServiceProvider::class,
],

4. Publish config if the provider exposes it:

php artisan vendor:publish --provider="Smorken\Service\ServiceProvider"

5. First use case — extract one fat controller method. Create a service that extends the package base class and owns a single business operation:

namespace App\Services;

use Smorken\Service\Service;

class RegisterUser extends Service
{
    public function __construct(
        protected UserRepository $users,
        protected Mailer $mailer,
    ) {}

    public function handle(RegisterData $data): User
    {
        $user = $this->users->create($data->toArray());
        $this->mailer->sendWelcome($user);

        return $user;
    }
}

Then inject it into the controller and keep the controller to validation + delegation. That's the smallest end-to-end slice that proves the package works in your app.

Implementation Patterns

Thin controller, service owns the transaction. The day-to-day win is moving orchestration out of controllers and Eloquent models. Controllers validate and return; services coordinate repositories, events, mail, and jobs.

public function store(RegisterRequest $request, RegisterUser $service)
{
    $user = $service->handle(RegisterData::fromRequest($request));

    return new UserResource($user);
}

Bind interfaces, not concretes. If the base class is designed to be resolved from the container, bind an interface to your implementation in a dedicated App\Providers\ServiceProvider and type-hint the interface everywhere. This is what makes the pattern testable and swappable:

$this->app->bind(RegisterUserContract::class, RegisterUser::class);

Compose services by constructor injection, never app(). A checkout service that calls app(ChargeCard::class) mid-method is untestable and hides dependencies. Inject child services; the container resolves the graph for you.

Group services by domain, not by layer. App\Services\Billing\ChargeCard ages far better than App\Services\ChargeCardService. If the package offers a make:service-style command, point its namespace at the domain folder.

Use it inside queued jobs and commands too. Services are the natural unit for handle() in a job — the job becomes a serialization wrapper, the service holds the logic, and both HTTP and CLI paths share one implementation.

Testing pattern — swap the binding, assert the delegation. For controller tests, bind a fake so you're testing routing/validation, not business logic:

$this->mock(RegisterUserContract::class)
     ->shouldReceive('handle')->once()->andReturn($user);

Then unit-test the service directly with real collaborators and RefreshDatabase.

Config-driven resolution. If the package resolves class names from config (a common "driver" pattern), keep your own map in config/services.php and reference it — it gives you a single place to swap implementations per environment without touching code.

Modular monolith integration. Register each module's service bindings in a module-level provider registered after the package provider, so your overrides win.

Gotchas and Tips

  • Treat it as unvetted code. Zero stars, zero dependents, no release cadence means no community safety net. Pin to a commit ("dev-master#abc1234") or fork it into your VCS. Budget an hour to read the source before it goes anywhere near production.
  • Bitbucket + Composer auth is the #1 setup failure. "Repository not found" almost always means missing credentials, not a wrong URL. Set COMPOSER_AUTH with bitbucket-oauth or use an SSH URL, and mirror that in CI.
  • Singleton state leaks. If the provider registers anything as singleton, that instance survives for the whole request — and across jobs in a queue worker, and across requests under Octane. Any mutable property on a service becomes a cross-request bug. Prefer bind, and resolve fresh inside handle() if the worker is long-lived.
  • Resolution happens at construction. Don't resolve services in a service provider's register() or in a model's constructor — the container and config may not be ready, and you'll fight "target class does not exist" errors during config:cache.
  • config:cache freezes your class-map config. Changing an implementation map in .env does nothing until php artisan config:clear. This bites everyone once.
  • Namespace/autoload mismatch. If composer require succeeds but classes aren't found, check the package's composer.json PSR-4 prefix against the actual folder. Fix with a fork + composer dump-autoload, not by hand-editing vendor/.
  • Laravel version drift. No stars usually means no recent compatibility testing. Check the illuminate/* constraint against your framework version; a package written for Laravel 8 will often still install on 10 and then break on a changed container or provider signature.
  • Debugging resolution: app()->bound(Foo::class), app()->getBindings() dumped in tinker, and php artisan about will tell you whether the provider loaded at all. Add Log::info() in the provider's register() as a first smoke test.
  • Extension point: don't edit vendor/. Override by re-binding in a provider registered later, or publish the config and override values. If neither works, fork — with a package this small, a fork you control is cheaper than a fragile override.
  • Don't over-adopt the pattern. A "service layer" package tends to invite a service class per controller method. Resist it: create a service when the operation has more than one collaborator, a transaction boundary, or a second caller. Otherwise a well-named action or a model method is less ceremony.
Weaver

How can I help you explore Laravel packages today?

Conversation history is not saved when not logged in.
Prompt
Add packages to context
No packages found.
skyraptor/chatter
edlugz/sasapay
abbasudo/artisan-route
mirvan/ippanel
ignibyte/aic-framework
glavweb/data-schema-bundle
glavweb/datagrid-bundle
tacticmedia/scim-server
uni-method/frame
neika-ai/sdk
adachsoft/php-code-style
pharaonic/laravel-notification-dependencies
beautinow/translug
dev-brainiac/bot-cloaked
octoberrain/html
octoberrain/extension
octoberrain/exception
octoberrain/contracts
adachsoft/api-version-bundle
cgv/localized-resource