I have a some Controllers, Twig Extensions and other classes that all need caching. I'm using Redis as a cache. Currently I setup a new RedisCache in each of these places, like so:
public function __construct(LoggerInterface $logger)
{
$this->logger = $logger;
$this->cache = new RedisCache(RedisAdapter::createConnection(getenv('REDIS_URL')), 'ImageHelper');
}
But this creates many connections to the Redis backend, which I believe is not good for performance.
What is the best way to either share a RedisConnection/Client between my Controllers/extensions/classes, or even share the RedisCache, but keep namespacing ability?
I'm using Symfony 4.
You should make the Redis cache a service, and inject it into your controllers (or other dependents) later on. Refer to Symfony's dependency injection docs if needed.
Here's an example, you might need to fine-tune it later on:
# config/services.yaml
services:
redis_connection:
class: 'RedisConnection'
factory: ['RedisAdapter', createConnection]
arguments:
- '%env(REDIS_URL)%'
redis_cache:
class: 'RedisCache'
arguments:
- '#redis_connection'
Note that I don't know which namespaces your classes have, so you'd have to adjust the config accordingly.
And then you would also have to set up the framework to inject the service to your controllers (or other dependents):
# config/services.yaml
services:
# ...
App\Controller\ExampleController:
arguments:
- '#logger'
- '#redis_cache'
Also, you should update the controllers to accept the new argument in constructor, like so:
public function __construct(LoggerInterface $logger, RedisCache $cache)
{
$this->logger = $logger;
$this->cache = $cache;
}
Refer to the service container documentation if you have any questions.
Related
Here is a method signature in the controller :
public function myAction(Request $request, SessionInterface $session, LoggerInterface $logger)
This is what I wrote in services.yml :
AppBundle\Controller\:
resource: '../../Controller'
public: true
tags: ['controller.service_arguments']
Normally, all of the parameters (request, session and logger) are automatically injected.
I call this action of the controller using Ajax but there is a problem for logger (request and session are well injected):
Controller myAction requires that you provide a value for the "$logger" argument
This happens for one method only because the ajax calls to some other methods of the controller work perfectly.
The only difference I can see using some debug tools is that the one that does not work has connection : close though the others have connection: keep-alive.
I dont see why it is done differently.
When I erase the LoggerInterface parameter, it works good and connection is keep-alive like the others.
Can you help ?
Symfony 3 platform
services:
_defaults:
# automatically injects dependencies in your services
autowire: true
# automatically registers your services as commands, event subscribers, etc.
autoconfigure: true
# this means you cannot fetch services directly from the container via $container->get()
# if you need to do this, you can override this setting on individual services
public: false
I am trying to make a link that resends a confirmation token to a user after registering in Symfony3.
However, I get a deprecation message as follows:
User Deprecated: The "fos_user.mailer" service is private, getting it
from the container is deprecated since Symfony 3.2 and will fail in
4.0. You should either make the service public, or stop using the container directly and use dependency injection instead.
Here is my controller:
public function resendActivationEmail($token, UserManagerInterface $userManager)
{
$user = $userManager->findUserByConfirmationToken($token);
if (is_null($user)) {return $this->redirectToRoute('fos_user_registration_register');}
$mailer = $this->get('fos_user.mailer');
$mailer->sendConfirmationEmailMessage($user);
return $this->redirectToRoute('fos_user_registration_check_email');
}
My services.yml:
services:
# default configuration for services in *this* file
_defaults:
autowire: truesubscribers, etc.
autoconfigure: true
public: false
I looked into the docs, it says that in Symfony3.4, services are private by default. I am using autowiring in my app, so how I get the fos_user.mailer without any deprecation warnings?
I tried setting Fosuserbundle services to public, doesnt help:
services.yml:
....
FOS\UserBundle:
public: true
Any help appreciated!
It's better to use DependencyInjection instead of call container directly. You should pass your mailer to your method:
public function resendActivationEmail($token, UserManagerInterface $userManager, \FOS\UserBundle\Mailer\MailerInterface $mailer)
{
$user = $userManager->findUserByConfirmationToken($token);
if (is_null($user)) {return $this->redirectToRoute('fos_user_registration_register');}
$mailer->sendConfirmationEmailMessage($user);
return $this->redirectToRoute('fos_user_registration_check_email');
}
For more informations about dependencyInjection : https://symfony.com/doc/3.4/service_container/injection_types.html
Use $mailer = $this->container->get('fos_user.mailer');
instead of $mailer = $this->get('fos_user.mailer');
I am starting to work with services in Symfony and therefore created the example service from the symfony documentation:
namespace AppBundle\Service;
use Psr\Log\LoggerInterface;
class MessageGenerator
{
private $logger;
public function __construct(LoggerInterface $logger){
}
public function getMessage()
{
$this->logger->info('Success!');
}
}
I call that service in my controller (I also have the use Statement:
: use AppBundle\Service\MessageGenerator;
$messageGenerator = $this->get(MessageGenerator::class);
$message = $messageGenerator->getMessage();
$this->addFlash('success', $message);
My service is defined in the services.yml file:
app.message_generator:
class: AppBundle\Service\MessageGenerator
public: true
so in my eyes I did everything exactly as described in the documentation and when calling:
php app/console debug:container app.message_generator
in my commandline I get my service:
Option Value
------------------ ------------------------------------
Service ID app.message_generator
Class AppBundle\Service\MessageGenerator
Tags -
Scope container
Public yes
Synthetic no
Lazy no
Synchronized no
Abstract no
Autowired no
Autowiring Types -
Now when I execute the controller function where I call my service I still get the error:
You have requested a non-existent service "appbundle\service\messagegenerator".
Any ideas?
Symfony is a bit confusing at naming: you retrieve the service by requesting it by its defined name: app.message_generator.
$messageGenerator = $this->get('app.message_generator');
Symfony has recently suggested switching from a give-name (app.message_generator) that you are defining the service as, to the class name (AppBundle\Service\MessageGenerator). They are both just 'a name' to call the service.
You are trying to use both, when only the given name is defined.
In the long term, it's suggested to use the ::class based name, and quite possibly allow the framework to find the classes itself, and configure them itself too. This means that, by default, all services are private, and are handled by the framework & it's service container.
In the meantime, while you are learning, you can either:
$messageGenerator = $this->get('app.message_generator');
or define explicitly define the service, and make it public, so it can be fetched with ->get(...) from the container.
# services.yml
AppBundle\Service\MessageGenerator:
class: AppBundle\Service\MessageGenerator
public: true
# php controller
$messageGenerator = $this->get(MessageGenerator::class);
or just injected automatically into the controller, when that is requested
public function __construct(LoggerInterface $logger, MessageGenerator $msgGen)
{
$this->messageGenerator = $msgGen;
}
public function getMessage()
{
$result = $this->messageGenerator->do_things(....);
$this->logger->info('Success!');
}
I used this version to implement the 2 bundles. However, I want the user to set a password for his account so after successful authentification and user creation, he'll be refirected to a form where he'll enter that. Since FOSUBUserProvider is a service, I was thinking to make another service that will handle the password form. I injected the second service into the first one but I need #templating which I've set up as a parameter but I have no idea how to take it and I'm getting a warning that he's missing. How do I solve this?
# /FOSUBUserProvider
$passwordSetter = Controller::get('register_social_password_picker');
I understand that it needs a second parameter(for templating), where do I take it from? Or am I approaching this the wrong way?
class RegisterPassword
{
protected $user;
protected $templating;
public function __construct($user, $templating)
{
$this->user = $user;
$this->templating = $templating;
}
...
}
services.yml
my_user_provider:
class: AppBundle\Security\Core\FOSUBUserProvider
arguments: [#fos_user.user_manager,{facebook: facebook_id, twitter: twitter_id, linkedin: linkedin_id}, "#register_password" ]
register_password:
class: AppBundle\Service\RegisterPassword
arguments: ["#templating" ]
LE:
services.yml
register_password:
class: AppBundle\Service\RegisterPassword
arguments: [ setMailer, ["#templating"] ]
class RegisterPassword
{
protected $user;
protected $templating;
public function __construct( )
{
}
public function setPassword(User $user, $templating)
{
$this->templating = $templating;
...
}
Your problem is that the service constructor is expecting two arguments ($user and $templating) and you are passing only one (#templating). These parameters are passed when the service is built, so my guess is that you do not want $user to be a parameter for the constructor (at this point you do not know which user is going to use the service), so just pass the $templating parameter and you can add the user later on with a different call.
Since this seems to generate a circular reference problem, you may use one of three different strategies:
Inject the full service container and get the templating service when you need it (not recommended)
Use setter injection (http://symfony.com/doc/current/components/dependency_injection/types.html)
Since this calls the setter method after the service is built my guess is that the circular reference will be avoided (not 100% sure)
If this does not work, create the setter method but do not call it at service construction point but rather call it later when you want to use the service
However, the approach I tried was a little off, the redirection after login can be easily set in security.yml>security>firewalls>firewall_name>default_target_path
Mine looks like:
default_target_path: /redirect
I'm creating a service that uses ImageWorkshop. In order to init a new image, I need to call:
$layer = ImageWorkshop::initFromPath(__DIR__.'/../path/to/myimage.jpg');
I'd like to inject ImageWorkshop as a dependency, but I can't figure out how to do this since it uses static methods. I know I could just call ImageWorkshop statically from my service, but I'm trying to declare my dependencies.
That's the perfect use case for service factories.
You declare your $layer as a service and create it with the static factory method in the service container.
services:
myimage_layer:
class: PHPImageWorkshop\Core\ImageWorkshopLayer
factory_class: PHPImageWorkshop\ImageWorkshop
factory_method: initFromPath
arguments:
- "%kernel.root_dir%/../path/to/myimage.jpg"
Now you can inject the myimage_layer service into your service as a service argument.
EDIT: If you need the ImageWorkshop directly to call them, but don't want to write ImageWorkshop::initFromPath('...') directly in your code, you can decouple it with the class name. It's not really useful, because ImageWorkshop is not directly replaceable, but it helps for mocking in tests.
services:
myimage_whatever:
class: Acme\Bundle\AcmeBundle\Image\Whatever
arguments:
- "PHPImageWorkshop\\ImageWorkshop"
Your service:
namespace Acme\Bundle\AcmeBundle\Image;
class Whatever
{
private $imageWorkshop;
public function __construct($imageWorkshop)
{
$this->imageWorkshop = $imageWorkshop;
}
public function doWhatever($path)
{
$layer = $this->imageWorkshop::initFromPath($path);
// ...
}
}
Beware yourself, $imageWorkshop is no instance. Instead it's a string containing the fully qualified class name of ImageWorkshop to call the static method on it. I hope this should work.
Reference for calling a static method on a string variable containing the class name: http://php.net/manual/en/language.oop5.static.php#example-214
I would create a wrapper class and implement the static class methods in it
e.g
Class ImageWorkshopWrapper
{
public function initFromPath($path)
{
ImageWorkshop::initFromPath($path);
}
}
and inject ImageWorkshopWrapper class