Circular reference detected for services

Upozornění: Tohle vlákno je hodně staré a informace nemusí být platné pro současné Nette.
Pert Jančálek
Člen | 5
+
0
-

Ahojte, potřeboval bych pomoct s předáváním parametrů … všechno mi fungovalo až do té doby, než jsem přidal do DI containeru následující třídu:

use Nette\Application\LinkGenerator;
use Nette\Application\UI\ITemplateFactory;
use Nette\Mail\Message;
use Latte\Engine;
use Nette\Mail\SmtpMailer;

class Mailer
{
	/** @var LinkGenerator */
	private $linkGenerator;
	/** @var ITemplateFactory */
	private $templateFactory;
	/** @var SmtpMailer  */
	private $sendMailer;

	/**
	 * Mailer constructor.
	 * @param LinkGenerator $linkGenerator
	 * @param ITemplateFactory $templateFactory
	 */
	public function __construct(LinkGenerator $linkGenerator, ITemplateFactory $templateFactory )
	{
		$this->sendMailer = new SmtpMailer([
			'host' => '***',
			'username' => '***',
			'password' => '***',
			'secure' => 'ssl'
		]);
		$this->linkGenerator = $linkGenerator;
		$this->templateFactory = $templateFactory;
	}

	/**
	 * @param array $params
	 */
	public function sendKey(array $params)
	{
		$latte = new Engine();
		$template = $this->createTemplate();

		$template->name = 'name';
		$template->setFile(__DIR__ . '/mailTemplates/key.latte');

		$mail = new Message;
		$mail->setHtmlBody($latte->renderToString($template, $params));

		$this->sendMailer->send($mail);
	}

	protected function createTemplate()
	{
		$template = $this->templateFactory->createTemplate();
		$template->getLatte()->addProvider('uiControl', $this->linkGenerator);

		return $template;
	}
}

Jediná třída, která tento objekt vyžadaduje je třída userFacade.
Hned potom, co tuto třídu předám userFacadě jako parametr, tak dostanu chybovou hlášku:

Circular reference detected for services: application.4, 71_App_Forms_StatusFormFactory, security.user, 77_App_Model_Facades_UserFacade, 78_App_Model_Utils_Mailer, latte.templateFactory

Absolutně netuším, kde by mohl být problém. Mohli byste mi prosím pomoci ?

Editoval Pert Jančálek (12. 9. 2017 19:45)

David Matějka
Moderator | 6445
+
+2
-

tipuju, ze user facade implementuje authorizator? nejlepsi bude z authorizatoru udelat samotnou sluzbu (ktera nebude zavisla na user facade)

CZechBoY
Člen | 3608
+
0
-

btw. lepsi si nakonfigurovat smtp v config.locan.neon a do konstruktoru si vyzadat IMailer.

Pert Jančálek
Člen | 5
+
0
-

Třída implementuje rozhraní IAuthenticator. Příjde mi to tak logické, protože používám Doctrine a tato třída má potřebné objekty, ale tu entitu vytáhla.
Tak díky za odpovědi, ale škoda, že to neřeší můj problém.

Editoval Pert Jančálek (12. 9. 2017 20:55)

CZechBoY
Člen | 3608
+
0
-

Pošli ještě ty další třídy a můžem ti poradit konkrétněji. (App\Forms\StatusFactory, App\Model\Facades\UserFacade)

Editoval CZechBoY (12. 9. 2017 21:26)

Pert Jančálek
Člen | 5
+
0
-
<?php

namespace App\Forms;

use App\Model\Facades\StatusFacade;
use App\Model\Facades\UserFacade;
use Czubehead\BootstrapForms\BootstrapForm as Form;
use Nette\Security\User;
use Nette\Utils\ArrayHash;


class StatusFormFactory extends BaseFormFactory
{
	/** @var  StatusFacade */
	protected $statusFacade;

	/**
	 * StatusFormFactory constructor.
	 * @param StatusFacade $statusFacade
	 */
	public function __construct(StatusFacade $statusFacade, User $user, UserFacade $userFacade)
	{
		parent::__construct($user, $userFacade);
		$this->statusFacade = $statusFacade;
	}


