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

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

Related

Symfony override autowired services

I'm writing a Symfony 4 bundle and inside, in a compiler pass, I create multiple service definitions based on an abstract one (also enabling autowiring based on the argument name):
$managerDefinition = new ChildDefinition(Manager::class);
$managerDefinition->replaceArgument(0, $managerName);
...
$container->registerAliasForArgument($managerId, Manager::class, $managerName . 'Manager');
And this is the abstract service definition:
services:
MyBundle\Manager:
abstract: true
arguments:
- # manager name
So, in my App controller I can have this and it works correctly:
public function __construct(MyBundle\Manager $barManager)
{
// $barManager is MyBundle\Manager
}
Now, let's say at some point I decide to extend the Manager class in my App with additional methods:
class MyManager extends \MyBundle\Manager
{
public function newMethod() {
...
}
}
I override the bundle's abstract service like this:
services:
MyBundle\Manager:
class: App\Manager
abstract: true
arguments:
- # manager name
Everything still works as expected:
public function __construct(MyBundle\Manager $barManager)
{
// $barManager is App\Manager
$barManager->newMethod(); // Works
}
However, the IDE complains that newMethod() does not exist, as it doesn't exist in the typehinted MyBundle\Manager.
So, it seems more correct to change my constructor definition to let it know the actual class it's going to receive:
public function __construct(App\Manager $barManager)
However, I can't write this, as auto-wiring no longer works.
I suppose I could write a compiler pass in my App that registers autowiring for my custom App\Manager, but that seems like an overkill.
I can't shake the feeling that I'm doing something fundamentally wrong.
I guess my question is, what would be the best way to allow easy overriding of the abstract Manager definition in the bundle?

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.

Service in symfony2 - how service file should look like?

