CompilerExtension and easier way to add code to the initialize method

David Grudl
Nette Core | 8218
+
+8
-

I am thinking (in fact it is implemented in nette/di @dev) about this feature. I think code will be enough instead of words:

Before:

final class ConstantsExtension extends Nette\DI\CompilerExtension
{
	public function afterCompile(Nette\PhpGenerator\ClassType $class)
	{
		$initialize = $class->getMethod('initialize');
		foreach ($this->getConfig() as $name => $value) {
			$initialize->addBody('define(?, ?);', [$name, $value]);
		}
	}
}

After:

final class ConstantsExtension extends Nette\DI\CompilerExtension
{
	public function loadConfiguration()
	{
		foreach ($this->getConfig() as $name => $value) {
			$this->initialization->addBody('define(?, ?);', [$name, $value]);
			// can be used everywhere, even in loadConfiguration()
		}
	}
}

Advantages:

  • simple syntax
  • can be used everywhere (in loadConfiguration(), …)
  • generates the initialize() method in more safe and readable form:

public function initialize()
{
	// di
	(function () {
		$this->getService('tracy.bar')->addPanel(new Nette\Bridges\DITracy\ContainerPanel($this));
	})();

	// http
	(function () {
		$response = $this->getService('http.response');
		$response->setHeader('X-Powered-By', 'Nette Framework 3');
		$response->setHeader('Content-Type', 'text/html; charset=utf-8');
		$response->setHeader('X-Frame-Options', 'SAMEORIGIN');
		$response->setCookie('nette-samesite', '1', 0, '/', null, null, true, 'Strict');
	})();

	// session
	(function () {
		$this->getService('session.session')->exists() && $this->getService('session.session')->start();
	})();

	// tracy
	(function () {
		Tracy\Debugger::getLogger()->mailer = [new Tracy\Bridges\Nette\MailSender($this->getService('mail.mailer')), 'send'];
		$this->getService('session.session')->start();
		Tracy\Debugger::dispatch();
	})();
}

David Grudl
Nette Core | 8218
+
+1
-

An extension can be made this way to work backwards compatible:

final class ConstantsExtension extends Nette\DI\CompilerExtension
{
	public function afterCompile(Nette\PhpGenerator\ClassType $class)
	{
		$initialize = $this->initialization ?? $class->getMethod('initialize');
		foreach ($this->getConfig() as $name => $value) {
			$initialize->addBody('define(?, ?);', [$name, $value]);
		}
	}
}
Toanir
Member | 57
+
0
-

Hi,

I like how neat usage gets this way, that's a definitive ++. The generated code looks a bit funny to me because of those IIFEs. Since those are already function calls, would it make sense to create actual functions for each module? The modules are named so we could have something like

public function initialize()
{
	$this->initializeDi();
	$this->initializeHttp();
	...
}

private function initializeDi() {
	$this->getService('tracy.bar')->addPanel(new Nette\Bridges\DITracy\ContainerPanel($this));
};

private function initializeHttp() {
	$response = $this->getService('http.response');
	$response->setHeader('X-Powered-By', 'Nette Framework 3');
	$response->setHeader('Content-Type', 'text/html; charset=utf-8');
	$response->setHeader('X-Frame-Options', 'SAMEORIGIN');
	$response->setCookie('nette-samesite', '1', 0, '/', null, null, true, 'Strict');
};
...

As far as my understanding of PHP goes, these should be equivalent, and they are only a bit easier to the human eye, should one have the need to debug output of their extension.

David Grudl
Nette Core | 8218
+
0
-

@Toanir Yes, it can be done like this.