Symfony2 use Doctrine in ExceptionController - symfony

Hi I'm trying to use Doctrine inside the default ExeptionController but I get the following error:
Fatal error: Call to undefined method Symfony\Bundle\TwigBundle\Controller\ExceptionController::getDoctrine()
when I try to call:
$manager = $this->getDoctrine()->getManager();
What I'm trying to do is to have a custom 404 page where I can present some items from the database.
Could you please help me? Thank you!

You may also inject the Doctrine service as a dependency in your Controller (in that case you don't need to entend class Controller)

You will have to create your own ExceptionController extending the default one. You'll have to declare it as described here: http://symfony.com/doc/current/cookbook/controller/error_pages.html#custom-exception-controller. Your custom controller must have a constructor with at least an argument of type Registry (Doctrine). You have to declare that controller as a service in your service.yml (or xml depending on your config) Have a look at the symfony doc for further explanation on how to do that. For the moment I can't help you much more as I'm outside with my Android and it's rather difficult to make long answers

Related

Fix circular reference in symfony when using SerializerInterface

I'm getting a circular reference error when serializing a component. Usually this can be fixed using
$normalizer->setCircularReferenceHandler()
However, I'm using the SerializerInterface like this:
/**
* #Route("/get/{id}", name="get_order_by_id", methods="GET")
*/
public function getOrderById(SerializerInterface $serializer, OrderRepository $orderRepository, $id): Response
{
return new Response($serializer->serialize(
$orderRepository->find($id),
'json',
array('groups' => array('default')))
);
}
Is it possible to fix a circular reference error when serializing using this interface?
You totally can. Just add this in your framework config.
framework:
serializer:
circular_reference_handler: App\Serializer\MyCustomCircularReferenceHandler
This handler will work globally. Make sure you register it as a service. I does not need to implement any interface. So just a class with an __invoke() will suffice. That invoke will receive the object that is being "circle referenced" as the only argument.
You can either return the id or do some really cool stuff, like creating a uri for the resource. But the implementation details are totally up to you, as long as you don't return the same object, everything will be fine.
:)
According to the Symfony API Reference on the interface there doesn't look to be a way to execute that function or retrieve the normalizer.
Even in the Serializer, there doesn't look to be a way to retrieve the normalizer after creating the serializer.
You're best off creating the normalizer before the serializer to achieve this, rather than injecting the interface via config files. (Relevant docs link)

Symfony3 Docs WSSE fail "Cannot replace arguments if none have been configured yet"