	public function statusForm()
	{
		$form = new Form();

		$form->addTextArea('content')
			->setAttribute('id', 'preview')
			->addRule(Form::MAX_LENGTH, 'Maximální délka statusu je %d znaků.', 3000)
			->setRequired('Vyplňte prosím status před jeho odesláním.');

		$form->addMultiUpload('images', 'Obrázky')
			->setRequired(false)
			->setOmitted(false);

		$form->addSubmit('submit', 'submit');

		$form->onSuccess[] = [$this, 'statusSucceeded'];

		return $form;
	}

	public function statusSucceeded(Form $form, $vals)
	{
		$vals->user = $this->userFacade->getUser($this->user->getId());
		$this->statusFacade->createStatus($form, $vals);
	}

	public function infoForm()
	{
		$form = $this->texyArea('content', 'Obsah');

		$form->addSubmit('submit', 'odeslat');

		$form->onSuccess[] = [$this, 'infoSuccess'];

		return $form;
	}

	public function infoSuccess(Form $form, ArrayHash $vals)
	{
		$vals->user = $this->userFacade->getUser($this->user->getId());
		$this->statusFacade->createInfo($vals);
	}

}
<?php

namespace App\Model\Facades;


use App\Model\Entities\User as UserEntity;
use App\Model\Entities\UserSettings;
use App\Model\Utils\Mailer;
use App\Model\Utils\RouteParser;
use Doctrine\DBAL\Exception\UniqueConstraintViolationException;
use Doctrine\ORM\EntityManager;
use Nette\Application\UI\Form;
use Nette\InvalidArgumentException;
use Nette\Mail\Message;
use Nette\Utils\ArrayHash;
use Nette\Security\AuthenticationException;
use Nette\Security\IAuthenticator;
use Nette\Security\Identity;
use Nette\Security\IIdentity;
use Nette\Security\Passwords;
use Nette\Mail\SmtpMailer;

class UserFacade extends BaseFacade implements IAuthenticator
{
	/** @var Mailer  */
	private $mailer;

	/**
	 * UserFacade constructor.
	 * @param EntityManager $entityManager
	 */
	public function __construct(EntityManager $entityManager)
	{
	//	$this->mailer = $mailer;
		$this->mailer = new SmtpMailer([
			...
		]);
		$this->em = $entityManager;
	}

	/**
	 * @param int $id
	 * @return UserEntity|null
	 */
	public function getUser($id)
	{
		return $this->em->find(UserEntity::class, $id);
	}

	/**
	 * @param string $name
	 * @return null|UserEntity
	 */
	public function getUserByName($name)
	{
		return $this->em->getRepository(UserEntity::class)->findOneBy(array('name' => $name));
	}

	/**
	 * @param string $route
	 * @return UserEntity|null
	 */
	public function getUserByRoute($route)
	{
		return $this->em->getRepository(UserEntity::class)->findOneBy(['route' => $route]);
	}

