I want to migrate a symfony2.8 api to symfony5.4. I transferred all my Symfony commands, changed containerAwareCommand to \Symfony\Component\Console\Command\Command and changed the command configuration to match the configuration of the new version .
However, my commands are still not visible in the bin/console list and I cannot use them without declaring them in the services.yml. I have more than thirty commands to transfer and adding a paragraph in the service.yml seems to me tedious and not necessary. Did I forget something?
Second question, how can I access the service container directly in the commands? On Symfony 2.8 I was accessing it with $this->getContainer()->get('toto') but it doesn't seem to work anymore on version 5.
services.yml:
tags:
- { name: 'console.command', command: 'word:manager:expiration_email' }
arguments:
- '#service_container'
- '#logger'
Command:
protected function configure()
{
$this
->setName('word:manager:expiration_email')
->setDescription('Alert email : Send an email x days before the expiration.')
->addOption(
'days',
'd',
InputOption::VALUE_REQUIRED,
'How many days before the expiration',
1
);
}
/**
* {#inheritdoc}
*/
protected function execute(InputInterface $input, OutputInterface $output): int
{
$days = intval($input->getOption('days'));
if ($days > 0) {
try {
$this->getManager()->sendExpirationEmail($days);
} catch (\Exception $e) {
$this->container->get('logger')->error(sprintf(
'aemCommand: an error occurred in file "%s" (L%d): "%s"',
$e->getFile(),
$e->getLine(),
$e->getMessage()
));
}
}
return 0;
}
/**
* #return aem
*/
private function getManager()
{
return $this->container->get('word.manager.alert_employee');
}
services.yml:
imports:
- { resource: "#WORDCoreBundle/Resources/config/services.yml" }
- { resource: "#WORDAlertBundle/Resources/config/services.yml" }
- { resource: "#WORDEmployeeBundle/Resources/config/services.yml" }
- { resource: "#WORDTimeManagementBundle/Resources/config/services.yml" }
- { resource: "#WORDUserBundle/Resources/config/services.yml" }
- { resource: "#WORDFileBundle/Resources/config/services.yml" }
- { resource: "#WORDLocalisationBundle/Resources/config/services.yml" }
- { resource: "#WORDPlatformBundle/Resources/config/services.yml" }
- { resource: "#WORDCompanyBundle/Resources/config/services.yml" }
- { resource: "#WORDContractBundle/Resources/config/services.yml" }
- { resource: "#WORDFolderBundle/Resources/config/services.yml" }
- { resource: "#WORDTrainingBundle/Resources/config/services.yml" }
- { resource: "#WORDExpenseReportBundle/Resources/config/services.yml" }
- { resource: "#WORDBookStoreBundle/Resources/config/services.yml" }
- { resource: "#WORDPaymentBundle/Resources/config/services.yml" }
- { resource: "#WORDInvoiceBundle/Resources/config/services.yml" }
- { resource: "#WORDAgendaBundle/Resources/config/services.yml" }
- { resource: "#WORDAlertBundle/Resources/config/services.yml" }
- { resource: "#WORDAnnualReviewBundle/Resources/config/services.yml" }
- { resource: "#WORDProReviewBundle/Resources/config/services.yml" }
- { resource: "#WORDWidgetBundle/Resources/config/services.yml" }
- { resource: "#WORDCommentBundle/Resources/config/services.yml"}
- { resource: "#WORDFrontBundle/Resources/config/services.yml" }
- { resource: "#WORDNotificationBundle/Resources/config/services.yml" }
services:
App\:
resource: '../src/'
exclude:
- '../src/DependencyInjection/'
- '../src/Entity/'
- '../src/Kernel.php'
_defaults:
autowire: true
autoconfigure: true
WORD\CoreBundle\Controller\ApiController:
calls:
- method: setContainer
arguments: ['#service_container']
WORD\PlatformBundle\Controller\PlatformRESTController:
calls:
- method: setContainer
arguments: ['#service_container']
WORD\UserBundle\Controller\UserRESTController:
calls:
- method: setContainer
arguments: ['#service_container']
WORD\AlertBundle\Controller\AlertRESTController:
calls:
- method: setContainer
arguments: ['#service_container']
WORD\EmployeeBundle\Controller\EmployeeRESTController:
calls:
- method: setContainer
arguments: ['#service_container']
WORD\CompanyBundle\Controller\CompanyRESTController:
calls:
- method: setContainer
arguments: ['#service_container']
WORD\CompanyBundle\Controller\ContactRESTController:
calls:
- method: setContainer
arguments: ['#service_container']
WORD\CompanyBundle\Controller\ServiceRESTController:
calls:
- method: setContainer
arguments: ['#service_container']
WORD\CompanyBundle\Controller\ChartRESTController:
calls:
- method: setContainer
arguments: ['#service_container']
WORD\BookStoreBundle\Controller\InfoRESTController:
calls:
- method: setContainer
arguments: ['#service_container']
WORD.user.manager.user:
class: 'WORD\UserBundle\Service\UserManager'
public: false
lazy: true
arguments:
- '#fos_user.util.password_updater'
- '#fos_user.util.canonical_fields_updater'
- '#fos_user.object_manager'
- 'WORD\UserBundle\Entity\User'
WORD\AgendaBundle\DataFixtures\AgendaFixtures:
tags: [doctrine.fixture.orm]
WORD\UserBundle\DataFixtures\UserFixtures:
tags: [doctrine.fixture.orm]
arguments:
$fos_manager: '#fos_user.user_manager'
WORD\LocalisationBundle\DataFixtures\CountryFixtures:
tags: [doctrine.fixture.orm]
App\DataFixtures\Processor\UserProcessor:
tags: [ { name: fidry_alice_data_fixtures.processor } ]
WORD\AlertBundle\Command\AlertExpirationEmailCommand:
tags:
- { name: 'console.command', command: 'WORD:alert:expiration_email' }
arguments:
- '#service_container'
- '#logger'
Composer.json
"autoload": {
"psr-4": {
"App\\": "src/",
"WORD\\": "WORD"
}
},
Namespace PSR-4 mapping
In order for composer to properly map out your files to their associated namespaces, the namespace and path must be declared in the autoload.psr-4 and/or autoload.classmap composer.json configuration.
composer.json autoload mappings
"autoload": {
"psr-4": {
"App\\": "src/",
"W2D\\": "W2D/",
"WORD\\": "WORD/"
},
"classmap": {
"src/",
"W2D/",
"WORD/"
}
},
Convert Commands to be Lazy Loaded
First to reduce configuration and instantiation overhead, make all of your commands lazy loaded by declaring the name using protected static $defaultName = '.....'.
Repeat the changes for each command in the namespace.
Command changes
// /WORD/AlertBundle/Command/AlertExpirationEmailCommand.php
namespace WORD\AlertBundle\Command;
class AlertExpirationEmailCommand extends Command
{
protected static $defaultName = 'WORD:alert:expiration_email';
protected function configure()
{
$this
/* ->setName('WORD:alert:expiration_email') */
// ...
}
}
Fix Service Loading
All services in Symfony 3.4+ are public: false by default (including the Logger) making them inaccessible by using container->get('service'). Additionally if a service is not explicitly injected in a service configuration or by autowire, the services are removed from the Container to reduce overhead. Because of this, you should never inject the entire container into a service - since the service you may be looking for may have inadvertently been removed.
Due to the way services are handled, _defaults should be the first line of any services declaration. It is also important to note, that _defaults will not be inherited/cascaded by any other file or configuration imports.
Since App\ is the default namespace being autowired, you will need to replace it with yours to ensure that the appropriate namespace(s) are being loaded as services for autowire.
Application services autowire
# /config/services.yaml
services:
# default configuration for services in *this* file
_defaults:
autowire: true
autoconfigure: true
# makes classes in App/ available to be used as services
# this creates a service per class whose id is the fully-qualified class name
App\:
resource: '../src/'
exclude:
- '../src/DependencyInjection/'
- '../src/Entity/'
- '../src/Kernel.php'
# makes classes in WORD/ available to be used as services
# this creates a service per class whose id is the fully-qualified class name
WORD\: # <--- root namespace to autowire eg: WORD\Command\MyCommand
resource: '../WORD/' # location of the namespace
exclude:
- '../WORD/DependencyInjection/'
- '../WORD/Entity/'
- '../WORD/*Bundle/' # Never autowire Bundles as they should be self-contained
# declare manually wired and overridden services below this line
# ...
Fix Bundle Configurations to be self-contained
With the introduction of autowiring and changes to order of operations in Symfony Flex (4.0+), loading Bundle services in the main application config/services.yaml will often cause conflicts in loading. Because of this it is best to use an Extension for each bundle to load the services configurations, until you migrate away from the Bundle file hierarchy in favor of using a flat application configuration.
Again repeat the changes for each registered Bundle.
Ultimately it is best-practice to explicitly define all services
within the Bundles and NOT to use autowire: true. However, considering that this is a migration from
2.8 to 5.x, you will want to refactor away from using Bundles.
Bundle services.yaml and autowire
# /WORD/AlertBundle/Resources/config/services.yml
services:
# default configuration for services in *this* file
_defaults:
autowire: true # Automatically injects dependencies in your services.
autoconfigure: true # Automatically registers your services as commands, event subscribers, etc.
# makes classes in WORD/AlertBundle/ available to be used as services
# this creates a service per class whose id is the fully-qualified class name
WORD\AlertBundle\:
resource: '../../*'
exclude:
- '../../DependencyInjection/'
- '../../Entity/'
# Bundle specific services below this line
# ...
Bundle Extension
/* /WORD/AlertBundle/DependencyInjection/WORDAlertExtension.php */
namespace WORD\AlertBundle\DependencyInjection;
use Symfony\Component\DependencyInjection\ContainerBuilder;
use Symfony\Component\DependencyInjection\Extension\Extension;
use Symfony\Component\DependencyInjection\Loader\YamlFileLoader;
class WORDAlertExtension extends Extension
{
public function load(array $configs, ContainerBuilder $container)
{
$loader = new YamlFileLoader(
$container,
new FileLocator(__DIR__ . '/../Resources/config')
);
$loader->load('services.yml');
}
}
Manual Bundle Extension Loading
When not using the Bundle file hierarchy naming conventions, you may need to manually instantiate the Bundle's extension in the Bundle::getContainerExtension() method.
/* /WORD/AlertBundle/WordAlertBundle.php */
namespace WORD\AlertBundle;
// ...
use WORD\AlertBundle\DependencyInjection\AlertExtension;
class WordAlertBundle extends Bundle
{
public function getContainerExtension()
{
return new AlertExtension();
}
}
Container Access and Autowire
As mentioned above, as of Symfony 3.4+ you should never inject the entire container directly into a service.
Instead, the desired services should be explicitly type-hinted in the __construct() method of the class, which will be injected with the autowire: true services configuration.
# EXAMPLE ONLY
#
# /WORD/AlertBundle/Resources/config/services.yml
services:
# default configuration for services in *this* file
_defaults:
autowire: true # Automatically injects dependencies in your services.
autoconfigure: true
# example autowire and autoconfig
WORD\AlertBundle\:
resource: '../../*'
# EXAMPLE - Service Override Declaration
WORD\AlertBundle\Service\EmployeeManager:
arguments:
- '#fos_user.util.password_updater'
- '#fos_user.util.canonical_fields_updater'
- '#fos_user.object_manager'
- 'WORD\UserBundle\Entity\User'
# EXAMPLE - declaration of alias
word.manager.alert_employee:
alias: WORD\AlertBundle\Service\EmployeeManager
public: true # to prevent deletion of service when not used
# EXAMPLE OVERRIDE - manually declare all files under Command as a command service
WORD\AlertBundle\Command\:
resource: '../../Command/*'
tags:
- { name: 'console.command' } # force as a command
// /WORD/AlertBundle/Command/AlertExpirationEmailCommand.php
namespace WORD\AlertBundle\Command;
// ...
use Psr\Log\LoggerInterface;
use WORD\AlertBundle\Service\EmployeeManager;
class AlertExpirationEmailCommand extends Command
{
protected static $defaultName = 'WORD:alert:expiration_email';
private LoggerInterface $logger;
private EmployeeManager $manager;
public function __construct(LoggerInterface $logger, EmployeeManager $manager)
{
$this->logger = $logger;
$this->manager = $manager;
}
/**
* {#inheritdoc}
*/
protected function execute(InputInterface $input, OutputInterface $output): int
{
$days = intval($input->getOption('days'));
if ($days > 0) {
try {
$this->getManager()->sendExpirationEmail($days);
} catch (\Exception $e) {
$this->logger->error(sprintf(
'aemCommand: an error occurred in file "%s" (L%d): "%s"',
$e->getFile(),
$e->getLine(),
$e->getMessage()
));
}
}
return 0;
}
/**
* #return EmployeeManager
*/
private function getManager(): EmployeeManager
{
return $this->manager;
}
}
Moving Forward
Go through each of the service classes that uses the container directly and refactor them to use the explicit service type-hints or manual wiring in their respective services.yml configurations.
You should aim to migrate away from the 2.x - 3.4 Bundle file hierarchy and naming conventions and port to a flat configuration such as moving WORD\AlertBundle to WORD\Alert\... or WORD\Command\Alert\.... This way you have a single cohesive application, as opposed to several mini-applications as Bundles, significantly reducing code complexity, overhead, page loads, and cache warm-up times.
Alternatively, if each Bundle provides functionality shared across several of your applications, move each Bundle to their own repositories, following the Bundle best-practices, so they are segregated from the main application. Which will require another directory refactoring.
Related
In the default Symfony 5 config, I looking this:
services:
...
App\:
resource: '../src/*'
exclude: '../src/{DependencyInjection,Entity,Exception,Migrations,Tests,Kernel.php}'
...
App\Controller\:
resource: '../src/Controller'
tags: ['controller.service_arguments']
It looks very cool. As if I can configure some default settings for a group of services by namespace, like that
...
App\Service\MySpecialTaskServices\:
resource: '../src/Service/MySpecialTaskServices/*'
public: true
... other default params\args for this NS ...
But this stuff not work: configuration loaded correctly, but parameters can not be applied.
May be I doing something wrong? It's bad idea, clogging services.yaml with a bunch of services with the same params.
I found a good reason, with using custom tags functionality. Maybe it help someone else.
Instantiate you services in group from single interface/abstract
Add _instanceof option in services.yml in global services: block
services:
_default:
....
_instanceof:
App\Service\MySpecialTaskServices\MySpecialTaskInterface:
tags: ['app.public_service']
Define a custom compiler pass for support you new tag with this process method
class PublicServicesGroupPass implements CompilerPassInterface
{
...
public function process(ContainerBuilder $container): void
{
$services = $container->findTaggedServiceIds('app.public_service');
foreach (array_keys($services) as $id) {
$definition = $container->findDefinition($id);
$definition->setPublic(true);
// other works with params
}
}
...
}
And register them in your kernel
class Kernel extends BaseKernel
{
...
protected function build(ContainerBuilder $container): void
{
$container->addCompilerPass(new PublicServicesGroupPass());
}
...
}
And this working correct. But I would like to make it easier.
I would like to have an ability to create services by theirs class-names.
One of the way to do it: is setting "public" property in "services.yaml"
BUT I DON'T WANT to set "public" property for ALL classes in my project
services.yaml
services:
_defaults:
public: false # it helps to optimize performance, doesn't it?
App\Service\Service1
public: true
App\Service\Service2
public: true
App\Service\Service3
public: true
App\Service\* # why I can't use something like "*" here ???
public: true
Service1.php
namespace App\Service;
class Service1
{
// important: every service can have one or more dependencies (Foo, Bar, Baz ... etc)
public function __construct(Foo $foo, Bar $bar)
{
$this->foo = $foo;
//..........
}
}
MyController.php
public function __construct(ContainerInterface $container)
{
$this->container = $container;
}
// hier $className is any class name like "App/Service/xxxx"
public function myAction (string $className)
{
return $this->container->get($className);
}
Questions:
is there a way to set "public" property for directory?
is there a better way to create an instance of service by class-name?
Thanks
Perhaps you can utilize YAML import features to generalize the configuration that you need, making it easier to maintain.
For example:
# /config/services.yaml
imports:
- { resource: 'services/public/*.yaml' } # Import public services
- { resource: 'services/private/*.yaml' } # Import private services
Though, as mentioned, forcing container services to be public goes against Symfony conventions, and will not be supported in future versions. Use dependency injection instead of interacting with the service container directly.
Yes, this should be possible. I tested it with Symfony 5.3.
See the last part in this services.yaml:
services:
# default configuration for services in *this* file
_defaults:
autowire: true # Automatically injects dependencies in your services.
autoconfigure: true # Automatically registers your services as commands, event subscribers, etc.
bind:
$projectDir: '%kernel.project_dir%'
# makes classes in src/ available to be used as services
# this creates a service per class whose id is the fully-qualified class name
App\:
resource: '../src/'
exclude:
- '../src/Kernel.php'
# controllers are imported separately to make sure services can be injected
# as action arguments even if you don't extend any base controller class
App\Controller\:
resource: '../src/Controller/'
tags: ['controller.service_arguments']
# add more service definitions when explicit configuration is needed
# please note that last definitions always *replace* previous ones
App\Messenger\Handler\Event\:
resource: '../src/Messenger/Handler/Event/'
public: true
Similar to controllers you can include multiple classes via resource and make them public. But please be aware of the other answers/comments. Usually you should use dependency injection to load the services you need.
Alternatively (or even better) you could set a tag and use an iterable via dependency injection. See https://symfony.com/doc/5.3/service_container/tags.html#reference-tagged-services for more information.
Why would you need to set services to public? It goes against Symfony best practices which discourage fetching services from the container directly.
If you wish to define services named by their class name, it is enough to reference them with:
services:
App\Service\Service1: ~
App\Service\Service2: ~
App\Service\Service3: ~
App\Service\Service4:
arguments:
- '#App\Service\Service1'
- '#App\Service\Service2'
- '#App\Service\Service3'
and then inject them to your controllers directly, instead of using the container.
My config/service.yaml contains several services that have huge configuration. Now, I want to move the configuration of that services to a separate file.
I tried to do like this:
At the end of service.yaml:
imports:
- { resource: 'packages/custom_service.yaml' }
config/packages/custom_service.yaml:
services:
App\Service\CustomService:
arguments:
$forms:
- location: 'room1'
id: 43543
- location: 'room2'
id: 6476546
- location: 'room3'
id: 121231
...
src/Service/CustomService.php:
/**
* #var array
*/
protected $forms;
public function __construct(array $forms)
{
$this->forms = $forms;
}
But when I try to autowire in some Controller, I am getting this error:
Cannot resolve argument $customService of "App\Controller\CustomController::customAction()": Cannot autowire service "App\Service\CustomService": argument "$forms" of method "__construct()" is type-hinted "array", you should configure its value explicitly.
But if I remove type hint, Then i get this error:
Cannot resolve argument $customService of "App\Controller\CustomController::customAction()": Cannot autowire service "App\Service\CustomService": argument "$forms" of method "__construct()" has no type-hint, you should configure its value explicitly.
The other two answers are partially correct but they don't quite cover everything.
To start with, anytime you decide you want multiple autowired service files you have to be careful that a given service is only autowired by one and only one file. Otherwise Symfony will attempt to define the service multiple times and usually fail. There are different approaches to accomplishing this but the most straight forward one is to explicitly exclude any custom services your config/services.yaml file:
# config/services.yaml
parameters:
imports:
- { resource: 'services/custom_service.yaml' }
services:
# default configuration for services in *this* file
_defaults:
autowire: true # Automatically injects dependencies in your services.
autoconfigure: true # Automatically registers your services as commands, event subscribers, etc.
# makes classes in src/ available to be used as services
# this creates a service per class whose id is the fully-qualified class name
App\:
resource: '../src/'
exclude:
- '../src/Service/CustomService.php' # ADD THIS
- '../src/DependencyInjection/'
- # the rest of the default excludes
So that takes care of skipping CustomService.php in the main config file.
Secondly, notice in the above file I loaded the custom_service.yaml file from config/services instead of config/packages. The packages is really reserved for bundle specific configuration. Every file in there is loaded by default. Putting service files in there may or may not work but it definitely changes the loading order of things and could cause problems. So make a directory just for custom services and use it.
You also need to repeat the _defaults section in each service file since _defaults are considered to be file specific. Bit of a pain perhaps but that is the way it is:
# config/services/custom_service.yaml
services:
_defaults:
autowire: true
autoconfigure: true
App\Service\CustomService:
arguments:
$forms:
-
location: 'room1'
id: 43543
-
location: 'room2'
id: 6476546
-
location: 'room3'
id: 121231
Also note that your yaml array syntax was messed up. Possibly just a copy/paste issue.
And that should do it.
Autowiring has file scope.
You need to set it in config/packages/custom_service.yaml as well.
Modify that file as follows
services:
_defaults:
autowire: true
App\Service\CustomService:
[...]
You should also add autoconfigure and/or visibility (public keyword) if needed.
Unfortunately, I cannot add a comment so I have to use an answer.
I have to make the following assumption, I believe you have the following snippet inside your services.yaml:
App\:
resource: '../src/'
exclude:
- '../src/DependencyInjection/'
- '../src/Entity/'
- '../src/Kernel.php'
- '../src/Tests/'
which already includes your desired class App\Service\CustomService and so Symfony will try to autowire it before looking inside the imports which leads to the error because you have that class defined inside the import which will only be evaluated if the root services.yaml does not already find the class.
The following example will work. However, you will lose auto-discovery of classes inside the App namespace and it is not recommended to do this.
services.yaml:
parameters:
imports:
- { resource: 'custom_services.yaml' }
services:
# default configuration for services in *this* file
_defaults:
autowire: true
autoconfigure: true
App\Controller\:
resource: '../src/Controller/'
tags: ['controller.service_arguments']
custom_services.yaml:
services:
_defaults:
autowire: true
autoconfigure: true
App\Service\SomeService:
arguments:
$forms:
- location: 'room1'
id: 43543
- location: 'room2'
id: 6476546
- location: 'room3'
id: 121231
src/Service/SomeService.php:
<?php
declare(strict_types=1);
namespace App\Service;
class SomeService
{
protected array $forms;
public function __construct(array $forms)
{
$this->forms = $forms;
}
public function getForms()
{
return $this->forms;
}
}
src/Controller/HomeController:
<?php
declare(strict_types=1);
namespace App\Controller;
use App\Service\SomeService;
use Symfony\Component\HttpFoundation\JsonResponse;
use Symfony\Component\Routing\Annotation\Route;
class HomeController
{
#[Route('/', 'home')]
public function __invoke(SomeService $someService)
{
return new JsonResponse($someService->getForms());
}
}
What you can do instead is import the file in the Kernel instead in the yaml file which will overwrite the service definition.
So remove the imports part inside your services.yaml and make sure that you import the file inside the src/Kernel.php:
protected function configureContainer(ContainerConfigurator $container): void
{
$container->import('../config/{packages}/*.yaml');
$container->import('../config/{packages}/'.$this->environment.'/*.yaml');
if (is_file(\dirname(__DIR__).'/config/services.yaml')) {
$container->import('../config/services.yaml');
// Import your custom service files after services.yaml to overwrite already existing definitions
$container->import('../config/custom_services.yaml');
$container->import('../config/{services}_'.$this->environment.'.yaml');
} elseif (is_file($path = \dirname(__DIR__).'/config/services.php')) {
(require $path)($container->withPath($path), $this);
}
}
I followed the example on Symfony 3.4 docs in order to load a Twig extension at runtime but it doesn't load: what I'am doing wrong?
IN: src/PlotlyBundle/Twig/AppRuntime.php
<?php
namespace PlotlyBundle\Twig;
class AppRuntime
{
public function __construct()
{
}
public function biDraw()
{
return 'awesome text here';
}
}
IN: src/PlotlyBundle/Resources/config/services.yml
services:
plotly.twig_runtime:
class: PlotlyBundle\Twig\AppRuntime
public: true
tags:
- { name: twig.runtime }
IN: src/PlotlyBundle/Twig/AppExtension.php
<?php
namespace PlotlyBundle\Twig;
use PlotlyBundle\Twig\AppRuntime;
class AppExtension extends \Twig_Extension
{
public function getFunctions()
{
return [
new \Twig_SimpleFunction(
'bi_draw',
array(AppRuntime::class, 'biDraw')
),
];
}
}
IN: src/AppBundle/Controller/DashboardController.php
$twig = $this->get('plotly.twig_runtime');
return $this->render(
'dashboard/index.html.twig'
);
IN: app/Resources/views/dashboard/index.html.twig
{{ bi_draw() }}
Thanks to #Federkun comments I fixed it by autowiring the Twig extension:
IN: src/PlotlyBundle/Resources/config/services.yml
services:
# default configuration for services in *this* file
_defaults:
# automatically injects dependencies in your services
autowire: true
# automatically registers your services as commands, event subscribers, etc.
autoconfigure: true
# this means you cannot fetch services directly from the container via $container->get()
# if you need to do this, you can override this setting on individual services
public: false
# this creates a service per class whose id is the fully-qualified class name
PlotlyBundle\Twig\:
resource: '../../../../src/PlotlyBundle/Twig/*'
tags:
- { name: twig.runtime }
The example on the Symfony docs (Creating Lazy-Loaded Twig Extensions) need an update to mention that auto-wiring MUST be enabled (as explained in the autoconfigure Option) in order for the example to work.
I submitted a PR to the Symfony docs.
(1/1) ServiceNotFoundException The service
"AppBundle\Controller\FormController" has a dependency on a
non-existent service "Symfony\Component\Serializer\Serializer".
My controller is:
<?php
namespace AppBundle\Controller;
use AppBundle\Service\SubscriberService;
use Sensio\Bundle\FrameworkExtraBundle\Configuration\Route;
use Symfony\Bundle\FrameworkBundle\Controller\Controller;
use Symfony\Component\HttpFoundation\Request;
use Symfony\Component\HttpFoundation\Response;
class FormController extends Controller
{
private $subscriber;
public function __construct(SubscriberService $subscriber)
{
$this->subscriber = $subscriber;
}
/**
* #Route("/", name="form")
*/
public function indexAction(): Response
{
$categories = $this->subscriber->getCategories();
return $this->render('subscription/form.html.twig', ['categories' => $categories]);
}
/**
* #param Request $request
* #return Response
* #Route("/save", name="save_subscriber")
*/
public function save(Request $request): Response
{
$this->subscriber->save($request->get('name'), $request->get('email'), $request->get('categories'));
}
}
Services.yml:
# Learn more about services, parameters and containers at
# https://symfony.com/doc/current/service_container.html
parameters:
#parameter_name: value
services:
# default configuration for services in *this* file
_defaults:
# automatically injects dependencies in your services
autowire: false
# automatically registers your services as commands, event subscribers, etc.
autoconfigure: true
# this means you cannot fetch services directly from the container via $container->get()
# if you need to do this, you can override this setting on individual services
public: false
# makes classes in src/AppBundle available to be used as services
# this creates a service per class whose id is the fully-qualified class name
AppBundle\:
resource: '../../src/AppBundle/*'
# you can exclude directories or files
# but if a service is unused, it's removed anyway
exclude: '../../src/AppBundle/{Entity,Tests}'
# controllers are imported separately to make sure they're public
# and have a tag that allows actions to type-hint services
AppBundle\Controller\:
resource: '../../src/AppBundle/Controller'
public: true
tags: ['controller.service_arguments']
# add more services, or override services that need manual wiring
# AppBundle\Service\ExampleService:
# arguments:
# $someArgument: 'some_value'
AppBundle\Service\SubscriberService:
arguments:
$validator: '#Symfony\Component\Validator\Validator\ValidatorInterface'
$subscriberRepository: '#AppBundle\Repository\SubscriberRepository'
AppBundle\Controller\FormController:
arguments:
$subscriber: '#AppBundle\Service\SubscriberService'
AppBundle\Repository\SubscriberRepository:
arguments:
$serializer: '#Symfony\Component\Serializer\Serializer'
For me error does not make any sense. We can clearly see that there is no such dependency in the controller.
How to fix this?
Your SubscriberRepository has a dependency on serializer. Because serializer service is not available by default, you have to turn it on, activate it in your configuration before using:
# app/config/config.yml
framework:
# ...
serializer:
enabled: true
More Information: http://symfony.com/doc/3.4/serializer.html
Thanks!