Pimple PHP: Complete Guide with Examples for DI Container

Online Python Trainer for Beginners

Learn Python easily without overwhelming theory. Solve practical tasks with automatic checking, get hints in Russian, and write code directly in your browser — no installation required.

Start Course

Pimple PHP: Complete Guide to a Simple DI Container

1. What is Pimple and Why Use It?

Pimple is a lightweight Dependency Injection Container for PHP, created by Fabien Potencier, the founder of the Symfony Framework. Unlike bulky DI containers, Pimple offers a minimalistic solution that you can integrate into any project without unnecessary overhead. This library implements the Service Locator pattern and provides a simple API for managing objects and their dependencies.

Pimple's main goal is to simplify dependency management in PHP applications. Instead of manually creating objects with new and passing them into constructors, you register services in the container and retrieve them by name. The container automatically resolves dependencies, creates objects once (singleton) or fresh each time (factory), and manages their lifecycle. This makes your code more modular, testable, and maintainable.

Pimple is ideal for small to medium-sized projects where you don't need the full power of Symfony DI or PHP-DI. It doesn't use annotations, requires no complex configuration, and works with PHP 5.3 and above. The library is actively used in Silex (micro-framework) and other PHP projects where simplicity and speed matter.

2. Installation

Install Pimple via Composer, the standard package manager for PHP. Run this in your terminal:

composer require pimple/pimple

After installation, include the Composer autoloader in your PHP file:

require_once __DIR__ . '/vendor/autoload.php';

If you don't have a composer.json yet, create one:

{
    "require": {
        "pimple/pimple": "^3.5"
    }
}

3. Quick Start — Minimal Working Example

Create a file called index.php and add the following code:

<?php
require_once __DIR__ . '/vendor/autoload.php';

use Pimple\Container;

// Create the container
$container = new Container();

// Define a simple service (closure)
$container['greeting'] = function ($c) {
    return 'Hello, World!';
};

// Define a service with a parameter
$container['personal_greeting'] = function ($c) {
    return function ($name) use ($c) {
        return 'Hello, ' . $name . '!';
    };
};

// Retrieve services
echo $container['greeting'] . "\n"; // Hello, World!
echo $container['personal_greeting']('Alice') . "\n"; // Hello, Alice!

// Define an object service
class Logger {
    public function log($message) {
        echo "[LOG] $message\n";
    }
}

$container['logger'] = function ($c) {
    return new Logger();
};

$container['logger']->log('Application started');

Run the script: php index.php. You'll see three messages printed. This example demonstrates basic service registration and retrieval from the container.

4. Key Methods and Classes

4.1. Pimple\Container::__construct()

Signature: public function __construct(array $values = [])

Description: Creates a new container instance. You can pass an array of predefined values/services.

$container = new Container([
    'db.host' => 'localhost',
    'db.name' => 'test'
]);

4.2. offsetSet($id, $value) (magic method via [])

Signature: public function offsetSet($id, $value): void

Description: Registers a service or value in the container. The $id is the service name, and $value can be a closure (for lazy loading) or any PHP value.

// Register a closure (service definition)
$container['mailer'] = function ($c) {
    return new Mailer($c['config']['smtp_host']);
};

// Register a plain value
$container['app.name'] = 'My Awesome App';

Important: When you assign a closure to a container key, Pimple wraps it in a Pimple\Service object. The closure is only executed when you first access the service. Subsequent accesses return the same instance (singleton behavior by default).

4.3. offsetGet($id) (magic method via [])

Signature: public function offsetGet($id): mixed

Description: Retrieves a registered service or value by its identifier. If the service is a closure, it gets executed and its return value is cached for future calls.

$mailer = $container['mailer']; // Executes the closure, returns Mailer instance
$appName = $container['app.name']; // Returns 'My Awesome App'

4.4. offsetExists($id) (magic method via isset())

Signature: public function offsetExists($id): bool

Description: Checks whether a service or value with the given ID is registered in the container.

