Access parameter from the Command Class - symfony

I am adding a new command line.
I would like to have access to the value of a parameter (parameters.yml) in my Class.
I read that i should add this class as a service to have access to the parameter. So
//config.yml
imports:
- { resource: services.yml }
//services.yml
services:
less_css_compiler:
class: MyVendor\MyBundle\Command\ThemeCommand
arguments: [%less_compiler%]
//parameters.yml
parameters:
less_compiler: WinLess.exe
it is said that normaly the argument is in the constructor of the class but if I do this :
public function __construct($less_compiler) {
$this->less_compiler = $less_compiler;
}
I have a warning saying that the first argument is missing. In the Command mother class there is a name as then unique argument of the constructor but even though I write :
public function __construct($name, $less_compiler) {
}
It does not change anything..
Other possibility is to call the service inside my class :
$service = $this->getContainer()->get('less_css_compiler');
But how do I get the argument ?
Thank you

Simple way, let command extend ContainerAwareCommand
$this->getContainer()->getParameter('parameter_name');
or
You should create seperate service class
$service = $this->getContainer()->get('less_css_compiler');
//services.yml
services:
less_css_compiler:
class: MyVendor\MyBundle\Service\LessCompiler
arguments: [%less_compiler%]
In service class, create constructor like above you mentioned
public function __construct($less_compiler) {
$this->less_compiler = $less_compiler;
}
Call the service from command class.
Thats it.
Reason: You are making command class itself as service, bit command class contructor expects the command name as the first argument.

I had the same problem when using Symfony 4.4. This blog post explains it very well.
If you use autowire just update your code as below:
use Symfony\Component\DependencyInjection\ParameterBag\ParameterBagInterface;
class MessageGenerator
{
private $params;
public function __construct(ParameterBagInterface $params)
{
$this->params = $params;
}
public function someMethod()
{
$parameterValue = $this->params->get('parameter_name');
// ...
}
}

Related

Dynamically get an instance of a service in controller by some value

I want to get a service instance in controller (symfony 4) just by value that might look like this:
$provider = 'youtube'
That's my setup:
Class videoProvider {
//Here I have all common methods for all services
}
Class YoutubeService extends videoProvider {}
Class OtherVideoService extends videoProvider {}
Now the question is how to do something like this. If $provider = 'youtube'
Then use YouTube service new YoutubeService () and go on. But what if service does not exist? What then?
Is that even possible?
You can do something like this
Create a folder with the name Provider
Create an interface, for example, VideoProviderInterface, and put into the folder
To your interface add the method getProviderName(): string
Create your providers and put them into the folder and implement the interface
To your services.yaml add the _instanceof: option, and add to your interface some tag
Exclude your provider folders from the App\: option in the services.yaml
Create class, ProviderManager and inject your service locator
More information you can find here
services.yaml
_instanceof:
App\Provider\VideoProviderInterface:
tags: ['app.provider']
App\Provider\:
resource: '../../src/Provider/*'
App\Provider\ProviderManager:
arguments:
- !tagged_locator { tag: 'app.provider', default_index_method: 'getProviderName' }
App\:
resource: '../src/*'
exclude: '../src/{DependencyInjection,Entity,Migrations,Tests,Kernel.php,Provider}'
VideoProviderInterface
<?php
namespace App\Provider;
interface VideoProviderInterface
{
public function getProviderName(): string
}
ProviderManager
<?php
namespace App\Provider;
use Symfony\Component\DependencyInjection\ServiceLocator;
class ProviderManager
{
/**
* #var ServiceLocator
*/
private $serviceLocator;
public function __construct(ServiceLocator $serviceLocator)
{
$this->serviceLocator = $serviceLocator;
}
public function findByName(string $name): ?VideoProviderInterface
{
return $this->serviceLocator->has($name) ? $this->serviceLocator->get($name) : null;
}
}
$this->container->has('my-service-name') and $this->container->get('my-service-name') in a controller is probably what you are looking for. The my-service-name is the name you give the service in your service config and make sure your service is public.
Exemple config (see doc here)
# this is the service's name
site_video_provider.youtube:
public: true
class: App\Provider\YoutubeService
[...]
Then in a controller, or any container aware classes: (see doc here)
$service_name = 'site_video_provider.'.$provider;
if($this->container->has($service_name)){
$service = $this->container->get($service_name);
}

