upgrade symfony3.4 with bundles extension to symfony4 flex - symfony

I have a project in symfony 3.4 then I tried to upgrade to symfony 4 /flex.
the problem is in my code there a 3 bundles TBAdminBundle, TBPlatformBundle and TBSecurityBundle.
Also for each bundle there Extension class exemple.
class TBPlatformExtension extends Extension
{
public function load(array $configs, ContainerBuilder $container)
{
$configuration = new PlatformConfiguration();
$config = $this->processConfiguration($configuration, $configs);
$loader = new Loader\YamlFileLoader($container, new FileLocator(__DIR__.'/../Resources/config'));
$loader->load('services.yml');
if (!in_array(strtolower($config['db_driver']), array('custom', 'mongodb', 'orm'))) {
throw new \InvalidArgumentException(sprintf('Invalid db driver "%s".', $config['db_driver']));
}
if ('custom' !== $config['db_driver']) {
$loader->load(sprintf('%s.yml', $config['db_driver']));
$def = new Definition('Doctrine\ORM\EntityManager', array('%tb_notification.model_manager_name%'));
$def->setPublic(false);
if (method_exists($def, 'setFactory')) {
$def->setFactory(array(new Reference('doctrine'), 'getManager'));
} else {
// To be removed when dependency on Symfony DependencyInjection is bumped to 2.6
$def->setFactoryService('doctrine');
$def->setFactoryMethod('getManager');
}
$container->setDefinition('tb_notification.entity_manager', $def);
}
foreach (array('form', 'command', 'events') as $basename) {
$loader->load(sprintf('%s.yml', $basename));
}
$container->setParameter('tb_notification.model_manager_name', $config['model_manager_name']);
$container->setParameter('tb_notification.form.notification.type', $config['form']['notification']['type']);
$container->setParameter('tb_notification.form.notification.name', $config['form']['notification']['name']);
$container->setParameter('tb_trip.form.trip.type', $config['form']['trip']['type']);
$container->setParameter('tb_trip.form.trip.name', $config['form']['trip']['name']);
$container->setParameter(
'tb_notification.form.delete_notification.type',
$config['form']['delete_notification']['type']
);
$container->setParameter(
'tb_notification.form.delete_notification.name',
$config['form']['delete_notification']['name']
);
$container->setParameter('tb_trip.form.trip.update_trip.type', $config['form']['update_trip']['type']);
$container->setParameter('tb_trip.form.trip.update_trip.name', $config['form']['update_trip']['name']);
$container->setParameter('tb_action.form.action.type', $config['form']['action']['type']);
$container->setParameter('tb_action.form.action.name', $config['form']['action']['name']);
$container->setParameter('tb_place.form.place.type', $config['form']['place']['type']);
$container->setParameter('tb_place.form.place.name', $config['form']['place']['name']);
$container->setParameter('tb_action_privacy.form.action.type', $config['form']['privacy']['type']);
$container->setParameter('tb_action_privacy.form.action.name', $config['form']['privacy']['name']);
$container->setParameter('tb_notification.model.notification.class', $config['class']['model']['notification']);
$container->setParameter('tb_trip.model.trip.class', $config['class']['model']['trip']);
$container->setParameter('tb_like.model.like.class', $config['class']['model']['like']);
$container->setParameter('tb_helpful.model.helpful.class', $config['class']['model']['helpful']);
$container->setParameter('tb_action.model.action.class', $config['class']['model']['action']);
$container->setParameter('tb_budget.model.class', $config['class']['model']['budget']);
$container->setParameter('tb_action.model.action_privacy_policy.class', $config['class']['model']['privacy']);
$container->setParameter('tb_rating.model.rating.class', $config['class']['model']['vote']);
$container->setParameter('tb_place.model.place.class', $config['class']['model']['place']);
$container->setParameter('tb_destination.model.destination.class', $config['class']['model']['destination']);
// parameters for hydrating object with doctrine
$container->setParameter('tb_action.hydrate.action', $config['hydrate']['action']);
$container->setParameter('tb_google.key', $config['google']['key']);
$container->setParameter('compare_text.default_percent', $config['compare']['text']);
$container->setAlias('tb_trip.manager.trip', $config['service']['manager']['trip']);
$container->setAlias('tb_like.manager.post', $config['service']['manager']['like']);
$container->setAlias('tb_helpful.manager.post', $config['service']['manager']['helpful']);
$container->setAlias('tb_budget_manager', $config['service']['manager']['budget']);
$container->setAlias('tb_place_manager', $config['service']['manager']['place']);
$container->setAlias('tb_destination_manager', $config['service']['manager']['destination']);
$container->setAlias('tb_compare_string', 'similar_text.manager');
$container->setAlias('foursquare.manager', 'foursquare.manager.default');
$container->setAlias('tb_action.form_factory', $config['service']['form_factory']['action']);
$container->setAlias('tb_place.form_factory', $config['service']['form_factory']['place']);
$container->setAlias('tb_action_privacy.form_factory', $config['service']['form_factory']['privacy']);
$container->setAlias(
'tb_notification.form_factory.notification',
$config['service']['form_factory']['notification']
);
$container->setAlias('tb_trip.form_factory', $config['service']['form_factory']['trip']);
$container->setAlias(
'tb_notification.form_factory.notification_delete',
$config['service']['form_factory']['delete_notification']
);
$container->setAlias('tb_action.customer_repository', 'tb_action.customer_repository_default');
$container->setAlias('tb_notification.customer_repository', 'tb_notification.customer_repository_default');
$container->setAlias('tb_trip.customer_repository', 'tb_trip.customer_repository_default');
}
}
According to documentation
"In Symfony versions prior to 4.0, it was recommended to organize your own application code using bundles. This is no longer recommended and bundles should only be used to share code and features between multiple applications."
I know this it's no more possible with symfony4/flex
How can I rewrite this to match with flex configuration?
thanks