I am trying to create service in symfony2 which will verify if session contains certain information and if not redirect the user to another controller. I want this piece of code to work as a service as I will be using it in many controllers.
I have problem as manual on Symfony2 book does not provide information how service file should look like. Should it be a normal php class?
Please find below dump of my files with information on error that I receive.
In \AppBundle\Services I create file my_isbookchosencheck.php containing:
<?php
namespace AppBundle\my_isbookchosencheck;
class my_isbookchosencheck
{
public function __construct();
{
$session = new Session();
$session->getFlashBag()->add('msg', 'No book choosen. Redirected to proper form');
if(!$session->get("App_Books_Chosen_Lp")) return new RedirectResponse($this->generateUrl('app_listbooks'));
}
}
My service.yml:
my_isbookchosencheck:
class: AppBundle\Services\my_isbookchosencheck
My conntroller file:
/**
* This code is aimed at checking if the book is choseen and therefore whether any further works may be carried out
*/
$checker = $this->get('my_isbookchosencheck');
Error:
FileLoaderLoadException in FileLoader.php line 125: There is no extension able to load the configuration for "my_isbookchosencheck" (in C:/wamp/www/symfony_learn/app/config\services.yml). Looked for namespace "my_isbookchosencheck", found "framework", "security", "twig", "monolog", "swiftmailer", "assetic", "doctrine", "sensio_framework_extra", "fos_user", "knp_paginator", "genemu_form", "debug", "acme_demo", "web_profiler", "sensio_distribution" in C:/wamp/www/symfony_learn/app/config\services.yml (which is being imported from "C:/wamp/www/symfony_learn/app/config\config.yml").
There are few mistakes that you made, which I am going to explain in short, and I will give you an example of the service you want to create.
You created your service in AppBundle\Services, yet your namespace is registered differently - namespace AppBundle\Services\my_isbookchosencheck;. It should be namespace AppBundle\Services;. I would also advise you to use singular names when creating directories - in this case Service would be better, instead of Services.
You're using your __constructor directly to apply some logic and return the result of it. Better way would be to create a custom method, which could be accessed when necessary.
You're creating new instance of Session which means that you wont be able to access anything that was previously added and stored in session. The right way here, would be to inject RequestStack which holds the current Request and get the session from there.
I believe you also registered your service wrong. In your services.yml file, it should be under services: option. This is why you got the error you pasted.
So, let's see how your service should like.
services.yml
services:
book_service:
class: AppBundle\Service\BookService
arguments:
- #request_stack
- #router
BookService.php
namespace AppBundle\Service;
use Symfony\Component\HttpFoundation\Request;
use Symfony\Component\HttpFoundation\RequestStack;
use Symfony\Component\Routing\RouterInterface;
class BookService {
/* #var $request Request */
private $request;
/* #var $router RouterInterface */
private $router;
public function __construct(RequestStack $requestStack, RouterInterface $router) {
$this->request = $requestStack->getCurrentRequest();
$this->router = $router;
}
public function isBookChoosen() {
$session = $this->request->getSession();
// Now you can access session the proper way.
// If anything was added in session from your controller
// you can access it here as well.
// Apply your logic here and use $this->router->generate()
}
}
Now in your controller you can simply use it like this:
$this->get('book_service')->isBookChoosen()
Well this is a short example, but I hope you got the idea.
try
services:
my_isbookchosencheck:
class: AppBundle\Services\my_isbookchosencheck
in your services.yml, and check that you use the correct namespaces.
Your Class is fine and it should work, however may i suggest that you use
symfony2 session service instead of creating the session object yourself, you can pass it as a constructor argument:
<?php
// namespace edited
namespace AppBundle\Services;
use Symfony\Component\HttpFoundation\Session\Session;
class my_isbookchosencheck
{
public function __construct(Session $session);
{
$session->getFlashBag()->add('msg', 'No book choosen. Redirected to proper form');
if(!$session->get("App_Books_Chosen_Lp")) return new RedirectResponse($this->generateUrl('app_listbooks'));
}
}
and then edit your services.yml accordingly, so the service container will inject the session object:
services:
my_isbookchosencheck:
class: AppBundle\Services\my_isbookchosencheck
arguments: [#session]
Also check out his question on so:
How do you access a users session from a service in Symfony2?
Services are just regular PHP classes, nothing special. But you must register it in order to be recognized by the system. Here are the steps how you do it,
Create a regular PHP class (you can inject other services if it requires)
namespace Acme\DemoBundle\Service;
class MyService
{
private $session;
public function _construct(SessionInterface $session /* here we're injecting the session service which implements the SessionInterface */)
{
$this->session = $session;
}
// other methods go here, which holds the business logic of this class
}
ok, we created a class, we need to register it to be able to use it by service container, here how you do it:
the simplest way is to put it into config.yml file, like this:
services:
my_service:
class: Acme\DemoBundle\Service\MyService
arguments:
- #session
or, another way, is to create a file (e.g. services.yml, may be in config folder), and import it inside the config.yml file (the content of the file is the same as the first way):
imports:
- { resource: services.yml }
or, you can create a services.yml(the content of the file is the same as the first way) file inside you bundle's Resources folder, specify it under the load method of your Extension class (under the DependencyInjection folder), (this way requires some special directory and file structure, read about it in the doc):
class AcmeDemoExtension extends Extension
{
public function load(array $configs, ContainerBuilder $container)
{
$loader = new YamlFileLoader($container, new FileLocator(__DIR__ . '/../Resources'));
$loader->load('services.yml');
}
}
In you case, you're not registering your service, the service container just couldn't find it. Register it by one of the above ways.

get container parameter in my custom classes Symfony2

I have the class, it declare as service. When I get() my service I run some method and this method require two params what I want to let user configure in config.yml. How I can get these parameters in this class? Maybe exist some way to do this in my service definition? Or I need extend my class from ContainerAware (if I am right its bad practice)? Thanks!
You can inject parameters into your service using %param_name% syntax
services.yml
services:
your_service:
class: Acme\DemoBundle\YourClass
arguments: [#some.other.service, %my_parameter%]
parameters.yml
parameters:
my_parameter: my_value
You can use call them using the constructor
acme.your_service:
class: Acme\DemoBundle\YourService
arguments: [%param1%]
in the class
class YourService {
protected $param1;
public function __construct($param1) {
$this->param1 = $param1;
}
}

How to use a cookie in routing configuration in Symfony2?

I have a City parameter stored in a cookie. I would like to include its value as a pattern prefix in my routing configuration like so:
# MyBundle/Resources/config/routing.yml
MyBundle_hotel:
resource: "#MyBundle/Resources/config/routing/hotel.yml"
prefix: /%cityNameFromCookie%/hotel
How can I achieve that?
Give us a use case on how you would want this to work because I don't see the difficulty. Routes are made of parameters that you can specify through the generateUrl function, the url twig function or the path twig function.
In Twig you can do this
{{ path('MyBundle_hotel', {cityNameFromCookie: app.request.cookies.get('cityNameFromCookie')}) }}
In a controller action
$cookieValue = $this->get('request')->cookies->get('cityNameFromCookie');
$url = $this->generateUrl('MyBundle_hotel', array('cityNameFromCookie' => $cookieValue));
Or from any places that have access to the container
$cookieValue = $this->container->get('request')->cookies->get('cityNameFromCookie');
$url = $this->container->get('router')->generate('MyBundle_hotel', array('cityNameFromCookie' => $cookieValue));
In the last example, you will probably want to change how the container is being accessed.
If you are concerned about how complicated it looks like, you can abstract this logic and put it inside a service or extend the router service.
You can find documentation about services and the service container in the Symfony's documentation.
You can also list the services via the command php app/console container:debug and will find the router service and its namespace and from this you can try to figure out how to extend the router service (a very good way to learn how services work).
Otherwise, here is simple way to create a service.
In your services.yml (either in your Bundle or in app/config/config.yml)
services:
city:
class: MyBundle\Service\CityService
arguments: [#router, #request]
In your CityService class
namespace MyBundle\Service
class CityService
{
protected $router;
protected $request;
public function __construct($router, $request)
{
$this->router = $router;
$this->request = $request;
}
public function generateUrl($routeName, $routeParams, $absoluteUrl)
{
$cookieValue = $this->request->cookies->get('cityNameFromCookie');
$routeParams = array_merge($routeParams, array('cityNameFromCookie' => $cookieValue));
return $this->router->generateUrl($routeName, $routeParams, $absoluteUrl);
}
}
Anywhere you have access to the container, you will be able to do the following
$this->container->get('city')->generateUrl('yourroute', $params);
If you still think that it isn't a great solution; you will have to extend the router service (or find a better way to extend the router component to make it behave the way you are expecting it to).
I personally use the method above so I can pass an entity to a path method in Twig. You can find an example in my MainService class and PathExtension Twig class defined in the services.yml.
In Twig, I can do forum_path('routename', ForumEntity) and in a container aware environment I can do $this->container->get('cornichon.forum')->forumPath('routename', ForumEntity).
You should have enough information to make an informed decision

Resources