Pimple: a frozen PHP DI container that still earns its place
A small PHP dependency injection container
At a glance
- What is it?
- Pimple is a small PHP dependency injection container that is closed to new features but still receives PHP compatibility and security fixes. Here is how its factory, protect and extend mechanisms work, and when a plain array would serve you better.
- Who is it for?
- Adopt Pimple if you want a container you can read in one sitting and you are comfortable with the constraint that it is closed to changes: the README states that only compatibility with newer PHP versions and security issue fixes are accepted. Do not adopt it if you need autowiring, attribute-based configuration, or a container that will gain features; Pimple's README explicitly closes the door on new features and cosmetic changes.
- Can I use it commercially?
- Yes. MIT is a permissive licence: you can use, modify and sell software built on it, as long as you keep its copyright and licence notices.
- Is it still maintained?
- Activity is slowing. The repository last received commits 7 months ago.
- What is it written in?
- Mainly PHP, according to GitHub's language statistics.
Answers come from the project's GitHub data, last synced on September 24, 2026, and from our analysis. They are not legal advice.
Editorial analysis
What Pimple is for, and who ends up using it
Pimple is a dependency injection container for PHP. The problem it addresses is wiring: a session object needs a session storage object, a mailer needs configuration values, and constructing that graph by hand in every entry point gets repetitive. Pimple lets you declare each service as an anonymous function and defer construction until the moment something asks for it.
The README describes two kinds of data the container manages: services and parameters. A service is an object that does something as part of a larger system, and the README gives a database connection, a templating engine, or a mailer as examples. A parameter is a plain value, such as a cookie name or a class name, that you want to override from outside the container without rewriting a service definition.
The audience is narrow in a useful way. If you are building a framework, a library that needs to ship its own service definitions, or a small application where you want explicit wiring without a configuration format, Pimple fits. It is not aimed at teams who want the container to discover dependencies by reading type hints. There is no autowiring here. Every service is a closure you write by hand, and the README's own example shows the pattern: a session service closure receives the container and pulls the session storage service out of it.
How lazy closures, factory and extend shape the container
The mechanism is a closure stored under a string key. When you write `$container['session'] = fn($c) => new Session($c['session_storage']);`, Pimple stores the closure rather than the object. The object is created the first time you read `$container['session']`, and the README notes that because objects are only created when you get them, the order of the definitions does not matter. That property is what makes a container file readable: you can define a service before the service it depends on.
By default the same instance comes back on every read. That is a shared service, and it is the behaviour most people expect from a container. If you want a fresh instance each time, you wrap the closure with `factory()`. The README's example is `$container['session'] = $container->factory(fn($c) => new Session($c['session_storage']));`, after which each call returns a new session object. This distinction matters for stateful objects: a shared service that accumulates state across a request will surprise you, and a factory service that you expected to be shared will silently discard work.
The `extend()` method runs additional code on a service just after it is created. The README shows `$container->extend('session_storage', function ($storage, $c) { ... return $storage; });`, where the first argument is the service name and the second receives the object instance and the container. This is the hook for third-party packages that want to decorate a service without replacing its definition. The trade-off is that extend chains are invisible at the definition site: reading the closure tells you nothing about the decorators applied later, so a container with several providers can be harder to trace than the line count suggests.
One sharp edge is worth stating plainly. Because Pimple treats anonymous functions as service definitions, storing a closure as a parameter requires `protect()`. The README's example is `$container['random_func'] = $container->protect(fn() => rand());`. Without it, the container would call the function and store the result. Forgetting `protect()` is a common source of confusion, and the API gives you no warning.
Installing Pimple and defining your first services
The README gives a single installation command, run through the Composer phar. It adds `pimple/pimple` at `^3.0` to your `composer.json`:
$ ./composer.phar require pimple/pimple "^3.0"After that, creating a container is one line. The README's example imports the class and instantiates it:
use Pimple\Container;
$container = new Container();The next step is a real use: define a parameter, then a service that reads it. The README's session storage example, rewritten with a factory-style closure, looks like this:
$container['cookie_name'] = 'SESSION_ID';
$container['session_storage_class'] = 'SessionStorage';
$container['session_storage'] = fn($c) => new $c['session_storage_class']($c['cookie_name']);Reading `$container['session_storage']` constructs the object and caches it. If you later change the cookie name by overriding the `cookie_name` parameter, the README states that you change the configuration from outside instead of redefining the service. That is the whole point of separating parameters from services, and it is the pattern to copy when you want a container file that other environments can adjust.
If you want the raw closure rather than the constructed object, the README documents `raw()`: `$sessionFunction = $container->raw('session');`. That is useful when a provider needs to wrap or replace a definition rather than consume it.
PSR-11 support is a wrapper, not the container itself
For historical reasons, the README states that the `Container` class does not implement the PSR-11 `ContainerInterface`. Pimple ships a helper instead. `Pimple\Psr11\Container` wraps an underlying Pimple container and exposes it through the PSR-11 methods, so code that type-hints the interface can accept it.
The README's example makes the indirection clear:
use Pimple\Container;
use Pimple\Psr11\Container as PsrContainer;
$container = new Container();
$container['service'] = fn($c) => new Service();
$psr11 = new PsrContainer($container);
$controller = function (PsrContainer $container) {
$service = $container->get('service');
};
$controller($psr11);Note the access difference: Pimple uses array syntax, PSR-11 uses `get()`. If your codebase is written against the interface, you pay one wrapper object and lose the array access shorthand inside that code path. The benefit is that your controllers stop naming the Pimple class directly, which is exactly the decoupling the README describes.
There is a second helper, `ServiceLocator`, aimed at a different problem. When a service needs several other services but will not necessarily use all of them, injecting the whole container gives it too broad an access to the rest of the application and hides its actual dependencies, as the README puts it. The `ServiceLocator` exposes a predefined set of services and instantiates them only when needed. It also lets you publish a service under a different name than the one it was registered with, which is useful when an object expects a specific interface name.
Where Pimple stops being the right tool
The most consequential limitation is stated at the top of the README: Pimple is closed for changes. No new features will be added, and no cosmetic changes will be accepted either. The only accepted changes are compatibility with newer PHP versions and security issue fixes. That is a deliberate end state, not neglect, but it means you should not pick Pimple expecting it to grow into something else. The last push to the repository was on 2026-03-02, so the project is not abandoned, but the scope of what will ever land is fixed by the README's own words.
The second limitation is the absence of autowiring. Pimple has no mechanism to inspect constructor type hints and resolve them for you. Every dependency edge is a closure you write, and every service name is a string. In a codebase with dozens of services, that is a lot of manual wiring, and a typo in a service name surfaces at the point of access rather than at build time. The repository layout shows `src/` with no compiled or generated container artifact, and the README documents no code generation step, so there is nothing that would catch those strings earlier.
A third case: if you only need to pass three or four collaborators to one class, a container is overhead. Constructor injection from your entry point is simpler and leaves no framework to learn. Pimple earns its place when the same definitions need to be reused across entry points, overridden per environment, or shipped as a provider by a library. Below that threshold, the array access syntax is doing more work than the problem requires.
Service providers and how Pimple compares to a full framework container
The extension mechanism is `Pimple\ServiceProviderInterface`. A provider is a class implementing `register(Container $pimple)` that registers services and parameters on the container it receives. The README's example is a `FooProvider` class, registered with `$pimple->register(new FooProvider());`. This is how a library ships its wiring: the application registers the provider, and the provider owns the keys it defines. The cost is key collisions. Two providers that both define `session` will overwrite each other, and Pimple offers no namespacing convention in the README. Prefixing keys is left to you.
A natural alternative is a full framework container such as the one bundled with Symfony. The difference in approach is scope. Symfony's container is compiled: definitions are dumped into generated PHP classes ahead of time, which is where its performance comes from, and it supports autowiring, tagged services, and attribute-based configuration. Pimple does none of that. It resolves closures at runtime and keeps the container as a plain object. The README itself points at this history: it notes that recent versions of Pimple are more focused on performance than the 1.x line, and that reading the 1.x code is a good way to learn how to create a simple container.
If you are already inside a framework, use that framework's container. Adopting Pimple alongside it means two registries and two ways to resolve a service. Pimple's value is highest in the space the framework containers do not cover: a standalone library that wants to offer a container-agnostic provider, or a small application that deliberately avoids a framework. For that second case, the provider interface is the interoperability story, and it is a small one on purpose.
Licence, maintenance and upgrade cost
Pimple is released under the MIT licence. That is a permissive licence, and the repository includes a `LICENSE` file at the top level alongside `composer.json`, `phpunit.xml.dist`, and `src/`. MIT generally allows use in closed-source products provided the copyright notice and permission notice are retained, but the exact obligations depend on how you distribute the software, so read the file rather than a summary. Nothing here is legal advice.
On maintenance, the picture is stable rather than active. The repository is not archived, and the last push was on 2026-03-02. The README commits to PHP compatibility and security fixes, which is the maintenance you actually need from a container this small. What you will not get is new API surface. The top-level entries show a `CHANGELOG`, so version history is tracked, but no recent releases were retrieved, which means you should check the CHANGELOG and Packagist directly before pinning a version.
The upgrade cost is low by construction. The public surface is a `Container` class with array access, a handful of methods (`factory`, `protect`, `extend`, `raw`, `register`), and the PSR-11 wrapper. There is no configuration file format to migrate and no compiled cache to invalidate. The realistic upgrade risk is PHP version drift, which is precisely the category the README says will continue to receive changes. If you are on an older PHP release, verify the constraint in `composer.json` before you build on it.
Editorial conclusion
Adopt Pimple if you want a container you can read in one sitting and you are comfortable with the constraint that it is closed to changes: the README states that only compatibility with newer PHP versions and security issue fixes are accepted. Do not adopt it if you need autowiring, attribute-based configuration, or a container that will gain features; Pimple's README explicitly closes the door on new features and cosmetic changes. Before committing, check the CHANGELOG against your PHP version and confirm whether you need PSR-11 directly, since the Container class does not implement ContainerInterface and you must wrap it in Pimple\Psr11\Container.
Frequently asked questions
How do I install Pimple in a PHP project?
Add it with Composer using the command the README gives: `./composer.phar require pimple/pimple "^3.0"`. That writes the dependency into your `composer.json` and installs the package.
Is Pimple still maintained?
The repository is not archived and its last push was on 2026-03-02, but the README states that Pimple is closed for changes and that only compatibility with newer PHP versions and security issue fixes will be accepted.
Does Pimple implement PSR-11?
Not directly. The README says the `Container` class does not implement the PSR-11 `ContainerInterface` for historical reasons, but the `Pimple\Psr11\Container` class wraps an underlying Pimple container and exposes it through the interface's methods.
How do I store a closure as a parameter in Pimple?
Wrap it with the `protect()` method. Because Pimple treats anonymous functions as service definitions, the README shows `$container['random_func'] = $container->protect(fn() => rand());` to store the function itself rather than its result.
Official sources
Add this badge to your README
If you maintain this project, the badge below links readers to this analysis and shows its maintenance status from the daily GitHub snapshot. Paste the markdown into your README; add ?metric=license or ?metric=stars to the image URL for a different field.
[](https://hysenlabs.com/projects/silexphp-pimple)