I am trying to understand the necessary steps for creating reusable bundles. I'm using Symfony 5.2 and PHP 8.0.
I have two sibling directories containing the projects MainProject and FirstModule.
composer.json for project MainProject:
{
"type": "project",
"name": "modulartest/main_project",
"license": "unlicense",
"minimum-stability": "dev",
"prefer-stable": true,
"repositories": [{
"type": "path",
"url": "../FirstModule/"
}],
"require": {
"php": ">=8.0",
"ext-ctype": "*",
"ext-iconv": "*",
"doctrine/annotations": "^1.12",
"modulartest/first_module": "dev-master",
"sensio/framework-extra-bundle": "^6.1",
"symfony/console": "5.2.*",
"symfony/dotenv": "5.2.*",
"symfony/flex": "^1.3.1",
"symfony/framework-bundle": "5.2.*",
"symfony/maker-bundle": "^1.29",
"symfony/yaml": "5.2.*"
},
"config": {
"optimize-autoloader": true,
"preferred-install": {
"*": "dist"
},
"sort-packages": true
},
"autoload": {
"psr-4": {
"App\\": "src/"
}
},
"autoload-dev": {
"psr-4": {
"App\\Tests\\": "tests/"
}
},
"replace": {
"symfony/polyfill-ctype": "*",
"symfony/polyfill-iconv": "*",
"symfony/polyfill-php72": "*"
},
"scripts": {
"auto-scripts": {
"cache:clear": "symfony-cmd",
"assets:install %PUBLIC_DIR%": "symfony-cmd"
},
"post-install-cmd": [
"#auto-scripts"
],
"post-update-cmd": [
"#auto-scripts"
]
},
"conflict": {
"symfony/symfony": "*"
},
"extra": {
"symfony": {
"allow-contrib": false,
"require": "5.2.*"
}
}
}
composer.json for project FirstModule:
{
"type": "symfony-bundle",
"name": "modulartest/first_module",
"license": "unlicense",
"minimum-stability": "dev",
"prefer-stable": true,
"require": {
"php": ">=8.0",
"ext-ctype": "*",
"ext-iconv": "*",
"sensio/framework-extra-bundle": "^6.1",
"symfony/console": "5.2.*",
"symfony/dotenv": "5.2.*",
"symfony/flex": "^1.3.1",
"symfony/framework-bundle": "5.2.*",
"symfony/maker-bundle": "^1.29",
"symfony/yaml": "5.2.*"
},
"config": {
"optimize-autoloader": true,
"preferred-install": {
"*": "dist"
},
"sort-packages": true
},
"autoload": {
"psr-4": {
"ModularTest\\FirstModuleBundle\\": "src/"
}
},
"autoload-dev": {
"psr-4": {
"ModularTest\\FirstModuleBundle\\Tests\\": "tests/"
}
},
"conflict": {
"symfony/symfony": "*"
},
"extra": {
"symfony": {
"allow-contrib": false,
"require": "5.2.*"
}
}
}
In projeto FirstModule, I implemented a service, and a controller that uses that serve, all too simple, just for testing.
Service code in FirstModule\src\Controller\FirstController.php:
<?php
namespace ModularTest\FirstModuleBundle\Service;
/**
* Class FirstService
* #package ModularTest\FirstModuleBundle\Service
*/
class FirstService
{
/**
* #return string
*/
public function now(): string
{
return 'First Service time: '.date('c');
}
}
Code for controller in FirstModule\src\Controller\FirstController.php:
<?php
namespace ModularTest\FirstModuleBundle\Controller;
use ModularTest\FirstModuleBundle\Service\FirstService;
use Symfony\Bundle\FrameworkBundle\Controller\AbstractController;
use Symfony\Component\HttpFoundation\Response;
use Symfony\Component\Routing\Annotation\Route;
/**
* Class FirstController
* #package ModularTest\FirstModuleBundle\Controller
*/
class FirstController extends AbstractController
{
/**
* FirstController constructor.
* #param FirstService $firstService
*/
public function __construct(private FirstService $firstService)
{
}
/**
* #return Response
*/
#[Route('/first', name: 'modular_test_first_module_first')]
public function index(): Response
{
return $this->json([
'message' => 'Welcome to your new controller!',
'path' => 'src/Controller/FirstController.php',
'date' => $this->firstService->now()
]);
}
}
I tried to follow rigorously the instructions given by Symfony documentation in those three pages:
https://symfony.com/doc/current/bundles.html
https://symfony.com/doc/current/bundles/best_practices.html
https://symfony.com/doc/current/bundles/extension.html
Thus, I implemented bundle's class this way:
<?php
namespace ModularTest\FirstModuleBundle;
use Symfony\Component\HttpKernel\Bundle\Bundle;
/**
* Class ModularTestFirstModuleBundle
* #package ModularTest\FirstModuleBundle
*/
class ModularTestFirstModuleBundle extends Bundle
{
/**
* #return string
*/
public function getPath(): string
{
return \dirname(__DIR__);
}
}
I implemented extension's class this way:
<?php
namespace ModularTest\FirstModuleBundle\DependencyInjection;
use Symfony\Component\Config\FileLocator;
use Symfony\Component\DependencyInjection\ContainerBuilder;
use Symfony\Component\DependencyInjection\Extension\Extension;
use Symfony\Component\DependencyInjection\Loader\YamlFileLoader;
/**
* Class ModularTestFirstModuleExtension
* #package ModularTest\FirstModuleBundle\DependencyInjection
*/
class ModularTestFirstModuleExtension extends Extension
{
/**
* #param array $configs
* #param ContainerBuilder $container
* #throws \Exception
*/
public function load(array $configs, ContainerBuilder $container)
{
$loader = new YamlFileLoader(
$container,
new FileLocator(__DIR__.'/../Resources/config')
);
$loader->load('services.yaml');
}
}
And the content inside file FirstModule\src\Resources\config\services.yaml is as follows:
# 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:
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
ModularTest\FirstModuleBundle\:
resource: '../src/'
exclude:
- '../src/DependencyInjection/'
- '../src/Entity/'
- '../src/Kernel.php'
- '../src/Tests/'
# controllers are imported separately to make sure services can be injected
# as action arguments even if you don't extend any base controller class
ModularTest\FirstModuleBundle\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
Ok, all that done, I ran the server in MainProject with symfony server:start, e there were no immediate errors. Then I tried to browser the route that points to the controller that was defined by the bundle, that is, https://127.0.0.1:8000/first, and I received this:
FileLocatorFileNotFoundException
The file "../src/" does not exist (in: "C:\Users\eu\dev\php\symfony\modulartest\FirstModule\src\DependencyInjection/../Resources/config").
This is the Stack Trace:
Symfony\Component\Config\Exception\FileLocatorFileNotFoundException:
The file "../src/" does not exist (in: "C:\Users\eu\dev\php\symfony\modulartest\FirstModule\src\DependencyInjection/../Resources/config").
at C:\Users\eu\dev\php\symfony\modulartest\MainProject\vendor\symfony\config\FileLocator.php:71
at Symfony\Component\Config\FileLocator->locate('../src/', 'C:\\Users\\eu\\dev\\php\\symfony\\modulartest\\FirstModule\\src\\DependencyInjection/../Resources/config', true)
(C:\Users\eu\dev\php\symfony\modulartest\MainProject\vendor\symfony\config\Loader\FileLoader.php:117)
at Symfony\Component\Config\Loader\FileLoader->glob('', true, array(object(FileExistenceResource)), false, false, array())
(C:\Users\eu\dev\php\symfony\modulartest\MainProject\vendor\symfony\dependency-injection\Loader\FileLoader.php:176)
at Symfony\Component\DependencyInjection\Loader\FileLoader->findClasses('ModularTest\\FirstModuleBundle\\', '../src/', array('../src/DependencyInjection/', '../src/Entity/', '../src/Kernel.php', '../src/Tests/'))
(C:\Users\eu\dev\php\symfony\modulartest\MainProject\vendor\symfony\dependency-injection\Loader\FileLoader.php:99)
at Symfony\Component\DependencyInjection\Loader\FileLoader->registerClasses(object(Definition), 'ModularTest\\FirstModuleBundle\\', '../src/', array('../src/DependencyInjection/', '../src/Entity/', '../src/Kernel.php', '../src/Tests/'))
(C:\Users\eu\dev\php\symfony\modulartest\MainProject\vendor\symfony\dependency-injection\Loader\YamlFileLoader.php:671)
at Symfony\Component\DependencyInjection\Loader\YamlFileLoader->parseDefinition('ModularTest\\FirstModuleBundle\\', array('resource' => '../src/', 'exclude' => array('../src/DependencyInjection/', '../src/Entity/', '../src/Kernel.php', '../src/Tests/')), 'C:\\Users\\eu\\dev\\php\\symfony\\modulartest\\FirstModule\\src\\DependencyInjection/../Resources/config\\services.yaml', array('autowire' => true, 'autoconfigure' => true))
(C:\Users\eu\dev\php\symfony\modulartest\MainProject\vendor\symfony\dependency-injection\Loader\YamlFileLoader.php:234)
at Symfony\Component\DependencyInjection\Loader\YamlFileLoader->parseDefinitions(array('parameters' => null, 'services' => array('ModularTest\FirstModuleBundle\' => array('resource' => '../src/', 'exclude' => array('../src/DependencyInjection/', '../src/Entity/', '../src/Kernel.php', '../src/Tests/')), 'ModularTest\FirstModuleBundle\Controller\' => array('resource' => '../src/Controller/', 'tags' => array('controller.service_arguments')))), 'C:\\Users\\eu\\dev\\php\\symfony\\modulartest\\FirstModule\\src\\DependencyInjection/../Resources/config\\services.yaml')
(C:\Users\eu\dev\php\symfony\modulartest\MainProject\vendor\symfony\dependency-injection\Loader\YamlFileLoader.php:154)
at Symfony\Component\DependencyInjection\Loader\YamlFileLoader->load('services.yaml')
(C:\Users\eu\dev\php\symfony\modulartest\FirstModule\src\DependencyInjection\ModularTestFirstModuleExtension.php:30)
at ModularTest\FirstModuleBundle\DependencyInjection\ModularTestFirstModuleExtension->load(array(array()), object(MergeExtensionConfigurationContainerBuilder))
(C:\Users\eu\dev\php\symfony\modulartest\MainProject\vendor\symfony\dependency-injection\Compiler\MergeExtensionConfigurationPass.php:76)
at Symfony\Component\DependencyInjection\Compiler\MergeExtensionConfigurationPass->process(object(ContainerBuilder))
(C:\Users\eu\dev\php\symfony\modulartest\MainProject\vendor\symfony\http-kernel\DependencyInjection\MergeExtensionConfigurationPass.php:39)
at Symfony\Component\HttpKernel\DependencyInjection\MergeExtensionConfigurationPass->process(object(ContainerBuilder))
(C:\Users\eu\dev\php\symfony\modulartest\MainProject\vendor\symfony\dependency-injection\Compiler\Compiler.php:91)
at Symfony\Component\DependencyInjection\Compiler\Compiler->compile(object(ContainerBuilder))
(C:\Users\eu\dev\php\symfony\modulartest\MainProject\vendor\symfony\dependency-injection\ContainerBuilder.php:736)
at Symfony\Component\DependencyInjection\ContainerBuilder->compile()
(C:\Users\eu\dev\php\symfony\modulartest\MainProject\vendor\symfony\http-kernel\Kernel.php:541)
at Symfony\Component\HttpKernel\Kernel->initializeContainer()
(C:\Users\eu\dev\php\symfony\modulartest\MainProject\vendor\symfony\http-kernel\Kernel.php:780)
at Symfony\Component\HttpKernel\Kernel->preBoot()
(C:\Users\eu\dev\php\symfony\modulartest\MainProject\vendor\symfony\http-kernel\Kernel.php:183)
at Symfony\Component\HttpKernel\Kernel->handle(object(Request))
(C:\Users\eu\dev\php\symfony\modulartest\MainProject\public\index.php:20)
What can I try next?
You were almost there. The error message was misleading since it indicated src was a missing file and of course it is a directory. Plus it is obviously right there. The error is actually coming from the autowire section in services.yaml. You copied the lines from config/services.yaml but failed to account for the fact that in a bundle the relative path to the src directory is different.
# first_module/src/Resources/config/services.yaml
services:
_defaults:
autowire: true
autoconfigure: true
ModularTest\FirstModuleBundle\:
resource: '../../../src/' # Tweak the relative path
exclude:
- '../DependencyInjection/'
- '../Entity/'
- '../Kernel.php'
- '../Tests/'
ModularTest\FirstModuleBundle\Controller\:
resource: '../../../src/Controller/'
tags: ['controller.service_arguments']
I find it easier to trouble shoot these sorts of wiring problems from the command line. I just make a console command in main project and injected FirstService into it.
You don't need Bundle::getPath(). Probably had that in there when debugging.
Be aware that if these bundles are truly meant to be shared across multiple applications then autowiring is discouraged. It works but you might want to consider just manually defining services like the other bundles do.
By the way, I had forgotten about using the path attribute for repositories in composer.json. So thank you for that. Seems to work better than a symbolic link.
Related
I have recently been trying to upgrade from Symfony 6.1 to 6.2. In doing so though..something appears to have broken. Does anyone have any clue what changed from 6.1 to 6.2? I have tried looking and continue to do so..but I haven't been able to find any meaningful change that breaks it. The setup docs all show the same formatting for all the available files.
I am on PHP 8.1.11
When I update my composer.json to bring everything up to 6.2, the console throws the following error:
Executing script cache:clear [KO]
[KO]
Script cache:clear returned with error code 1
!!
!! In FileLoader.php line 178:
!!
!! Invalid service id: "MyNamespace\" in /var/www/html/config/services.yaml (whic
!! h is being imported from "/var/www/html/src/Kernel.php").
!!
!!
!! In ContainerBuilder.php line 911:
!!
!! Invalid service id: "MyNamespace\".
!!
!!
!!
Script #auto-scripts was called via post-update-cmd
Nothing has changed, and if I change it all back to 6.1 and reupdate composer..it works just fine.
Below is my 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.
# makes classes in src/ available to be used as services
# this creates a service per class whose id is the fully-qualified class name
MyNamespace\:
resource: '../src/'
exclude:
- '../src/DependencyInjection/'
- '../src/Entity/'
- '../src/Kernel.php'
- '../vendor/'
- '../var/'
_instanceof:
MyNamespace\Enum\AbstractEnumType:
tags: ['app.doctrine_enum_type']
# add more service definitions when explicit configuration is needed
# please note that last definitions always *replace* previous ones
I have downgraded back to 6.1, that works.
I have tried renaming or removing parts of my class..that doesn't work.
Update: Here is a copy of my kernel file:
namespace MyNamespace;
use MyNamespace\Enum\AbstractEnumType;
use Symfony\Bundle\FrameworkBundle\Kernel\MicroKernelTrait;
use Symfony\Component\DependencyInjection\ContainerBuilder;
use Symfony\Component\HttpKernel\Kernel as BaseKernel;
class Kernel extends BaseKernel
{
use MicroKernelTrait;
public function process(ContainerBuilder $container): void
{
$typesDefinition = [];
if ($container->hasParameter('doctrine.dbal.connection_factory.types')) {
$typesDefinition = $container->getParameter('doctrine.dbal.connection_factory.types');
}
$taggedEnums = $container->findTaggedServiceIds('app.doctrine_enum_type');
foreach ($taggedEnums as $enumType => $definition) {
/** #var $enumType AbstractEnumType */
$typesDefinition[$enumType::NAME] = ['class' => $enumType];
}
$container->setParameter('doctrine.dbal.connection_factory.types', $typesDefinition);
}
}
Update 2: I have attached the code for my abstract enum & composer.json
composer.json
{
"type": "project",
"license": "proprietary",
"minimum-stability": "stable",
"prefer-stable": true,
"require": {
"php": ">=8.1",
"ext-ctype": "*",
"ext-curl": "*",
"ext-iconv": "*",
"aws/aws-sdk-php": "^3.252",
"doctrine/annotations": "^1.0",
"doctrine/doctrine-bundle": "^2.7",
"doctrine/doctrine-migrations-bundle": "^3.2",
"doctrine/orm": "^2.13",
"friendsofsymfony/rest-bundle": "^3.4",
"jms/serializer-bundle": "^4.2",
"monolog/monolog": "^3.2",
"nelmio/cors-bundle": "^2.2",
"phpdocumentor/reflection-docblock": "^5.3",
"phpstan/phpdoc-parser": "^1.9",
"ramsey/uuid": "^4.5",
"stripe/stripe-php": "^9.7.0",
"symfony/amazon-mailer": "6.1.*",
"symfony/apache-pack": "^1.0",
"symfony/console": "6.1.*",
"symfony/dom-crawler": "6.1.*",
"symfony/dotenv": "6.1.*",
"symfony/flex": "^2",
"symfony/framework-bundle": "6.1.*",
"symfony/http-client": "6.1.*",
"symfony/maker-bundle": "^1.47",
"symfony/mime": "6.1.*",
"symfony/monolog-bundle": "^3.8",
"symfony/property-access": "6.1.*",
"symfony/property-info": "6.1.*",
"symfony/proxy-manager-bridge": "6.1.*",
"symfony/runtime": "6.1.*",
"symfony/security-bundle": "6.1.*",
"symfony/serializer": "6.1.*",
"symfony/twig-bundle": "6.1.*",
"symfony/yaml": "6.1.*"
},
"config": {
"allow-plugins": {
"composer/package-versions-deprecated": true,
"symfony/flex": true,
"symfony/runtime": true,
"phpstan/extension-installer": true
},
"optimize-autoloader": true,
"preferred-install": {
"*": "dist"
},
"sort-packages": true
},
"autoload": {
"psr-4": {
"MyNamespace\\": "src/"
}
},
"autoload-dev": {
"psr-4": {
"MyNamespace\\Tests\\": "tests/"
}
},
"replace": {
"symfony/polyfill-ctype": "*",
"symfony/polyfill-iconv": "*",
"symfony/polyfill-php72": "*",
"symfony/polyfill-php73": "*",
"symfony/polyfill-php74": "*",
"symfony/polyfill-php80": "*",
"symfony/polyfill-php81": "*"
},
"scripts": {
"auto-scripts": {
"cache:clear": "symfony-cmd",
"assets:install %PUBLIC_DIR%": "symfony-cmd"
},
"post-install-cmd": [
"#auto-scripts"
],
"post-update-cmd": [
"#auto-scripts"
]
},
"conflict": {
"symfony/symfony": "*"
},
"extra": {
"symfony": {
"allow-contrib": false,
"require": "6.1.*"
},
"public-dir": "/"
},
"require-dev": {
"phpstan/extension-installer": "^1.1",
"phpstan/phpstan-symfony": "^1.2",
"phpunit/phpunit": "^9.5",
"symfony/browser-kit": "6.1.*",
"symfony/css-selector": "6.1.*",
"symfony/phpunit-bridge": "^6.2",
"symfony/stopwatch": "6.1.*",
"symfony/web-profiler-bundle": "6.1.*"
}
}
AbstractEnumType.php
namespace MyNamespace\Enum;
use Doctrine\DBAL\Platforms\AbstractPlatform;
use Doctrine\DBAL\Types\StringType;
abstract class AbstractEnumType extends StringType
{
abstract public static function getEnumClass(): string;
public function getName(): string
{
throw new \Exception('getName should be overwritten by child class');
}
public function convertToPHPValue($value, AbstractPlatform $platform): ?\BackedEnum
{
if($value instanceof \BackedEnum){
return $value;
}
$value = parent::convertToPHPValue($value, $platform);
if($value === null){
return null;
}
// 🔥 https://www.php.net/manual/en/backedenum.tryfrom.php
return $this::getEnumClass()::tryFrom($value);
}
public function convertToDatabaseValue($value, AbstractPlatform $platform): ?string
{
return $value?->value;
}
public function getSQLDeclaration(array $fieldDeclaration, AbstractPlatform $platform): string
{
return 'TEXT';
}
}
I created a symfony 5.3 api.
My plan is to inject the "EntityManager" via "EntityManagerInterface".
Error that comes:
Could not resolve argument $entityManager of "app\controller\createuseraccountcontroller()", maybe you forgot to register the controller as a service or missed tagging it with the "controller.service_arguments"?
My services.yaml
parameters:
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/DependencyInjection/'
- '../src/Entity/'
- '../src/Kernel.php'
- '../src/Tests/'
My CreateUserAccountController.php in src/Controller/:
<?php
declare(strict_types=1);
namespace App\Controller;
use Doctrine\ORM\EntityManagerInterface;
use Symfony\Component\HttpFoundation\Response;
use Symfony\Component\Routing\Annotation\Route;
class CreateUserAccountController
{
private EntityManagerInterface $entityManager;
function __construct(EntityManagerInterface $entityManager)
{
$this->entityManager = $entityManager;
}
#[Route('/user/create', name: 'create_user_account')]
public function __invoke(): Response
{
return new Response('hello world');
}
}
My composer.json:
{
"type": "project",
"license": "proprietary",
"minimum-stability": "stable",
"prefer-stable": true,
"require": {
"php": "8.0.*",
"ext-ctype": "*",
"ext-iconv": "*",
"composer/package-versions-deprecated": "1.11.99.2",
"doctrine/doctrine-bundle": "^2.4",
"doctrine/doctrine-migrations-bundle": "^3.1",
"doctrine/orm": "^2.9",
"phpseclib/phpseclib": "^3.0",
"symfony/console": "5.3.*",
"symfony/dependency-injection": "5.3.*",
"symfony/dotenv": "5.3.*",
"symfony/flex": "^1.3.1",
"symfony/framework-bundle": "5.3.*",
"symfony/proxy-manager-bridge": "5.3.*",
"symfony/runtime": "5.3.*",
"symfony/yaml": "5.3.*"
},
"require-dev": {
"symfony/maker-bundle": "^1.31",
"symfony/stopwatch": "5.3.*"
},
"config": {
"optimize-autoloader": true,
"preferred-install": {
"*": "dist"
},
"sort-packages": true
},
"autoload": {
"psr-4": {
"App\\": "src/"
}
},
"autoload-dev": {
"psr-4": {
"App\\Tests\\": "tests/"
}
},
"replace": {
"symfony/polyfill-ctype": "*",
"symfony/polyfill-iconv": "*",
"symfony/polyfill-php72": "*"
},
"scripts": {
"auto-scripts": {
"cache:clear": "symfony-cmd",
"assets:install %PUBLIC_DIR%": "symfony-cmd"
},
"post-install-cmd": [
"#auto-scripts"
],
"post-update-cmd": [
"#auto-scripts"
]
},
"conflict": {
"symfony/symfony": "*"
},
"extra": {
"symfony": {
"allow-contrib": false,
"require": "5.3.*"
}
}
}
I know that I dont use the Manager, but it doesn´t work for me.
The webserver I use: symfony cli webserver
Has anyone a idea, where the problem is?
This is actually a somewhat interesting edge case.
The bottom line is that autowire is defining your controller service as private. This in turn means the controller resolver is simply new'ing the controller instead of pulling it from the container. Hence the missing argument error messages.
Notice that your controller does not extend AbstractController. If it did then autoconfigure would know that it is a controller and end up making it public. All would be well.
Prior to 5.3, the services.yaml file contained:
App\Controller\:
resource: '../src/Controller/'
tags: ['controller.service_arguments']
Those lines would automatically define anything in the controller directory as a controller. Most of the time they were not needed since most controllers extend AbstractController. Which is why the lines ended up being dropped in 5.3.
So if you really don't want to extend AbstractController then add those lines back in. You can use bin/console debug:container CreateUserAccountController to see what is happening.
Finally, you also have the option of simply manually defining your controller service as public. Something like:
services:
App\Controller\CreateUserAccountController:
public: true
tags: ['controller.service_arguments'] # optional
I am developing a Symfony project which somebody else started on and I was happy that I had it up and running. Unfortunatelly after running composer update today, the Symfony page that I have throws an error.
After running composer update the following error was displayed:
Composer update error
Executing script cache:clear [KO]
[KO]
Script cache:clear returned with error code 255
!! Symfony\Component\ErrorHandler\Error\ClassNotFoundError {#132
!! #message: """
!! Attempted to load class "MappingDriverChain" from namespace "Doctrine\Common\Persistence\Mapping\Driver".\n
!! Did you forget a "use" statement for "Doctrine\Persistence\Mapping\Driver\MappingDriverChain"?
!! """
!! #code: 0
!! #file: "./var/cache/dev/ContainerFcUQG2T/App_KernelDevDebugContainer.php"
!! #line: 869
!! trace: {
!! ./var/cache/dev/ContainerFcUQG2T/App_KernelDevDebugContainer.php:869 {
!! ContainerFcUQG2T\App_KernelDevDebugContainer->getDoctrine_Orm_DefaultEntityManagerService($lazyLoad = true)
!! ›
!! › $b = new \Doctrine\Common\Persistence\Mapping\Driver\MappingDriverChain();
!! › $b->addDriver(new \Doctrine\ORM\Mapping\Driver\AnnotationDriver(($this->privates['annotations.cached_reader'] ?? $this->getAnnotations_CachedReaderService()), [0 => (\dirname(__DIR__, 4).'/src/Entity')]), 'App\\Entity');
!! }
!! ./var/cache/dev/ContainerFcUQG2T/App_KernelDevDebugContainer.php:5160 { …}
!! ./var/cache/dev/ContainerFcUQG2T/App_KernelDevDebugContainer.php:5204 { …}
!! ./var/cache/dev/ContainerFcUQG2T/App_KernelDevDebugContainer.php:569 { …}
!! ./vendor/symfony/http-kernel/CacheWarmer/CacheWarmerAggregate.php:87 { …}
!! ./vendor/symfony/http-kernel/Kernel.php:553 { …}
!! ./vendor/symfony/http-kernel/Kernel.php:126 { …}
!! ./vendor/symfony/framework-bundle/Console/Application.php:168 { …}
!! ./vendor/symfony/framework-bundle/Console/Application.php:74 { …}
!! ./vendor/symfony/console/Application.php:140 { …}
!! ./bin/console:42 { …}
!! }
!! }
!! 2020-07-31T08:18:05+00:00 [critical] Uncaught Error: Class 'Doctrine\Common\Persistence\Mapping\Driver\MappingDriverChain' not found
!!
After accessing the Symfony page, this TypeError was printed:
TypeError
Argument 1 passed to Doctrine\Bundle\DoctrineBundle\DataCollector\DoctrineDataCollector::__construct() must be an instance of Doctrine\Common\Persistence\ManagerRegistry, instance of Doctrine\Bundle\DoctrineBundle\Registry given, called in /var/www/html/mgo/var/cache/dev/ContainerFcUQG2T/App_KernelDevDebugContainer.php on line 1176
To be honest I have no idea what caused this issue, but I think it has nothing to do with the changes I made in the twig and Symfony php files of the project.
I'll add some files, if there is anything else that I should be looking into, let me know.
/config/packages/doctrine.yaml
doctrine:
dbal:
url: '%env(resolve:DATABASE_URL)%'
# IMPORTANT: You MUST configure your server version,
# either here or in the DATABASE_URL env var (see .env file)
#server_version: '5.7'
orm:
auto_generate_proxy_classes: true
naming_strategy: doctrine.orm.naming_strategy.underscore_number_aware
auto_mapping: true
mappings:
App:
is_bundle: false
type: annotation
dir: '%kernel.project_dir%/src/Entity'
prefix: 'App\Entity'
alias: App
composer.json
{
"name": "project",
"description": "project description",
"type": "path",
"license": "MIT",
"minimum-stability": "dev",
"require": {
"php": "^7.4",
"ext-ctype": "*",
"ext-iconv": "*",
"mgo/mgo-client-php": "2.0.0",
"sensio/framework-extra-bundle": "^5.1",
"symfony/asset": "5.0.*",
"symfony/console": "5.0.*",
"symfony/dotenv": "5.0.*",
"symfony/expression-language": "5.0.*",
"symfony/flex": "^1.3.1",
"symfony/form": "5.0.*",
"symfony/framework-bundle": "5.0.*",
"symfony/http-client": "5.0.*",
"symfony/intl": "5.0.*",
"symfony/mailer": "5.0.*",
"symfony/monolog-bundle": "^3.1",
"symfony/notifier": "5.0.*",
"symfony/orm-pack": "*",
"symfony/process": "5.0.*",
"symfony/security-bundle": "5.0.*",
"symfony/serializer-pack": "*",
"symfony/string": "5.0.*",
"symfony/translation": "5.0.*",
"symfony/twig-pack": "*",
"symfony/validator": "5.0.*",
"symfony/web-link": "5.0.*",
"symfony/yaml": "5.0.*",
"ext-json": "*"
},
"repositories": [
{
"type": "path",
"url": "/home/*user*/mgo-client-php",
"symlink": false
}
],
"require-dev": {
"symfony/debug-pack": "*",
"symfony/maker-bundle": "^1.0",
"symfony/profiler-pack": "*",
"symfony/test-pack": "*",
"mgo-client-php": "2.0.0",
"monolog/monolog": "^1.23"
},
"config": {
"preferred-install": {
"*": "dist"
},
"sort-packages": true
},
"autoload": {
"psr-4": {
"App\\": "src/"
}
},
"autoload-dev": {
"psr-4": {
"App\\Tests\\": "tests/"
}
},
"replace": {
"paragonie/random_compat": "2.*",
"symfony/polyfill-ctype": "*",
"symfony/polyfill-iconv": "*",
"symfony/polyfill-php72": "*",
"symfony/polyfill-php71": "*",
"symfony/polyfill-php70": "*",
"symfony/polyfill-php56": "*"
},
"scripts": {
"auto-scripts": {
"cache:clear": "symfony-cmd",
"assets:install %PUBLIC_DIR%": "symfony-cmd"
},
"post-install-cmd": [
"#auto-scripts"
],
"post-update-cmd": [
"#auto-scripts"
]
},
"conflict": {
"symfony/symfony": "*"
},
"extra": {
"symfony": {
"allow-contrib": false,
"require": "5.0.*"
}
}
}
EDIT:
As #Alexandre Tranchant suggested I removed my /var/cache file to get the original error. The error that pops up is the following:
Attempted to load class "MappingDriverChain" from namespace "Doctrine\Common\Persistence\Mapping\Driver".
Did you forget a "use" statement for "Doctrine\Persistence\Mapping\Driver\MappingDriverChain"?
It's correct there is no link with your update on twig files.
As your composer.json file doesn't fix the doctrine version, it upgraded your doctrine package version. In the newest version of Doctrine, the pattern of repository files have change.
There is 2 solutions:
Solution1 (Not tested and not recommended): you fix the doctrine package by adding it in the composer.json. You can have a look on the old composer.lock of your application. This isn't the best solution. (But it's only my opinion)
Solution2: Upgrade your files to be compliant with the new version of doctrine.
I see three major steps:
Update the configuration file by updating recipes.
Update the declaration of repositories (see below)
Update fixtures files. (but I don't see fixtures in your composer files)
So, I guess that you have to upgrade your repositories declaration.
Here is the new pattern of a repository
#src/Repository/Foo.php
use App\Entity\Foo;
use Doctrine\Bundle\DoctrineBundle\Repository\ServiceEntityRepository;
use Doctrine\ORM\NonUniqueResultException;
use Doctrine\Persistence\ManagerRegistry;
/**
* Some repository coded in the new way.
*
* #method Article|null find($id, $lockMode = null, $lockVersion = null)
* #method Article|null findOneBy(array $criteria, array $orderBy = null)
* #method Article[] findAll()
* #method Article[] findBy(array $criteria, array $orderBy = null, $limit = null, $offset = null)
*/
class ArticleRepository extends ServiceEntityRepository
{
/**
* FooRepository constructor.
*
* #param ManagerRegistry $registry injected by dependency injection
*/
public function __construct(ManagerRegistry $registry)
{
parent::__construct($registry, Foo::class);
}
If you don't use repositories, you have to find where in your code you was using Doctrine\Bundle\DoctrineBundle\Registry and find a way to replace it by the new ManagerRegistry.
I'd like to implement Messenger async handlers and be able to queue some tasks in Redis, but I can't for some reason.
Here is my global config :
PHP 7.3.16
Symfony 4.4.7
Messenger 4.4
Redis 5.0.3
Predis 1.1
I tried to follow this guide :
https://symfony.com/doc/4.4/messenger.html
Everything is a copy/paste from the doc, except that I replaced my controller with a command.
This dispatch command seems to work :
php bin/console app:dispatch-command
^ Symfony\Component\Messenger\Envelope^ {#5465 -stamps: []
-message: App\Message\SmsNotification^ {#5475
-content: "Look! I created a message!" } }
This command returns no configured handlers :
php bin/console debug:messenger
Messenger
=========
This second command returns an error while trying to consume a message
php bin/console messenger:consume async
TypeError {#174
#message: "The first argument must be an instance of "Symfony\Component\Messenger\RoutableMessageBus"."
#code: 0
#file: "./vendor/symfony/messenger/Command/ConsumeMessagesCommand.php"
#line: 54
trace: {
./vendor/symfony/messenger/Command/ConsumeMessagesCommand.php:54 { …}
./var/cache/dev/ContainerM8fc2IB/srcApp_KernelDevDebugContainer.php:3736 {
ContainerM8fc2IB\srcApp_KernelDevDebugContainer->getConsole_Command_MessengerConsumeMessagesService()^
›
› $this->privates['console.command.messenger_consume_messages'] = $instance = new \Symfony\Component\Messenger\Command\ConsumeMessagesCommand('', ($this->privates['messenger.receiver_locator'] ?? ($this->privates['messenger.receiver_locator'] = new \Symfony\Component\DependencyInjection\Argument\ServiceLocator($this->getService, [], []))), ($this->services['event_dispatcher'] ?? $this->getEventDispatcherService()), ($this->privates['monolog.logger.messenger'] ?? $this->getMonolog_Logger_MessengerService()), []);
›
arguments: {
$routableBus: ""
$receiverLocator: Symfony\Component\DependencyInjection\Argument\ServiceLocator {#178 …}
$eventDispatcher: Symfony\Component\HttpKernel\Debug\TraceableEventDispatcher {#188 …}
$logger: Symfony\Bridge\Monolog\Logger {#179 …}
$receiverNames: []
}
}
./vendor/symfony/dependency-injection/Container.php:450 { …}
./vendor/symfony/dependency-injection/Argument/ServiceLocator.php:40 { …}
./vendor/symfony/console/CommandLoader/ContainerCommandLoader.php:45 { …}
./vendor/symfony/console/Application.php:541 { …}
./vendor/symfony/console/Application.php:634 { …}
./vendor/symfony/framework-bundle/Console/Application.php:117 { …}
./vendor/symfony/console/Application.php:235 { …}
./vendor/symfony/framework-bundle/Console/Application.php:83 { …}
./vendor/symfony/console/Application.php:147 { …}
./bin/console:42 { …}
}
}
2020-04-20T20:08:02+02:00 [critical] Uncaught Error: The first argument must be an instance of "Symfony\Component\Messenger\RoutableMessageBus".
Here are some relevant files...
# .env
MESSENGER_TRANSPORT_DSN=redis://127.0.0.1:6379/messages/?auto_setup=true&serializer=1&stream_max_entries=0&dbindex=0
# config/packages/messenger.yaml
framework:
messenger:
transports:
async: '%env(MESSENGER_TRANSPORT_DSN)%'
routing:
'App\Message\SmsNotification': async
# config/services.yaml
parameters:
services:
_defaults:
autowire: true
autoconfigure: true
App\:
resource: '../src/*'
exclude: '../src/{DependencyInjection,Entity,Migrations,Tests,Kernel.php}'
App\Controller\:
resource: '../src/Controller'
tags: ['controller.service_arguments']
<?php
// src/Message/SmsNotification.php
namespace App\Message;
class SmsNotification
{
private $content;
public function __construct(string $content)
{
$this->content = $content;
}
public function getContent(): string
{
return $this->content;
}
}
<?php
// src/MessageHandler/SmsNotificationHandler.php
namespace App\MessageHandler;
use App\Message\SmsNotification;
use Symfony\Component\Messenger\Handler\MessageHandlerInterface;
class SmsNotificationHandler implements MessageHandlerInterface
{
public function __invoke(SmsNotification $message)
{
dump('ok!');
echo('handler');
}
}
<?php
// src/Command/DispatchCommand
namespace App\Command;
use Symfony\Component\Console\Command\Command;
use Symfony\Component\Console\Input\InputArgument;
use Symfony\Component\Console\Input\InputInterface;
use Symfony\Component\Console\Input\InputOption;
use Symfony\Component\Console\Output\OutputInterface;
use Symfony\Component\Console\Style\SymfonyStyle;
use App\Message\SmsNotification;
use Symfony\Component\Messenger\MessageBusInterface;
class DispatchCommand extends Command
{
protected $bus;
protected static $defaultName = 'app:dispatch-command';
public function __construct(MessageBusInterface $bus)
{
$this->bus = $bus;
}
protected function configure()
{
$this
->setDescription('Dispatch test command')
->setHelp('Dispatch test command');
}
protected function execute(InputInterface $input, OutputInterface $output)
{
$dispatch = $this->bus->dispatch(new SmsNotification('Look! I created a message!'));
dump($dispatch);
}
Can someone please help me?
Cheers!
Update
I tried to manually route my handlers as explained in the doc :
# config/services.yaml
App\MessageHandler\SmsNotificationHandler:
tags: [messenger.message_handler]
But it changes nothing.
I also checked if my message and message Handler were registered as services, and it is :
php bin/console debug:container App
[70 ] App\Message\SmsNotification
[71 ] App\MessageHandler\SmsNotificationHandler
I still don't know what causes this issue, but I just succeeded to resolve it by installing a whole new Symfony 4.4.7 project and by overwriting it.
I also figured out that swiftmailer was replaced by mailer, and twig-bundle by twig-pack, but only for new projects, without beeing notice by composer for existing installations (even using composer update/upgrade/recipes. It may be the cause of my problems but I'm not sure.
Step 1 : create a new Symfony 4.4.7 project
composer create-project symfony/website-skeleton newProj ^4.4.7
sudo chown -R $USER:www-data
cd /newProj
Step 2 : overwrite with my git project
git init
git remote add origin $url_of_clone_source
git fetch origin
git checkout -b master --track origin/master
Step 3 : install vendors and replace obsolete dependencies
composer install
composer remove symfony/swiftmailer
composer require symfony/mailer
composer remove symfony/twig-bundle
composer require symfony/twig-pack
Step 4 : check messenger
$ php bin/console debug:messenger
Messenger
=========
messenger.bus.default
---------------------
The following messages can be dispatched:
----------------------------------------------------------
App\Message\SmsNotification
handled by App\MessageHandler\SmsNotificationHandler
Symfony\Component\Mailer\Messenger\SendEmailMessage
handled by mailer.messenger.message_handler
----------------------------------------------------------
Here is my actual composer.json file
{
"type": "project",
"license": "proprietary",
"require": {
"php": "7.3.*",
"ext-ctype": "*",
"ext-iconv": "*",
"guzzlehttp/guzzle": "^6.5",
"impulze/intervention-image-bundle": "^1.2",
"laminas/laminas-code": "^3.4",
"laminas/laminas-eventmanager": "^3.2",
"nelmio/api-doc-bundle": "^3.6",
"predis/predis": "^1.1",
"sensio/framework-extra-bundle": "^5.1",
"symfony/asset": "4.4.*",
"symfony/console": "4.4.*",
"symfony/dotenv": "4.4.*",
"symfony/error-handler": "4.4.*",
"symfony/expression-language": "4.4.*",
"symfony/flex": "^1.3.1",
"symfony/form": "4.4.*",
"symfony/framework-bundle": "4.4.*",
"symfony/http-client": "4.4.*",
"symfony/intl": "4.4.*",
"symfony/mailer": "4.4.*",
"symfony/messenger": "4.4.*",
"symfony/monolog-bundle": "^3.1",
"symfony/orm-pack": "*",
"symfony/phpunit-bridge": "^5.0",
"symfony/process": "4.4.*",
"symfony/security-bundle": "4.4.*",
"symfony/serializer-pack": "*",
"symfony/translation": "4.4.*",
"symfony/twig-pack": "^1.0",
"symfony/validator": "4.4.*",
"symfony/web-link": "4.4.*",
"symfony/webpack-encore-bundle": "^1.7",
"symfony/yaml": "4.4.*"
},
"require-dev": {
"doctrine/doctrine-fixtures-bundle": "^3.3",
"sensiolabs/security-checker": "^6.0",
"symfony/debug-pack": "*",
"symfony/maker-bundle": "^1.0",
"symfony/profiler-pack": "*",
"symfony/test-pack": "*"
},
"config": {
"preferred-install": {
"*": "dist"
},
"sort-packages": true
},
"autoload": {
"psr-4": {
"App\\": "src/"
}
},
"autoload-dev": {
"psr-4": {
"App\\Tests\\": "tests/"
}
},
"replace": {
"paragonie/random_compat": "2.*",
"symfony/polyfill-ctype": "*",
"symfony/polyfill-iconv": "*",
"symfony/polyfill-php72": "*",
"symfony/polyfill-php71": "*",
"symfony/polyfill-php70": "*",
"symfony/polyfill-php56": "*"
},
"scripts": {
"auto-scripts": {
"cache:clear": "symfony-cmd",
"assets:install %PUBLIC_DIR%": "symfony-cmd",
"security-checker security:check": "script"
},
"post-install-cmd": [
"#auto-scripts"
],
"post-update-cmd": [
"#auto-scripts"
]
},
"conflict": {
"symfony/symfony": "*"
},
"extra": {
"symfony": {
"allow-contrib": false,
"require": "4.4.*"
}
}
}
I'm trying to implement simple library without web server. That means I actually don't have any entry point where I can include autoload.php.
I trying to add bunch of classes, but PhpStorm cannot recognise namespacing and and my classes cannot being auto-imported.
E.g.
I'm trying to import file App/Engines/Contracts/BaseEngine.php which is placed in src/Engines/Contracts/BaseEngine.php via
use App\Engines\Contracts\BaseEngine;
The PhpStorm says Undefined Class
All PhpStorm configuration seems to be correct: I've specified composer.json and checked auto-import in namespace scope.
So, basically the question is: How to deal with composer autoloader in such a library?
Attaching my composer.json code.
{
"name": "mom/task",
"description": "Todo",
"authors": [
{
"name": "Foo Bar",
"email": "foo.mail.com"
}
],
"require": {
"php": "^7.4"
},
"require-dev": {
"phpunit/phpunit": "^8",
"squizlabs/php_codesniffer": "3.*"
},
"autoload": {
"psr-4": {
"App\\": "src/"
}
},
"autoload-dev": {
"psr-4": {
"Tests\\": "tests/"
}
}
}
UPD: attaching BaseEngine code.
<?php
namespace App\Engines\Contracts;
interface BaseEngine
{
/**
* #param int $variable
* #param int $multiplier
* #return mixed
*/
public function compute(int $variable, int $multiplier): int;
}