Access Doctrine within a custom service in Symfony4

Aware that there is a lot of information around the net regarding this, I am still having a lot of trouble getting this to work.
I have created a custom service:
<?php
namespace App\Service;
use Doctrine\ORM\EntityManagerInterface;
use App\Entity\AccommodationType;
use App\Entity\Night;
class AvailabilityChecks {
private $em;
public function __construct(EntityManagerInterface $em)
{
$this->em = $em;
}
public function nightAvailable(string $RoomCode, string $NightDate) {
$GetRoom = $this->em->getDoctrine()->getRepository(AccommodationType::class)->findOneBy([
'RoomCode' => $RoomCode
]);
$RoomQnt = $GetRoom->getNightlyQnt();
$GetNight = $this->em->getDoctrine()->getRepository(Night::class)->findOneBy([
'RoomCode' => $RoomCode,
'NightDate' => $NightDate
]);
$NumberOfNights = $GetNight->count();
if($NumberOfNights<$RoomQnt) {
return true;
}
else {
return false;
}
}
}
and have put this in services.yaml:
AvailabilityChecks.service:
class: App\Service\AvailabilityChecks
arguments: ['#doctrine.orm.entity_manager']
So when I try and use this in my controller, I get this error:
Too few arguments to function App\Service\AvailabilityChecks::__construct(), 0 passed in /mypath/src/Controller/BookController.php on line 40 and exactly 1 expected
I just can't figure out why it's not injecting the ORM stuff into the constructor! Any help greatly appreciated
The problem is in your BookController. Even though you didn't posted its code I can assume you create new AvailabilityChecks in it (on line 40).
In Symfony every service is intantiated by service container. You should never intantiate service objects by yourself. Instead BookController must ask service container for AvailabilityChecks service. How should it do it ?
In Symfony <3.3 we used generally :
use Symfony\Bundle\FrameworkBundle\Controller\Controller;
class MyController extends Controller
{
public function myAction()
{
$em = $this->get('doctrine.orm.entity_manager');
// ...
}
}
Nowadays services can be injected in controllers using autowiring which is way easier:
use Doctrine\ORM\EntityManagerInterface;
class MyController extends Controller
{
public function myAction(EntityManagerInterface $em)
{
// ...
}
}
You are using the wrong service for what you want to do. The alias doctrine that is used, e.g. in the AbstractController when you call getDoctrine() is bound to the service Doctrine\Common\Persistence\ManagerRegistry.
So the code you wrote fits better with that and you should either add #doctrine or #Doctrine\Common\Persistence\ManagerRegistry to the service definition.
Both with your current configuration or the changed one, you don't have to call $this->em->getDoctrine(), because $this->em is already equivalent to $this->getDoctrine() from your controller. Instead you could create a (private) method to make it look more like that code, e.g.:
private function getDoctrine()
{
return $this->em;
}
Then you can call $this->getDoctrine()->getRepository(...) or use $this->em->getRepository(...) directly.
In Symfony 4, you dont need to create it as services. This is automatically now. Just inject the dependencies what you need in the constructor. Be sure that you have autowire property with true value in services.yml (it is by default)
Remove this from services.yml:
AvailabilityChecks.service:
class: App\Service\AvailabilityChecks
arguments: ['#doctrine.orm.entity_manager']
You dont need EntityManagerInterface because you are not persisting anything, so inject repositories only.
<?php
namespace App\Service;
use App\Entity\AccommodationType;
use App\Entity\Night;
use App\Repository\AccommodationTypeRepository;
use App\Repository\NightRepository;
class AvailabilityChecks {
private $accommodationTypeRepository;
private $nightRepository
public function __construct(
AcommodationTypeRepository $acommodationTypeRepository,
NightRepository $nightRepository
)
{
$this->acommodationTypeRepository = $acommodationTypeRepository;
$this->nightRepository = $nightRepository;
}
public function nightAvailable(string $RoomCode, string $NightDate) {
$GetRoom = $this->acommodationTypeRepository->findOneBy([
'RoomCode' => $RoomCode
]);
$RoomQnt = $GetRoom->getNightlyQnt();
$GetNight = $this->nightRepository->findOneBy([
'RoomCode' => $RoomCode,
'NightDate' => $NightDate
]);
$NumberOfNights = $GetNight->count();
if($NumberOfNights<$RoomQnt) {
return true;
}
else {
return false;
}
}
}
In SF4, you no longer need to specify dependencies required by your custom service in the service.yaml file. All you have to do is to use dependency injection.
So remove config lines, and call your service directly in the controller method :
<?php
namespace App\Controller;
use App\Service\AvailabilityChecks ;
use Symfony\Bundle\FrameworkBundle\Controller\AbstractController;
class AppController extends AbstractController
{
public function index(AvailabilityChecks $service)
{
...
}
}
Having said that, i think you don't need custom service to do simple operations on database. Use repository instead.