Following this
http://symfony.com/doc/current/security/custom_authentication_provider.html
Results in
Service "security.authentication.provider.wsse.wsse_secured": Cannot replace arguments if none have been configured yet.
I cannot find anything about this error anywhere. This uses the doc's WSSE code and it fails.
This repo shows it failing https://github.com/jakenoble/wsse_test
I want to get it working eventually with the FOS User Bundle. But I cannot get it to work with a basic Symfony3 install so FOS User Bundle is out of the question at the moment.
Having dug around a bit...
There is a an arg at element index_0 on class Symfony\Component\DependencyInjection\ChildDefinition the object for the arg at element index_0 has an id of fos_user.user_provider.username_email.
The replace call then attempts to get the arguments of fos_user.user_provider.username_email, but there are none. Then the error occurs.
Any ideas?
TL;DR Move your service definitions below the autoloading definitions in services.yml and change the WsseFactory code to
$container
->setDefinition($providerId, new ChildDefinition(WsseProvider::class))
->setArgument('$userProvider', new Reference($userProvider))
;
Full explanation.
The first mistake in the supplied code is that the services definitions prepend the autoloading lines. The autoloading will just override the previous definitions and will cause the AuthenticationManagerInterface failure. Moving the definitions below will fix the issue. The other way to fix the issue is aliases as #yceruto and #gintko pointed.
But only that move will not make the code work despite on your answer. You probably didnt notice how changed something else to make it work.
The second issue, the failure of replaceArgument, is related to Symfony's order of the container compilation as was correctly supposed. The order is defined in the PassConfig class:
$this->optimizationPasses = array(array(
new ExtensionCompilerPass(),
new ResolveDefinitionTemplatesPass(),
...
$autowirePass = new AutowirePass(false),
...
));
I omitted the irrelevant passes. The security.authentication.provider.wsse.wsse_secured definition created by the WsseFactory is produced first. Then ResolveDefinitionTemplatesPass will take place, and will try to replace the arguments of the definition and raise the Cannot replace arguments exception you got:
foreach ($definition->getArguments() as $k => $v) {
if (is_numeric($k)) {
$def->addArgument($v);
} elseif (0 === strpos($k, 'index_')) {
$def->replaceArgument((int) substr($k, strlen('index_')), $v);
} else {
$def->setArgument($k, $v);
}
}
The issue will appear cause the pass will call Definition::replaceArgument for index_0. As the parent definition doesn't have an argument at position 0 neither in the former services.xml nor in the fixed one. AutowirePass wasn't executed yet, the autogenerated definitions has no arguments, the manual definition has the named $cachePool argument only.
So to fix the issue you could use rather:
->setArgument(0, new Reference($userProvider)); //proposed by #yceruto
->replaceArgument('$userProvider', new Reference($userProvider)); //proposed by #gintko
->setArgument('$userProvider', new Reference($userProvider)); // by me
All of them will entail the calls of Definition::addArgument or Definition::setArgument and will work out. There are only the little difference:
- setArgument(0, ... could not work for some other scenarios;
- I like ->setArgument('$userProvider' more than ->replaceArgument('$userProvider' due to semantics. Nothing to replace yet!
Hope the details why the issue appears now clear.
PS. There are also few other funny ways to overcome the issue.
Fix the config a bit:
AppBundle\Security\Authentication\Provider\WsseProvider:
arguments:
0: ''
$cachePool: '#cache.app'
public: false
Or set an alias of Symfony\Component\Security\Core\User\UserProviderInterface to let the autowire do the rest for you.
$container
->setDefinition($providerId, new ChildDefinition(WsseProvider::class))
// ->replaceArgument(0, new Reference($userProvider))
;
$container
->setAlias('Symfony\Component\Security\Core\User\UserProviderInterface',$userProvider)
;
This look like at this moment the provider definition doesn't have the autowired arguments ready, maybe related to the order in which the "CompilerPass" are processed, by now you can solve it with these little tweaks:
change this line in WsseFactory.php:
->replaceArgument(0, new Reference($userProvider))
by:
->setArgument(0, new Reference($userProvider))
and add this alias to services.yml to complete the autowired arguments of the new provider:
Symfony\Component\Security\Core\Authentication\AuthenticationManagerInterface: '#security.authentication.manager'
EDIT: (deleted the previous contents due to the comment)
I see you don't use FOSUserBundle that makes things more comples. I did not yet create PR.
I think you are missing custom user entity. (see the link I have provided to create your own entity - my PR would be similar to these lines, if you are unable to create your own entity using my description and the link I'll provide PR, but that will take longer).
The steps you have to take:
Create your own User entity via src/AppBundle/Entity/User.php
Create DB table via php bin/console doctrine:schema:update --force
Configure Security to load your Entity - use AppBundle\Entity\User;
Then you have to create your own user. This can be tricky see password encoding for more.
(optional) Forbid interactive users If you want to login using
username OR email you can create Custom Query to Load the User
These steps should be enough for you to create your own user Entity and you don't have to use the FOSUserBundle.
EDIT:
Ok, so read some source code, and in ChildDefinition:100 you can see, that arguments are also indexed by argument's $name. So, it must be, that at compile time arguments of autowired services are passed with their named indexes, instead of numbered indexes.
WsseFactory.php
->replaceArgument(0, new Reference($userProvider));
so argument should be referenced by it's name:
->replaceArgument('$userProvider', new Reference($userProvider));
After this change, you will get new exception error, which says that it's not possible to autowire $authenticationManager in WsseListener service by it's interface. You can fix it simply by specifying alias for that interface in your services.yml:
Symfony\Component\Security\Core\Authentication\AuthenticationManagerInterface: '#security.authentication.manager'
I guess this issue is related with new autowire feature, I will try to investigate it later why it is so.
For now, you can use old way to define services:
services.yml:
app.security.wsse_provider:
class: AppBundle\Security\Authentication\Provider\WsseProvider
arguments:
- ''
- '#cache.app'
public: false
app.security.wsse_listener:
class: AppBundle\Security\Firewall\WsseListener
arguments: ['#security.token_storage', '#security.authentication.manager']
public: false
in WsseFactory.php, following lines:
new ChildDefinition(WsseFactory::class);
new ChildDefinition(WsseListener::class);
translates into:
new ChildDefinition('app.security.wsse_provider');
new ChildDefinition('app.security.wsse_listener');
It seems the issue is because my config in services.yml came before the new auto wiring stuff.
So simply moving it below the auto wiring fixes it.

Symfony2: Best way for logging from anywhere

I am working on my first Symfony project. I wonder what is the best / recommendet method to write log messages from anywhere in my code.
So far I used Monolog which works great when being used in a controller:
public function indexAction() {
$logger = $this->get('logger');
$logger->info('I just got the logger');
$logger->error('An error occurred');
// ...
}
But how can I use this code from any classe/code from my project? Doctrine entity classes for example cannot use $this->get('logger') to create the logger. How can I access the service in these classes? Or what other methode to log message is recommended in these cases?
EDIT: Of course I could create the logger in any controller and pass it down to all other classes. But this would be quite very cumbersome. There has to be a better way.
IMO, a first approach could be the creation of Event Listeners for specific actions in order to log only what you have decided to.
Have a look to this chapter : http://symfony.com/doc/current/cookbook/event_dispatcher/event_listener.html
Hope it will help you.

Automatic Localization in symfony2

Please look at the following code:
public function __construct($error_code)
{
$translator = new Translator('en');
$translator->addLoader('yaml', new YamlFileLoader());
$translator->addResource('yaml', dirname(__DIR__).'/Resources/translations/messages.en.yml', 'en');
$this->setErrorCode($translator->trans($error_code));
}
I am new to symfony. I have created a class MyProjectExceptions which extends Exception. Now when I have to throw a custom exception I call this class where I get the $error_code. Now this $error_code is a constant of another class which has its locale in MyBundle/Resources/transalations/messages.en.yml which will be used to throw as exception message.
Now my question are following:
How can I avoid addResource, so it can automatically add it based on Locale and find the string?
How to access serviceContainer in this class so that I can access session to set and get locales OR other services.
Can we set the default Loader as well.
In above code I am creating an instance of Translator class and manually passing 'en'. but it should pick default locale or user set locale.
I tried many solutions but not able to get the desired results.
Any help would be appreciated. Thanks in advance.
You need to register your class as Symfony service. Read the documentation: http://symfony.com/doc/current/book/service_container.html#creating-configuring-services-in-the-container
After that you can inject other services (like Translation) in your constructor. It will use all parameters that you have already set.
If you inject translator service it will pick the parameters that you have already set. For example, if you defined parameters for translator (including default locale) at config.yml, then you overrode this locale with parameter in route, you will get translator service set up with this locale. And it will automatically use resources that are lied in appropriate directories.

Symfony2 Set Controller in the kernelControllerEvent using bundle:controller:action notation

I am trying to do something like the following question:
Trying to swap a controller using an event listener with Symfony2
However, when I use the code (as recommended in the answer):
$event->setController('MyMainBundle:Manage:show');
I just get an error:
LogicException: The controller must be a callable (MyMainBundle:Manage:show given).
Is there a way to use that Bundle:Controller:Method syntax in setController? Or maybe some other method I can call to resolve that to a "callable"?
What you should give to $event->setController is a callable.
What you give a string representing the logical path to a callable.
You can resolve this string using symfony's ControllerResolver.
You have to inject the controller_resolver service in your listener, and then use it like this:
$request = new Symfony\Component\HttpFoundation\Request();
$request->attributes->set('_controller', 'MyMainBundle:Manage:show'));
$event->setController($this->resolver->getController($request));
But you are clearly doing the framework's job here.

Resources