	/**
	 * @param string $route
	 * @return UserEntity|null
	 */
	public function getIdByRoute($route)
	{
		return $this->em->createQuery('
			SELECT PARTIAL u.{id}
			FROM App\Model\Entities\USER u
			WHERE u.route = :route
			')
			->setParameter('route', $route)
			->setMaxResults(1)
			->getOneOrNullResult();
	}

	/**
	 * Performs an authentication against e.g. database.
	 * and returns IIdentity on success or throws AuthenticationException
	 * @return IIdentity
	 * @throws AuthenticationException
	 */
	function authenticate(array $credentials)
	{
		list($name, $password) = $credentials;

		$user = $this->getPartialUser($name);

		if (!$user) throw new AuthenticationException('Špatné jméno');
		if (!Passwords::verify($password, $user->password)) throw new AuthenticationException('Špatné heslo');
		if($user->settings->key !== NULL) throw new KeyException();
		if (Passwords::needsRehash($user->password)) {
			$user->password = Passwords::hash($password);
			$this->em - flush();
		}

		return new Identity($user->id, explode(' ', $user->role), ['route' => $user->route]);
	}

	function userChecked(UserEntity $user)
	{
		$user->settings->key = NULL;
		$this->em->flush();
	}

	/**
	 * @param $name
	 * @return UserEntity|null
	 */
	private function getPartialUser($name)
	{
		return $this->em->createQuery('
			SELECT PARTIAL u.{id, name, password, role, route}, PARTIAL s.{id,key}
			FROM App\Model\Entities\User u
			INNER JOIN u.settings s
			WHERE u.name = :name
			')
			->setParameter('name', $name)
			->setMaxResults(1)
			->getOneOrNullResult();
	}

	/**
	 * @param ArrayHash $vals
	 * @throws InvalidArgumentException
	 * @throws UniqueConstraintViolationException
	 */
	public function adminEdit(Form $form, ArrayHash $vals)
	{
		$user = $this->getUser($vals->userId);
		if (is_null($user))
			throw new InvalidArgumentException('Uživatel, na kterého se snažíte dotázat, neexistuje');

		if ($user->role === UserEntity::ROLE_ADMIN && $this->vals->userRoute !== $user->route)
			throw new InvalidArgumentException('Nemůžeš upravovat jiného administrátora.');

		$user->name = $vals->name;
		$user->role = $vals->role;
		if ($vals->password)
			$user->password = Passwords::hash($vals->name);
		if ($vals->route)
			$user->route = RouteParser::parseRoute($vals->route);
		if ($vals->description)
			$user->settings->description = $vals->description;
		$user->email = $vals->email;
		$this->em->flush();
		$this->filesUpload($form, $vals);
	}

	/**
	 * @param ArrayHash $vals
	 * @throws UniqueConstraintViolationException
	 */
	public function registerUser(ArrayHash $vals)
	{
		$user = new UserEntity();
		$user->name = $vals->name;
		$user->password = Passwords::hash($vals->password);
		$user->email = $vals->email;
		$user->points = 0;
		$user->route = RouteParser::parseRoute($user->name);

		$settings = new UserSettings();
		$settings->key = $this->generateKey();
		$user->role = UserEntity::ROLE_USER;
		$user->settings = $settings;
		$settings->register_at = new \DateTime();

		$this->em->persist($settings);
		$this->em->persist($user);
		$this->em->flush();

	//	$this->mailer->sendKey(['name' => $vals->name, 'route' => $user->route, 'key' => $settings->key]);
		$this->sendKey($vals->email, $user->route, $user->settings->key);
	}

	private function sendKey($email, $route, $key)
	{
		$mail = new Message();
		$mail->setFrom('Oaza <admin@...>')
			->addTo($email)
			->setSubject('Potvrzení registrace')
			->setHtmlBody('<h1>Registrace na webu</h1><br>
					<p>Pro potvrzení tvého účtu přejdi na odkaz níže</p>
					<br>
					<a>odkaz ...</a>
				');
		$this->mailer->send($mail);
	}

	/**
	 * @return string
	 */
	private function generateKey()
	{
		$characters = '0123456789abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ';
		$length = mb_strlen($characters);
		$key = '';
		for ($i = 0; $i < 50; $i++)
		{
			$key .= $characters[rand(0, $length - 1)];
		}
		return $key;
	}

	protected function filesUpload(Form $form, ArrayHash $vals)
	{
		if ($vals->profile->isOk())
			$vals->profile->move('images/players/profile/' . $vals->userId . '.png');
		else if ($vals->profile->getName() !== null && !$vals->profile->isOk())
			$form->addError('Obrázek avatara se po cestě poškodil. Nahrajte ho proísm znovu.');
		if ($vals->background->isOk())
			$vals->background->move('images/players/background/' . $vals->userId . '.png');
		else if ($vals->background->getName() !== null && !$vals->background->isOk())
			$form->addError('Backgound se po cestě poškodil. Nahrajte ho proísm znovu.');
	}

	public function userEdit(Form $form, ArrayHash $vals)
	{
		$user = $this->getUser($vals->userId);
		if ($vals->password)
			$user->password = Passwords::hash($vals->password);
		$user->settings->description = $vals->description;
		$this->em->flush();

		$this->filesUpload($form, $vals);
	}
}

class KeyException extends \Exception
{

}

U třídy userFacade jsem tu závislost momentálně oddělal a prozatimní funkcí.

David Matějka
Moderator | 6445
+
+3
-

Tak díky za odpovědi, ale škoda, že to neřeší můj problém.

resi to tvuj problem. Nette\Security\User vyzaduje IAuthenticator, ktery mas implementovany v UserFacade. Ta pak vyzaduje ten Mailer, ten vyzaduje ITemplateFactory a to vyzaduje Nette\Security\User – a je tu circular reference.

kdyz udelas z authenticatoru samotnou sluzbu, problem zmizi