How to automate property generation for a class in phpstorm? - php

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

Related

How to handle routing with locale in Symfony 4.3 KernelTestCase?

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;
}
}

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 override bundled Doctrine repository in Symfony

I have an independent Symfony bundle (installed with Composer) with entities and repositories to share between my applications that connect same database.
Entities are attached to every applications using configuration (yml shown):
doctrine:
orm:
mappings:
acme:
type: annotation
dir: %kernel.root_dir%/../vendor/acme/entities/src/Entities
prefix: Acme\Entities
alias: Acme
Well, it was the easiest way to include external entities in application, but looks a bit ugly.
Whenever I get repository from entity manager:
$entityManager->getRepository('Acme:User');
I get either preconfigured repository (in entity configuration) or default Doctrine\ORM\EntityRepository.
Now I want to override bundled (or default) repository class for a single entity. Is there any chance to do it with some configuration/extension/etc?
I think, the best looking way is something like:
doctrine:
orm:
....:
Acme\Entities\User:
repositoryClass: My\Super\Repository
Or with tags:
my.super.repository:
class: My\Super\Repository
tags:
- { name: doctrine.custom.repository, entity: Acme\Entities\User }
You can use LoadClassMetadata event:
class LoadClassMetadataSubscriber implements EventSubscriber
{
/**
* #inheritdoc
*/
public function getSubscribedEvents()
{
return [
Events::loadClassMetadata
];
}
/**
* #param LoadClassMetadataEventArgs $eventArgs
*/
public function loadClassMetadata(LoadClassMetadataEventArgs $eventArgs)
{
/**
* #var \Doctrine\ORM\Mapping\ClassMetadata $classMetadata
*/
$classMetadata = $eventArgs->getClassMetadata();
if ($classMetadata->getName() !== 'Acme\Entities\User') {
return;
}
$classMetadata->customRepositoryClassName = 'My\Super\Repository';
}
}
Doctrine Events
Entities are attached to every applications using configuration (yml shown):
Well, it was the easiest way to include external entities in application, but looks a bit ugly.
You can enable auto_mapping
Works for Doctrine versions <2.5
In addition to Artur Vesker answer I've found another way: override global repository_factory.
config.yml:
doctrine:
orm:
repository_factory: new.doctrine.repository_factory
services.yml:
new.doctrine.repository_factory:
class: My\Super\RepositoryFactory
Repository Factory:
namespace My\Super;
use Doctrine\ORM\EntityManagerInterface;
use Doctrine\ORM\Repository\DefaultRepositoryFactory;
class RepositoryFactory extends DefaultRepositoryFactory
{
/**
* #inheritdoc
*/
protected function createRepository(EntityManagerInterface $entityManager, $entityName)
{
if ($entityName === Acme\Entities\User::class) {
$metadata = $entityManager->getClassMetadata($entityName);
return new ApplicationRepository($entityManager, $metadata);
}
return parent::createRepository($entityManager, $entityName);
}
}
No doubt implementing LoadClassMetadataSubscriber is a better way.
With current symfony 5.3 and doctrine 2.9.5
In your configuration define the service and doctrine.orm.repository_factory:
doctrine:
orm:
#Replace repository factory
repository_factory: 'MyBundle\Factory\RepositoryFactory'
services:
MyBundle\Factory\RepositoryFactory:
arguments: [ '#router', '#translator', '%kernel.secret%' ]
Add you MyBundle/Factory/RepositoryFactory.php file:
<?php declare(strict_types=1);
namespace MyBundle\Factory;
use Doctrine\ORM\EntityManagerInterface;
use Doctrine\ORM\Repository\RepositoryFactory as RepositoryFactoryInterface;
use Doctrine\Persistence\ObjectRepository;
use Symfony\Component\Routing\RouterInterface;
use Symfony\Contracts\Translation\TranslatorInterface;
/**
* This factory is used to create default repository objects for entities at runtime.
*/
final class RepositoryFactory implements RepositoryFactoryInterface {
/**
* The list of EntityRepository instances
*
* #var ObjectRepository[]
*/
private $repositoryList = [];
/**
* The kernel secret
*
* #var string
*/
private $secret;
/**
* The RouterInterface instance
*
* #var RouterInterface
*/
private $router;
/**
* The TranslatorInterface instance
*
* #var TranslatorInterface
*/
private $translator;
/**
* Initializes a new RepositoryFactory instance
*
* #param RouterInterface $router The router instance
* #param TranslatorInterface $translator The TranslatorInterface instance
* #param string $secret The kernel secret
*/
public function __construct(RouterInterface $router, TranslatorInterface $translator, string $secret) {
//Set router
$this->router = $router;
//Set secret
$this->secret = $secret;
//Set translator
$this->translator = $translator;
}
/**
* {#inheritdoc}
*/
public function getRepository(EntityManagerInterface $entityManager, $entityName): ObjectRepository {
//Set repository hash
$repositoryHash = $entityManager->getClassMetadata($entityName)->getName() . spl_object_hash($entityManager);
//With entity repository instance
if (isset($this->repositoryList[$repositoryHash])) {
//Return existing entity repository instance
return $this->repositoryList[$repositoryHash];
}
//Store and return created entity repository instance
return $this->repositoryList[$repositoryHash] = $this->createRepository($entityManager, $entityName);
}
/**
* Create a new repository instance for an entity class
*
* #param EntityManagerInterface $entityManager The EntityManager instance.
* #param string $entityName The name of the entity.
*/
private function createRepository(EntityManagerInterface $entityManager, string $entityName): ObjectRepository {
//Get class metadata
$metadata = $entityManager->getClassMetadata($entityName);
//Get repository class
$repositoryClass = $metadata->customRepositoryClassName ?: $entityManager->getConfiguration()->getDefaultRepositoryClassName();
//Return repository class instance
//XXX: router, translator and secret arguments will be ignored by default
return new $repositoryClass($entityManager, $metadata, $this->router, $this->translator, $this->secret);
}
}
Then define your MyBundle/Repository/EntityRepository.php:
<?php declare(strict_types=1);
namespace MyBundle\Repository;
use Doctrine\ORM\EntityManagerInterface;
use Doctrine\ORM\EntityRepository as BaseEntityRepository;
use Doctrine\ORM\Mapping\ClassMetadata;
use Symfony\Component\Routing\RouterInterface;
use Symfony\Contracts\Translation\TranslatorInterface;
/**
* EntityRepository
*
* {#inheritdoc}
*/
class EntityRepository extends BaseEntityRepository {
/**
* The RouterInterface instance
*
* #var RouterInterface
*/
protected RouterInterface $router;
/**
* The table keys array
*
* #var array
*/
protected array $tableKeys;
/**
* The table values array
*
* #var array
*/
protected array $tableValues;
/**
* The TranslatorInterface instance
*
* #var TranslatorInterface
*/
protected TranslatorInterface $translator;
/**
* The kernel secret
*
* #var string
*/
protected string $secret;
/**
* Initializes a new LocationRepository instance
*
* #param EntityManagerInterface $manager The EntityManagerInterface instance
* #param ClassMetadata $class The ClassMetadata instance
* #param RouterInterface $router The router instance
* #param TranslatorInterface $translator The TranslatorInterface instance
* #param string $secret The kernel secret
*/
public function __construct(EntityManagerInterface $manager, ClassMetadata $class, RouterInterface $router, TranslatorInterface $translator, string $secret) {
//Call parent constructor
parent::__construct($manager, $class);
//Set secret
$this->secret = $secret;
//Set router
$this->router = $router;
//Set slugger
$this->slugger = $slugger;
//Set translator
$this->translator = $translator;
//Get quote strategy
$qs = $manager->getConfiguration()->getQuoteStrategy();
$dp = $manager->getConnection()->getDatabasePlatform();
//Set quoted table names
//XXX: remember to place longer prefix before shorter to avoid strange replacings
$tables = [
'MyBundle:UserGroup' => $qs->getJoinTableName($manager->getClassMetadata('MyBundle:User')->getAssociationMapping('groups'), $manager->getClassMetadata('MyBundle:User'), $dp),
'MyBundle:Group' => $qs->getTableName($manager->getClassMetadata('MyBundle:Group'), $dp),
'MyBundle:User' => $qs->getTableName($manager->getClassMetadata('MyBundle:User'), $dp),
//XXX: Set limit used to workaround mariadb subselect optimization
':limit' => PHP_INT_MAX,
"\t" => '',
"\n" => ' '
];
//Set quoted table name keys
$this->tableKeys = array_keys($tables);
//Set quoted table name values
$this->tableValues = array_values($tables);
}
}
Then simply extend it in MyBundle/Repository/UserRepository.php:
<?php declare(strict_types=1);
namespace MyBundle\Repository;
/**
* UserRepository
*/
class UserRepository extends EntityRepository {
}

Categories