SearchExtension – použití v jiné CompilerExtension

Gappa
Nette Blogger | 208
+
0
-

Ahoj,

o SearchExtension vím a funguje skvěle.

Jde ale tomuto rozšíření předat po zaregistrování nějaké jiné extension další místa (a nastavení), kde hledat služby?

Felix
Nette Core | 1245
+
+2
-

Ahoj. Vzdycky si danou extension muzes nainstancovat sam a ovladat ji dle potreby.

<?php

use Nette\DI\CompilerExtension;
use Nette\DI\Extensions\SearchExtension;

class MyExtension extends CompilerExtension
{

	public function loadConfiguration()
	{
		$config = [...];

		$ext = new SearchExtension($this->tempDir);
		$ext->setCompiler($this->compiler, $this->prefix('innersearch'));
		$ext->setConfig($config);

		$ext->loadConfiguration();
		$ext->beforeCompile();

		$ext->findClasses($config);
	}

}
Gappa
Nette Blogger | 208
+
0
-

Díky za nakopnutí, já se původně snažil buď nějak „hijacknout“ globální $config, kam bych doplnil ono nastavení/sekce (nereagovalo), nebo nějak získal tuto službu (nějak se skrývá, nejde vzít z kontejneru).

Gappa
Nette Blogger | 208
+
0
-

Funkční prototyp:

<?php
declare(strict_types=1);

use Nette\DI\Extensions\SearchExtension;
use Nette\DI\InvalidConfigurationException;
use Nette\Schema\Processor;
use Nette\Schema\Schema;
use Nette\Schema\ValidationException;

final class Extension extends CompilerExtension
{

	public function loadConfiguration(): void
	{
		$builder = $this->getContainerBuilder();

		$extConfig = [
			'in' => __DIR__ . '/../',
			'classes' => [
				'*Facade',
				'*Package',
				'*Factory',
				'*Presenter',
				'*Repository',
			]
		];

		$name = 'packagesearch';
		$searchExt = new SearchExtension($builder->parameters['tempDir'] . '/cache/' . $name);
		$searchExt->setCompiler($this->compiler, $this->prefix($name));
		$config = $this->processSchema($searchExt->getConfigSchema(), $extConfig);
		$searchExt->setConfig($config);
		$searchExt->loadConfiguration();
		$searchExt->beforeCompile();
	}


	/**
	 * Merges and validates configuration against scheme.
	 * @param Schema $schema
	 * @param array $config
	 * @return array|object
	 */
	private function processSchema(Schema $schema, array $config)
	{
		$processor = new Processor;
		try {
			return $processor->process($schema, $config);
		} catch (ValidationException $e) {
			throw new InvalidConfigurationException($e->getMessage());
		}
	}
}

Doplnění:

  • Metoda findClasses není potřeba volat ručně, volá se už v loadConfiguration.
  • Pro větší pohodlí jsem tam doplnil validaci pomocí Scheme, když už tam je :)

Editoval Gappa (3. 2. 2020 21:03)

Felix
Nette Core | 1245
+
0
-

Pekny. BeforeCompile bych volal tez v beforeCompile. At zachovas lifecycle.

Gappa
Nette Blogger | 208
+
0
-

Felix napsal(a):

Pekny. BeforeCompile bych volal tez v beforeCompile. At zachovas lifecycle.

To jsem zkoušel, ale pokud je takto zaregistrovaný jakýkoliv presenter, tak pak začne Nette vyhazovat následující chybu:

Error
Call to a member function isSent() on null

File: .../src/Application/UI/Presenter.php:200

