Buy

Migrating Services & Security

With a Subscription, click any sentence in the script to jump to that part of the video!

Login Subscribe

Ok, remember our goal: to move our code - which mostly lives in config/ - into the new directory structure.

Migrating the doctrine Config

The next section is doctrine... and there's nothing special here: this is the default config from Symfony 3. Compare this with config/packages/doctrine.yaml. If you look closely, they're almost the same - but with a few improvements!

Instead of having multiple config entries for the database host, username and password, it's all combined into one url. The DATABASE_URL environment variable is waiting to be configured in the .env file.

But there is one important difference: mappings. In a Flex project, we expect your entities to live in src/Entity. But currently, our classes live in src/AppBundle/Entity.

And yes yes, we are going to move them... eventually. But let's pretend like moving them is too big of a change right now: I want to make my files work where they are. How can we do that? Add a second mapping! This one will look in the src/AppBundle/Entity directory for classes that start with AppBundle\Entity. Update the alias to AppBundle - that's what lets you say AppBundle:Genus.

34 lines config/packages/doctrine.yaml
... lines 1 - 7
doctrine:
... lines 9 - 16
orm:
... lines 18 - 20
mappings:
... lines 22 - 27
AppBundle:
is_bundle: false
type: annotation
dir: '%kernel.project_dir%/src/AppBundle/Entity'
prefix: 'AppBundle\Entity'
alias: AppBundle

Simple & explicit. I love it! Go delete the old doctrine config!

Migrating doctrine_cache and stof_doctrine_extensions

The last two sections are for doctrine_cache and stof_doctrine_extensions. Both bundles are installed, so we just need to move the config. Huh, but the DoctrineCacheBundle did not create a config file. That's normal: some bundles don't need configuration, so their recipes don't add a file. Create it manually: doctrine_cache.yaml. And move all the config into it.

7 lines config/packages/doctrine_cache.yaml
doctrine_cache:
providers:
my_markdown_cache:
type: '%cache_type%'
file_system:
directory: '%kernel.cache_dir%/markdown_cache'

All of the files in this directory are automatically loaded, so we don't need to do anything else.

Then, for stof_doctrine_extensions, it does have a config file, but we need to paste our custom config at the bottom.

8 lines config/packages/stof_doctrine_extensions.yaml
... lines 1 - 2
stof_doctrine_extensions:
... line 4
orm:
default:
sluggable: true

And... that's it! Delete config.yml. Victory!

Migrating Services

Close a few files, but keep the new services.yaml open... because this is our next target! Open the old services.yml file. This has the normal autowiring and auto-registration stuff, as well as some aliases and custom service wiring.

Because we're not going to move our classes out of AppBundle yet, we need to continue to register those classes as services. But in the new file, to get things working, we explicitly excluded the AppBundle directory, because those classes do not have the App\ namespace.

No problem! Copy the 2 auto-registration sections from services.yml and paste them into the new file. And I'll add a comment: when we eventually move everything out of AppBundle, we can delete this. Change the paths: we're now one level less deep.

68 lines config/services.yaml
... lines 1 - 6
services:
... lines 8 - 27
# REMOVE when AppBundle is removed
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}'
AppBundle\Controller\:
resource: '../src/AppBundle/Controller'
public: true
tags: ['controller.service_arguments']
# END REMOVE
... lines 39 - 68

Next, copy the existing aliases and services and paste them into the new file.

68 lines config/services.yaml
... lines 1 - 6
services:
... lines 8 - 39
# add more service definitions when explicit configuration is needed
# please note that last definitions always *replace* previous ones
Knp\Bundle\MarkdownBundle\MarkdownParserInterface: '@markdown.parser'
Doctrine\ORM\EntityManager: '@doctrine.orm.default_entity_manager'
AppBundle\Service\MarkdownTransformer:
arguments:
$cacheDriver: '@doctrine_cache.providers.my_markdown_cache'
AppBundle\Doctrine\HashPasswordListener:
tags: [doctrine.event_subscriber]
AppBundle\Form\TypeExtension\HelpFormExtension:
tags:
- { name: form.type_extension, extended_type: Symfony\Component\Form\Extension\Core\Type\FormType }
AppBundle\Service\MessageManager:
arguments:
- ['You can do it!', 'Dude, sweet!', 'Woot!']
- ['We are *never* going to figure this out', 'Why even try again?', 'Facepalm']
AppBundle\EventSubscriber\AddNiceHeaderEventSubscriber:
arguments:
$showDiscouragingMessage: true
# example of adding aliases, if one does not exist
# Symfony\Component\Security\Guard\GuardAuthenticatorHandler: '@security.authentication.guard_handler'

