Open-source project
nette/di avatar
nette/di

nette/di: a dependency injection container that compiles to PHP code

💎 Flexible, compiled and full-featured Dependency Injection Container with perfectly usable autowiring and support for all new PHP 8 features.

923 stars73 forksPHPNOASSERTION

At a glance

What is it?
Nette DI takes a NEON configuration file and generates an ordinary PHP class that assembles your services, so the container costs nothing at run time and can be read like any other class. The price is a build step, and a licence file that GitHub cannot classify.
Who is it for?
nette/di suits PHP teams that want constructor injection with autowiring and want the container to be generated code rather than a runtime reflection engine, especially where the container is a build artefact that can be committed and reviewed. It does not suit a project that cannot accept a compilation step, or one whose service wiring changes per environment in ways that are easier to express at run time.
Can I use it commercially?
Check first. The repository uses a licence we do not classify automatically, so read its LICENSE file before any commercial use.
Is it still maintained?
Yes. The repository last received commits 2 days 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 November 7, 2026, and from our analysis. They are not legal advice.

From daily snapshots

Star history

  • nette/di923 ★ · -1
Star history
Repository2026-10-212026-11-07
nette/di924923

-1 Stars in the last 17 days, measured from the daily GitHub snapshot. Compare with other projects

Editorial analysis

Classes stop hunting for their own dependencies

The purpose of dependency injection, as the README puts it, is to free classes from the responsibility of obtaining objects that they need for their operation, and to pass them those services on instantiation instead. Objects that a class needs are called services.

The example makes the point concretely. There is a Mail value object, a Mailer interface with a send method, a Logger interface with a log method, and a NewsletterManager that takes a Mailer and a Logger through its constructor. The README states the rule plainly: the code respects Dependency Injection, meaning each object uses only variables that were passed into it.

That is what makes the container replaceable. Because no class depends on the container, the handwritten one in the README can be swapped for the one Nette DI generates, and neither the Mailer nor the Logger nor the manager has to change.

The repository description calls this a flexible, compiled and full-featured dependency injection container with autowiring and support for new PHP 8 features, and the README calls it one of the most interesting parts of the Nette framework, extremely fast and easy to configure. The people who wrote it are, not surprisingly, the same people, and the licence file at the root of the tree is the reason GitHub cannot classify it.

The handwritten container the generator replaces

Before looking at the generator, it helps to see the code it produces, because the README shows the handwritten version in full. The container holds nullable properties for the logger and the mailer, and each getter creates the service only if the property is not already set:

php
class Container
{
	private ?Logger $logger;
	private ?Mailer $mailer;

