How to handle routing with locale in Symfony 4.3 KernelTestCase? - php

I need to test (PHPUnit) a service in Symfony 4.3 application and that service depends on the framework. It uses Translator and Router and it also depends on User. This means I want to write a KernelTestCase with User (done). The problem is the Router fails because the Routes need _locale. How can I address the issue?
1) App\Tests\blahblah\MenuFactoryTest::testMenuItemsByRoles with data set "ROLE_ADMIN" (array('ROLE_ADMIN'), array(array('Menu Label 1', 'Menu Label 2')))
Symfony\Component\Routing\Exception\MissingMandatoryParametersException: Some mandatory parameters are missing ("_locale") to generate a URL for route "default_dashboard".
class MenuFactoryTest extends KernelTestCase
{
use LogUserTrait; // my trait allowing to emulate a user with particular roles
/** #var MenuFactory */
private $menuFactory;
protected function setUp(): void
{
static::bootKernel();
$this->menuFactory = static::$container->get(MenuFactory::class);
}
// ...
/**
* #param array $roles
* #param array $menuLabels
*
* #dataProvider provideMenuItemsByRoles
*/
public function testMenuItemsByRoles(array $roles, array $menuLabels): void
{
$this->logIn($roles);
$this->assertMenuItemsHaveLabels(
$menuLabels,
$this->menuFactory->getMenuItems()
);
}
// ...
}
class MenuFactory implements MenuFactoryInterface
{
/** #var RouterInterface */
private $router;
/** #var AuthorizationCheckerInterface */
private $securityChecker;
/** #var TranslatorInterface */
private $translator;
public function __construct(
RouterInterface $router,
AuthorizationCheckerInterface $securityChecker,
TranslatorInterface $translator
) {
$this->router = $router;
$this->securityChecker = $securityChecker;
$this->translator = $translator;
}
public function getMenuItems(string $appMenuName = null): array
{
$menuItems = [];
$dashboardMenu = new DefaultMenuItem(
$this->translator->trans('menu.dashboard'),
$this->router->generate('default_dashboard'),
'fa fa-dashboard'
);
$menuItems[] = $dashboardMenu;
// ...
return $menuItems;
}
}

Related

Symfony 3 captcha bundle without forms

