I'm using Symony 3.3 and Monolog as application logger.
All services are using the injected logger. For example:
public function __construct(LoggerInterface $logger)
{
$this->logger = $logger;
}
public function work() {
$this->logger->info("Some info");
$this->logger->debug("Some debug");
}
It happens that I use these services from both controllers and Symfony commands. What I'd like is to handle the logs differently if they are executed from commands.
For example: I have some commands whose purpose is to process a record and the business requires that I store the processing history (logs) in the database for each job.
For example, the command
(server)$ bin/console process:record 12345
should save the log content into record's table, inside "processing_logs" field.
The question is: how to I buffer and extract the list of logs? Ideally, without changing the services and without changing the controllers.
Related
I'm using Symfony 4 with VichUploaderBundle 1.9 and I'm having hard time injecting the DownloadHandler service in my controller in order to send file to the client.
I'm also using HashidsBundle in order to convert my entity ID to something like jFaJ in my URLs.
As stated in the VichUploaderBundle documentation, I'm injecting the service in my controller like this :
public function download(Wallpaper $wallpaper, DownloadHandler $downloadHandler)
{
return $downloadHandler->downloadObject($wallpaper->getMedia(), 'uploadedFile');
}
Here is the error I'm having:
Argument 2 passed to App\Controller\WallpapersController::download()
must be an instance of Vich\UploaderBundle\Handler\DownloadHandler,
integer given, called in
/mnt/c/Users/user/Documents/Dev/symfony/vendor/symfony/http-kernel/HttpKernel.php
on line 151
I also tried to manually call the service by adding the following line in my controller:
$this->get('vich_uploader.download_handler');
But it's still not working, I have this error now:
Service "vich_uploader.download_handler" not found: even though it exists in the app's container, the container inside "App\Controller\WallpapersController" is a smaller service locator that only knows about the "doctrine", "form.factory", "http_kernel", "parameter_bag", "request_stack", "router", "security.authorization_checker", "security.csrf.token_manager", "security.token_storage", "serializer", "session" and "twig" services. Try using dependency injection instead.
You can return the file using BinaryFileResponse.
public function download(Wallpaper $wallpaper): BinaryFileResponse
{
$file = new BinaryFileResponse($wallpaper->getMedia());
return $file;
}
For more info, check
https://github.com/aythanztdev/prbtcnccd/blob/master/src/Controller/MediaObject/ShowMediaObjectAction.php
I have a service which I use both from a custom command and an HTML page. I want to prevent multiple executions of the the service in parallel. For the command there is the Lock component that does that. But is it possible to achieve the same thing for a controller method ?
The lock component doesn't work if the service is called from a controller:
$store = new FlockStore(sys_get_temp_dir());
$factory = new Factory($store);
$lock = $factory->createLock('MY_SERVICE');
I wanted to avoid calling the command from the controller (that's why I created a service) mainly because the service doesn't have the same output for the HTML page and the CLI.
Inject the lock Factory into your service directly instead of creating the lock in the command AND in the controller.
First you have to install Lock Component:
composer require symfony/lock
Then, for example, you can declare your service like this:
use Symfony\Component\Lock\Factory as LockFactory;
class MyService {
private $lock;
public function __construct(LockFactory $lockFactory) {
$this->lock = $lockFactory->createLock('LOCK_KEY');
}
public function doWork() {
$this->lock->acquire();
try {
// DO THINGS
} finally {
$this->lock->release();
}
}
}
I said:
The lock component doesn't work if the service is called from a controller:
Actually the issue I had was the Symfony built-in dev server which is single-threaded, so requests can't be executed in parallel, while the CLI PHP is multi-threaded. I couldn't run the script in parallel through the dev server, request were queued, service script was never locked.
The lock component is working the same whether it's called from a command or a controller.
Using the lock like this in the service works fine:
use Symfony\Component\Lock\Factory;
use Symfony\Component\Lock\Store\FlockStore;
$store = new FlockStore(sys_get_temp_dir());
$factory = new Factory($store);
$lock = $factory->createLock('LOCK_KEY');
if ($lock->acquire()) {
//some locked code
$lock->release();
}
I am trying to understand how HWIOauthBUndle works. I can see how the initial authorization request to a resource owner is built and made.
I do not see however, how a callback made from a resource owner triggers any controller/action in my application (which it most obviously does, though).
When following the generally available instructions, the callback will be made to something like <path to my app>/check-[resourceOwner], e.g. http://www.example.com/oauth/check-facebook.
In my routing.yml file, I put
facebook_login:
pattern: /oauth/check-facebook
I don't see how any controller is associated with that route, so what actually happens when a callback is made to my application?
The authentication provider system is one of the more complicated features. You will probably want to read through here: http://symfony.com/doc/current/cookbook/security/custom_authentication_provider.html
Callbacks are handled through a request listener. Specifically:
namespace HWI\Bundle\OAuthBundle\Security\Http\Firewall\OAuthListener;
use Symfony\Component\Security\Http\Firewall\AbstractAuthenticationListener;
class OAuthListener extends AbstractAuthenticationListener
{
public function requiresAuthentication(Request $request)
{
// Check if the route matches one of the check paths
foreach ($this->checkPaths as $checkPath) {
if ($this->httpUtils->checkRequestPath($request, $checkPath)) {
return true;
}
}
return false;
}
protected function attemptAuthentication(Request $request)
{
// Lots of good stuff here
How checkPaths get's initialized and how all the calls are made would require a very long explanation. But the authentication provider chapter will get you going.
I am writing a console command which generates data files to be used by external services (for example, a Google feed, inventory feed, etc). Should the location of the generated data files be within the Symfony app? I know they can actually be anywhere, I'm just wondering if there is a standard way to do it.
It's up to you, but it is better to have this path in a parameter. For example you can you have a parameter group related to your command. This allows you to have different configurations depending on the current environment:
parameters:
# /app/config.yml
# #see MyExportCommand.php
my_export_command:
base_path: '/data/ftp/export'
other_command_related_param: true
In your command, get and store those parameters in the initialize function:
// MyExportCommand.php
protected function initialize(InputInterface $input, OutputInterface $output)
{
$this->parameters = $this->getContainer()->getParameter('my_export_command');
}
Finally in your execute function, you can use something like this: ($this->fs is an instance of the Symfony2 Filesystem component)
// execute()
// Write the file
$filePath = $this->parameters['base_path']. '/'. $this->fileName;
$this->fs->dumpFile($filePath, $myContent);
There was 2 questions here saying injecting the whole service container should solve this. But question ... see below (note difference between try 2 & 3) ...
Try 1
public function __construct(SecurityContext $securityContext) {
$this->securityContext = $securityContext);
}
Curcular Reference. Okay ...
Try 2
public function __construct(ContainerInterface $container) {
$this->securityContext = $container->get('security.context');
}
Circular Reference (Why?, I am injecting the container like in try 3 except I got the security context only)
Try 3
public function __construct(ContainerInterface $container) {
$this->container = $container;
}
Works.
This happens because your security context depends on this listener, probably via the entity manager being injected into a user provider. The best solution is to inject the container into the listener and access the security context lazily.
I typically don't like injecting the entire container into a service, but make an exception with Doctrine listeners because they are eagerly loaded and should therefore be as lazy as possible.
As of Symfony 2.6 this issue should be fixed. A pull request has just been accepted into the master. Your problem is described in here.
https://github.com/symfony/symfony/pull/11690
As of Symfony 2.6, you can inject the security.token_storage into your listener. This service will contain the token as used by the SecurityContext in <=2.5. In 3.0 this service will replace the SecurityContext::getToken() altogether. You can see a basic change list here: http://symfony.com/blog/new-in-symfony-2-6-security-component-improvements#deprecated-the-security-context-service
Example usage in 2.6:
Your configuration:
services:
my.listener:
class: EntityListener
arguments:
- "#security.token_storage"
tags:
- { name: doctrine.event_listener, event: prePersist }
Your Listener
use Doctrine\ORM\Event\LifecycleEventArgs;
use Symfony\Component\DependencyInjection\ContainerInterface;
use Symfony\Component\Security\Core\Authentication\Token\Storage\TokenStorageInterface;
class EntityListener
{
private $token_storage;
public function __construct(TokenStorageInterface $token_storage)
{
$this->token_storage = $token_storage;
}
public function prePersist(LifeCycleEventArgs $args)
{
$entity = $args->getEntity();
$entity->setCreatedBy($this->token_storage->getToken()->getUsername());
}
}
The reason "2" fails and "3" does not is because in option 2 you are trying to access the security context immediately from the container when it is likely not populated yet.
As best I can tell, Symfony2 parses through the config and instantiates the service one after the other and then moves onto the handling the rest of the request.
This means you cannot necessarily access the various parts of the container because it may be loading them in a different order. So you have the memory pointer to the container, and store that, but then let the framework finish building the full container before you try to access parts of it. A notable exception to this is when you directly inject the service into another service, at which point the container is making sure it has that service loaded first.
You can see the effects of this by making two services. A and B. A is passed B, and B is passed A. Now you have a circular reference. If you instead passed the container into both A and B, you could not access A from B and B from A without a problem.
You should always try to avoid injecting container directly to your services.
I think the best possible solution to the «circular reference» problem as well as to possible performance issues, would be to use «Lazy Services» feature available starting from Symfony 2.3.
Just mark you dependency as lazy in your service container configuration and install ProxyManager Bridge (look for details in Lazy Services documentation above).
I hope that helps, cheers.