190:
191:
192:        public function run(Application\Request $request): Application\IResponse
193:        {
194:            try {
195:                // STARTUP
196:                $this->request = $request;
197:                $this->payload = $this->payload ?: new \stdClass;
198:                $this->setParent($this->getParent(), $request->getPresenterName());
199:
200:                if (!$this->httpResponse->isSent()) {
201:                    $this->httpResponse->addHeader('Vary', 'X-Requested-With');
202:                }
203:
204:                $this->initGlobalParameters();

Vůbec se nevytvoří httpResponse objekt, pokud se v browseru snažím dostat na takto zaregistrovaný presenter.

Pokud beforeCompile() zavolám v loadConfiguration(), tak je vše ok – divné :)

CZechBoY
Člen | 3608
+
0
-

Ukaž jak si to měl s tím beforeCompile… Podle mě sis jen udělal 2 instance SearchExtension.

Gappa
Nette Blogger | 208
+
0
-

Takto:

<?php
declare(strict_types=1);

use Nette\DI\CompilerExtension;
use Nette\DI\Extensions\SearchExtension;
use Nette\DI\InvalidConfigurationException;
use Nette\Schema\Processor;
use Nette\Schema\Schema;
use Nette\Schema\ValidationException;

final class Extension extends CompilerExtension
{
	/** @var SearchExtension */
	private $searchExt;


	public function loadConfiguration(): void
	{
		$builder = $this->getContainerBuilder();

		$extConfig = [
			'in' => __DIR__ . '/../',
			'classes' => [
				'*Facade',
				'*Package',
				'*Factory',
				'*Presenter',
				'*Repository',
			]
		];

		$name = 'packagesearch';
		$this->searchExt = new SearchExtension($builder->parameters['tempDir'] . '/cache/' . $name);
		$this->searchExt->setCompiler($this->compiler, $this->prefix($name));
		$config = $this->processSchema($this->searchExt->getConfigSchema(), $extConfig);
		$this->searchExt->setConfig($config);
		$this->searchExt->loadConfiguration();

	}


	public function beforeCompile(): void
	{
		$this->searchExt->beforeCompile();
	}


	/**
	 * Merges and validates configuration against scheme.
	 * @param Schema $schema
	 * @param array $config
	 * @return array|object
	 */
	private function processSchema(Schema $schema, array $config)
	{
		$processor = new Processor;
		try {
			return $processor->process($schema, $config);
		} catch (ValidationException $e) {
			throw new InvalidConfigurationException($e->getMessage());
		}
	}
}

Editoval Gappa (4. 2. 2020 10:06)

Gappa
Nette Blogger | 208
+
0
-

Po nějaké době jsem se k tomu vrátil a nevím jak dříve, ale s aktuálními verzemi balíčků to hlásí stejnou chybu, i když se použije standardní způsob registrace v neonu:

search:
	in: '%appDir%/packages/NewsPackage/'
	classes:
		- '*Facade'
		- '*Package'
		- '*Factory'
		- '*Presenter'
		- '*Repository'

Z čehož tedy usuzuji, že registrace presenterů v BeforeCompile je už pozdě, ve vygenerovaném kontejneru se pro presentery nevolá injectPrimary, kde se předává spousta věcí:

public function createService047(): App\Packages\NewsPackage\Presenters\AdminPresenter
{
	return new App\Packages\NewsPackage\Presenters\AdminPresenter(
		$this->getService('045'),
		$this->getService('041'),
		$this->getService('042')
	);
}

vs automaticky zaregistrovaný presenter:

public function createServiceApplication__6(): App\Module\Front\Presenters\HomepagePresenter
{
	$service = new App\Module\Front\Presenters\HomepagePresenter;
	$service->injectPrimary(
		$this,
		$this->getService('application.presenterFactory'),
		$this->getService('routing.router'),
		$this->getService('http.request'),
		$this->getService('http.response'),
		$this->getService('session.session'),
		$this->getService('security.user'),
		$this->getService('latte.templateFactory')
	);
	// dalsi sluzby
	$service->invalidLinkMode = 5;
	return $service;
}

Vůbec se nemůžu dopátrat, kde se rozhoduje, jestli se použije injectPrimary nebo ne, nemůžu to najít ani jako string kromě místa, kde je metoda definovaná, ani hledání Primary nepomohlo :)

Editoval Gappa (22. 6. 2020 10:39)

Marek Bartoš
Nette Blogger | 1274
+
+1
-

Vůbec se nemůžu dopátrat, kde se rozhoduje, jestli se použije injectPrimary nebo ne

Jde čistě jen o pořadí spouštění, žádnou podmínku nenajdeš.

