Do not (always) use FOSUserBundle
And what to consider before using it
Our journey
- What is FOSUserBundle
- Why it's not the right fit for a professional project
- Implementing your own User Manager
- Choosing a Bundle, the Right Way Β©
Hi there! π
- @damienalexandre π«π·
- Symfony consultant since 2008
- Contributor, writer, speaker
- Elasticsearch π and Unicode enthusiast
- Beer πΊ and Bike π², not in that order
- First time public speaking in English π¬π§
I'm a consultant
I do audits, developments, trainings in Paris
We build web and mobile apps for awesome clients.
It all started with a new project
- Install Symfony and FOSUserBundle π
- Client wants some modifications
- Tweak the bundle
- Rewrite part of the bundle
- Take a step back
- "What the hell did I do" π₯
One year ago
I had to let it go:
A bit of history
- May, 2010: Original idea and first implementation by KNPLabs π
- Was called DoctrineUserBundle in reference to our good old sfDoctrineGuardPlugin π΄
- December 2010: it was moved to FriendOfSymfony
A bit of history
By then you had to use Git Submodule to install bundles:
git submodule add \
git://github.com/knplabs/DoctrineUserBundle.git \
src/Bundle/DoctrineUserBundle
π¨
Symfony 2 release
July 2011: Version 1.0 was tagged,
1 month after Symfony 2.0 release.
Yes we had a deps file π¬
[FOSUserBundle]
git=git://github.com/FriendsOfSymfony/FOSUserBundle.git
target=bundles/FOS/UserBundle
- πͺ 336 contributors
- π· 20 releases
- π 5β700β000 downloads
- π Documentation available on Symfony.com
- π Recommended on the official documentation
- π
Most popular on KnpBundles
- π ~5000 lines of code (NCLOC: excluding the tests and comments)
All the features
- Provide stored User object for Symfony projects:
- Doctrine ORM
- Doctrine ODM
- Propel (removed from 2.0, new separated bundle)
- Registration form with optional confirmation email
- Profile editing form
- Password reset form and handling
- Console commands for users management
What is not in FOSUser
- Authentication (Form login, basic, LDAP...)
- Authorisation (Access controls...)
- Session management
- Remember Me
- Impersonate User
- Doctrine User Provider...
Two versions
- 1.3.6: latest stable version (1.3.7 last week)
- Not compatible with Symfony 3
- 54k monthly downloads [source]
- 2.0.0-alpha3: released 1 year ago (beta1 2 days ago)
- Major update, lots of BC Break
- 24k monthly downloads
dev-master has 135k monthly downloads π±
By the way
Congrats @XWB and @stof for the two releases!
π
Why you
should not
use it π
It's an implementation, not a framework
- Some choices are done for the greater good
- Not everything will work for you
- You will spend a lot of time bending the implementation to your needs
- This will cause some pain
Pain #1: Username and email as identifier
- Login form uses username by default π€
- Both email and username are unique,
so email is also valid as identifier β
- Most professional websites use email as identifier
- You can't really switch to emails only π₯
Pain #1: Username and email as identifier
Two user provider to chose from:
fos_user.user_provider.username
fos_user.user_provider.username_email
Pain #1: Username and email as identifier
- Symfony refers to username everywhere:
UserProviderInterface::loadUserByUsername
UserInterface::getUsername
- It really mean "identifier", and may change in the future...
- It's ok to have
getUsername
return an email
- But removing the username field is not possible, you have to hack!
Pain #1: Bye Username
- Hack the entity setter:
public function setEmail($email) {
$email = is_null($email) ? '' : $email;
parent::setEmail($email);
$this->setUsername($email);
return $this;
}
-
Remove the field from the FormType:
public function buildForm(FormBuilder $builder, array $options) {
parent::buildForm($builder, $options);
$builder->remove('username');
}
Pain #1: Bye Username
- Completely rewrite the validation constraints and implement new validation groups
- Switch the user provider to email OR username
- Full instructions here
- The username field is still in the database π‘
Pain #1: username_email provider is a hack β
public function findUserByUsernameOrEmail($usernameOrEmail)
{
if (preg_match('/^.+\@\S+\.\S+$/', $usernameOrEmail)) {
return $this->findUserByEmail($usernameOrEmail);
}
return $this->findUserByUsername($usernameOrEmail);
}
Creative usernames will never be found:
[@lpha.org], π @3.14...
Pain #2: Database
The default table is full of fields you may never use
and π« cannot be removed π«
username
and username_canonical
email_canonical
β
salt
, not needed since bcrypt (UserInterface)
last_login
, an UPDATE on each login β
roles
is (DC2Type:array)
, no interoperability
Pain #2: Database
- AdvancedUserInterface is implemented π
locked
expired
expires_at
credentials_expired
credentials_expire_at
- Doctrine's AttributeOverrides may allow to edit some fields options,
but no removing or type switching.
Pain #3: Wording
Obviously default template must be customized π¨
Pain #3: Wording
Pain #3: Wording
The user has been created successfully
π π€
Pain #3: Wording
- Good: All the texts are provided and translated in 40 languages π thanks to awesome contributors π
- Bad: Messages are very generics, so you still have some translation work to do β
Pain #3: Wording
app/Resources/translations/FOSUserBundle.en.yml
registration:
flash:
user_created: >
Your account has been created successfully,
congratulation!
Pain #3: Wording
- Emails are Twig files with translations,
and only the txt part is provided
- The Mailer use Twig blocks to get the subject, text and html body parts
- There is a MailerInterface, because the default one is opinionated
- I often build a custom FOSUser Mailer as bridge to my real application Mailer π
Pain #4: Extendability
- Adding a field to the user profile is easy, it's your entity:
/**
* @var \DateTime
* @ORM\Column(name="birth_date", type="date")
* @Assert\LessThan("-13 years", groups={"Registration", "Profile"})
*/
protected $birthDate;
- DO NOT forget the validation groups if you need validation
- What about form types then?
Pain #4: Extendability
class RegistrationType extends AbstractType
{
public function buildForm(FormBuilderInterface $builder, array $options)
{
$builder->add('birthDate');
}
// Extends the bundle form type
public function getParent()
{
return RegistrationFormType::class;
}
}
Pain #4: Extendability
fos_user:
...
registration:
form:
type: "AppBundle\Form\RegistrationType"
To do for both the registration and profile types π
File madness π¨
app/Resources
βββ FOSUserBundle
β βββ views
β βββ Registration
β β βββ email.txt.twig
β β βββ confirmed.html.twig
β β βββ check_email.html.twig
β β βββ register_content.html.twig
β βββ Resetting
β β βββ check_email.html.twig
β β βββ request.html.twig
β β βββ reset.html.twig
β βββ Security
β βββ login.html.twig
βββ translations
βββ FOSUserBundle.en.yml
βββ validators.en.yml
src/
βββ AppBundle
βββ Form
βΒ Β βββ RegistrationType.php
βΒ Β βββ ProfileType.php
βββ Manager
Β Β βββ FOSUserEmail.php
So much files and overwriting
Very soon in your projects, you will note that
you are building a lot on top of the Bundle,
increasing your dependency on it.
That's called Technical Debt.
Why not drop the bundle and write them anyway? π€
Separated User entities
- You need a Customer entity and an Admin entity?
- Install PUGXMultiUserBundle (ORM only), a Bundle that extends FOSUserBundle!
- π¨ At your own risks π¨
Separated Login Forms
- Custom login form for backend and customer access?
- custom routes for everything
- a new firewall and new
access_control
entries
- a new
SecurityController
extending the bundle one,
with some conditions to know what template to render π
- and of course the new template
Why you shouldn't use it
- Technical debt generator if you need customization
- Hard to adapt, lots of file to write
- Hardly maintained π§ (maybe not anymore)
- Makes visible your usage of Symfony + FOSUserBundle, which can be a security concern π΅
inurl:"/resetting/request" "Username or email address"
Why we need it!
- Symfony Security is hard to learn π
- It enables a WOW effect for new developers π
- Still a great piece of software if you stay in the lines π
Why we need it!
- This is the most used third party bundle π
- It's the entry point of a lot of new users,
the user base that does not know Symfony very well πΆ
Maybe should help do better for the sake of Symfony? FOSUserBundle should be helpful, not scary.
Hardly maintained π°
Issue overflow: everyone asks questions about the Symfony Security here,
thinking Security is FOSUser concern:
Hardly maintained π°
- Plenty of other examples, just looking at the first page:
- #2251 is resolved but the user does not close it
- #2223 is out of topic
- #2206 is user pushing stress on contributors to release a new version
- #2215 is question about email going to spam...
Hardly maintained π°
- No release for more than a year (until this month)
- Slowed down by it's own popularity π
- Also, no strong labels management, no triage like FOSRest or FOSHttpCache π
π Hack Day π
- What are you doing this Saturday?
- Let's try to help the most used Bundle. Let's try to help newcomers.
- That's some serious "DX" (Developer eXperience) task!
- I want to help!
Building your own
- Your own entities, no fuss, no extra fields
- Your own mailer from the start
- 100% flexibility, 100% integrated to your code base
- Some code to write:
- Your forms for register / profile and password reset
- The appropriate controllers
- The User Manager itself to create / persist, send emails, etc.
The User Entity
There is a cookbook for that
- Create your User entity, implement UserInterface
- Configure Security to load from your Entity,
that's part of Symfony
The Registration form
There is a cookbook for that
- Create a Form for the Entity
- Handle the Form Submission in a custom controller, where you have to encode the password
- Write a template
Building your own
That's basic Symfony development. Same for password reset, profile edit...
Just some decisions to make based on your needs and environment π π π
Like, do you need canonical fields?
Canonical fields
- FOSUserBundle adds canonical versions of all the username and email fields
mb_convert_case($string, MB_CASE_LOWER);
- This makes sure we can't have two accounts with the same email:
foobar@example.org
FoOBAR@example.org
With a proper collation
Γ’
= Γ
= A
= a
π€
Canonical fields
It works for SELECT:
SELECT 'fΓΆoBAR@example.org' COLLATE UTF8_GENERAL_CI
= 'foobar@example.org' COLLATE UTF8_GENERAL_CI;
=> 1
Canonical fields
It also works with UNIQUE KEY:
CREATE TABLE `test` (
`username` varchar(180) COLLATE utf8mb4_unicode_ci,
UNIQUE KEY `uniqueindextest` (`username`)
) ENGINE=InnoDB DEFAULT
CHARSET=utf8mb4 COLLATE=utf8mb4_unicode_ci;
INSERT INTO `test` (username) VALUES
('FOOBAR@example.org'), ('foobΓ€r@example.org');
=> Duplicate entry 'foobΓ€r@example.org'
=> for key 'uniqueindextest'
You do not need Canonical fields
π
Choosing a bundle
The right way Β©
Where to look
- http://symfohub.com/ is dead π
- KNPBundles.com is ok but not up to date π
- Packagist.org is the best source! π
- Add
&type=symfony-bundle
to filter only the Symfony Bundle!
Do not use Composer to install
π
jQuery π
Please
What to check
- Is it maintained? Can I contribute? Bus factor?
- Is there any tests? Are they green?
- Is there a recent stable version?
- Is there a reasonable amount of issues?
- What are the dependencies? What are the dependencies of the dependencies?!
Almost 8200 bundles to choose from
- Never install a bundle you do not trust 100%
- What does it do and how easily can it be changed?
- Is there a complete documentation?
- Look at the issue tracker for long pending issues
- Does it add a real feature to Symfony, or is it just a library?
Wrapper Bundles
- Wrapper around a perfectly fine PHP library
- The library moves faster than the bundle, and you have to update things manually
- In some cases the Symfony Bundle adds constraints on top of the Library, like FOSElasticaBundle:
- Adds strict Configuration for the Elasticsearch mapping
- Elasticsearch mapping move faster than the bundle
- This has nothing to do with Elastica BTW
Implementation Bundles
- FOSUserBundle is an User Management implementation
- You can use it if it fits your needs well
- Otherwise you add a burden on your project, in term of maintenance and quality
- You do not want a 5000+ lines of code Bundle to only use some parts of it
3rd party Bundles
- Keep your number of Bundle as low as possible β¬
- Prefer libraries over Bundles π π
- Stay awesome π
π― Wrap up π―
- FOSUserBundle is a great tool, but it's an opinionated implementation, and should not be used on professional applications:
- Too much code to write on top of it for customization
- Makes maintenance and updates harder
- We can help, join on Saturday π
π― Wrap up π―
- Building your own User Management system is easy
- Third party bundles are good for quick win, but think about the long term consequences
- Each Bundle you install is long term engagement π§
Thanks to all FOSUserBundle contributors
I'm done β
Throw your questions at me!
β€ Did I told you I love emoji? π¦
@damienalexandre
Personnal Wishlist!
- Add strong label management for issues
- Remove the Groups
- Remove the AdvancedUserInterface, but provide example of how to add it
- Remove the username field, but provide example of how to use it
- Remove the last_login field, or provide a way to disable the listener
- https://github.com/FriendsOfSymfony/FOSUserBundle/issues/1309