And... ready? Delete services.yml! That was a big step! Suddenly, almost all of our existing code is being used: we just hooked our old code into the new app.

But, does it work! Maybe....? Try it!

./bin/console

Migrating Security

Ah! Not quite: a class not found error from Symfony's Guard security component. Why? Because we haven't installed security yet! Let's do it:

composer require security

It downloads and then... another error! Interesting:

LoginFormAuthenticator contains 1 abstract method

Ah! I think we missed a deprecation warning, and now we're seeing a fatal error. Open AppBundle/Security/LoginFormAuthenticator.php.

PhpStorm agrees: class must implement method onAuthenticationSuccess. Let's walk through this change together. First, remove getDefaultSuccessRedirectUrl(): that's not used anymore. Then, go to the Code->Generate menu - or Command+N on a Mac - select "Implement methods" and choose onAuthenticationSuccess.

Previously, this method was handled by the base class for you. But now, it's your responsibility. No worries: it's pretty simple. To help, at the top, use a trait called TargetPathTrait.

89 lines src/AppBundle/Security/LoginFormAuthenticator.php
... lines 1 - 19
class LoginFormAuthenticator extends AbstractFormLoginAuthenticator
{
use TargetPathTrait;
... lines 23 - 74
public function onAuthenticationSuccess(Request $request, TokenInterface $token, $providerKey)
{
... lines 77 - 81
}
... lines 83 - 87
}

Back down in onAuthenticationSuccess, this allows us to say if $targetPath = $this->getTargetPath() with $request->getSession() and main.

89 lines src/AppBundle/Security/LoginFormAuthenticator.php
... lines 1 - 19
class LoginFormAuthenticator extends AbstractFormLoginAuthenticator
{
... lines 22 - 74
public function onAuthenticationSuccess(Request $request, TokenInterface $token, $providerKey)
{
if ($targetPath = $this->getTargetPath($request->getSession(), 'main')) {
... line 78
}
... line 81
}
... lines 83 - 87
}

Let's break this down. First, the main string is just the name of our firewall. In both the old and new security config, that's its key.

Second, what does getTargetPath() do? Well, suppose the user originally tried to go to /admin, and then they were redirected to the login page. After they login, we should probably send them back to /admin, right? The getTargetPath() method returns the URL that the user originally tried to access, if any.

So if there is a target path, return new RedirectResponse($targetPath). Else, return new RedirectResponse and generate a URL to the homepage.

89 lines src/AppBundle/Security/LoginFormAuthenticator.php
... lines 1 - 19
class LoginFormAuthenticator extends AbstractFormLoginAuthenticator
{
... lines 22 - 74
public function onAuthenticationSuccess(Request $request, TokenInterface $token, $providerKey)
{
if ($targetPath = $this->getTargetPath($request->getSession(), 'main')) {
return new RedirectResponse($targetPath);
}
return new RedirectResponse($this->router->generate('homepage'));
}
... lines 83 - 87
}

PhpStorm thinks this isn't a real route, but it is!

Problem solved! Is that enough to make our app happy? Find out!

./bin/console

It is! But before we move on, we need to migrate the security config. Copy all of the old security.yml, and completely replace the new security.yaml. To celebrate, delete the old file!

41 lines config/packages/security.yaml
... lines 1 - 2
security:
encoders:
AppBundle\Entity\User: bcrypt
role_hierarchy:
ROLE_ADMIN: [ROLE_MANAGE_GENUS, ROLE_ALLOWED_TO_SWITCH]
... lines 9 - 14
firewalls:
... lines 16 - 20
main:
anonymous: ~
guard:
authenticators:
- AppBundle\Security\LoginFormAuthenticator
logout:
path: /logout
switch_user: ~
logout_on_user_change: true
... lines 31 - 38
access_control:
# - { path: ^/admin, roles: ROLE_ADMIN }

And... ah! We're super close. Only a few more files to deal with! By the end of the next chapter, our app/config/ directory will be gone!

