Accessing a service inside an event subscriber - symfony

I have an event subscriber with Doctrine events. Inside that, I am trying to call a service I have registered. I've called it already from in a controller and it works there, but when I try to call it in my event subscriber I get an error:
Attempted to call method "get" on class "Path\To\My\Class".
Did you mean to call "getSubscribedEvents"?
The code looks like this:
$embedcode_service = $this->get('myproject.mynamespace.myfield.update');
$embedcode_service->refreshMyField($document);
Why can't I access my service inside this event subscriber? How can I get access to it?

Cerad already answered I'm just going to elaborate:
Inject your service in your subscriber:
services:
...
my.subscriber:
class: Acme\SearchBundle\EventListener\SearchIndexerSubscriber
# Service you want to inject
arguments: [ #service_i_want_to_inject.custom ]
tags:
- { name: doctrine.event_subscriber, connection: default }
...
In the php code for your subscriber, assign the injected service to a class variable to be able to access it later on:
...
class SearchIndexerSubscriber implements EventSubscriber {
private $myservice;
public function __construct($myservice) {
$this->myservice = $myservice;
}
...
Access the service methods through the class variable from any method within the subscriber:
$this->myservice->refreshMyField($document);
Listeners/subscribers in Symfony2
Services in Symfony2
Happy new year.

Related

How to use libphonenumber.phone_number_util in Symfony 4

To parse phone number I need to use libphonenumber.phone_number_util in my controller ( Symfony 4) as like as :
$parsed = $this->get('libphonenumber.phone_number_util')->parse($phoneNo);
as we have libphonenumber.phone_number_util in private I wanted to make it public by adding this helper in service as below:
services:
libphonenumber\PhoneNumberUtil:
alias: libphonenumber.phone_number_util
public: true
But this returns Exception and message:
"message": "The \"libphonenumber.phone_number_util\" service or alias has been removed or inlined when the container was compiled. You should either make it public, or stop using the container directly and use dependency injection instead.",
"class": "Symfony\\Component\\DependencyInjection\\Exception\\ServiceNotFoundException",
If you are using this in a controller method (which I presume you do based on $this->get(...)), you need to
1) Declare your controller as a service and tag it with controller.service_arguments tag
2) Make sure your util service id matches the class name (I suppose it does already). You don't need it to be public - that's and ancient approach
3) Require the util as a parameter to your controller's action method.
E.g.
services:
libphonenumber\PhoneNumberUtil:
alias: libphonenumber.phone_number_util
AppBundle\Controller\MyController:
tags: ['controller.service_arguments']
and
public function validatePhoneAction(Request $request, PhoneNumberUtil $phoneNumberUtil)
{
...
$phoneNumberUtil->parse($request->request->get('phone_number');
...
}
There is a nice Symfony blog post about these changes in dependency management: https://symfony.com/blog/new-in-symfony-3-4-services-are-private-by-default

Lazy load services dynamically

After watching the Laravell Nova presentation I wanted to create similar functionality to Lenses in my own app.
I have the following concepts:
Entity: Standard Doctrine Entity
Resource: A class that describes a resource including the target entity and available lenses.
Lens: Has an method apply(Request $request, QueryBuilder $qb) that allow you to modify the QueryBuilder based on the Request.
The goal is to define all Lenses as a service and then somehow assign them to a Resource. This is the problem I'm trying to solve.
Attempt 1: Directly inject the Lenses into the resource
ProjectResource.php
<?php
class ProjectResource
{
protected $lenses = [];
public function __construct(
ProjectRepository $repository,
LensInterface $activeProjectLens,
LensInterface $starredProjectLens
) {
$this->lenses = [
$activeProjectLens,
$starredProjectLens
];
}
public function getLenses() {
return $this->lenses;
}
}
The downside of this is that each Lens service is instantiated and needs to be defined manually
Attempt 2: Inject tagged Lenses into the resource
In my services.yaml tag the services and assign them as an argument to the resource:
App\Lens\ActiveProjectLens:
tags: ['resource.project.lens']
App\Lens\StarredProjectLens:
tags: ['resource.project.lens']
App\Resource\ProjectResource:
arguments:
$lenses: !tagged resource.project.lens
ProjectResource.php
<?php
class ProjectResource
{
protected $lenses = [];
public function __construct(
ProjectRepository $repository,
iterable $lenses
) {
$this->lenses = $lenses;
}
public function getLenses() {
return $this->lenses;
}
}
The downside of this approach is every Lens service and Resource must be tagged and cannot be an auto-configured service.
**Attempt 3: Add a compiler pass **
I attempted to add the process() method to the Kernel but I didn't get too far with that.
My goal is to define a list of services somehow in the Resource and have them injected. Is there any established pattern for this?
Your approach with the tags seems good. Symfony provides a way to automatically add tags to classes that implement a certain interface: Interface-based service configuration.
To use that you have to do the following:
If you don't already have one, create an interface (e.g. App\Lens\LensInterface) and let your lens classes implement the interface.
In your services.yaml file add this config:
services:
// ...
_instanceof:
App\Lens\LensInterface:
tags: ['resource.project.lens']
App\Resource\ProjectResource:
arguments:
$lenses: [!tagged resource.project.lens]
// ...
Then every class implementing your LensInterface would be injected into the ProjectResource without having to explicitly configure every single lens.

How to create and use a form in a custom class in Symfony 2

I need to submit data to a FormType outside of a controller in a custom class that is registered as a service. The FormType itself is also registered as as service and injected into my custom class (ApiResponseMapper). This is a simplified snippet:
class ApiResponseMapper
{
private $bestSellerListsType;
public function mapResponse($response)
{
$form = $this->bestSellerListsType;
$form->submit($response);
}
public function __construct(BestsellerListType $bestSellerListsType)
{
$this->bestSellerListsType = $bestSellerListsType;
}
}
The submit() method does not exist on the form.
How can I initialize my form properly so that I have access to the submit() method?
If the form was used in a controller, I'd have access to the createForm method and do:
$form = $this->createForm('my_form_as_a_service');
Since I'm trying to use the form in a class that does not extend the Controller, there is no such option
You need FormFactory to work with custom form types like you do in controllers. Besides your form inject form factory to your service:
arguments: [#your_form, #form.factory]
Then use create method like you do in controllers:
$this->formFactory->create(new MyType(), $data, $options);
Btw, Symfony uses facade design pattern in controllers that let you easy access to various data. You can just check implementation of controller's method and do same yourself.
I thought I'd post another in case somebody wants to handle creating a form with form.factory in the configuration of the services (services.yml in my case):
bestseller_list_form_type:
class: Air\BookishBundle\Form\Type\BestsellerListType
name:
- { name: form.type, alias: bestseller_list_form }
bestseller_list_form:
factory_service: form.factory
factory_method: create
class: Symfony\Component\Form\Form
arguments: [#bestseller_list_form_type]
api_response_mapper:
class: Air\BookishBundle\Lib\ApiResponseMapper
arguments: [#bestseller_list_form]
This way the factory method is invoked upon object instantiation. Then you have the form ready to use in api_response_mapper and there is no need to call the factory manually ($this->formFactory->create(...);).

Dependency Injection Symfony 2 simple code returning error

I was under the impression I could get the request object like in the code below. Something to do with Dependency Injection.
This below is activated as a service and everything seems to be setup correctly except for the first argument which gives this error:
ErrorException: Catchable Fatal Error: Argument 1 passed to....
namespace Acme\Bundle\BundleName\EventListener;
use Symfony\Component\HttpKernel\Event\GetResponseEvent;
class RequestListener
{
public function onKernelRequest(GetResponseEvent $event)
{
$request = $event->getRequest();
// etc....
I'm guessing the above is not how you do it?
If you want to declare an event listener on the kernel request, you should declare it that way (notice the tags parameters):
services:
acme.demobundle.listener.request:
class: Acme\Bundle\BundleName\EventListener\RequestListener
tags:
- { name: kernel.event_listener, event: kernel.request, method: onKernelRequest }
Otherwise, if you want to create just a service, you should declare it that way
services:
acme.demobundle.demo.service:
class: Acme\Bundle\BundleName\Service\DemoService
arguments: [#service_container]
For a service or a listener, I'd advise injecting only the services that are needed.
It's good to know that a service will be initialized on the first call.
A service, a listener and a twig extension can be accessed through the container.
$this->container->get('your.listener.name')
$this->container->get('your.service.name')
$this->container->get('your.extension.name')

Symfony2-How to use access a service from outside of a controller

In my Symfony2 controller, this works fine:
$uploadManager = $this->get('upload.upload_manager');
but when I move it to a custom Listener:
use Doctrine\ORM\Event\LifecycleEventArgs;
use Acme\UploadBundle\Upload\UploadManager;
class PersonChange
{
public function postRemove(LifecycleEventArgs $args)
{
$entity = $args->getEntity();
$entityManager = $args->getEntityManager();
$uploadManager = $this->get('ep_upload.upload_manager');
echo "the upload dir is " . $uploadManager->getUploadDir();
}
}
I get an error:
Fatal error: Call to undefined method Acme\MainBundle\Listener\PersonChange::get() in /home/frank/...
I know I must need a use statement but don't know what to use.
Update: Defining controllers as services is no longer officially recommended in Symfony.
The get() method in the Controller class is just a helper method to get services from the container, and it was meant to get new Symfony2 developers up to speed faster. Once people get comfortable with the framework and dependency injection, it's recommended to define controllers as services and inject each required service explicitly.
Since your PersonChange class is not a controller and doesn't extend the Controller class, you don't have that get() helper method. Instead, you need to define your class as a service and inject needed services explicitly. Read the Service Container chapter for details.
As I ran into the exact same problem maybe I can help
What Elnur said is perfectly fine and I'll just try to pop up a real life example.
In my case I wanted to access
$lucenemanager = $this->get('ivory.lucene.manager')
Even by extending the controller I couldn't get it to work while the controller does access the container (I still did not understand why)
In config.yml my listener (searchindexer.listener) is declared as follow :
services:
searchindexer.listener:
class: ripr\WfBundle\Listener\SearchIndexer
arguments:
luceneSearch: "#ivory_lucene_search"
tags:
- { name: doctrine.event_listener, event: postPersist }
A service (ivory.lucene.search) is passed as argument in my service/listener.
Then in my class
protected $lucenemanager;
public function __construct($luceneSearch)
{
$this->lucenemanager = $luceneSearch;
}
Then you can use the get method against $this
An approach that always works, despite not being the best practice in OO
global $kernel;
$assetsManager = $kernel->getContainer()->get('acme_assets.assets_manager');‏
If you need to access a Service, define it in the class constructor:
class PersonChange{
protected $uploadManager;
public function __construct(UploadManager $uploadManager){
$this->uploadManager = $uploadManager;
}
// Now you can use $this->uploadManager.
}
Now you can pass the Service as argument when calling the class (example 1) or define the clas itself as a Service (recommended, example 2)
Example 1:
use Acme\PersonChange;
class appController{
function buzzAction(){
$uploadManager = $this->get('upload.upload_manager');
$personChange = new PersonChange($uploadManager);
Example 2 (better):
Define PersonChange as a Service itself, and define the other Service as an argument in services.yml file:
# app/config/services.yml
services:
upload.upload_manager:
class: AppBundle\uploadManager
PersonChange:
class: AppBundle\PersonChange
arguments: ['#upload.upload_manager']
In this way, you don't have to bother with the upload_manager service in the Controller, since it's implicitely passed as an argument for the constructor, so your Controller can be:
class appController{
function buzzAction(){
$personChange = $this->get('PersonChange');

Resources