if (isset($container['mailer'])) {
    // Service exists
}

4.5. offsetUnset($id) (magic method via unset())

Signature: public function offsetUnset($id): void

Description: Removes a registered service or value from the container.

unset($container['mailer']); // Removes the mailer service

4.6. factory($callable)

Signature: public function factory($callable): \Closure

Description: Wraps a closure so that it returns a new instance every time the service is accessed, instead of the default singleton behavior.

$container['new_logger'] = $container->factory(function ($c) {
    return new Logger();
});

$logger1 = $container['new_logger']; // New instance
$logger2 = $container['new_logger']; // Another new instance
// $logger1 !== $logger2

4.7. protect($callable)

Signature: public function protect($callable): \Closure

Description: Stores a closure as a plain value without executing it. Use this when you want to store a callback function in the container.

$container['my_callback'] = $container->protect(function ($arg) {
    return strtoupper($arg);
});

// The closure is returned as-is, not executed
$callback = $container['my_callback'];
echo $callback('hello'); // HELLO

4.8. raw($id)

Signature: public function raw($id): mixed

Description: Returns the raw closure or value stored for a service, without executing it. Useful for extending or inspecting service definitions.

$rawDefinition = $container->raw('mailer');
// $rawDefinition is the closure, not the Mailer instance

4.9. extend($id, $callable)

Signature: public function extend($id, $callable): void

Description: Extends an existing service definition. The $callable receives the original service instance and the container, and must return the modified/ extended service.

// Original service
$container['mailer'] = function ($c) {
    return new Mailer();
};

// Extend it with logging
$container->extend('mailer', function ($mailer, $c) {
    $mailer->setLogger($c['logger']);
    return $mailer;
});

4.10. keys()

Signature: public function keys(): array

Description: Returns an array of all registered service IDs in the container.

$allKeys = $container->keys();
// ['mailer', 'logger', 'app.name', ...]

5. Practical Examples

5.1. Database Connection with Configuration

$container['config'] = [
    'db.host' => 'localhost',
    'db.name' => 'myapp',
    'db.user' => 'root',
    'db.pass' => 'secret'
];

$container['db'] = function ($c) {
    $config = $c['config'];
    return new PDO(
        "mysql:host={$config['db.host']};dbname={$config['db.name']}",
        $config['db.user'],
        $config['db.pass']
    );
};

// Usage
$db = $container['db'];
$stmt = $db->query('SELECT * FROM users');

5.3. Service Depending on Another Service

$container['user_repository'] = function ($c) {
    return new UserRepository($c['db']);
};

$container['user_service'] = function ($c) {
    return new UserService($c['user_repository']);
};

// Usage
$userService = $container['user_service'];
$users = $userService->getAllUsers();

5.4. Using Factory for Non-Singleton Services

$container['request'] = $container->factory(function ($c) {
    return new Request();
});

// Each call creates a new Request object
$request1 = $container['request'];
$request2 = $container['request'];
// $request1 !== $request2

6. Best Practices and Tips

  • Keep services lazy: Always define services as closures so they're only created when needed.
  • Use meaningful names: Name your services clearly (e.g., db, mailer, logger).
  • Don't overuse the container: Avoid passing the entire container to your classes. Inject only the specific services they need.
  • Leverage extend(): Use it to modify services without changing their original definitions — great for plugins or modules.
  • Use factory() for stateful services: If a service should not be shared (e.g., request objects), wrap it with factory().
  • Keep configuration separate: Store config values in a dedicated array or config file, not scattered across service definitions.

7. Conclusion

Pimple is a powerful yet simple DI container that brings order to dependency management in PHP projects. Its minimal API — just a handful of methods — makes it easy to learn and integrate. Whether you're building a small script or a medium-sized application, Pimple helps you write cleaner, more testable code.

Start using Pimple today to simplify your PHP development. Install it via Composer, define your services as closures, and let the container handle the rest. For more advanced needs, consider Symfony DI or PHP-DI, but for most projects, Pimple is all you need.

Recommendations