Get twilio instance in FOSUserBundle event - symfony

I am trying to send SMS to users who register in my website through Twilio, I got the vresh/twilio-bundle and it works fine.
I am trying to pass twilio instance to the event but I think I am missing something, here is what I am doing:
In config.yml i set the servide like this:
services:
registration.completed.listener:
class: Jaguar\AloBundle\EventListener\RegistrationEventListener
arguments:
entityManager: ["#doctrine.orm.voipswitch_entity_manager", "vresh_twilio"]
tags:
- { name: kernel.event_subscriber, event: performOnRegistrationCompleted }
I have declared the twilio config:
vresh_twilio:
sid: 'xxx'
authToken: 'xxx'
version: '2010-04-01'
retryAttempts: 3
Then, in my method I try to get the instance:
public function performOnRegistrationCompleted(UserEvent $event)
{
$twilio = $event->get('vresh_twilio');
}
But it fails...
Any help on this, please?
Thanks a lot!

There are a few issues with your service setup.
You are not actually passing the Twilio instance as you have no # sign preceding the service name. #vresh_twilio is a service, vresh_twilio is just a string.
You are passing in an associative array with a key of entityManager and a value that is also an array with the values of the service #doctrine.orm.voipswitch_entity_manager and the string vresh_twilio.
You're not passing the Twilio instance in your event you are building a listener with the Twilio instance in the constructor.
Your service should actually look like...
services:
registration.completed.listener:
class: Jaguar\AloBundle\EventListener\RegistrationEventListener
arguments:
entityManager: "#doctrine.orm.voipswitch_entity_manager"
twilio: "#vresh_twilio"
// Or
// - #doctrine.orm.voipswitch_entity_manager
// - #vresh_twilio
// Or
// [#doctrine.orm.voipswitch_entity_manager, #vresh_twilio]
//
// As they all mean the same thing and the keys aren't
// used in your actual service __construct
tags:
- { name: kernel.event_subscriber, event: performOnRegistrationCompleted }
This would mean your listener would then have a constructor to receive those services like..
protected $entityManager;
protected $twilio;
public function __conctruct(ObjectManager $entityManager, TwilioWrapper $twilio)
{
$this->entityManager = $entityManager;
$this->twilio = $twilio;
}
Meaning that you could then call it in your class using $this->twilio.
Also, from looking at the services that the Vresh\TwilioBundle creates it looks like the service that you would want to be injecting would be #twilio.api rather than #vresh_twilio as it doesn't seem to exist but I may be wrong there (I haven't used the bundle myself).

Related

Symfony existing public service throws error message "You have requested a non-existing service"

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!');
}

how to pass username to rollbar via monolog, symfony2

