Skip to main content
Version: CANARY 🚧

Service Container

Introduction​

BowPHP's dependency injection container (DIC), called Capsule, is a powerful tool for managing your application's dependencies.

Dependency Injection (DI)​

Dependency injection consists of providing the objects a class needs rather than having it create them itself. This makes the code more modular and testable.

Inversion of Control (IoC)​

Inversion of control delegates the management of objects and their dependencies to a container. The container creates and manages the objects you need for you.

Accessing the container​

Via the app() helper​

// Get the container instance
$container = app();

// Resolve a class directly
$service = app(App\Services\UserService::class);

Via the Capsule class​

use Bow\Container\Capsule;

$container = Capsule::getInstance();

Registering bindings​

The container offers three primitives, distinguished by their lifecycle:

MethodWhen is the object created?Cached?
bind($key, $value)On the first resolutionYes β€” reused afterwards
instance($key, $obj)Immediately (you pass it in)Yes β€” the provided instance is always returned
factory($key, $value)On every resolutionNo β€” a new instance on each call

Simple binding (bind)​

Registers a service that will be instantiated once, then cached:

use Bow\Container\Capsule;
use App\Services\PaymentService;

app()->bind('payment', function (Capsule $container) {
return new PaymentService();
});

// Bind an interface to an implementation: Bow uses reflection to
// instantiate PaymentService and resolves its constructor dependencies.
app()->bind(PaymentServiceInterface::class, PaymentService::class);

Instance (singleton)​

Registers an already-created instance:

use App\Services\Config;

$config = new Config(['debug' => true]);
app()->instance('config', $config);

Factory​

Creates a new instance on each resolution:

use Bow\Container\Capsule;

app()->factory('uuid', function (Capsule $container) {
return new UuidGenerator();
});

// Each call creates a new instance
$uuid1 = app()->make('uuid');
$uuid2 = app()->make('uuid'); // Different instance

Resolving dependencies​

Simple resolution​

// Via make()
$service = app()->make('payment');

// Via the helper
$service = app('payment');

// Via a class
$service = app(App\Services\PaymentService::class);

Resolution with parameters​

$service = app()->makeWith(App\Services\ReportService::class, [
'startDate' => '2024-01-01',
'endDate' => '2024-12-31'
]);

// Shortcut via the helper: if the 2nd argument is non-empty, app() automatically
// delegates to makeWith().
$service = app(App\Services\ReportService::class, [
'startDate' => '2024-01-01',
'endDate' => '2024-12-31',
]);

Array access​

The container implements ArrayAccess:

// Check for existence
if (isset(app()['payment'])) {
// The service exists
}

// Retrieve a service
$service = app()['payment'];

// Register a service
app()['cache'] = function () {
return new CacheService();
};

// Remove a service
unset(app()['cache']);

Automatic injection​

The container automatically resolves constructor dependencies:

namespace App\Services;

use App\Repositories\UserRepository;
use App\Services\EmailService;

class UserService
{
public function __construct(
private UserRepository $repository,
private EmailService $emailService
) {
}

public function register(array $data): User
{
$user = $this->repository->create($data);
$this->emailService->sendWelcome($user);
return $user;
}
}

When you request UserService, the container:

  1. Detects the constructor dependencies
  2. Resolves UserRepository and EmailService
  3. Instantiates UserService with those dependencies
// The container automatically creates all dependencies
$userService = app(App\Services\UserService::class);

Interface binding​

Bind an interface to its concrete implementation:

// Registration
app()->bind(
App\Contracts\PaymentGatewayInterface::class,
App\Services\StripePaymentGateway::class
);

// Usage in a class
class OrderService
{
public function __construct(
private PaymentGatewayInterface $gateway
) {
}
}

// The container injects StripePaymentGateway
$orderService = app(OrderService::class);

Usage in controllers​

Dependencies are automatically injected into controllers:

namespace App\Http\Controllers;

