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.
}
Related
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.
my constructor of my Controller looks like:
function __construct(){#
var_dump($this->get('translator'));
exit();
}
this will give a FatalErrorException: Error: Call to a member function get() on a non-object. But why? If I use it inside a action it will work.
Base controller's method get() is a shortcut for $this->container->get($id);. $this->container is set in one of the controller's parent - abstract class ContainerAware. So,
until object construction is finished, there is no Controller object that would have get() method. In general, container is not available in Controller's constructor.
This is because Controller method get needs the container property. Controller extends ContainerAware which has a method setContainer. This method let the property container be aware of the Container.
Upon instanciation, no method are called, here is the workflow
$controller = new MyController($parameters);
$controller->setContainer($container);
Before calling __construct, controller has no property $container
public function __construct($parameters)
{
var_dump($this->container); // NULL
}
So, by calling $this->get() you are doing
$this->get('translator');
// =
$this->container->get('translator');
// =
null->get('translator');
Hence the error.
If you need the validator, you'll have to ask it in your constructor (and respect the Law of Demeter).
To do so, you'll need to declare your controller as a service
services.yml
services:
my_controller:
class: Acme\FooBundle\Controller\MyController
arguments:
- "#translator"
calls:
- [ "setContainer", [ "#service_container" ] ]
routing.yml
bar_route:
path: /bar
defaults: { _controller: my_controller:barAction }
MyController
class MyController extends Controller
{
protected $translator;
public function __construct(TranslatorInterface $translator)
{
$this->translator = $translator;
}
}
Surely a dumb thing but I'm not able to register MyService from MyBundle
src/Me/MyBundle/
$ ls -R src/Me/MyBundle/
DependencyInjection/
MeMyBundleExtension.php
Configuration.php
Resources/
config/
services.yml
Services/
MyService.php
MyBundle.php
src/Me/MyBundle/DependencyInjection/MeMyBundleExtension.php
namespace Me\MyBundle\DependencyInjection;
// standard stuff
class MeMyBundleExtension extends Extension
{
public function load(array $configs, ContainerBuilder $container)
{
// standard stuff loading the yml file
}
}
src/Me/MyBundle/DependencyInjection/Configuration.php
namespace Me\MyBundle\DependencyInjection;
use Symfony\Component\Config\Definition\Builder\TreeBuilder;
use Symfony\Component\Config\Definition\ConfigurationInterface;
class Configuration implements ConfigurationInterface
{
public function getConfigTreeBuilder()
{
$treeBuilder = new TreeBuilder();
$rootNode = $treeBuilder->root('my_services');
return $treeBuilder;
}
}
src/Me/MyBundle/Resources/config/services.yml
services:
me.myservice:
class: Me\MyBundle\Services\MyService
src/Me/MyBundle/Services/MyService.php
namespace Me\MyBundle\Services;
class MyService
{
public function __construct()
{
die('test');
}
}
I can see that my service is not registered when I try to instantiate it from a controler
$test = $this->get('me.myservice');
You have requested a non-existent service "me.myservice"
Is there something wrong here ?
$ php app/console container:debug | grep me.mybundle
Nothing matches
src/Me/MyBundle/Resources/config/services.yml
services:
me.myservice:
class: Me\MyBundle\Services\MyService.yml
This should refer a class not a yml-file. So it should be class: Me\MyBundle\Services\MyService
Further should you check your namespaces. If your bundle is not a child-bundle (getParent('SonataUserBundle')) you should put your classes in your own namespace.
src/Me/MyBundle/Services/MyService.php
namespace Me\MyBundle\Services;
src/Me/MyBundle/DependencyInjection/Configuration.php
namespace Me\MyBundle\DependencyInjection;
Also should your MyBundle.php be MeMyBundle.php according to symfony's naming strategy.
There is an automatic mapping thing going on in which the extension class name is derived from the bundle name. I had trouble with this some time ago. So I routinely just explicitly specify the extension in the Bundle class. Something like:
namespace Cerad\Bundle\AppBundle;
use Symfony\Component\HttpKernel\Bundle\Bundle;
use Cerad\Bundle\AppBundle\DependencyInjection\AppExtension;
class CeradAppBundle extends Bundle
{
public function getContainerExtension()
{
return new AppExtension();
}
}
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');
// ...
}
}
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)