Migrating Services & Security
Keep on Learning!
If you liked what you've learned so far, dive in! Subscribe to get access to this tutorial plus video, code and script downloads.
With a Subscription, click any sentence in the script to jump to that part of the video!
Login SubscribeOk, 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
.
// ... 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.
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.
// ... 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.
// ... 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.
// ... 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
.
// ... 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
.
// ... 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.
// ... 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!
// ... 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!
Don't know if I missed some forum rule or something, I tried twice with my question and it just gets removed, not sure whether that's automatic or intentionally done by someone. Too much code sample in the question?