How to use callbacks in services

dsar
Backer | 53
+
0
-

Hi guys,

I would like to have a sort of persistent/global variables in Latte (such as globals in twig for Symfony).

I used this solution (adapted to work with modern Nette) but I don't like it, it smells really bad :-)

In my opinion the right place is to use onCreate() of the templateFactory, but I'm not able to set the right syntax to make it work (unfortunately the documentation and unit testing files don't help much about it).

parameters:
        globals:
                testkey: testval

services:
[...]
        latte.templateFactory:
                setup:
                        - '@self::onCreate[] = function(){$template->setParameters(%globals%);}'

This (of course) doesn't work.
The problem is twofold: %globals% doesn't work in that kind of string, also passing an array directly doesn't work because of syntax error (Expected function, method or property name, 'onCreate[] […] given).

Any idea?

Thank you

Marek Bartoš
Nette Blogger | 1146
+
0
-

I would use CompilerExtension, programming in neon is not much flexible and is error-prone. Your neon example would fail in case of $templateFactory->createTemplate($control, NotADefaultTemplate::class);

namespace Example;

use Nette\Bridges\ApplicationLatte\DefaultTemplate;
use Nette\Bridges\ApplicationLatte\Template;
use Nette\Bridges\ApplicationLatte\TemplateFactory;
use Nette\DI\CompilerExtension;
use Nette\DI\Definitions\ServiceDefinition;

final class ExampleExtension extends CompilerExtension
{

	public function beforeCompile(): void
	{
		parent::beforeCompile();

		$builder = $this->getContainerBuilder();

		$templateFactoryDefinition = $builder->getDefinitionByType(TemplateFactory::class);
		assert($templateFactoryDefinition instanceof ServiceDefinition);
		$templateFactoryDefinition->addSetup(
			[self::class, 'doStuff'],
			[$templateFactoryDefinition, $builder->parameters['globals'] ?? []]
		);
	}

	/**
	 * @param array<mixed> $globals
	 */
	public static function doStuff(TemplateFactory $templateFactory, array $globals): void
	{
		$templateFactory->onCreate[] = static function (Template $template) use ($globals): void {
			if ($template instanceof DefaultTemplate) {
				$template->setParameters($globals);
			} else {
				foreach ($globals as $key => $value) {
					if (property_exists($template, $key)) {
						$template->$key = $value;
					}
				}
			}
		};
	}

}
dsar
Backer | 53
+
0
-

Thank you, it works :-)

However with getDefinitionByType(TemplateFactory::class) gives Service of type ‘Nette\Bridges\ApplicationLatte\TemplateFactory’ not found but it does work with getDefinition(‘latte.templateFactory’).

Furthermore, $builder->parameters seems to be empty in this stage, but at this point for this case I prefer to use a separate configuration.

Here the working solution:

extensions:
	latteGlobals: App\Extensions\LatteGlobalsExtension

latteGlobals:
	testkey: testval
<?php

declare(strict_types=1);

namespace App\Extensions;

use Nette\Bridges\ApplicationLatte\DefaultTemplate;
use Nette\Bridges\ApplicationLatte\Template;
use Nette\Bridges\ApplicationLatte\TemplateFactory;
use Nette\DI\CompilerExtension;
use Nette\DI\Definitions\ServiceDefinition;

final class LatteGlobalsExtension extends CompilerExtension
{
        public function beforeCompile(): void
        {
                parent::beforeCompile();

                $builder = $this->getContainerBuilder();

                //$templateFactoryDefinition = $builder->getDefinitionByType(TemplateFactory::class);
                $templateFactoryDefinition = $builder->getDefinition('latte.templateFactory');
                assert($templateFactoryDefinition instanceof ServiceDefinition);
                $templateFactoryDefinition->addSetup(
                        [self::class, 'setGlobals'],
                        [$templateFactoryDefinition, $this->config ?? []]
                );
        }


		public static function setGlobals(TemplateFactory $templateFactory, array $globals): void
        {
                $templateFactory->onCreate[] = static function (Template $template) use ($globals): void {
                        if ($template instanceof DefaultTemplate) {
                                $template->setParameters($globals);
                        } else {
                                foreach ($globals as $key => $value) {
                                        if (property_exists($template, $key)) {
                                                $template->$key = $value;
                                        }
                                }
                        }
                };
        }
}

Last edited by dsar (2021-02-21 14:18)

dsar
Backer | 53
+
0
-

Fetching by type only works if I force the type as below:

services:
	latte.templateFactory:
		class: Nette\Bridges\ApplicationLatte\TemplateFactory

without that, it gives:

Service of type 'Nette\Bridges\ApplicationLatte\TemplateFactory' not found.

is that normal?

Last edited by dsar (2021-02-16 16:27)

Marek Bartoš
Nette Blogger | 1146
+
0
-

Created PR fixing this issue. And yes, it's normal – service is autowired only by it's type (which is often deduced from factory, but in this case is explicitly set to Nette\Application\UI\TemplateFactory)

https://github.com/…ion/pull/283