i got a little error trying to implement a MaintenanceListener service, who will display a maintenance page
Here's my services.yml
# https://symfony.com/doc/current/service_container.html
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
# makes classes in Cocorico\CoreBundle\DataFixtures\ORM\ available to be used as services
# this creates a service per class whose id is the fully-qualified class name
# Cocorico\CoreBundle\DataFixtures\ORM\:
# resource: '../../src/Cocorico/CoreBundle/DataFixtures/ORM/*'
# 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,Repository,Tests,Event}'
# 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'
maintenance_listener:
class: AppBundle\Event\MaintenanceListener
arguments:
tags:
- { name: kernel.event_listener, event: kernel.request, method: onKernelRequest }```
Here's my class:
<?php
namespace MListener;
use Symfony\Component\HttpKernel\Event\GetResponseEvent;
use Symfony\Component\HttpFoundation\Response;
class MListener
{
public function onKernelRequest(GetResponseEvent $event)
{
$event->setResponse(new Response('Iziparty is in maintenance mode', Response::HTTP_SERVICE_UNAVAILABLE));
$event->stopPropagation();
}
}
and here's the error i get:
FastCGI sent in stderr: "PHP message: PHP Fatal error: Cannot declare class MListener\MListener, because the name is already in use in /var/www/Symfony/src/AppBundle/Event/MaintenanceListener.php on line 9" while reading response header from upstream
Thanks for the help.
it all comes down to autoload magic. The autoload magic assumes a certain directory structure, that is (among other things) defined in composer.json. It essentially says:
namespace AppBundle\... is in directory src/AppBundle/...
and every class AppBundle\Something\Else is therefore located in src/AppBundle/Something/Else.php
now, symfony starts to load the service that is supposed to handle an event (due to your configuration) AppBundle\Event\MaintenanceListener, which it tries to instantiate, which leads to the auto loader loading the file src/AppBundle/Event/MaintenanceListener.php which only contains the class MListener/MListener.
Since auto-loading is a bit hacky, usually, it will try other approaches / definitions and possibly try to read that file again and it'll then fail to re-declare the MListener/MListener class, since it already exists.
Just to be explicit about this: These approaches work very well if standards are followed (specifically PSR-4 in this case), which bind the directory structure to namespace structure. If you put something in a file, that - according to PSR-4 - doesn't belong there, you gonna get problems, like the one you got.
The fix is easy and obvious: namespace is the directory (with backslash instead of whatever directory separator your OS has), filename is the classname (without .php obviously). So either, rename your file to src/MListener/MListener.php and adapt the services.yaml accordingly: MListener\Mlistener: ... or you rename your namespace and class in that file to AppBundle\Event and MaintenanceListener respectively.
Be sure to create the file src/Event/MaintenanceListener.php
<?php
namespace AppBundle\Event;
use Symfony\Component\HttpKernel\Event\GetResponseEvent;
use Symfony\Component\HttpFoundation\Response;
class MaintenanceListener
{
public function onKernelRequest(GetResponseEvent $event)
{
$event->setResponse(new Response('Iziparty is in maintenance mode', Response::HTTP_SERVICE_UNAVAILABLE));
$event->stopPropagation();
}
}
With same name declared in the file service.yaml
Related
I have created a custom Symfony 5.3 bundle to share code between different projects. In src/controller/SomeController.php the bundle implements a controller class which extends from Symfony\Bundle\FrameworkBundle\Controller\AbstractController.
When accessing this controller via a route in my Symfony project I get the following error:
"XY\CommensBundle\Controller\SomeController" has no container set, did
you forget to define it as a service subscriber?
AbstractController has a setContainer method which is used to inject the service container. On controllers implemented directly in my Symfony project this method is called automatically by autowire / autoconfigure.
However, regarding to the Symfony docs autowire / autoconfigure should not be used for bundle services. Instead, all services should be defined explicitly. So I added this to the bundles services.yaml:
# config/services.yaml
services:
xy_commons.controller.some_controller:
class: XY\CommensBundle\Controller\SomeController
public: false
calls:
- [ setContainer, [ '#service_container' ]]
After adding the bundle to my Symfony project using Composer the console shows, that the controller is correctly added as a service. Everything seems fine.
php bin/console debug:container 'xy_commons.controller.some_controller'
Information for Service "xy_commons.controller.some_controller"
=============================================================
---------------- -------------------------------------------------------
Option Value
---------------- -------------------------------------------------------
Service ID xy_commons.controller.some_controller
Class XY\CommensBundle\Controller\SomeController
Tags -
Calls setContainer
Public no
Synthetic no
Lazy no
Shared yes
Abstract no
Autowired no
Autoconfigured no
---------------- -------------------------------------------------------
However, the error is still the same. So how to configure controllers / services in Bundles correctly?
EDIT:
SomeController is just a very basic subclass of AbstractController:
class SomeController extends AbstractController {
public function some(): Response {
return new Response("<html><body>OK</body></html>");
}
}
Fully-qualified class name as Service ID
Usually I use FQCNs as Service ID. However, in this case I followed the advise from the Symfony docs (linked above) which explicitly say not to do so:
If the bundle defines services, they must be prefixed with the bundle
alias instead of using fully qualified class names like you do in your
project services. For example, AcmeBlogBundle services must be
prefixed with acme_blog. The reason is that bundles shouldn’t rely on
features such as service autowiring or autoconfiguration to not impose
an overhead when compiling application services.
In addition, services not meant to be used by the application
directly, should be defined as private.
This is way I used a snake name as ID and made the service private. The controller is not really used as service but only created automatically by Symfony when accessing / calling it via a route.
I recently struggle with the same problem. This is the solution.
Suppose we have the Vendor/FooBundle bundle.
Controller with route annotations
<?php
namespace Vendor\FooBundle\Controller;
use Symfony\Bundle\FrameworkBundle\Controller\AbstractController;
use Symfony\Component\Routing\Annotation\Route;
class EntryCategoryController extends AbstractController
{
/**
* #Route("/")
*/
public function list()
{
$entryCategories = [];
return $this->render('#VendorFoo/entry_category/list.html.twig', [
'entryCategories' => $entryCategories
]);
}
}
Register controller as service. The service id must be the controller fully-qualified class name, not a snake case string like vendor_foo.controller.entry_category. This is the key thing! Eventually you can create an alias for the controller service, but this is redundant.
# Resources/config/services.yaml
services:
# The service id must be the controller fully-qualified class name.
# If it a snake case string like `vendor_foo.controller.entry_category`,
# then an alias must be created.
# Vendor\FooBundle\Controller\EntryCategoryController:
# alias: '#vendor_foo.controller.entry_category'
# public: false
Vendor\FooBundle\Controller\EntryCategoryController:
# Setting class is redundant, but adds autocompletions for the IDE.
class: Vendor\FooBundle\Controller\EntryCategoryController
arguments:
# Add this tag to inject services into controller actions.
tags: ['controller.service_arguments']
# Call the setContainer method to get access to the services via
# $this->get() method.
calls:
- ['setContainer', ['#service_container']]
Routing file.
# Resources/config/routing.yaml
vendorfoo_entrycategories:
resource: '#VendorFooBundle/Controller/EntryCategoryController.php'
type: annotation
prefix: /entry-categories
Import routing file in your app
# config/routes.yaml
vendor_foo:
resource: '#VendorFooBundle/Resources/config/routing.yml'
prefix: /foo
That's all.
So #ArturDoruch has the correct main points. You should use the fully qualified class name as the service id though I suppose you could use snake case as long as you also used it in your routes file. But there is no particular reason not to use the class name.
Your controller service also needs to be public so the controller resolver can pull it from the DI container. If the service is not public then the resolver just tries to new the controller and will never call set container or inject any constructor args. That is why you get the error about no container set. By the way, a magical side effect of tagging the service with controller.service_arguments is that the service becomes public.
The thing is that using controllers in bundles is just not something you see much anymore mostly because it's a pain if application want to slightly tweak the way controller works.
So if you look in the best practices you see things like bundle controllers should not extend AbstractConroller and bundles should not use autowire. Good advice for most bundles but if you just want to get stuff working then it's easy to get bogged down.
I would suggest starting with the same services.yaml file that comes with the application. You need to tweak the paths slightly:
# Resources/config/services.yaml
services:
_defaults:
autowire: true
autoconfigure: true
MyBundle\:
resource: '../../'
exclude:
- '../../Resources/'
- '../../DependencyInjection/'
- '../../Entity/'
# bin/console debug:container MyController
Service ID MyBundle\Controller\MyController
Class MyBundle\Controller\MyController
Tags controller.service_arguments
container.service_subscriber
Calls setContainer
Public yes
Synthetic no
Lazy no
Shared yes
Abstract no
Autowired yes
Autoconfigured yes
Now you can focus on just getting your bundle to work. Let autowire do the heavy lifting. Once the bundle has stabilized then maybe you can go back in and start to manually define your services as recommended in the best practices. Or maybe not.
I have an application with symfony 3.4, and I am updating it to flex and then going to version 4.
The problem is that i have many bundles, and in FLEX they do not work with these ... so I am looking for a way to maintain my bundles ...
This is what i have in my SRC folder (controller, entity and repository are empty):
> SRC
> CONTROLLER
> ENTITY
> REPOSITORY
> Kernel.php
> .htaccess
> H360 (the place of my bundles)
> comercialBundle
> jasperBundle
> generalBundle
> ...
This is the error message it returns to me:
In FileLoader.php line 168:
Expected to find class "App\H360\JasperBundle\Controller\DefaultController" in file "C:\inetpub\wwwroot\360forhotels\src/H360\JasperBundle\Controller\DefaultController.php" while importing services from resource "../src/*", but it was not found! Check the name
space prefix used with the resource in C:\inetpub\wwwroot\360forhotels\config/services.yaml (which is loaded in resource "C:\inetpub\wwwroot\360forhotels\config/services.yaml").
In FileLoader.php line 157:
Expected to find class "App\H360\JasperBundle\Controller\DefaultController" in file "C:\inetpub\wwwroot\360forhotels\src/H360\JasperBundle\Controller\DefaultController.php" while importing services from resource "../src/*", but it was not found! Check the name
space prefix used with the resource.
So this is part of my "services.yaml" file (I know it's not right):
# 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']
Start by adding H360 to the exclude section under App. Trying to autowire complete bundles will not only result in those App prefix errors but will cause a considerable amount of fun besides. Hopefully your bundles are already working so there will be no need yo autowire them. In fact you might consider just turning autowire off completely until you get your app working.
You might then have issues with the psr4 section of composer.json. You need to setup autoloading (not autowire) so you classes can be loaded.
I'm trying something so much time but maybe it's not even possible.
Sorry for my bad language.
So, I followed Symfony Doc https://symfony.com/doc/current/bundles.html to create new Bundle, and than I followed https://symfony.com/doc/current/bundles/extension.html to create DI extension.
My files:
AcmeHelloExtension.php
namespace App\Acme\HelloBundle\DependencyInjection;
use Symfony\Component\Config\FileLocator;
use Symfony\Component\DependencyInjection\Loader\XmlFileLoader;
use Symfony\Component\DependencyInjection\Extension\Extension;
use Symfony\Component\DependencyInjection\ContainerBuilder;
class AcmeHelloExtension extends Extension
{
public function load(array $configs, ContainerBuilder $container)
{
$loader = new XmlFileLoader(
$container,
new FileLocator(__DIR__.'/../Resources/config')
);
$loader->load('services.xml');
}
}
AcmeHelloBundle.php
namespace App\Acme\HelloBundle;
use Symfony\Component\HttpKernel\Bundle\Bundle;
class AcmeHelloBundle extends Bundle
{
}
I added it to config/bundles.php
src/Acme/HelloBundle/Resources/config/services.yaml
services:
App\Acme\HelloBundle\AcmeHelloBundle:
tags: ['example-tags']
This service file isn't auto loaded, do I need to do next steps or it should work? When I check it with debug:container ....bundle...
Option-Tags has empty value. When I put this code in config/services.yaml it works.
The basic problem was that the bundle's source code was located under the project's src directory:
project
src
Ztest
ZtestBundle.php
This in turn was causing the bundle's services to be be autowired by the application's config/services.yaml file:
# project\config\services
App\:
resource: '../src/'
exclude:
- '../src/DependencyInjection/'
...
- '../src/Ztest' # Add this to fix the problem
Excluding the bundle's source code fixed the issues. Autowiring at the application level overrides any manual wiring done at the bundle level.
Of course in general if you do decide you need a bundle then it's source code should be in it's own independent directory:
project
src
src-ztest-bundle
For this to work you also need to update the psr-4 section of composer.json and run "composer dump-autoload".
Keep in mind that in Symfony 4+, the only recommended usage of custom bundles is for code shared across multiple Symfony applications. In which case, the bundle should ultimately end up in it's own repository and composer package.
However, custom bundles inside of an application are still supported and there are times where they come in useful.
I have a little external library that expose many classes.
Into my symfony4 project I would like to declare my class from vendor, as a service with autowire and public.
So I have include my library with composer and add psr configuration like this into composer.json:
"autoload": {
"psr-4": {
"App\\": "src/",
"ExternalLibrary\\": "vendor/external-library/api/src/"
}
}
After that I have tried to change my services.yaml into symfony like this:
ExternalLibrary\:
resource: '../vendor/external-library/api/src/*'
public: true
autowire: true
If I launch tests or run the application returns me this error:
Cannot autowire service "App\Domain\Service\MyService": argument "$repository" of method "__construct()" references interface "ExternalLibrary\Domain\Model\Repository" but no such service exists. You should maybe alias this interface to the existing "App\Infrastructure\Domain\Model\MysqlRepository" service.
If I declare into services.yaml the interface this works fine:
ExternalLibrary\Domain\Model\Lotto\Repository:
class: '../vendor/external-library/api/src/Domain/Model/Repository.php'
public: true
autowire: true
But I have many classes and I don't want to declare each class, how can I fix services.yaml without declare every single service?
Thanks
You need to create services by hand:
I did not test it but it should look like this
services.yaml
Some\Vendor\:
resource: '../vendor/external-library/api/src/*'
public: true # should be false
Some\Vendor\FooInterface:
alias: Some\Vendor\Foo # Interface implementation
Some\Vendor\Bar:
class: Some\Vendor\Bar
autowire: true
php
<?php
namespace Some\Vendor;
class Foo implements FooInterface
{
}
class Bar
{
public function __construct(FooInterface $foo)
{
}
}
To be more precise you should have something like
ExternalLibrary\Domain\Model\Repository:
alias: App\Infrastructure\Domain\Model\MysqlRepository
Let's take Dompdf as an example :
When you try to add type-hint Dompdf in your action controller or service method , an error will be occurred saying that auto-wiring isn't possible because Dompdf is an external PHP library
So to solve this problem we'll make a little change in our services.yaml file by adding this short config
Dompdf\: #Add the global namespace
resource: '../vendor/dompdf/dompdf/src/*' #Where can we find your external lib ?
autowire: true #Turn autowire to true
Apply the above example to all external PHP libs :)
That's all !
I had the same problem and someone gave me this solution, which works fine for me:
Use an external repository with symfony4 trouble with autoload and parameters
I copy the other solution by user #DasBen here just in case:
I think that you don't have to import each service separately. Your are already doing that with the "Puc\SapClient" part.
The problem could be that you are importing your models, which should not be imported.
In the symfony example project there is this part vor "services.yaml":
# 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/{Bundle,DependencyInjection,Entity,Model,Migrations,Tests,Kernel.php}'
Then your part would be:
# makes classes in src/ available to be used as services
# this creates a service per class whose id is the fully-qualified class name
Puc\SapClient\:
resource: '../vendor/puc/sap-client/src/*'
exclude: ''../vendor/puc/sap-client/src/{Entity,Model,"etc."}'
"etc." Would be everything that is not needed as service.
I have a bundle which has interface Optimax\HealthCheckBundle\Service\HealthInterface
I need set tags to all services which implement this interface. I do it with the following directive:
_instanceof:
Optimax\HealthCheckBundle\Service\HealthInterface:
tags: ['health.service']
It works fine when I put this directive into config/services.yaml. But if I put this code into my bundle's config (which required via composer) vendor/optimax/health-check/src/Resources/config/services.yaml it doesn't work. I don't want copy-paste this directive into services.yaml every time when I require this bundle to a new project.
How can I move this directive into services.yaml which is in my Bundle's directory or at least into another file in config/packages folder of the project?
To expand on the issue for others.
_instanceof functions identically to the _defaults definition. In that the _instanceof definition only applies to the file it is used in.
This prevents third-party bundle definitions from affecting your entire application with definitions like:
_defaults:
public: true
autowire: false
_instanceof:
Psr\Log\LoggerAwareInterface:
- method: setLogger
arguments:
- '#custom_logger'
tags:
- { name: monologer.log, channel: 'custom_channel' }
Therefor if the service you are attempting to tag with _instanceof is not declared in the same services.yml file the tag will not be added.
To tag services that implements an Interface in the entire application, you would need to use the method provided by #Jeroen
Did you try auto tagging all services with this interface in your Bundle extension like this:
$container->registerForAutoconfiguration(CustomInterface::class)
->addTag('app.custom_tag')
;
Taken from the Symfony docs:
https://symfony.com/doc/current/service_container/tags.html