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();
}
}
Related
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);
}
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.
I'm writing a Behat extension meant to be used with Symfony and Symfony2Extension.
For some services, I need to inject services defined in the Symfony application. Is there a way to do that?
In your FeatureContext.php file, you need to implement KernelAwareInterface and define setKernel() method. Methods getParameter() and getService() are option and for demonstration purposes.
Example
namespace Football\TeamBundle\Features\Context;
use Behat\MinkExtension\Context\MinkContext;
use Behat\Symfony2Extension\Context\KernelAwareInterface;
use Symfony\Component\HttpKernel\KernelInterface;
class FeatureContext extends MinkContext implements KernelAwareInterface
{
private $kernel;
public function setKernel(KernelInterface $kernelInterface)
{
$this->kernel = $kernelInterface;
}
public function getParameter()
{
$myParameter = $this->kernel->getContainer()->getParameter('name_of_the_param');
}
public function getService()
{
$myService = $this->kernel->getContainer()->get('name_of_the_service');
}
}
Question is simple but...
So we have main service:
class ManagerOne {}
and have several another services we want to use in main service:
class ServiceOne{}
class ServiceTwo{}
class ServiceThree{}
class ServiceFour{}
...
Each named as (in services.yml)
service.one
service.two
service.three
service.four
...
Locations of services is different, not in one folder (but I don't think it's a huge trouble for custom autoloader).
Regarding manual we can inject them via __construct() in main service (ManagerOne) but what if we got 20 such services need to be injected? Or use only that we need. Describe them in services as simple inject? O.o I think it's not good idea so.... Also we can inject container and that's it. BUT! Everywhere people saying that inject container worst solution.
What I want. I need method for ManagerOne service which will load service i need by 'service.name' or 'path' with checker 'service exist'.
You could use service tagging and tag each service you want to use in your ManagerOne class. And either use constructor dependency injection or method injection.
Example:
First of all you need a compiler pass to collect your tagged services:
namespace ...\DependencyInjection\Compiler;
use Symfony\Component\DependencyInjection\Compiler\CompilerPassInterface;
use Symfony\Component\DependencyInjection\Reference;
use Symfony\Component\DependencyInjection\ContainerBuilder;
class ExamplePass implements CompilerPassInterface
{
public function process(ContainerBuilder $container)
{
if (!$container->hasDefinition("manager.one")) {
return;
}
$services = array();
foreach ($container->findTaggedServiceIds('managed_service') as $serviceId => $tag) {
$alias = isset($tag[0]['alias'])
? $tag[0]['alias']
: $serviceId;
// Flip, because we want tag aliases (= type identifiers) as keys
$services[$alias] = new Reference($serviceId);
}
$container->getDefinition('manager.one')->replaceArgument(0, $services);
}
}
Then you need to add the compiler pass to your bundle class:
namespace Example\ExampleBundle;
use Symfony\Component\HttpKernel\Bundle\Bundle;
use Symfony\Component\DependencyInjection\ContainerBuilder;
use ...\DependencyInjection\Compiler\ExamplePass;
class ExampleBundle extends Bundle
{
public function build(ContainerBuilder $container)
{
parent::build($container);
$container->addCompilerPass(new ExamplePass());
}
}
Now you can use your services:
# services.yml
manager.one:
class: ManagerClass
arguments:
- [] # will be replaced by our compiler pass
services.one:
class: ServiceOne
tags:
- { name: managed_service, alias: service_one }
services.two:
class: ServiceTwo
tags:
- { name: managed_service, alias: service_two }
But caution if you get your manager, all service classes will be automatically created. If this is a performance drawback for you could pass only the service ids (not the Reference) to your management class. Add the #service_container as second argument and create the service as needed.
Since 2017, Symfony 3.3 and Symplify\PackageBuilder this gets even easier.
Thanks to this package, you can:
drop tags
have simple 5 line CompilerPass using strict types over strings
Let's get to your example
Suppose you have
1 manager - UpdateManager class
many updaters - a class that implements UpdaterInterface
1. Service Config using PSR-4 autodiscovery
# app/config/services.yml
services:
_defaults:
autowire: true
App\:
resource: ../../src/App
2. Collecting Compiler Pass
use Symfony\Component\DependencyInjection\Compiler\CompilerPassInterface;
use Symplify\PackageBuilder\Adapter\Symfony\DependencyInjection\DefinitionCollector;
final class CollectorCompilerPass implements CompilerPassInterface
{
public function process(ContainerBuilder $containerBuilder)
{
DefinitionCollector::loadCollectorWithType(
$containerBuilder,
UpdateManager::class,
UpdaterInterface::class,
'addUpdater'
);
}
}
It collect all services of UpdaterInterface type
and adds them via addUpdater() method to UpdateManager.
3. Register Compiler Pass in Bundle
namespace App;
use Symfony\Component\HttpKernel\Bundle\Bundle;
use Symfony\Component\DependencyInjection\ContainerBuilder;
final class UpdaterBundle extends Bundle
{
public function build(ContainerBuilder $container)
{
$container->addCompilerPass(new CollectorCompilerPass);
}
}
And that's all!
How to add new updater?
Just create class, that implements UpdaterInterface and it will be loaded to UpdateManager.
no tagging
no manual service registration
no boring work
Enjoy!
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.
}