symfony[2.8] how to implement an interface in a service - symfony

Here is my code for my class listener :
<?php
namespace AppBundle\EventSubscriber;
use Lolautruche\PaylineBundle\Event\PaylineEvents;
use Lolautruche\PaylineBundle\Event\ResultEvent;
use Psr\Log\LoggerInterface;
use Symfony\Component\EventDispatcher\EventSubscriberInterface;
class PaymentListener implements EventSubscriberInterface
{
private $logger;
public function __construct(LoggerInterface $logger)
{
$this->logger = $logger;
}
public static function getSubscribedEvents()
{
return [
PaylineEvents::WEB_TRANSACTION_VERIFY => 'onTransactionVerify',
];
}
public function onTransactionVerify(ResultEvent $event)
{ break;
// You can access to the result object from the transaction verification.
/** #var \Lolautruche\PaylineBundle\Payline\PaylineResult $paylineResult */
$paylineResult = $event->getResult();
$transactionId = $paylineResult->getItem('[transaction][id]');
if (!$paylineResult->isSuccessful()) {
break;
if ($paylineResult->isCanceled()){
$this->logger->info("Transaction #$transactionId was canceled by user", ['paylineResult' => $paylineResult->getResultHash()]);
}
elseif ($paylineResult->isDuplicate()){
$this->logger->warning("Transaction #$transactionId is a duplicate", ['paylineResult' => $paylineResult->getResultHash()]);
}
else {
$this->logger->error("Transaction #$transactionId was refused by bank.", ['paylineResult' => $paylineResult->getResultHash()]);
}
return;
}
break;
// Transaction was validated, do whatever you need to update your order
// ...
// Assuming you have set a private data with "internal_id" key when initiating the transaction.
$internalId = $paylineResult->getPrivateData('idCommande');
$repoCommande = $this->getDoctrine()->getManager()->getRepository('CommandeBundle:Commande');
$commande = $repoCommande->find($id);
$commande->setValide(1);
$em = $this->getDoctrine()->getManager();
$em->persist($commande);
$em->flush();
$this->logger->info("Transaction #$transactionId is valid. Internal ID is $internalId");
}
}
then I declared it as a service
services:
app.payment_listener:
class: AppBundle\EventSubscriber\PaymentListener
arguments: ["#LoggerInterface"]
tags:
- { name: kernel.event_subscriber }
But the arguments is not good. The constructor asks a loggerInterface argument and it returns me the following error :
ServiceNotFoundException in CheckExceptionOnInvalidReferenceBehaviorPass.php line 58: The service "app.payment_listener" has a dependency on a non-existent service "loggerinterface".
I explain what I would like to do, in fact I want use the payline bundle but I am stuck here.
Please, help me.

