Symfony 3.3.6 change to service auto loading from 3.2 - symfony

I updated to 3.3.6 yesterday and in setting up a new service I have created in AppBundle. Old style of service registration works - when I try the new approach it is not.
I have a service
AppBundle/Service/SomeService.php
namespace AppBundle\Service;
class SomeService {
}
services.yml
services:
# THIS WORKS
#app.some_service:
# class: AppBundle\Service\SomeService
# arguments: []
_defaults:
autowire: true
autoconfigure: true
public: false
AppBundle\:
resource: '../../src/AppBundle/*'
Inside my action I try and use the service:
$someService = $this->get(SomeService::class);
// THIS WORKS
//$someService = $this->get('app.some_service');
I get the following error:
You have requested a non-existent service
"AppBundle\Service\SomeService".
What am I missing or not understanding about the changes made to 3.3?
I feel it's likely something I missed due to upgrading from 3.2 to 3.3+
Ideas?

Related

Symfony 3.4 NotFoundHttpException No route found for "GET /lucky/number"

I'v created a symfony 3.4 project,and I've create a controller following the doc (I had to change the controller namespace)
<?php
namespace App\Controller;
//namespace AppBundle\Controller; this is the default namespace in the doc
use Symfony\Component\HttpFoundation\Response;
use Symfony\Component\Routing\Annotation\Route;
class LuckyController
{
/**
* #Route("/lucky/number")
*/
public function numberAction()
{
$number = random_int(0, 100);
return new Response(
'<html><body>Lucky number: '.$number.'</body></html>'
);
}
}
But when I try to access to http://127.0.0.1:8000/lucky/number I get this error:
NotFoundHttpException No route found for "GET /lucky/number"
I tried to clean the cache but didn't work and think I dont have to download any anootations library, I don't know what is wrong
If you changed the namespace you also need to make some changes to your config.
You can tag the individual controller with controller.service_arguments:
# app/config/services.yml
services:
# ...
# explicitly configure the service
App\Controller\LuckyController:
tags: [controller.service_arguments]
Or if you have changed the entire AppBundle namespace to App and you are using the Symfony Standard Edition (version 3.4) services.yml configuration, just change all instances of AppBundle to App:
# app/config/services.yml
services:
# default configuration for services in *this* file
_defaults:
autowire: true
autoconfigure: true
public: false
# makes classes in src/App available to be used as services
App\:
resource: '../../src/App/*'
# you can exclude directories or files
# but if a service is unused, it's removed anyway
exclude: '../../src/App/{Entity,Repository}'
# controllers are imported separately to make sure they're public
# and have a tag that allows actions to type-hint services
App\Controller\:
resource: '../../src/App/Controller'
public: true
tags: ['controller.service_arguments']
Of course always clear the cache after making any changes.

How to override namespace argument in RedisAdapter for a Symfony4 app to cache

I want to use custom namespace in our RedisAdapter to cache in our Symfony4 app. However, when i want to set arguments like this in our services.yaml;
cache.adapter.redis:
class: Symfony\Component\Cache\Adapter\RedisAdapter
arguments:
- '#Redis'
- 'app'
I see this error message:
Symfony\Component\Cache\Traits\RedisTrait::init() expects parameter 1 to be Redis, RedisArray, RedisCluster or Predis\ClientInterface, string given.
By the way, our cache config(config/packages/cache.yaml) is simple like below. So, how can I set namespace directly from any config?
cache:
app: cache.adapter.redis
default_redis_provider: 'redis://%env(REDIS_HOST)%:%env(REDIS_PORT)%'
I solved it with this config:
app.cache.adapter.redis:
parent: 'cache.adapter.redis'
public: true
autowire: true
autoconfigure: false
tags:
- {name: 'cache.pool', namespace: 'app'}
If you need the namespace to be interoperable with a third-party app,
you can take control over auto-generation by setting the namespace attribute of the cache.pool service tag.
For example, you can override the service definition of the adapter:
# config/services.yaml
services:
app.cache.adapter.redis:
parent: 'cache.adapter.redis'
tags:
- { name: 'cache.pool', namespace: 'my_custom_namespace' }
more details here: https://github.com/symfony/symfony-docs/pull/12527

Symfony 3.4 inject service inside another fail