ani hledání Primary nepomohlo :)

Ani to nenajdeš. InjectExtension hledá všechny inject metody a inject property u služeb, které mají tag nette.inject. Zkus jej přidat v SearchExtension k presenterům.

Editoval Mabar (22. 6. 2020 10:54)

Gappa
Nette Blogger | 208
+
0
-

Aha, díky za vysvětlení.

Bingo, stačí přidat tag a funguje to 👍, alespoň přes přímou konfiguraci v neonu.

Jdu zkoumat dál :)

Editoval Gappa (28. 7. 2020 9:29)

Gappa
Nette Blogger | 208
+
0
-

Uvádím pro úplnost – nakonec jsem zvolil toto řešení, uvidím, jak se osvědčí :)

Extension:

<?php

declare(strict_types=1);

namespace App\Packages\TestPackage\DI;

use Nette\DI\CompilerExtension;
use Some\Namespace\PackageServiceProvider;
use Nette\SmartObject;

final class TestPackageExtension extends CompilerExtension
{
	use SmartObject;
	use PackageServiceProvider;

	public function loadConfiguration(): void
	{
		$config = $this->loadFromFile(__DIR__ . '/config.neon');
		$this->compiler->loadDefinitionsFromConfig($config['services']);
		$this->loadServices();
	}
}

Trait:

<?php
declare(strict_types=1);

namespace Some\Namespace;

use ReflectionClass;
use Nette\DI\CompilerExtension;

/**
 * @mixin CompilerExtension
 */
trait PackageServiceProvider
{

	protected function loadServices(): void
	{
		$reflection = new ReflectionClass(get_class($this));
		$dir = dirname((string) $reflection->getFileName());

		$serviceProviderHelper = new ServiceProviderHelper($this->compiler, $dir, $this->getServicesMask());
		$serviceProviderHelper->loadServices();
	}


	protected function getServicesMask(): array
	{
		return [];
	}
}

Helper:

<?php
declare(strict_types=1);

namespace Some\Namespace;

use Nette\DI\Compiler;
use Nette\DI\Extensions\SearchExtension;
use Nette\DI\InvalidConfigurationException;
use Nette\Schema\Processor;
use Nette\Schema\Schema;
use Nette\Schema\ValidationException;

final class ServiceProviderHelper
{
	/** @var Compiler */
	private $compiler;

	/** @var SearchExtension */
	private $searchExtension;

	/** @var string */
	private $dir;

	/** @var string[] */
	private $defaultMask = [
		'*Facade',
		'*Manager',
		'*Factory',
		'*Repository',
	];

	/** @var string[] */
	private $customMask = [];


	public function __construct(Compiler $compiler, string $dir, array $customMask = [])
	{
		$this->compiler = $compiler;
		$this->searchExtension = $this->getSearchExtension();
		$this->dir = $dir;
		$this->customMask = $customMask;
	}


	public function loadServices(): void
	{
		$config = $this->processSchema($this->searchExtension->getConfigSchema(), $this->getConfig());
		$this->searchExtension->setConfig($config);
		$this->searchExtension->loadConfiguration();
	}


	private function getSearchExtension(): SearchExtension
	{
		$extension = $this->compiler->getExtensions()['search'];
		assert($extension instanceof SearchExtension);
		return $extension;
	}


	private function getConfig(): array
	{
		$mask = array_merge($this->defaultMask, $this->customMask);

		return [
			'presenters' => [
				'in' => $this->dir . '/../Presenters/',
				'classes' => ['*Presenter'],
				'tags' => ['nette.inject'],
			],
			'other' => [
				'in' => $this->dir . '/../',
				'classes' => $mask,
			]
		];
	}


	/**
	 * Merges and validates configuration against scheme.
	 * @param Schema $schema
	 * @param array $config
	 * @return array|object
	 */
	private function processSchema(Schema $schema, array $config)
	{
		$processor = new Processor;
		try {
			return $processor->process($schema, $config);
		} catch (ValidationException $e) {
			throw new InvalidConfigurationException($e->getMessage());
		}
	}
}