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?