Inject Container in my Repository Class

i try to inject Container in my RepositoryClass, but it does not work.
BaseRepository:
<?php
namespace MyApp\ApplicationBundle\Repository;
use Symfony\Component\DependencyInjection\ContainerAwareInterface;
use Symfony\Component\DependencyInjection\ContainerInterface;
class BaseRepository implements ContainerAwareInterface
{
protected $container;
public function setContainer(ContainerInterface $container=null)
{
echo "container";
var_dump($container);
}
public function __construct()
{
echo __CLASS__;
}
}
services.yml
services:
myapp.base_repository:
class: MyApp\ApplicationBundle\Repository\BaseRepository
calls:
- [ setContainer, [ '#service_container' ] ]
DefaultController:
$baseRep = new BaseRepository();
The only output that i get, is the echo FILE from the BaseRepository Construct.
The second way that i tried, is to inject the GuzzleClient self (this is the reason why i tried to inject the container, because i need my guzzle-configuraton-settings.
services.yml
myapp.base_repository:
class: MyApp\ApplicationBundle\Repository\BaseRepository
arguments: ['#csa_guzzle.client.mce']
BaseRepository:
use GuzzleHttp\Client;
class BaseRepository
{
public function __construct(Client $client)
{
var_dump($client);
echo __CLASS__;
}
}
But then i got the following error:
Type error: Argument 1 passed to
MyApp\ApplicationBundle\Repository\BaseRepository::__construct() must
be an instance of GuzzleHttp\Client, none given, called in
MyApp/src/Chameleon/DefaultBundle/Controller/DefaultController.php on
line 20
Anyone know what i can do?
Thank you!
To get the class that is managed by the Service Container you have to use said container to get the service with that id myapp.base_repository as Twifty says:
$this->get('myapp.base_repository');
// or more generally in classes implementing ContainerAwareInterface:
$this->container->get('myapp.base_repository');
If you create a new instance yourself you will have to manage all dependencies:
// In your controller extending Symfony's Controller:
$repository = new BaseRepository();
$repository->setContainer($this->container);
Similarly if you inject a Guzzle-client into the repository you have to either retrieve the service from the container or create it yourself with all the dependencies:
// $this->get() assumes you are in the controller as well
$repositoryWithClientFromServiceContainer = new BaseRepository(
$this->get('csa_guzzle.client.mce')
);
// This obviously works everywhere
$repositoryWithNewDefaultClient = new BaseRepository(
new GuzzleHttp\Client()
);
Furthermore injecting the service container into a class violates the dependency inversion you try to achieve by using the Service Container in the first place. This means, instead of making your repository ContainerAware you should only add the services you need in that repository, not the whole container. Just as you do in the 2nd example with the Guzzle-client.
Some people argue it's okay for controllers to violate that principle, but I personally prefer controller's being defined as services to be able to quickly see which dependencies they have by looking at the constructor.
As a general rule I would avoid using the ContainerAwareInterface.
Similarly if you inject a Guzzle-client into the repository you have
to either retrieve the service from the container or create it
yourself with all the dependencies:
// $this->get() assumes you are in the controller as well
$repositoryWithClientFromServiceContainer = new BaseRepository(
$this->get('csa_guzzle.client.mce')
);
// This obviously works everywhere
$repositoryWithNewDefaultClient = new BaseRepository(
new GuzzleHttp\Client()
);
Furthermore injecting the service container into a class violates the
dependency inversion you try to achieve by using the Service Container
in the first place. This means, instead of making your repository
ContainerAware you should only add the services you need in that
repository, not the whole container. Just as you do in the 2nd example
with the Guzzle-client.
Some people argue it's okay for controllers to violate that principle,
but I personally prefer [controller's being defined as services][1] to
be able to quickly see which dependencies they have by looking at the
constructor.
As a general rule I would avoid using the ContainerAwareInterface.
[1]: http://symfony.com/doc/current/cookbook/controller/service.html
Thank you.
So, it would be the better solution, if i inject only the guzzleClient, right?
As you can see, i have a few classes that extends from my BaseRepository and they need the guzzleClient.
But how is it possible to inject the guzzleClient for this scenario? If the programmer only want to create his basic "MyRep" Repositoryclass in the controller without any params.
services.yml
myapp.base_repository:
class: MyApp\ApplicationBundle\Repository\BaseRepository
arguments: ['#csa_guzzle.client.mce']
BaseRepository:
use GuzzleHttp\Client;
class BaseRepository
{
private $client = null;
public function __construct(Client $client)
{
var_dump($client);
$this->client = $client;
}
public getClient() {
return $this->client;
}
}
MyRepository:
MyRep extends BaseRepository:
use GuzzleHttp\Client;
class BaseRepository
{
public function __construct()
{
var_dump($this->getClient());
}
}
Thank you!

Cannot redeclare class in Symfony2 Twig extension

I'm creating a Twig extension and activating it with a service. Everything works great except I'm trying to simply use another class from my Twig extension.
The idea is to instantiate the new class then use it as needed. Instantiating is a problem as it errors with:
Error: Cannot redeclare class NewClass in .../Bundle/NewClass.php line 13
Surely it instantiates it once. Why is this happening?
namespace Bundle\Twig;
use Bundle\NewClass;
class NewExtension extends \Twig_Extension
{
private $request;
private $new_class;
public function __construct($container) {
//get the Request object
$this->request = $container->get('request');
//instantiate new class
$this->new_class = new NewClass(); // this part triggers the error
}
///etc.
You should make your NewClass into a service and then inject that and the #request service into your twig extension rather than the container. Injecting the container directly is a bad idea apparently.
For example in your services..
services:
# create new class as a instantiated service
acme_demo.new_class:
class: Acme\DemoBundle\NewClass
arguments: [ '#request' ]
acme_demo.twig.acme_extension:
class: Acme\DemoBundle\Twig\AcmeExtension
arguments: [ '#request', '#acme_demo.new_class' ]
# inject the Request service and your class from above
tags:
- { name: twig.extension }
And then in your Twig extension
namespace Bundle\Twig;
use Bundle\NewClass;
class NewExtension extends \Twig_Extension
{
private $new_class;
public function __construct(NewClass $newClass) {
$this->new_class = $newClass;
}
///etc.
In your NewClass:
namespace Bundle;
class NewClass
{
private $param1;
private $param2;
public function __construct(Request $request)
{
$this->param1 = $request->get('param1');
$this->param2 = $request->get('param2');
// You could also add some check in here to make sure they are valid.
}

Calling controller inside a controller

Why it gives the following error when calling controller inside a controller?
Fatal error: Call to a member function get() on a non-object in
/home/web/project/symfony2/vendor/symfony/src/Symfony/Bundle/FrameworkBundle/Controller/Controller.php
on line 149
In the controller I called a class that extends Controller:
class DemoController extends Controller
{
public function indexAction()
{
$object = new \Acme\DemoBundle\Service\Object();
$object->method();
}
// ...
}
The class is something like this:
# Acme/DemoBundle/Service/Object.php
class Object extends Controller
{
public function method()
{
$em = $this->getDoctrine()->getEntityManager(); // the problem
// ...
}
}
When I use $this to call service, doctrine, or something else like within a controller, the error occurred. Otherwise, it works.
How can I use, for example, doctrine inside this class?
Try
$object->setContainer($this->container);
before you call method()
Edit:
Basically it's a bad idea to have a service extend Controller but if you really need to do this, try to add this
your.service:
class: Your\Class
arguments: [...]
calls:
- [ setContainer, [#service_container] ]
in your service configuration file (probably service.yml)

Resources