use App\Services\UserService;
use Bow\Http\Request;

class UserController
{
public function __construct(
private UserService $userService
) {
}

public function index(Request $request)
{
return $this->userService->getAllUsers();
}

// Method injection
public function show(Request $request, UserService $service)
{
return $service->find($request->get('id'));
}
}

Complete example​

Define the interfaces​

namespace App\Contracts;

interface UserRepositoryInterface
{
public function find(int $id): ?User;
public function create(array $data): User;
public function update(int $id, array $data): bool;
public function delete(int $id): bool;
}

interface NotificationServiceInterface
{
public function send(User $user, string $message): void;
}

Implement the interfaces​

namespace App\Repositories;

use App\Contracts\UserRepositoryInterface;
use App\Models\User;

class UserRepository implements UserRepositoryInterface
{
public function find(int $id): ?User
{
return User::retrieve($id);
}

public function create(array $data): User
{
return User::create($data);
}

public function update(int $id, array $data): bool
{
return (bool) User::where('id', $id)->update($data);
}

public function delete(int $id): bool
{
return (bool) User::deleteBy('id', $id);
}
}
namespace App\Services;

use App\Contracts\NotificationServiceInterface;
use App\Models\User;

class EmailNotificationService implements NotificationServiceInterface
{
public function send(User $user, string $message): void
{
// Send an email
mail($user->email, 'Notification', $message);
}
}

Create the service​

namespace App\Services;

use App\Contracts\UserRepositoryInterface;
use App\Contracts\NotificationServiceInterface;

class UserService
{
public function __construct(
private UserRepositoryInterface $repository,
private NotificationServiceInterface $notifier
) {
}

public function register(array $data): User
{
$user = $this->repository->create($data);
$this->notifier->send($user, 'Welcome to our platform!');
return $user;
}

public function getUser(int $id): ?User
{
return $this->repository->find($id);
}
}

Register in a ServiceProvider​

namespace App\Configurations;

use Bow\Configuration\Configuration;
use Bow\Configuration\Loader;
use App\Contracts\UserRepositoryInterface;
use App\Contracts\NotificationServiceInterface;
use App\Repositories\UserRepository;
use App\Services\EmailNotificationService;

class AppServiceProvider extends Configuration
{
public function create(Loader $config): void
{
// Bind the interfaces to their implementations
$this->container->bind(
UserRepositoryInterface::class,
UserRepository::class
);

$this->container->bind(
NotificationServiceInterface::class,
EmailNotificationService::class
);
}

public function run(): void
{
//
}
}

Use in a controller​

namespace App\Http\Controllers;

use App\Services\UserService;
use Bow\Http\Request;

class UserController
{
public function __construct(
private UserService $userService
) {
}

public function show(int $id)
{
$user = $this->userService->getUser($id);

if (!$user) {
return response()->json(['error' => 'User not found'], 404);
}

return response()->json($user);
}

public function store(Request $request)
{
$user = $this->userService->register(
$request->only(['name', 'email', 'password'])
);

return response()->json($user, 201);
}
}

Best practices​

Recommendations
  1. Constructor injection: Clearly declare a class's dependencies
  2. Interfaces over concrete classes: Keep your code flexible and testable
  3. One service, one responsibility: Each service should have a single reason to change
  4. Avoid overusing singletons: Use bind() by default, instance() only when necessary
  5. Register in ServiceProviders: Centralize the container configuration

Container methods​

MethodDescription
bind($key, $value)Registers a service (singleton after first resolution)
factory($key, $value)Registers a factory (new instance every time)
instance($key, $object)Registers an existing instance
make($key)Resolves a service
makeWith($key, $params)Resolves with custom parameters
Benefits

Thanks to these principles, you can build cleaner, more maintainable, and more robust applications.

Is something missing?

If you run into problems with the documentation or have suggestions to improve the documentation or the project in general, please open an issue for us, or send a tweet mentioning the Twitter account @bowframework or directly on github.