Leave a comment!

  • 2018-07-04 Diego Aguiar

    Hey Xav

    I'm glad to hear that you could fix your problem, but I believe what happened was that for some reason the doctrine bundle recipe didn't get installed. When that happens, you can run composer fix-recipes and if it was missing, it will install it for you :)

    Cheers!

  • 2018-07-04 Xav

    Hi,
    I must have skipped one page, I don't have the config/packages/doctrine.yaml file. I tried "composer require doctrine/doctrine-bundle" which added "doctrine/doctrine-bundle": "^1.9" in my composer.json but nothing else happens. What am I missing here ? How can I install Doctrine in my project ?
    Thanks.

    Edit : I found that I should use "require symfony/orm-pack" but the console answers "nothing to install or update"
    Edit 2 : I deleted the vendor directory then re-ran (?) the previous command. Suddenly 83 installs, including doctrine, and "doctrine.yaml" is now in the packages directory (with several others - previously missing too - like google_recaptcha.yaml). All right, then, I can continue the migration ! I'll be back for many other questions ;)

  • 2018-07-02 Hugo Le Goff

    Yes, that's what I figured out after this message, that's easier to use and manage at all.

    Thank you !

  • 2018-07-02 Diego Aguiar

    In Symfony4 there is no more a bundle structure, everything lives inside "src" folder (if you check composer.json you will notice that "App" namespace points to the "src" folder), so, yes, you have to move all your files living in bundles into the root of "src", but without deleting "Entity", "Controller", etc. folders, you just have to merge them

    After moving the files, you have to double check that all your namespaces did change correctly, and fix any service configuration problem you may face (because all your classes will be automatically registered and configured as services)

  • 2018-06-30 Hugo Le Goff

    I think I clearly don't get the utility of the AppBundle lines in services.yaml. I guess it is loading my old code, but I at least don't get how doing it. In my project I have in src folder another folder called UTM where there is 6 bundles (CoreBundle, User Bundle...).

    I tried to only load CoreBundle by making the changes you suggested to me but I was facing the same error.

    So I removed the "AppBundle" block for the moment and it's working fine, but I guess it is the wrong way to do it.

    It would be super cool if you quickly explain me how to do it properly.

    Thank you very much !

    EDIT:

    Ok, I'm now moving my bundles into src, so maybe I do not need anymore those lines. Instead, I have another question, as I said, actually on my src folder I have a UTM folder where there is 6 bundles like this :

    src
    ├───Controller
    ├───Entity
    ├───Repository
    └───UTM
    --├───CoreBundle
    --├───FactionBundle
    --├───ForumBundle
    --├───LimitsBundle
    --├───ShopBundle
    --└───UserBundle
    Now should I move all thoses bundles on src and rename them without the "Bundle" like this (and also remove the Controller, Entity and Repository folder) ?

    src
    ├───Core
    ├───Faction
    ├───Forum
    ├───Limits
    ├───Shop
    └───User

    Thank you again

  • 2018-06-29 Diego Aguiar

    Hey Hugo Le Goff

    I believe I found your problem, here:


    UTM\Controller\:
    resource: '../src/UTM/Controller'


    you specified the folder ../src/UTM/Controller, but it actually lives in: ../src/UTM/CoreBundle/Controller, isn't it?

  • 2018-06-29 Hugo Le Goff

    Hi !

    I'm actually facing a really weird error, the problem is 99% due to me, but I'm stuck.

    First my error is :

    In FileLoader.php line 168:

    Expected to find class "UTM\CoreBundle\Controller\CoreController" in file "C:\Users\legof\Documents\GitHub\utm-website\src\UTM/CoreBundle\Controller\CoreController.php" while importing services from resource "../src/UTM/*", but it was not found! Check the namespa
    ce prefix used with the resource in C:\Users\legof\Documents\GitHub\utm-website\config/services.yaml (which is loaded in resource "C:\Users\legof\Documents\GitHub\utm-website\config/services.yaml").

    But I checked it couple times, the file and the namespace are ok.

    Here are my services.yaml and the CoreController.php file


    locale: fr

    services:
    _defaults:
    autowire: true # Automatically injects dependencies in your services.
    autoconfigure: true # Automatically registers your services as commands, event subscribers, etc.
    public: false # Allows optimizing the container by removing unused services; this also means
    # fetching services directly from the container via $container->get() won't work.
    # The best practice is to be explicit about your dependencies anyway.
    App\:
    resource: '../src/*'
    exclude: '../src/{Entity,Migrations,Tests,UTM}'

    App\Controller\:
    resource: '../src/Controller'
    tags: ['controller.service_arguments']

    # REMOVE when AppBundle is removed
    UTM\:
    resource: '../src/UTM/*'
    exclude: '../src/UTM/{Entity,Repository}'

    UTM\Controller\:
    resource: '../src/UTM/Controller'
    public: true
    tags: ['controller.service_arguments']
    # END REMOVE

    UTM\UserBundle\Security\LoginFormAuthenticator: ~

    UTM\UserBundle\Security\UserProvider: ~

    UTM\UserBundle\Mojang\MojangAPI: ~

    UTM\UserBundle\Mojang\Uuid: ~

    UTM\FactionBundle\Twig\Extension\FactionExtension: ~



    namespace UTM\CoreBundle\Controller;

    use Symfony\Bundle\FrameworkBundle\Controller\Controller;

    class CoreController extends Controller
    {
    public function indexAction()
    {
    return $this->render('UTMCoreBundle:Core:index.html.twig');
    }
    }

  • 2018-04-05 Victor Bocharsky

    Hey Vlad,

    1. Good question! And the shortest answer - wherever you want. But since we want to automatically load this configuration, we need to put it inside config/packages/ folder, file name is not important, but of course try to name those files wise, how about monolog.yaml and lexik_jwt_authentication.yaml? :) Well, actually you can find official names in recipes, search for those bundles on https://symfony.sh/ , find the bundle, go to its recipe and inside you will find config/packages dir with *official* name of the config file, for lexik/jwt-authentication-bundle it's - lexik_jwt_authentication.yaml! :) The same way for MonologBundle, etc.

    2. Probably the same answer, wherever you want :) Well, actually, as you may already know, Symfony 4 use environment vars instead of parameters, so you can refactor your code according to env vars. But if you want to do it later, or do not want to use env vars at all - you can put them inside config/services.yaml under the "parameters:" key. All your custom parameters are supposed to be declared there. Or if those parameters bundle-specific only, you can put them into that config/packages/monolog.yaml file, of course under "parameters:" key.

    Cheers!

  • 2018-04-04 Vlad

    Hello,
    1. In app/config/config.yml I also have other configurations such as Monolog handlers and Lexik JWT Authentication. Where do I put those?


    # Monolog Configuration
    monolog:
    handlers:
    # this "file_log" key could be anything
    file_log:
    type: rotating_file
    max_files: '%file_log_file_count%'
    path: "%kernel.logs_dir%/%file_log_file_name%.log"
    level: '%file_log_level%'
    formatter: app.service.monolog_formatter
    channels: [app]

    # custom postgresql log handler
    custom_postresql:
    type: service
    id: 'AppBundle\Log\PostgreSQLLogHandler'
    channels: [app]

    # JWT Authentication Configuration
    lexik_jwt_authentication:
    private_key_path: "%kernel.root_dir%/../var/jwt/private.pem"
    public_key_path: "%kernel.root_dir%/../var/jwt/public.pem"
    pass_phrase: "%jwt_key_pass_phrase%"
    token_ttl: "%jwt_token_ttl%"

    2. Where do I put custom parameters, such as file_log_file_count and file_log_file_name for the Monolog handler?

    Thanks!

  • 2018-01-29 Victor Bocharsky

    Hey Daniel,

    Yes, nice catch! Glad you found the error by yourself.

    Cheers!

  • 2018-01-28 Daniel

    Found my error, missed last AppBundle on autoload key. Thanks anyway

  • 2018-01-28 Daniel

    Hi, got stuck in a silly error:
    `Expected to find class "AppBundle\AppBundle" in file "/home/daniel/dev/knp/symfony4-upgrade/start-sf4/src/AppBundle/AppBundle.php" while importing services from resource "../src/AppBundle/*", but it was not found! Check the n amespace prefix used with the resource in /home/daniel/dev/knp/symfony4-upgrade/start-sf4/config/services.yaml (which is loaded in resource "/home/daniel/dev/knp/symfony4-upgrade/start-sf4/config/services.yaml").`

    What can it be?

  • 2018-01-22 Victor Bocharsky

    Hey the_nuts ,

    Haha, that's up to you ;) Actually, parameters.yml is great so you can totally continue using it. What about environment variables - they are really powerful, and on some PaaS it's very convenient to use them. IIRC, having nested values for parameters is not a good practice. Well, I have never used it, but to group some related parameters I used the same prefix like:


    parameters:
    prefix1.parameter1: ~
    prefix1.parameter2: ~
    prefix1.parameter3: ~
    prefix2.parameter1: ~
    prefix2.parameter2: ~

    But I think keeping parameters flat (not nested) is simple and robust.

    Anyway, I agree it depends, so in some cases you can get more benefits from parameters.yml than with environment vars. Actually, you can even mixed both env vars and parameters.yml at the same time ;)

    Cheers!

  • 2018-01-21 the_nuts

    Also you can't have different installations in the same fpm pool... :/ I'll stay with the old good parameters.yml file :)

  • 2018-01-20 the_nuts

    IMHO the parameters.yml file was much better than this dotenv... For example before you could have arrays, now you can't have something like $_ENV['features']['featureA']
    If your app has a lot of parameters, having all in one level is not very "beautiful"