I need to add a simple captcha to my Symfony login form and currently I am searching for correct bundle for it. I dont need any API/ajax js support, just a bundle which generates an image on the server and then performs user input validation.
Moreover I am not using forms in my project, so I need to render an image in my loginAction and after that perform manual validation somewhere.
Firstly I tried captcha.com bundle, but as far as I understand it is not free and also it required login to git.captcha.com when performing composer require ...
After that I tried to use Gregwar/CaptchaBundle but its docs contain only examples with form while I need something without them. Is there any way to use Gregwar/CaptchaBundle without forms?
Any advice would be welcome, thank you.
I've used Gregwar/CaptchaBundle like this:
namespace AppBundle\Security\Captcha;
use Symfony\Component\HttpFoundation\Session\Session;
use Symfony\Component\HttpFoundation\Session\SessionInterface;
use Symfony\Component\Templating\EngineInterface;
use Gregwar\CaptchaBundle\Generator\CaptchaGenerator as BaseCaptchaGenerator;
class CaptchaGenerator
{
/**
* #var EngineInterface
*/
private $templating;
/**
* #var BaseCaptchaGenerator
*/
private $generator;
/**
* #var Session
*/
private $session;
/**
* #var string
*/
private $sessionKey;
/**
* #var array
*/
private $options;
public function __construct(EngineInterface $templating, BaseCaptchaGenerator $generator, SessionInterface $session, $sessionKey, array $options)
{
$this->templating = $templating;
$this->generator = $generator;
$this->session = $session;
$this->sessionKey = $sessionKey;
$this->options = $options;
}
/**
* #return string
*/
public function generate()
{
$options = $this->options;
$code = $this->generator->getCaptchaCode($options);
$this->session->set($this->sessionKey, $options['phrase']);
return $this->templating->render('AppBundle:My/Security:captcha.html.twig', array(
'code' => $code,
'width' => $options['width'],
'height' => $options['height'],
));
}
}
namespace AppBundle\Security\Captcha;
use Symfony\Component\HttpFoundation\Session\Session;
use Symfony\Component\HttpFoundation\Session\SessionInterface;
class CaptchaValidator
{
/**
* #var Session
*/
private $session;
/**
* #var string
*/
private $sessionKey;
public function __construct(SessionInterface $session, $sessionKey)
{
$this->session = $session;
$this->sessionKey = $sessionKey;
}
/**
* #param string $value
* #return bool
*/
public function validate($value)
{
return $this->session->get($this->sessionKey, null) === $value;
}
}
{# AppBundle:My/Security:captcha.html.twig #}
<img class="captcha_image" src="{{ code }}" alt="" title="captcha" width="{{ width }}" height="{{ height }}" />
# services.yaml
parameters:
app.security.captcha.security_key: captcha
services:
AppBundle\Security\Captcha\CaptchaGenerator:
lazy: true
arguments:
$sessionKey: '%app.security.captcha.security_key%'
$options: '%gregwar_captcha.config%'
AppBundle\Security\Captcha\CaptchaValidator:
arguments:
$sessionKey: '%app.security.captcha.security_key%'
then validate value in AppBundle\Security\FormAuthenticator
public function authenticateToken(TokenInterface $token, UserProviderInterface $userProvider, $providerKey)
{
if (!$this->captchaValidator->validate($token->getCaptchaValue())) {
throw new CustomUserMessageAuthenticationException('security.captcha');
}
// ...
}

Logging from inside a repository in Symfony

I'm tracing a weird error in a Symfony 2 app and I'd like to know if there's a way to print log messages from a Repository PHP file. For example:
class OrderEntityRepository extends EntityRepository
{
/**
*
* #param mixed $filter
* #return type
*/
public function findByCriteria($filter) {
[...]
/* I'D LIKE TO LOG SOME VARIABLES FROM HERE */
}
}
I've tried using error_log() but nothing happens.
Is this possible? Thanks in advance,
It's possible but it's usually not a good practice. The good thing to do is to send back the Repository result to your Controller or Service and you log from them an error or something else.
But if you still want to do it, Repository are like services (when you implements ServiceEntityRepository see this slide for more information). If you want to log something specific inside you have to inject the LoggerInterface into your Repository configuration (like you do with service).
In your service.yml (or xml) if you don't use autowire:
Your\Repository:
arguments: ['#logger']
In your repository class:
/**
* #var LoggerInterface
*/
protected $logger;
public function __construct(LoggerInterface $logger)
{
$this->logger = $logger;
}
On symfony 3.8 I have
class BlahRepository extends ServiceEntityRepository
{
/* #var ContainerInterface $container */
private $container;
/* #var LoggerInterface $logger */
private $logger;
public function __construct(RegistryInterface $registry, ContainerInterface $container, LoggerInterface $logger)
{
parent::__construct($registry, Blah::class);
$this->container = $container;
$this->logger = $logger;
}
}
and I am able to use $this->logger->info("text")
I think the trick may be extending ServiceEntityRepository
In order to use dependency injection for Doctrine entity repositories, you can create a custom RepositoryFactory.
Tested on Symfony 3.4.
<?php
namespace AppBundle\Doctrine;
use Doctrine\Common\Persistence\ObjectRepository;
use Doctrine\ORM\Configuration;
use Doctrine\ORM\EntityManagerInterface;
use Doctrine\ORM\Repository\DefaultRepositoryFactory;
use Doctrine\ORM\Repository\RepositoryFactory as RepositoryFactoryInterface;
use Psr\Log\LoggerAwareInterface;
use Psr\Log\LoggerInterface;
class RepositoryFactory implements RepositoryFactoryInterface, LoggerAwareInterface
{
/** #var DefaultRepositoryFactory */
protected $defaultRepositoryFactory;
/** #var LoggerInterface */
private $logger;
/**
* #required (for autowiring)
* #param LoggerInterface $logger (Monolog will be the default one)
*/
public function setLogger(LoggerInterface $logger): void
{
$this->logger = $logger;
}
/**
* #see Configuration::getRepositoryFactory()
*/
public function __construct()
{
$this->defaultRepositoryFactory = new DefaultRepositoryFactory();
}
/**
* Gets the repository for an entity class.
*
* #param EntityManagerInterface $entityManager
* #param string $entityName The name of the entity.
* #return ObjectRepository
*/
public function getRepository(EntityManagerInterface $entityManager, $entityName): ObjectRepository
{
$repository = $this->defaultRepositoryFactory->getRepository($entityManager, $entityName);
if ($repository instanceof LoggerAwareInterface && $this->logger !== null) {
$repository->setLogger($this->logger);
}
return $repository;
}
}
Declare it in Doctrine configuration.
# app/config.yml
doctrine:
# ...
orm:
repository_factory: AppBundle\Doctrine\RepositoryFactory
And finally, make your repository class implement LoggerAwareInterface.
class OrderEntityRepository extends EntityRepository implements LoggerAwareInterface
{
/** #var LoggerInterface */
private $logger;
/**
* #param LoggerInterface $logger
*/
public function setLogger(LoggerInterface $logger): void
{
$this->logger = $logger;
}
/**
* #param mixed $filter
* #return type
*/
public function findByCriteria($filter) {
//[...]
$this->logger->alert('message');
}
}
You can also make a LoggerAwareTrait trait to spare yourself some code duplication.

Magento2:Extra parameters passed to parent construct:

I have this code in my custom controller:
namespace Myweb\CustomArt\Controller\Index;
use Magento\Framework\Controller\ResultFactory;
use Magento\Framework\Filesystem;
class Form extends \Magento\Framework\App\Action\Action
{
/**
* Contact action
*
* #return void
*/
/**
* #var \Magento\Framework\Mail\Template\TransportBuilder
*/
/**
* #var Google reCaptcha Options
*/
private static $_siteVerifyUrl = "https://www.google.com/recaptcha/api/siteverify?";
private $_secret;
private static $_version = "php_7.0";
/**
* Save Form Data
*
* #return array
*/
protected $context;
private $fileUploaderFactory;
private $fileSystem;
protected $_transportBuilder;
protected $scopeConfig;
protected $inlineTranslation;
public function __construct(
\Magento\Framework\App\Action\Context $context,
Filesystem $fileSystem,
\Magento\Framework\Mail\Template\TransportBuilder $transportBuilder,
\Magento\Framework\Translate\Inline\StateInterface $inlineTranslation,
\Magento\Framework\App\Config\ScopeConfigInterface $scopeConfig,
\Magento\MediaStorage\Model\File\UploaderFactory $fileUploaderFactory
) {
parent::__construct($context, $transportBuilder, $inlineTranslation, $scopeConfig );
$this->fileUploaderFactory = $fileUploaderFactory;
$this->fileSystem = $fileSystem;
$this->_transportBuilder = $transportBuilder;
$this->inlineTranslation = $inlineTranslation;
$this->scopeConfig = $scopeConfig;
}
I am getting the error like below, when running the command
php bin/magento setup:di:compile
Extra parameters passed to parent construct: $transportBuilder, $inlineTranslation, $scopeConfig. File:
I have followed this code from another post, though I am not facing any issue in working of the module.
You're extending \Magento\Framework\App\Action\Action whose constructor only has one parameter: \Magento\Framework\App\Action\Context.
So you should call
parent::__construct($context);
instead of
parent::__construct($context, $transportBuilder, $inlineTranslation, $scopeConfig);

Add Twig global variable after kernel.request event

I am currently working on developing a project in SAAS, each client can access his platform by a personalized url (site1.com, site2.com, etc.).
For each domain name a set of template customization data is defined in the back office and I must be able to access it from my Twig files. So I defined a listener on the kernerl.request event that adds a global variable to Twig based on the current domain name. Everything works fine in most cases, except when a page is first displayed, Twig must be run upstream and I get the following error:
Unable to add global "site" as the runtime or the extensions have
already been initialized.
Listener class
class SiteListener
{
public function __construct(
SiteHelper $siteHelper,
\Twig_Environment $twig
) {
$this->siteHelper = $siteHelper;
$this->twig = $twig;
}
/**
* Add current contexts to twig global.
*/
public function addContextsToTwigGlobal(GetResponseEvent $event)
{
$this->twig->addGlobal('site', $this->siteHelper);
}
}
Listener service declaration
multisite.listener.site:
class: MultisiteBundle\Listener\SiteListener
arguments:
- "#multisite.helper.site"
- "#twig"
tags:
- { name: kernel.event_listener, event: kernel.request, method: addContextsToTwigGlobal }
SiteHelper service
class SiteHelper
{
/**
* #var RequestStack
*/
protected $requestStack;
/**
* #var ContextConfigManager;
*/
protected $contextConfigManager;
/**
* #var ContextConfig
*/
protected $contextConfig;
public function __construct(
RequestStack $requestStack,
ContextConfigManager $contextConfigManager
) {
$this->requestStack = $requestStack;
$this->contextConfigManager = $contextConfigManager;
$this->contextConfig = $this->contextConfigManager
->findByHostOrStandard($this->getHost());
}
/**
* Get host from current request.
*
* #return string|null
*/
public function getHost()
{
$request = $this->requestStack->getCurrentRequest();
return ($request) ? $request->getHost() : null;
}
/**
* Get current context config
*
* #return ContextConfig
*/
public function getContextConfig()
{
return $this->contextConfig;
}
}
Any idea ?
I decided to write a Twig function to avoid this kind of problem. This seem to be a good solution.
class SiteExtension extends \Twig_Extension
{
/**
* #var SiteHelper
*/
private $siteHelper;
/**
* Constructor.
*
* #param SiteHelper $siteHelper
*/
public function __construct(SiteHelper $siteHelper)
{
$this->siteHelper = $siteHelper;
}
/**
* {#inheritdoc}
*/
public function getFunctions()
{
return array(
new \Twig_Function('site', array($this->siteHelper, 'getContext')),
);
}
}

How to automate property generation for a class in phpstorm?

If I implement a class, which gets some services injected, I have to write this bulk of code:
<?php
namespace Hn\AssetDbBundle\Services;
use Psr\Log\LoggerInterface;
use Symfony\Bundle\TwigBundle\TwigEngine;
use Symfony\Component\HttpKernel\KernelInterface;
/**
* Class SomeNewService
* #package Hn\AssetDbBundle\Services
*/
class SomeNewService {
/**
* #var TwigEngine
*/
private $engine;
/**
* #var KernelInterface
*/
private $kernel;
/**
* #var LoggerInterface
*/
private $logger;
public function __construct(TwigEngine $engine, KernelInterface $kernel, LoggerInterface $logger) {
$this->engine = $engine;
$this->kernel = $kernel;
$this->logger = $logger;
}
}
This seems redundant. Is there a way I can reduce the amount of code I have to write? Is there a live template for initializing the fields or can I autogenerate the bulk of this block otherwise?
You can use the Initialize field feature.
This way, you only have to write the constructor method this way:
class SomeNewService {
public function __construct(TwigEngine $engine, KernelInterface $kernel, LoggerInterface $logger) {
}
}
Then you can use initialize fields. Get the cursor over one property of the constructor, then on MacOS use Alt + Enter.
It looks something like this:
After you press enter you are confronted with a list of properties, which you can select by Shift and arrow keys. By selection all the properties, your code will look like this:
class SomeNewService {
/**
* #var TwigEngine
*/
private $engine;
/**
* #var KernelInterface
*/
private $kernel;
/**
* #var LoggerInterface
*/
private $logger;
/**
* #param TwigEngine $engine
* #param KernelInterface $kernel
* #param LoggerInterface $logger
*/
public function __construct(TwigEngine $engine, KernelInterface $kernel, LoggerInterface $logger) {
$this->engine = $engine;
$this->kernel = $kernel;
$this->logger = $logger;
}
}
You can also do the other way around, defining the properties first, and then in the "Generate" menu (Cmd+N), use "Constructor".
On Windows :
put the cursor on the arguemnt of your construct method ,
then press Alt + Enter , hover/selct over initialize field , then press
Alt + Enter , then select field & press Ok .
Enjoy

Categories