When you're passing an argument to constructor, as _construct(LoggerInterface $logger) you're telling that $logger argument can be any object whose class is the child of the LoggerInterface. So, in your service definition you can pass any logger service (#logger service, for example), not the interface itself. The answer to your question is, pass #logger service from Monolog bridge (or any other service name, which extends the LoggerInterface).
You can find more information here.

Related

Doctrine query outside the controller Symfony 2

I have some trouble since two days to do a query using a UserRepository outside a controller. I am trying to get a user from the database from a class that I named ApiKeyAuthenticator. I want to execute the query in the function getUsernameForApiKey like in the docs. I think I am suppose to use donctrine as a service but I don't get how to do this.
Thanks for you help in advance!
<?php
// src/AppBundle/Security/ApiKeyUserProvider.php
namespace AppBundle\Security;
use Symfony\Component\Security\Core\User\UserProviderInterface;
use Symfony\Component\Security\Core\User\User;
use Symfony\Component\Security\Core\User\UserInterface;
use Symfony\Component\Security\Core\Exception\UnsupportedUserException;
class ApiKeyUserProvider implements UserProviderInterface
{
public function getUsernameForApiKey($apiKey)
{
// Look up the username based on the token in the database, via
// an API call, or do something entirely different
$username = ...;
return $username;
}
public function loadUserByUsername($username)
{
return new User(
$username,
null,
// the roles for the user - you may choose to determine
// these dynamically somehow based on the user
array('ROLE_API')
);
}
public function refreshUser(UserInterface $user)
{
// this is used for storing authentication in the session
// but in this example, the token is sent in each request,
// so authentication can be stateless. Throwing this exception
// is proper to make things stateless
throw new UnsupportedUserException();
}
public function supportsClass($class)
{
return User::class === $class;
}
}
You have to make your ApiKeyUserProvider a service and inject the UserRepository as a dependency. Not sure if repositories are services in 2.8, so maybe you'll have to inject the EntityManager .
class ApiKeyUserProvider implements UserProviderInterface
{
private $em;
public function __construct(EntityManager $em)
{
$this->em = $em;
}
public function loadUserByUsername($username)
{
$repository = $this->em->getRepository(User::class);
// ...
Now register your class as a service in your services.yml file
services:
app.api_key_user_provider:
class: AppBundle\Security\ApiKeyUserProvider
arguments: ['#doctrine.orm.entity_manager']

Symfony 4 Accessing Swift_Mailer in Service

I have been looking at the Symfony 4.1 documentation on using the Swift_mailer. However, it appears the documentation is only assumed it being used in the Controller classes. I'm trying to create a Service with some reusable functions that send email.
I created a EmailService.php file in my service directory. When creating a new instance of this service, it quickly throws and error:
"Too few arguments to function
App\Service\EmailService::__construct(), 0 passed in
*MyApp\src\Controller\TestController.php on line 33
and exactly 1 expected"
I'm not sure how to pass \Swift_Mailer $mailer into the __construct correctly? I have auto wiring enabled in the services.yaml, so i'm not sure what I need to do differently?
class EmailService
{
private $from = 'support#******.com';
private $mailer;
public function __construct(\Swift_Mailer $mailer)
{
$this->mailer = $mailer;
}
How do I pass the \Swift_Mailer into this EmailService construct?
I tried adding this to my config\services.yaml with no success:
App\Service\EmailService:
arguments: ['#mailer']
As mentioned by dbrumann in a comment, I needed to follow the proper way of injecting services.
First, I needed to add the services to config/services.yaml
#config/services.yaml
emailservice:
class: App\Service\EmailService
arguments: ['#swiftmailer.mailer.default', '#twig']
public: true
Second, I need to setup the service to accept both the mailer, and twig for rendering the template.
#App/Service/EmailService.php
<?php
namespace App\Service;
class EmailService
{
private $from = 'support#*****.com';
private $mailer;
private $templating;
public function __construct(\Swift_Mailer $mailer, \Twig\Environment $templating)
{
$this->mailer = $mailer;
$this->templating = $templating;
}
public function userConfirmation(string $recipient, string $confCode) : bool
{
$message = (new \Swift_Message())
->setSubject('Some sort of string')
->setFrom($this->from)
->setTo($recipient)
->setBody(
$this->templating->render(
'email/UserConfirmation.html.twig',
array('confCode' => $confCode)
),
'text/html'
)
/*
* If you also want to include a plaintext version of the message
->addPart(
$this->renderView(
'emails/UserConfirmation.txt.twig',
array('confCode' => $confCode)
),
'text/plain'
)
*/
;
return $this->mailer->send($message);
}
}
Third, to call it from the controller, make sure your controller is extending Controller and not the AbstractController! Crucial step!! Here is an example based on the parameters I require in my service:
public function userConfirmation()
{
$emailService = $this->get('emailservice');
$sent = $emailService->userConfirmation('some#emailaddress.com', '2ndParam');
return new Response('Success') //Or whatever you want to return
}
I hope this helps people. AbstractController does not give you the proper access to the service containers.
#config/services.yaml
App\Service\EmailService
arguments: ['#swiftmailer.mailer.default']
public: true
And in your controller :
public function userConfirmation(EmailService $emailService)
{
$sent = $emailService->userConfirmation('some#emailaddress.com', '2ndParam');
return new Response('Success') //Or whatever you want to return
}
Use FQCN "App\Service\MyService" to declare services in services.yaml and a proper legacy_aliases.yaml file to declare legacy aliases like "app.service.my.service" it helps keep your services.yaml clean...

Symfony Undefined index

I am creating an application that fetches and search for product name from different sources (DB, XML, JSON, ...)(for this code Im testing only with the DB), my idea was to create an interface for that.
I created the interface ProductRepositoryInterface and the class DoctrineProductRepository then I declared them both as services.
In my controller, I call the search function with the product name as param.
Here is my interface ProductRepositoryInterface :
namespace Tyre\TyreBundle\Repository;
interface ProductRepositoryInterface
{
function search(string $needle);
}
My interface DoctrineProductRepository:
namespace Tyre\TyreBundle\Repository;
class DoctrineProductRepository implements ProductRepositoryInterface
{
public function __constructor(EntityManager $em)
{
$this->em = $em;
}
public function search(string $needle)
{
$repository = $this->em->getRepository('TyreTyreBundle:Products');
$query = $repository->createQueryBuilder('u')
->where("u.name LIKE '%".$needle."%' or u.manufacturer LIKE '%".$needle."%'")
->getQuery();
return $query->getArrayResult();
}
}
My Service.yml
services:
Tyre\TyreBundle\Repository\DoctrineProductRepository:
class: Tyre\TyreBundle\Repository\DoctrineProductRepository
Tyre\TyreBundle\Repository\ProductRepositoryInterface:
class: Tyre\TyreBundle\Repository\ProductRepositoryInterface
and finally my controller :
namespace Tyre\TyreBundle\Controller;
use Symfony\Bundle\FrameworkBundle\Controller\Controller;
use Symfony\Component\HttpFoundation\Request;
use Tyre\TyreBundle\Repository\DoctrineProductRepository;
use Tyre\TyreBundle\Repository\ProductRepositoryInterface;
class DefaultController extends Controller
{
public function indexAction()
{
return $this->render('TyreTyreBundle:Default:search.html.twig');
}
public function searchAction(Request $request) {
$repositoryMap = [
'db' => DoctrineProductRepository::class,
];
$serviceName = $repositoryMap[$request->get('db')]; /***This is Line 56 ***/
/** #var ProductRepositoryInterface */
$repository = $this->get($serviceName);
$results = $repository->search($request->get('search_for'));
return $this->render('TyreTyreBundle:Default:detail.html.twig', array('results' => $results));
}
public function detailAction()
{
//forward the user to the search page when he tries to access directly to the detail page
return $this->render('TyreTyreBundle:Default:search.html.twig');
}
}
But I get an error :
EDIT
When I try http://localhost:8000/search?db=db , I get other error (I var_dumped $repositoryMap) :
click to view
Am I missing anything?
The reason for your 'ContextErrorException' is :
$request->get('search_for')
is empty because you are passing nothing in the url for that key. Pass 'search_for' also in addition with 'db' like:
http://localhost:8000/search?db=db&search_for=myvalue

How to get current firewall's check_path?

Question: How to get the form_login.check_path by given firewall name?
We subscribe to Symfony\Component\Security\Http\SecurityEvent::INTERACTIVE_LOGIN in order to log successful logins inside an Application that has multiple firewalls.
One firewall uses JWT tokens via Guard authentication which has the negative effect that this event is triggered for every request with a valid token.
We have currently solved this by manually checking whether the current route matches the firewall's check-path and stopping the event-propagation together with an early return otherwise.
As we're adding more firewalls (with different tokens) I'd like to solve this more generally. Therefore I want to check whether the current route matches the current firewalls check-path without hardcoding any route or firewall-name.
There is a class to generate Logout URLs for the current firewall used by Twig logout_path() method which gets the logout route/path from the firewall listeners somehow. (Symfony\Component\Security\Http\Logout\LogoutUrlGenerator)
Before I hop into a long debugging session I thought maybe someone has solved this case before ;)
Any ideas?
Example code:
class UserEventSubscriber implements EventSubscriberInterface
{
/** #var LoggerInterface */
protected $logger;
/** #var FirewallMapInterface|FirewallMap */
protected $firewallMap;
public function __construct(LoggerInterface $logger, FirewallMapInterface $firewallMap)
{
$this->logger = $logger;
$this->firewallMap = $firewallMap;
}
public function onInteractiveLogin(InteractiveLoginEvent $event)
{
$request = $event->getRequest();
$firewallName = $this->firewallMap->getFirewallConfig($request)->getName();
$routeName = $request->get('_route');
if (('firewall_jwt' === $firewallName) && ('firewall_jwt_login_check' !== $routeName)) {
$event->stopPropagation();
return;
}
$this->logger->info(
'A User has logged in interactively.',
array(
'event' => SecurityEvents::INTERACTIVE_LOGIN,
'user' => $event->getAuthenticationToken()->getUser()->getUuid(),
));
The check_path option is only available from authentication factory/listener, so you could pass this configuration manually to the subscriber class while the container is building.
This solution take account that check_path could be a route name or path, that's why HttpUtils service is injected too:
namespace AppBundle\Subscriber;
use Psr\Log\LoggerInterface;
use Symfony\Component\EventDispatcher\EventSubscriberInterface;
use Symfony\Component\Security\Http\Event\InteractiveLoginEvent;
use Symfony\Component\Security\Http\FirewallMapInterface;
use Symfony\Component\Security\Http\HttpUtils;
use Symfony\Component\Security\Http\SecurityEvents;
class UserEventSubscriber implements EventSubscriberInterface
{
private $logger;
private $httpUtils;
private $firewallMap;
private $checkPathsPerFirewall;
public function __construct(LoggerInterface $logger, HttpUtils $httpUtils, FirewallMapInterface $firewallMap, array $checkPathsPerFirewall)
{
$this->logger = $logger;
$this->httpUtils = $httpUtils;
$this->firewallMap = $firewallMap;
$this->checkPathsPerFirewall = $checkPathsPerFirewall;
}
public function onInteractiveLogin(InteractiveLoginEvent $event)
{
$request = $event->getRequest();
$firewallName = $this->firewallMap->getFirewallConfig($request)->getName();
$checkPath = $this->checkPathsPerFirewall[$firewallName];
if (!$this->httpUtils->checkRequestPath($request, $checkPath)) {
$event->stopPropagation();
return;
}
$this->logger->info('A User has logged in interactively.', array(
'event' => SecurityEvents::INTERACTIVE_LOGIN,
'user' => $event->getAuthenticationToken()->getUser()->getUsername(),
));
}
public static function getSubscribedEvents()
{
return [SecurityEvents::INTERACTIVE_LOGIN => 'onInteractiveLogin'];
}
}
After regiter this subscriber as service (AppBundle\Subscriber\UserEventSubscriber) we need implement PrependExtensionInterface in your DI extension to be able to access the security configuration and complete the subscriber definition with the check paths per firewall:
namespace AppBundle\DependencyInjection;
use AppBundle\Subscriber\UserEventSubscriber;
use Symfony\Component\DependencyInjection\ContainerBuilder;
use Symfony\Component\DependencyInjection\Extension\Extension;
use Symfony\Component\DependencyInjection\Extension\PrependExtensionInterface;
class AppExtension extends Extension implements PrependExtensionInterface
{
// ...
public function prepend(ContainerBuilder $container)
{
$checkPathsPerFirewall = [];
$securityConfig = $container->getExtensionConfig('security');
foreach ($securityConfig[0]['firewalls'] as $name => $config) {
if (isset($config['security']) && false === $config['security']) {
continue; // skip firewalls without security
}
$checkPathsPerFirewall[$name] = isset($config['form_login']['check_path'])
? $config['form_login']['check_path']
: '/login_check'; // default one in Symfony
}
$subscriber = $container->getDefinition(UserEventSubscriber::class);
$subscriber->setArgument(3, $checkPathsPerFirewall);
}
}
I hope it fits your need.
for PHP8
In __construct :
public function __construct(
private RequestStack $requestStack,
private FirewallMapInterface $firewallMap
)
{
}
use this :
$firewallName = $this->firewallMap->getFirewallConfig($this->requestStack->getCurrentRequest())->getName();

Symfony2 access user and doctrine in a service

I'm running the equivalent of this code in lots and lots of controller actions, basically it grabs the user's username, and if that username is attached to a blog entity it will allow the user to see the blog entity(s):
$em = $this->getDoctrine()->getManager();
$user = $this->get('security.context')->getToken()->getUser();
$entities = $em->getRepository('MySiteBundle:Blog')->findBy(array('user' => $user));
return $this->render('MySiteBundle:Blog:index.html.twig', array(
'entities' => $entities,
I want to move it into a service so I can cut down on code repetition. I want to avoid doing as much logic in my controllers as possible.
That being said, I'm not sure how I can access the user session and doctrine in a service.
Here's my services.yml:
mysite.user.blog:
class: MySite\SiteBundle\Services\BlogUser
And here's how I was attempting to call it in the controller:
public function testAction() {
$response = $this->get('mysite.user.blog');
return new Response($response);
}
I did try using an event subscriber/listener tag, but that doesn't seem to accomplish the task I want.
Here is my completely horrible attempt at a service. I couldn't get any response from it without using a constructor.
namespace MySite\SiteBundle\Services;
use MySite\SiteBundle\Entity\Blog;
class BlogUser {
protected $entities;
public function __construct(){
$em = $this->getDoctrine()->getManager();
$user = $this->get('security.context')->getToken()->getUser();
$this->entities = $em->getRepository('MySiteBundle:Blog')->findBy(array('user' => $user));
}
}
Am I going about this the completely wrong way? Is there a better way that I'm missing?
EDIT/ANSWER:
modified my naming convention a little:
//services.yml
mysite.user.blog.entities:
class: Mysite\SiteBundle\Services\BlogUser
arguments: ["#doctrine.orm.entity_manager", "#security.context"]
In the controller action:
$userEntities = $this->get('mysite.user.blog.entities');
$entities = $userEntities->getEntities();
In the service itself:
class BlogUser {
protected $entities;
public function __construct($em, $securityContext){
$user = $securityContext->getToken()->getUser();
$this->entities = $em->getRepository('MySiteBundle:Blog')->findBy(array('user' => $user));
}
public function getEntities(){
return $this->entities;
}
}
Still needs two lines to get the $entities variable in the controller, but this is way better than defining the same thing over and over.
"Security.context" has been deprecated since Symfony 2.6
After some community discussions, it was decided that SecurityContext gives too many dependencies to retrieve a simple Token/User object. That's why, starting with Symfony 2.6, thesecurity.context service has been deprecated and split into two new services:security.authorization_checker and security.token_storage.
Source
Thus, the new way to do it would be, first configure your service as:
mysite.user.blog:
class: MySite\SiteBundle\Services\BlogUser
arguments: ["#doctrine.orm.entity_manager", "#security.token_storage"]
Then in the service class constructor:
class BlogUser
{
protected $user;
protected $entities;
public function __construct(EntityManager $em, TokenStorage $tokenStorage)
{
$this->user = $tokenStorage->getToken()->getUser();
$this->entities = $em->getRepository('MySiteBundle:Blog')->findBy(array('user' => $user));
}
}
Yes, you are doing it in wrong way. Let's look at your code:
# call to undefined object method getDoctrine()
$em = $this->getDoctrine()->getManager();
# call to undefined object method get()
$user = $this->get('security.context')->getToken()->getUser();
You cannot call getting entitymanager and security.context in your service in the same way like in your controller. Instead, you have to inject entitymanager and security.context services. Example:
# services.yml
mysite.user.blog:
class: MySite\SiteBundle\Services\BlogUser
calls:
- [ setUserFromSecurityContext, [ #security.context ]]
- [ setEntityManager, [ #doctrine.orm.entity_manager ]]
And improved service:
namespace Catablog\SiteBundle\Services;
use MySite\SiteBundle\Entity\Blog;
class BlogUser {
private $entityManager;
private $user;
public function setEntityManager(EntityManager $entityManager)
{
$this->entityManager = $entityManager;
}
public function setUserFromSecurityContext(SecurityContext $securityContext)
{
# notice, there are a cases when `getToken()` returns null, so improve this
$this->user = $securityContext->getToken()->getUser();
}
public function getEntities(){
# your code here
}
}
More info about Dependency injection
You are looking on how to 'inject' other services into your custom service. Take a look at Service Container documentation.
In your case, you can inject doctrine.orm.entity_manager and security.context services into your BlogUser class via constructor injection. For example:
class BlogUser {
public function __construct($em, $securityContext) {
$user = $securityContext->getToken()->getUser();
$this->entities = $em->getRepository('MySiteBundle:Blog')->findBy(array('user' => $user));
}
}
And configure your service as the following:
mysite.user.blog:
class: MySite\SiteBundle\Services\BlogUser
arguments: ["#doctrine.orm.entity_manager", "#security.context"]

Resources