My suggestion is to migrate progressively your bundles.
1. Put the bundle in the src of your SF4 application like this :
src/
Bundle/
TBAdminBundle
TBPlatformBundle
TBSecurityBundle
Command
Controller
Entity
...
2. Use Composer PSR-4 to autoload them (composer.json)
"autoload" : {
"psr-4" : {
"App\\" : "src/",
"TBAdminBundle\\" : "src/Bundle/TBAdminBundle/",
"TBPlatformBundle\\" : "src/Bundle/TBPlatformBundle/",
"TBSecurityBundle\\" : "src/Bundle/TBSecurityBundle/",
...
3. Exclude them from App Services autoload (config/services.yaml)
App\:
resource: '../src/*'
# you can exclude directories or files
# but if a service is unused, it's removed anyway
exclude: '../src/{Entity,Migrations,Repository,Bundle}'
4. Validate the new installation/configuration
Although SF4 is bundle less for core App application, it supports bundle ... Just check that you resolve all deprecated functions from those bundles
5. Start progressive migration of your bundles code:
As Hint :
Rework your config/parameters to use ENV, and services.yaml parameters
So you can transfer most config variables/parameters to the App level and easily share them ...
All your services definitions can be moved from Extention classes into services.yaml for simplicity and easy maintenance.
You will degrease bundles code as time go and you gain experience with the new SF4 services usability and orientations.

That note tells you that it is no longer a recommendation but it doesn't mean that you should refactor your code completely. If you already have your code packed as bundles you should only remove deprecated code. In general it is a good idea to pack your code in bundles only if it is reusable across projects.

Related

Symfony Bundle not creating bundle-configuration yaml file in project

A so far working bundle now needs its own configuration file inside the projects using the bundle, to manage bundle settings individually.
However, no matter which approach I use (the old one before Symfony 6.1 nor the new one extending AbstractBundle) there is - at no time - any new .yaml-File created inside the projects ./config/packages/ directory.
This is my code (the old style, prior to Symfony 6.1, extending Bundle):
Bundle Class
mycorpforms/src/MyCorpFormsBundle.php
<?php
namespace MyCorp\FormsBundle;
use Symfony\Component\Config\FileLocator;
use Symfony\Component\Config\Definition\Configurator\DefinitionConfigurator;
use Symfony\Component\DependencyInjection\ContainerBuilder;
use Symfony\Component\DependencyInjection\Extension\ExtensionInterface;
use Symfony\Component\DependencyInjection\Loader\Configurator\ContainerConfigurator;
use Symfony\Component\DependencyInjection\Loader\YamlFileLoader;
use Symfony\Component\HttpKernel\Bundle\Bundle;
class MyCorpFormsBundle extends Bundle
{
// empty
}
Configuration
mycorpforms/src/DependencyInjection/Configuration.php
<?php
namespace MyCorp\FormsBundle\DependencyInjection;
use Symfony\Component\Config\Definition\Builder\TreeBuilder;
use Symfony\Component\Config\Definition\ConfigurationInterface;
class Configuration implements ConfigurationInterface
{
public function getConfigTreeBuilder()
{
$treeBuilder = new TreeBuilder('mycorp_forms');
$treeBuilder->getRootNode()
->children()
->booleanNode('favorite_submenu_enabled')->defaultFalse()->end()
->end()
;
return $treeBuilder;
}
}
Extension
mycorpforms/src/DependencyInjection/MyCorpFormsExtension.php
<?php
namespace MyCorp\FormsBundle\DependencyInjection;
use Knp\Bundle\SnappyBundle\DependencyInjection\Configuration;
use Symfony\Component\Config\Definition\Processor;
use Symfony\Component\DependencyInjection\ContainerBuilder;
use Symfony\Component\DependencyInjection\Extension\Extension;
class MyCorpFormsExtension extends Extension
{
public function load(array $configs, ContainerBuilder $containerBuilder)
{
$loader = new YamlFileLoader(
$containerBuilder,
new FileLocator(__DIR__.'/../../config/packages')
);
$loader->load('mycorp_forms.yaml');
$configuration = new Configuration();
$processor = new Processor();
$config = $processor->processConfiguration($configuration, $configs);
$containerBuilder->setParameter('mycorp_forms.favorite_submenu_enabled', $config['favorite_submenu_enabled']);
}
}
Yaml
Additionally I added the desired mycorp_forms.yaml inside the bundles ./config/packages/ dir.
This is the actual file required in the projects:
mycorpforms/config/packages/mycorp_forms.yaml
mycorp_forms:
# Enable Favorite-Sub-Menu (Requires Knp-Snappy-Bundle !)
favorite_submenu_enabled: false
The bundle installs flawlessly in any of my projects, however no mycorp_forms.yaml file is created. Obviously this requires symfony/flex which is so far required by the bundle itself.
Q: What do I miss here?
Q: How can this yaml-file automatically be added when the bundle is installed?
I read the documentation up and down numerous times, but to be honest, I get more confused every time.
Thank you very much for any help or explanation!
This requires Symfony Flex
To have a .yaml config file being generated/updated automatically, there is 2 possible ways:
public bundle: register it in the public recipe repository
private bundle: set up your own private flex recipe repository

Install Symfony without symfony/runtime and with old index.php

I am trying to add Symfony 5.4 to my legacy project. There is a pretty nice documentation on how to do this, but there's a big problem - the documentation assumes "normal" Symfony, but each time I try to install Symfony using their recommended way of composer create-project, I get a Symfony version with symfony/runtime - the big problem here, is that this version has a completely different index.php:
<?php
use App\Kernel;
require_once dirname(_DIR_).'/vendor/autoload_runtime.php';
return function (array $context) {
return new Kernel($context['APP_ENV'], (bool) $context['APP_DEBUG']);
};
The documentation found here is based on a completely different index file.
I did find that I can remove the runtime package, and just copy old index, and it works for the most part, but then you also have problems with console.php and I worry that if I go this route there will be more and more problems caused by my installation expecting symfony/runtime and me manually removing it's
I tried installing Symfony 5.3 as well as different patches of 5.4, all came with this installed, even though I did work on some 5.3 / 5.4 projects and had the old school index.php file.
Does anyone know how to currently install Symfony with the "old" index.php, console.php etc.?
Thanks!
So the task is to migrate from a non-Symfony legacy app to a Symfony app. The basic idea is to allow the Symfony app to process a request and then hand it off to the legacy app if necessary. The Symfony docs show how to do this but but relies on the older style index.php file. The newer runtime based approach is a bit different.
But in the end all it really takes is a couple of fairly simple classes. A runner class takes care of creating a request object and turning it into a response. This is where you can add the bridge to your legacy app. It's a clone of Symfony's HttpKernelRunner class:
namespace App\Legacy;
use Symfony\Component\HttpFoundation\Request;
use Symfony\Component\HttpKernel\HttpKernelInterface;
use Symfony\Component\HttpKernel\TerminableInterface;
use Symfony\Component\Runtime\RunnerInterface;
class LegacyRunner implements RunnerInterface
{
private $kernel;
private $request;
public function __construct(HttpKernelInterface $kernel, Request $request)
{
$this->kernel = $kernel;
$this->request = $request;
}
public function run(): int
{
$response = $this->kernel->handle($this->request);
// check the response to see if it should be handed off to legacy app
dd('Response Code ' . $response->getStatusCode());
$response->send();
if ($this->kernel instanceof TerminableInterface) {
$this->kernel->terminate($this->request, $response);
}
return 0;
}
}
Next you need to wire up runner by extending the SymfonyRuntime::getRunner method:
namespace App\Legacy;
use Symfony\Component\HttpFoundation\Request;
use Symfony\Component\HttpKernel\HttpKernelInterface;
use Symfony\Component\Runtime\RunnerInterface;
use Symfony\Component\Runtime\SymfonyRuntime;
class LegacyRuntime extends SymfonyRuntime
{
public function getRunner(?object $application): RunnerInterface
{
if ($application instanceof HttpKernelInterface) {
return new LegacyRunner($application, Request::createFromGlobals());
}
return parent::getRunner($application);
}
}
Finally, update composer.json to use your legacy runtime class:
"extra": {
...
"runtime": {
"class": "App\\Legacy\\LegacyRuntime"
}
}
After updating composer.json do a composer update for the changes to take effect and start your server. Navigate to a route and you should hit the dd statement.

Load Symfony (5.2) config from database

I am a newbie in Symfony but I know how to use OOP in PHP.
I try (with frustration) to couple custom parameters with Symfony configs by using Doctrine entities.
To solve the problem I used for e.g. the answer from Michael Sivolobov: https://stackoverflow.com/a/28726681/2114615 and other sources.
My solution:
Step 1: Create new package in config folder
-> config
-> packages
-> project
-> services.yaml
-> project
-> src
-> ParameterLoaderBundle.php
-> DependencyInjection
-> Compiler
-> ParameterLoaderPass.php
Step 2: Import the new resource
# config/services.yaml
...
imports:
- { resource: 'packages/project/config/services.yaml' }
...
Step 3: Package coding
# packages/project/config/services.yaml
services:
Project\:
resource: "../src"
<?php
namespace Project;
use Project\DependencyInjection\Compiler\ParameterLoaderPass;
use Symfony\Component\DependencyInjection\Compiler\PassConfig;
use Symfony\Component\DependencyInjection\ContainerBuilder;
use Symfony\Component\HttpKernel\Bundle\Bundle;
class ParameterLoaderBundle extends Bundle
{
public function build(ContainerBuilder $container)
{
parent::build($container);
$container->addCompilerPass(new ParameterLoaderPass(), PassConfig::TYPE_AFTER_REMOVING);
}
}
<?php
namespace Project\DependencyInjection\Compiler;
use App\Entity\SettingCategory;
use Symfony\Component\DependencyInjection\Compiler\CompilerPassInterface;
use Symfony\Component\DependencyInjection\ContainerBuilder;
class ParameterLoaderPass implements CompilerPassInterface
{
public function process(ContainerBuilder $container)
{
$em = $container->get('doctrine.orm.default_entity_manager');
$setting = $em->getRepository(SettingCategory::class)->findAll();
$container->setParameter('test', $setting);
}
}
After at all I test the new Parameter in my API controller:
$this->getParameter('Test');
But the following error message appears:
The parameter \"test\" must be defined.
Couple of things going on here. First off, loading config from a database is very unusual in Symfony so it is not surprising that you are having difficulty. Secondly, your process code is never getting called. Part of debugging is making sure that code that you expect to be called is in fact being called. Third, you really got off on a tangent with attempting to add a bundle under config. Way back in Symfony 2 there used to be more bundle related stuff under app/config and it may be that you discovered some old articles and misunderstood them.
But, the big problem here is that Symfony has what is known as a 'compile' phase which basically processes all the configuration and caches it. Hence the CompilerPassInterface. Unfortunately, services themselves are not available during the compile phase. They simply don't exist yet so no entity manager. You need to open your own database connection if you really want to load config from a database. You will want to use just a database connection object and not the entity manager since part of the compile phase is to process the entities themselves.
So get rid of all your code and just adjust your Kernel class:
# src/Kernel.php
class Kernel extends BaseKernel implements CompilerPassInterface
{
use MicroKernelTrait;
public function process(ContainerBuilder $container)
{
$url = $_ENV['DATABASE_URL'];
$conn = DriverManager::getConnection(['url' => $url]);
$settings = $conn->executeQuery('SELECT * FROM settings')->fetchAllAssociative();
$container->setParameter('test',$settings);
}
And be aware that even if you get all this working, you will need to manually rebuild the Symfony cache after updating your settings table. It is not going to be automatic. You really might consider taking a completely different approach.

Symfony Bundle that needs a file that differs per project

I've written a small newsletter bundle for the multiple sites I host/develop for. The newsletter fetches the recipients from a newsletter source that differs from project to project. Could be a csv file, could be a database, ...
So in my Controller I thought about writing a NewsletterQueueImportModel() which is being called upon hitting the button "import".
...
$import = new NewsletterQueueImportModel();
$subscribers = $import->getSubscribers($this->getDoctrine());
...
However this file is still delivered with my bundle and in the vendor folder. So I need to change this file on a per project basis.
Override the file, but how? I do not think this is possible.
Remove the file from the newsletter bundle itself and refer to AppBundle/NewsletterQueueImportModel (e.g. use AppBundle instead of use NewsletterBundle - downsides: all projects need to be named AppBundle and I feel it is poor design
I thought about registering a service or something like that, but I really don't know the beste way to do. So what is the best way to create a file on which a bundle depends but differs from project to project?
Well, I have been doing something similar but with views.
So I had 2 sites running on one bundle - in one site I needed to see some sections which i don't need in other site.
I managed to do that with configuration.
1) In each of your site - app/config/config.yml you can define parameters. In my case it was something like
reviews_admin:
views:
table_favorite_block: true
table_brand_view: true
table_image_view: true
2) Then in bundle you must create folder named DependencyInjection with 2 files in it. Configuration and your bundle extension
class Configuration implements ConfigurationInterface
{
/**
* {#inheritdoc}
*/
public function getConfigTreeBuilder()
{
$treeBuilder = new TreeBuilder();
$rootNode = $treeBuilder->root('reviews_admin', 'array');
$rootNode
->children()
->arrayNode('views')
->children()
->booleanNode('table_favorite_block')->defaultTrue()->end()
->booleanNode('table_brand_view')->defaultTrue()->end()
->booleanNode('table_image_view')->defaultTrue()->end()
->end()
->end()
->end();
return $treeBuilder;
}
}
Extension
class ReviewsAdminExtension extends Extension
{
/**
* {#inheritdoc}
*/
public function load(array $configs, ContainerBuilder $container)
{
$configuration = new Configuration();
$config = $this->processConfiguration($configuration, $configs);
$loader = new Loader\YamlFileLoader($container, new FileLocator(__DIR__.'/../Resources/config'));
$loader->load('services.yml');
$container->setParameter('reviews_admin_view', $config['views']);
}
}
I'm not sure if this will suite your situation, but for me it seems like the most convenient way how to manage things in bundles which depends on projects.
Also you can try to make one base class in bundle (which contains things that will be same for all projects (for imports))
And then extend it in site side ?

Symfony call a reusable bundle like a service

I'm trying to create a reusable bundle to manage basket in webstore application...
I create every desired action in a reusable bundle using a controller like addBasketAction, removeBasketAction and so on... The classical
Everythings works fine, but now I'm litteraly stuck about how to "call" my bundle from my "principal" application
You dont call a bundle but you have to register your bundle in the AppKernel.php file:
class AppKernel extends Kernel
{
public function registerBundles()
{
$bundles = [
// ...
new AppBundle\AppBundle(),
new Company\CustomBundle\CompanyCustomBundle(),
];
}
// ...
}
Then you will need to import your routes too but i dont know if you use annotations for your routes or a yaml file.
read more:
http://symfony.com/doc/current/bundles.html
http://symfony.com/doc/current/bundles/best_practices.html
[edit]
Reading your topic title it looks like that you are confusing bundles and services. You can add a service in your bundle on the same way as you can add this service in your AppBundle:
http://symfony.com/doc/current/service_container.html#creating-configuring-services-in-the-container

Resources