I am doing a service in Symfony and I need to inject my mail service. The mail service depends on a parameter:
app.email:
class: AppBundle\Services\EmailNotificationService
autowire: false
public: true
arguments:
- '#doctrine.orm.entity_manager'
- '#swiftmailer.mailer'
- '#templating.engine.twig'
- '%mailer_user%'
- '#logger'
It works to inject it into one service:
app.booking:
class: AppBundle\Services\BookingService
autowire: false
public: true
arguments:
- '#doctrine'
- '#pay.pay'
- '#app.calendar'
- '#app.email'
- '#app.push'
but when I include it in another:
services:
app.servicios_extras:
class: AppBundle\Services\ServiciosExtrasService
autowire: false
public: true
arguments:
- '#app.email'
...
class ServiciosExtrasService
{
private $mailer;
public function __construct(EmailNotificationService $mailer)
{
$this->mailer = $mailer;
}
it gives me the error:
Cannot autowire service "AppBundle\Services\EmailNotificationService": argument "$mailerUser" of method "__construct()" has no type-hint, you should configure its value explicitly.
I'm missing something?
UPDATE
app/config/services.yml:
_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
I don't normally answer these sorts of questions because there are a number of similar questions with the same answer. But I could not find one that could be considered to be a duplicate.
The short answer is to add the Services directory to the list of excluded directories:
#app/config/services.yml
AppBundle\:
resource: '../../src/AppBundle/*'
exclude: '../../src/AppBundle/{Entity,Repository,Tests,Services}'
Doing so prevents autowire from trying to create a service with an id of AppBundle\Services\EmailNotificationService and then failing because $emailUser is a string.
The Symfony container has moved away from explicitly naming services with things like app.email. Instead, the class name (AppBundle\Services\EmailNotificationService) is preferred. So autowire finds all classes under src, looks in container to see if a service has been defined with the same class name and, if none is found, creates a new service. Autowire does not link app.email with your email class. Unless you are supporting a legacy project then it might be best to do away with app.* service names.
And, as a bonus, by going with class names you can eliminate all the service configuration except for a bind under the _default section. But it looks like you do have a legacy project in which case you should probably turn off autowire under _default just to preserve your own sanity.

Symfony 4 services local binding in different environments

I have to bind parameters with different values in different environments, and having problems with this.
I was trying this:
# config/services.yaml
services:
_defaults:
bind:
$param: 'param for PROD'
# config/services_dev.yaml
services:
_defaults:
bind:
$param: 'param for DEV'
# src/Controller/SomeController.php
class MyController extends AbstractController
{
public function example($param)
{
echo $param;
}
}
But it forces me to have all the services defined in both of services.yaml and services_dev.yaml files, otherwise it does not work.
I would like to have a services.yaml shared for any environment, and only override the custom services/bindings etc, not have two identical files with all services listed in them for changing one binding value.
The real problem is that I have to create two http clients (real and a dummy) with same interface, in production load the real one, and in development load the dummy, Symfony 4-s autowiring allows me to inject the interface in a controller and choose which client to use in binding:
# config/services.yaml
services:
_defaults:
bind:
'ClientInterface': '#real_client'
# More services here...
# config/services_dev.yaml
services:
_defaults:
bind:
'ClientInterface': '#dummy_client'
# Here I don't want to have another copy of the services,
# but it does not work without them
# Controller
public function someMethod(ClientInterface $client)
{
// ...
}
In Symfony 2 I was able to extend services.yml and in services_dev.yml only define the specific values I wanted to override/add, but in Symfony 4 services_dev.yaml can not use services from services.yaml and I have to keep my services identical in two different files which is pain.
Anny suggestions?
Thank you.
I'm updating the post again with a real example:
services.yaml
# This file is the entry point to configure your own services.
# Files in the packages/ subdirectory configure your dependencies.
# Put parameters here that don't need to change on each machine where the app is deployed
# https://symfony.com/doc/current/best_practices/configuration.html#application-related-configuration
parameters:
locale: 'en'
app.access_token: '%env(string:APP_ACCESS_TOKEN)%'
app.aws_version: '%env(string:AWS_VERSION)%'
app.aws_profile: '%env(string:AWS_PROFILE)%'
app.aws_region: '%env(string:AWS_REGION)%'
app.aws_queue_url_creation: '%env(string:AWS_QUEUE_URL_CAMPAIGN_CREATION)%'
app.aws_queue_url_edition: '%env(string:AWS_QUEUE_URL_CAMPAIGN_EDITION)%'
app.redis_host: '%env(string:REDIS_HOST)%'
app.redis_port: '%env(string:REDIS_PORT)%'
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.
public: false # Allows optimizing the container by removing unused services; this also means
# fetching services directly from the container via $container->get() won't work.
# The best practice is to be explicit about your dependencies anyway.
bind:
App\Service\MessageSenderServiceInterface: '#App\Service\MessageSenderSqsService'
# 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/{DependencyInjection,Entity,Migrations,Tests,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
# Authenticators
App\Security\ApiKeyAuthenticator:
arguments:
- "%app.access_token%"
# Clients
App\Client\AwsSqsClient:
arguments:
- "%app.aws_version%"
- "%app.aws_profile%"
- "%app.aws_region%"
App\Client\RedisClient:
arguments:
- "%app.redis_host%"
- "%app.redis_port%"
# Services
App\Service\MessageSenderSqsService:
arguments:
- '#App\Client\AwsSqsClient'
- '#App\Client\RedisClient'
- "%app.aws_queue_url_creation%"
- "%app.aws_queue_url_edition%"
App\Service\MessageSenderRedisService:
arguments:
- '#App\Client\RedisClient'
services_dev.yaml
imports:
- { resource: 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.
public: false # Allows optimizing the container by removing unused services; this also means
# fetching services directly from the container via $container->get() won't work.
# The best practice is to be explicit about your dependencies anyway.
bind:
App\Service\MessageSenderServiceInterface: '#App\Service\MessageSenderRedisService'
Controller.php
use Symfony\Bundle\FrameworkBundle\Controller\AbstractController;
class TestController extends AbstractController
{
/**
* #Route("/api/dummy")
*/
public function dummyEndpoint(MessageSenderServiceInterface $messageSender)
{
echo get_class($messageSender); exit;
}
}
And the echo from controller for both envs (prod and dev) is
App\Service\MessageSenderSqsService
But if I copy whole node "services" form services.yaml to services_dev.yaml and only change the binding config, it works fine and says that the injected class is:
App\Service\MessageSenderRedisService
I've just noticed that if I don't touch the "_defaults" node, it works as expected, the problems start only when I want to override the _defaults node of services...
You can define parameters in parameters section of config.yml and overwrite this parameters in config_dev.yml.
# config.yml
imports:
# ...
parameters:
parameter_1: value 1
parameter_2: value 2
# ...
framework:
# ...
# config_dev.yml
imports:
# ...
parameters:
parameter_1: dev value 1
# ...
framework:
# ...
This parameters can be used used in service.yml as:
# service.yml
services:
_defaults:
bind:
$param: '%parameter_1%'
Finally the problem was only in overriding the "_defaults" node (which I was touching in order to have different "bind" configs in the project).
Extending services.yaml without overriding _defaults, everything works as expected. And the solution is to have different configuration for services with their bindings by environment, and have "_defaults" only in services.yaml.
If we override the "_defaults" in other files, we'll have to redefine all the services too.
Thanks everyone for help.
You have some options:
1.Don't use bind and write different service configs for different environments
# services.yaml
App\Controller:
arguments:
- "#client"
# services_dev.yaml
App\Controller:
arguments:
- "#dummy_client"
2.Use bind and create service alias in each environment's services.yaml:
# services.yaml
services:
some.client:
alias: "#client"
# services_dev.yaml
services:
some.client:
alias: "#dummy_client"
3.Just configure only one ClientInterface service per environment:
# services.yaml
App\ClientInterface:
class: App\RealClient
# services_dev.yaml
App\ClientInterface:
class: App\DummyClient
4.Use factory which will create this client depends on environment (but this is not very good practice as for me)
# services.yaml
App\ClientInterface:
factory: ["#App\ClientFactory", create]
arguments:
- '%kernel.environment%'
class ClientFactory
{
public function create(string $env): ClientInterface
{
if ($env === 'dev') {
return new DummyClient();
} else {
return new Client();
}
}
}
5.In your case, when you have so much services and you want to inject same service in all of them, you can use option #3 or you can create one interface for all of them and use _instanceof:
# services.yaml
_instanceof:
App\SomeCommonInterface:
calls:
- method: setSomeService # interface's method
arguments:
- '#service'
# services_dev.yaml
_instanceof:
App\SomeCommonInterface:
calls:
- method: setSomeService
arguments:
- '#dummy_service'

Service autowire don't work with Symfony 4

I try to pass from Symfony 3.4 to Symfony 4.1, but I've a problem with autowire. I've the symfony/swiftmailer-bundle installed, and in an event subscriber I have:
public function __construct(\Swift_Mailer $mailer, EngineInterface $templating, EntityManagerInterface $em, $senderMail, $senderName)
{
$this->mailer = $mailer;
$this->templating = $templating;
$this->em = $em;
$this->senderMail = $senderMail;
$this->senderName = $senderName;
}
In the service.yaml:
# Put parameters here that don't need to change on each machine where the app is deployed
# https://symfony.com/doc/current/best_practices/configuration.html#application-related-configuration
parameters:
locale: 'en'
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.
public: false # Allows optimizing the container by removing unused services; this also means
# fetching services directly from the container via $container->get() won't work.
# The best practice is to be explicit about your dependencies anyway.
# 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/{Entity,Migrations,Tests,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
# Twig
twig.extension.text:
class: Twig_Extensions_Extension_Text
tags:
- { name: twig.extension }
# Listeners
App\EventListener\ContactNotificationSubscriber:
$senderMail: '%env(MAILER_SENDER_ADDRESS)%'
$senderName: '%env(MAILER_SENDER_NAME)%'
But I've an error:
Cannot autowire service "App\EventListener\ContactNotificationSubscriber": argument "$mailer" of method "__construct()" references class "Swift_Mailer" but no such service exists.
I don't understand why... The component exists, with PhpStorm, I can click on \Swift_Mailer and see the class, but Symfony always return to me an error...
If someone know why :-) Thanks a lot
I was having the same problem. For my case, the bundle was not being included in bundles.php. Adding the following in bundles.php solved it for me:
Symfony\Bundle\SwiftmailerBundle\SwiftmailerBundle::class => ['all' => true],
You can change
App\:
resource: '../src/*'
exclude: '../src/{Entity,Migrations,Tests,Kernel.php}'
to :
App\:
resource: '../src/*'
exclude: '../src/{Entity,EventListener,Migrations,Tests,Kernel.php}'

Resources