	public function getLogger(): Logger
	{
		if (!isset($this->logger)) {
			$this->logger = new FileLogger('log.txt');
		}
		return $this->logger;
	}

The README gives two reasons for this shape. The individual services are created only on demand, which is lazy loading, and a doubly called createNewsletterManager uses the same logger and mailer instances. The third reason is implicit in the code: the container is the only place that knows how to build anything, so swapping an implementation is a one line change here rather than a change spread across the codebase.

The instantiation then reads as ordinary use:

php
$container = new Container;
$manager = $container->createNewsletterManager();
$manager->distribute(...);

The alternative implementations, SendMailMailer using the mail function and FileLogger writing to a file with file_put_contents, show the other half of the design. Because the manager depends on interfaces rather than concrete classes, the decision of which implementation to use lives entirely in the container.

The container is generated PHP, not a runtime engine

Nette DI is the generator of containers. You instruct it, usually with configuration files, and that configuration leads to it generating nearly the same class as the handwritten Container above. The configuration for that example is three lines:

neon
services:
	- FileLogger( log.txt )
	- SendMailMailer
	- NewsletterManager

The README calls the shortness of the configuration the big advantage, and then explains why the generated code matters. Nette DI actually generates PHP code of the container, therefore it is extremely fast, and the developer can see the code, so he knows exactly what it is doing, and he can even trace it.

That last point is the one that matters most for adoption. A container that resolves services through reflection at run time is opaque: when something is null or wired to the wrong implementation, you read documentation and guess. A compiled container is a file of PHP in your cache directory that you can open, read, step through in a debugger, and commit to version control if you want the wiring reviewed alongside the code.

The trade-off is the build step. The container has to be generated before it can be used, and something has to detect that the configuration or the classes changed since the last generation.

Loading the container and the autoRebuild switch

Usage is described as very easy. You save the configuration to a file named config.neon and create the container with a ContainerLoader:

php
$loader = new Nette\DI\ContainerLoader(__DIR__ . '/temp');
$class = $loader->load(function($compiler) {
    $compiler->loadConfig(__DIR__ . '/config.neon');
});
$container = new $class;

Then you use the container to fetch the object by its type:

php
$manager = $container->getByType(NewsletterManager::class);
$manager->distribute(['[email protected]', ...]);

Two mechanisms are worth understanding. The container is generated only once and the code is stored in a cache directory, here __DIR__ . '/temp'. Because of that, the loading of the configuration file is placed inside the closure passed to $loader->load(), so it is called only once.

The second mechanism is for development. During development it is useful to activate auto-refresh mode, which automatically regenerates the container when any class or configuration file is changed. You enable it in the constructor of ContainerLoader by appending true as the second argument:

php
$loader = new Nette\DI\ContainerLoader(__DIR__ . '/temp', autoRebuild: true);

The named argument in that snippet is worth noting as a small marker of the project's PHP level. The README states that nette/di requires PHP version 8.2 and supports PHP up to 8.5, so named arguments and readonly style constructor promotion are both available to you in your own code as well.

Services, setup and the two ways to inject

Services are registered in the DI container and their dependencies are automatically passed. A named service is declared like this:

neon
services:
	manager: NewsletterManager

All dependencies declared in the constructor of this service are automatically passed, and the README states that constructor passing is the preferred way of dependency injection for services.

The alternative is setter injection, which requires the setup section of the service definition:

neon
services:
	manager:
		factory: NewsletterManager
		setup:
			- setAnotherService

The setup entry names a method to call with the resolved service, so setAnotherService receives the instance of AnotherService. That is the shape of a setter injection, and the README is not enthusiastic about it: constructor passing is preferred.

The repository description mentions autowiring, and the mechanism is implied by the first example. NewsletterManager is declared without arguments even though its constructor takes a Mailer and a Logger, and the container resolves both. The FileLogger entry is different: it is written as FileLogger( log.txt ), with the constructor argument supplied explicitly, because a string argument cannot be inferred from a type. That distinction, an autowired service versus a service with an explicit scalar argument, is the whole autowiring contract in one line of configuration.

The repository layout backs this up. There is src/ for the implementation, tests/ for the test suite, docs/ for the documentation sources, a phpstan.neon for static analysis, a .phpstorm.meta.php file for IDE support, and an AGENTS.md. The README badges point at a GitHub Actions test workflow and a Coveralls coverage status, so the library is covered by automated tests.

Licensing that GitHub cannot classify

The licence field on the repository says the project is under a custom licence that GitHub cannot classify, and the LICENSE file is at the root of the tree, named license.md. The README badge, however, reads New BSD and links to that same license.md file.

So the substance appears to be the New BSD licence, while GitHub's automated detection fails on the file, probably because of how it is formatted or which clauses it selects. That mismatch is worth resolving before you rely on it, because automated licence scanners reading the repository metadata will report something different from what the badge and the file say.

For a library like this, the practical obligation under the BSD family is small: retain the copyright notice, the conditions and the disclaimer in redistributions. There is no copyleft requirement reaching into your application code.

On maintenance, the repository is not archived and the last push was on 2026-10-14. The release record shows v3.2.7 on 2026-08-27, v3.2.6 on 2026-05-06 and v3.2.5 on 2025-10-30, which is a patch-level cadence on a stable 3.2 line. The PHP range also moves in step, and since the library tracks the PHP versions it supports, adopting it means checking that the PHP version your production hosts run is inside the 8.2 to 8.5 window.

Editorial conclusion

nette/di suits PHP teams that want constructor injection with autowiring and want the container to be generated code rather than a runtime reflection engine, especially where the container is a build artefact that can be committed and reviewed. It does not suit a project that cannot accept a compilation step, or one whose service wiring changes per environment in ways that are easier to express at run time. Before adopting, install it with composer require nette/di, write one service in a NEON file, load the container through Nette\DI\ContainerLoader and read the generated class in the cache directory, because seeing the generated PHP is the fastest way to understand exactly what the container will do.

Frequently asked questions

How do I install nette/di?

The recommended way is via Composer with composer require nette/di. The README states it requires PHP version 8.2 and supports PHP up to 8.5.

How is a Nette DI container generated and loaded?

You keep the configuration in a NEON file, then instantiate Nette\DI\ContainerLoader with a cache directory and call load() with a closure that calls loadConfig on your file. The loader returns a generated class name, and you instantiate that class. The container is generated only once and the code is stored in the cache directory.

What does autoRebuild do in nette/di?

Passing true as the second argument to the ContainerLoader constructor, as in new Nette\DI\ContainerLoader(__DIR__ . '/temp', autoRebuild: true), activates auto-refresh mode, which automatically regenerates the container when any class or configuration file is changed.

How are services declared and injected in nette/di?

Services are registered in the container and their dependencies are automatically passed. A service is written as services: manager: NewsletterManager, and all dependencies declared in its constructor are passed automatically. Setter injection is expressed with a setup section listing a setter method.

What licence does nette/di use?

The licence field on the repository reports a custom licence that GitHub cannot classify, and the file at the root is license.md, while the README badge reads New BSD. Check that file directly before relying on the licence, since automated scanners will report something different from the badge.

Official sources

  1. Issues
  2. nette/di on GitHub
  3. Project website
  4. README
  5. Releases
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.

Add this badge to your README

markdown
[![Hysen Labs](https://hysenlabs.com/badge/nette-di.svg)](https://hysenlabs.com/projects/nette-di)

Star history chart for your README

markdown
[![Star history](https://hysenlabs.com/chart/nette-di.svg)](https://hysenlabs.com/projects/nette-di)

Was this useful?