I am using rollbar.com to collect all details about exceptions in symfony2 app. However I don't understand how can I configure monolog so it would pass username and user id to rollbar.
I see that I can pass rollbar config as shown here and I am thinking person_fn is what I need. Still I don't know where to put this function (this should be in service because I need to check security token) and how to pass it to rollbar.
# config_prod.yml
rollbar:
type: rollbar
level: error
token: %rollbar_token%
config:
person_fn: getUserForRollbarRightAboutNowOrSomething
Found solution:
update monolog/monolog bundle to at least 1.17.0 version.
create ContextProcessor and update user information
#src/AppBundle/Monolog/RollbarContextProcessor
namespace AppBundle\Monolog;
use AppBundle\Entity\User;
use Symfony\Component\Security\Core\Authentication\Token\Storage\TokenStorageInterface;
class RollbarContextProcessor
{
private $tokenStorage;
public function __construct(TokenStorageInterface $tokenStorage)
{
$this->tokenStorage = $tokenStorage;
}
public function processRecord($record)
{
if ($this->tokenStorage->getToken()) {
$user = $this->tokenStorage->getToken()->getUser();
if ($user instanceof User) {
$record['context']['payload']['person'] = [
'id' => $user->getId(),
'username' => $user->getUsername(),
'email' => $user->getEmail(),
];
}
}
return $record;
}
}
configure ContextProcessor as service with monolog.processor tag.
# app/config/config_prod.yml
services:
monolog.processor.rollbar_context:
class: AppBundle\Monolog\RollbarContextProcessor
arguments: [#security.token_storage]
tags:
- { name: monolog.processor, method: processRecord, handler: rollbar }
monolog:
handlers:
rollbar:
type: rollbar
level: error
token: %rollbar_token%
Your question has two parts:
Rollbar
person_fn is exactly what you need. You should be able to add a reference to the function by using a string (e.g.: "MyClass::static_function_reference" or "my_function_name").
Symfony
Disclaimer: I don't use or know much about Symfony.
This question has some excellent examples of how to get the current user in Symfony. (Punch line: in a controller you can call $this.getUser())
This question has a good example of how to inject the current user in a service. (Make a Twig Extension that depends on the SecurityContext or TokenStorage, use those dependencies to get a user objet).
Finally, there's the classic PHP move: as soon as you have a user add it to $_REQUEST. I'm not sure if Symfony co-opts this, but it'd be a valid way in a non-framework PHP application.

HWIOAuth + FOSUser password redirection

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

In Symfony2 how can I use a Service within a Factory class?

I am trying to setup a Symfony implementation of this PHP library for Chargify https://github.com/johannez/chargify
I'm getting a bit lost working out the best / proper way to set it all up.
I think I need to setup Guzzle as a service, then create a Chargify factory and have that added as a service.
My problem is that in the factory class, when I try and use the Guzzle service I get a fatal error
Fatal error: Using $this when not in object context in /symfony/src/Acme/ChargifyBundle/Factory/ChargifyFactory.php on line 8
This is my Factory class
<?php
namespace Acme\ChargifyBundle\Factory;
class ChargifyFactory implements ChargifyFactoryInterface
{
public static function build($type)
{
$client = $this->get('chargify.guzzle.client');
$className = 'Chargify\\Controller\\' . ucfirst($type);
if (class_exists($className)) {
return new $className($client);
}
else {
throw new Exception("Invalid controller type given.");
}
}
}
If it's useful to see some config, this is my services.yml for the bundle
services:
chargify.guzzle.client.curl_auth:
class: %guzzle.plugin.curl_auth.class%
arguments:
api_key: %chargify_api_key%
chargify.guzzle.client:
class: %guzzle.client.class%
tags:
- { name: guzzle.client }
calls:
- [setBaseUrl, [%chargify_domain%]]
- [addSubscriber, [#chargify.guzzle.client.curl_auth]]
argument: %chargify_domain%
chargify.factory:
class: Acme\ChargifyBundle\Factory\ChargifyFactory
arguments:
- ["type"]
chargify.customer:
class: Acme\ChargifyBundle\Controller\CustomerController
factory_class: Acme\ChargifyBundle\Factory\ChargifyFactory
factory_method: build
arguments:
type: "customer"
How can I use the guzzle client in the Factory with out using
$client = $this->get('chargify.guzzle.client');
EDIT:
I have changed the code as per #alex's answer, but I'm still getting an error. I think this is because the function is static. I've looked though the documents, but I can't see where I can setup a factory without a static function, and when I get rid of static I get a different error.
Runtime Notice: Non-static method Acme\ChargifyBundle\Factory\ChargifyFactory::build() should not be called statically, assuming $this from incompatible context
That is being thrown from some generated code
protected function getChargify_CustomerService()
{
return $this->services['chargify.customer'] = \Acme\ChargifyBundle\Factory\ChargifyFactory::build('customer');
}

Best practice to implement Factory pattern using Symfony2

I am making a messenger which can send email messages or sms messages, and has the possibility to send them now or send them later (the information is saved in the DB). I've made 2 solutions, but neither is satisfying me.
I'm centralising the code in one Factory, and the code of the Factory pattern is very easy:
class MessageFactory
{
static public function get($type,$em)
{
$instance = null;
switch ($type) {
case 'email':
$instance = new EmailMessage($em);
break;
....
return $instance;
}
class EmailMessage implements MessangerInterface
{
...
public function send( $eMessage,array $receivers, $time=NULL)
{
interface MessangerInterface
{
public function send($message,array $receivers);
}
1st solution: Just call as an ordinary static method
$messanger = Factory\MessageFactory::get('email',$em);
$messanger->send($eMessage, array('tom'=>'tom#gmail.com'));
This is a bad solution, because I need to pass in a Doctrine Manager as a parameter to the method
2nd solution: To use it as a Symfony 2 Service
services:
my.messanger:
class: Bundle\Factory\MessangerInterface
factory_class: Bundle\Factory\MessageFactory
factory_method: get
arguments:
messanger_type: %messanger.type%
and also pass in Doctrine as an argument. But using such a solution I can't choose messanger.type in my code, it's defined using a configuration parameter as email or sms; I need to have the capability in code to choose the type.
Also I have a problem that inside the class I need to send email or sms, and that means that I need an external service, getting it like this:
class EmailMessage implements MessangerInterface
{
if ('AppCache' == get_class($kernel)) {
$kernel = $kernel->getKernel();
}
$kernel->getContainer()->get('mailer')->send($eMessage);
which seems like very bad practice.
Please, are you able to advise me on any better solutions?
I want to follow the "thin controller fat model" concept.
It seems like option 2, using Symfony 2 Services, would be best.
I considered suggesting that you let the Factory be the Service, and pass the type in to get the Messenger instance, rather than fixing it in config, but if what you want is to only have one of each type of Messenger then that's unhelpful (the Factory would keep creating more and more Messengers). So instead I think you need to define two Services, one for each Messenger.
And if you don't want to have to fetch another Service within your Messenger, you need to inject that in when you get the Messenger.
e.g.
services:
mailer:
class: Mailer
smser:
class: SMSer
email.messanger:
class: Bundle\Factory\MessangerInterface
factory_class: Bundle\Factory\MessageFactory
factory_method: get
arguments:
messanger_type: email
sender: #mailer
sms.messanger:
class: Bundle\Factory\MessangerInterface
factory_class: Bundle\Factory\MessageFactory
factory_method: get
arguments:
messanger_type: sms
sender: #smser
And your Factory needs to accept the new $sender argument:
class MessageFactory
{
static public function get($type,$em,$sender)
{
$instance = null;
switch ($type) {
case 'email':
$instance = new EmailMessage($em, $sender);
break;
....
return $instance;
}
interface MessangerInterface
{
public function send($message,$sender, array $receivers);
}
Then when you call it, you ask for either of the Messengers specifically:
$this->get('email.messenger')->send($emailMessage);
$this->get('sms.messenger')->send($smsMessage);

Resources