Číselníky v komponentě a přístup přes Facade

Danny
Člen | 146
+
0
-

Zdravím,
řeším takový problém, začal jsem používat architekturu kde jsou oddělené vrstvy s použití ORM

  • Entita
  • Repository
  • Service
  • Facade
  • Controller

Například tedy v komponentě kde je nějaký formulář mám UserFacade která řeší přidání uživatele a jeho sportu

$userFacade->addUser($values);

Chci se tedy odprostit od toho abych v komponentách injectoval Repository a sahal pouze do Facade. Jak ale řešit číselníky pokud jsou v databázi? Mám číselník/entitu Sport kde jsou jako hodnoty různé typy sportů a lze to editovat v administraci.

Abych naplnil selectbox formuláře bych musel udělat nějakou SportFacade ta by si injectovala SportRepository a mělo methodu getSports(), je to správné řešení? Přijde mi to hodně zbytečného psaní, ve chvíli kdy bych si mohl injectnout jen SportRepository do komponenty udělal bych

$form->addSelect('sport', 'Sport', $this->sportRepository()->fetchPairs('id','name'));

tak mám hotovo.

Díky za tipy

pavelmlejnek
Člen | 16
+
+3
-

Ahoj, nevím jestli je to správné řešení (to nechám na posouzení jiných), ale u opakujících se věcí používám vlastní formulářový prvek, který extenduje SelectBox. Celé to vypadá nějak takto:

<?php declare(strict_types=1);

use Nette\Forms\Container;
use Nette\Forms\Controls\SelectBox;


final class SportControl extends SelectBox
{

	/** @var array<Sport> */
	private array $sportsIndexedById;

	public function __construct(SportRepository $sportRepository, ?string $label = null)
	{
		$items = [];
		$sportsIndexedById = [];

		foreach ($sportRepository->findAll() as $sport) {
			assert($sport instanceof Sport);

			$items[$sport->id] = $sport->name;
			$sportsIndexedById[$sport->id] = $sport;
		}

		$this->sportsIndexedById = $sportsIndexedById;

		parent::__construct($label, $items);
	}

	public function setValue($value)
	{
		if ($value instanceof Sport) {
			$value = $value->id;
		}

		return parent::setValue($value);
	}

	public function getValue()
	{
		$sportId = parent::getValue();

		if ($sportId === null) {
			return null;
		}

		return $this->sportsIndexedById[(int) $sportId] ?? null;
	}

	public static function register(SportRepository $sportRepository, string $method = 'addSport'): void
	{
		Container::extensionMethod($method, static function (Container $container, string $name, ?string $label = null) use ($sportRepository) {
				$container[$name] = new SportControl($sportRepository, $label);

				return $container[$name];
		});
	}

}

Poté registrace v DI extension:

<?php declare(strict_types=1);

use Nette\DI\CompilerExtension;
use Nette\PhpGenerator\ClassType;

class AppExtension extends CompilerExtension
{

	public function afterCompile(ClassType $class)
	{
		parent::afterCompile($class);

		$containerBuilder = $this->getContainerBuilder();
		$initializeMethod = $class->getMethod('initialize');

		$initializeMethod->addBody(sprintf('%s::register($this->getService(?));', SportControl::class), [
			$containerBuilder->getByType(SportRepository::class),
		]);
	}

}

Došel jsem k tomuto řešení hlavně kvůli podpoře při mapování formulářových hodnot na datové třídy.

jiri.pudil
Nette Blogger | 1032
+
0
-

Nevidím nic špatného na tom sáhnout si z formuláře přímo do repository, obzvlášť pokud potřebuješ jenom vytáhnout seznam sportů. Ukládat a poskytovat uložené položky mi zní přesně jako zodpovědnost repository.

Balit to do další vrstvy má v takovém případě asi jen jednu přidanou hodnotu: hezčí API navenek (getSports() vs fetchPairs('id', 'name') – posuď sám, jestli je tenhle přínos pro tebe a případně ostatní v týmu natolik velký, aby vyvážil psaní více kódu. No a pak je tu ještě element času: třeba se ti ten kód bude časem hodit i na jiném místě a najednou bude dávat větší smysl ho obalit do fasády s jednoduchou metodou :)

Danny
Člen | 146
+
0
-

@pavelmlejnek @jiripudil Díky moc to jsem přesně potřeboval vědět :-)