If you liked what you've learned so far, dive in!
Subscribe to get access to this tutorial plus
video, code and script downloads.
Repeat after me, “We’re really great.” And our security system is almost as cool as we are. So let’s keep up the pace and load users from the database instead of the little list in security.yml.
What we’re about to do is similar to what the awesome open source FOSUserBundle gives you. We’re going to build this all ourselves so that we really understand how things work. Later, if you do use FOSUserBundle, you’ll be a lot more dangerous with it.
Ok, forget about security! Seriously! Just think about the fact that we want to store some user information in the database. To do this, we’ll need a User entity class.
That sounds like a lot of work, so let’s just use the doctrine:generate:entity app/console command:
php app/console doctrine:generate:entity
For entity shortcut name, use UserBundle:User. Remember, Doctrine uses this shortcut syntax for entities.
Give the class just 2 fields:
And of course, choose “yes” to generating the repository class. I’ll explain why these are so fabulous in a second.
Once the robots are done writing the code for us, we should have a new User class in the Entity directory of UserBundle. Let’s change the table name to be yoda_user:
// src/Yoda/UserBundle/Entity/User.php
namespace Yoda\UserBundle\Entity;
use Doctrine\ORM\Mapping as ORM;
/**
* @ORM\Table(name="yoda_user")
* @ORM\Entity(repositoryClass="Yoda\UserBundle\Entity\UserRepository")
*/
class User
{
// ... the generated properties and getter/setter functions
}
Right now, this is just a plain, regular Doctrine entity that has nothing to do with security. But, our goal is to load users from this table on login. The first step is to make your class implement a UserInterface:
// src/Yoda/UserBundle/Entity/User.php
// ...
use Symfony\Component\Security\Core\User\UserInterface;
class User implements UserInterface
{
// ...
}
This interface requires us to have 5 methods and hey! We already have 2 of them: getUsername() and getPassword():
// src/Yoda/UserBundle/Entity/User.php
// ...
public function getUsername()
{
return $this->username;
}
public function getPassword()
{
return $this->password;
}
Cool! So let’s add the other 3.
First, getRoles() returns an array of roles that the user should get. For now, we’ll hardcode a single role, ROLE_USER:
// src/Yoda/UserBundle/Entity/User.php
// ...
public function getRoles()
{
return array('ROLE_USER');
}
Second, add eraseCredentials. Keep this method blank for now. We will add some logic to this later:
public function eraseCredentials()
{
// blank for now
}
Finally, add getSalt() and just make it return null:
public function getSalt()
{
return null;
}
I’ll talk more about this method in a second.
Now that the User class implements UserInterface, Symfony’s authentication system will be able to use it. But before we hook that up, let’s add the yoda_user table to the database by running the doctrine:schema:update command:
php app/console doctrine:schema:update --force
And for the grand finale, let’s tell the security system to use our entity class!
In security.yml, replace the encoder entry with our user class and set its value to bcrypt:
# app/config/security.yml
security:
encoders:
Yoda\UserBundle\Entity\User: bcrypt
# ...
This tells Symfony that the password field on our User will be encoded using the bcrypt algorithm.
The one catch is that bcrypt isn’t supported until PHP 5.5. So if you’re using PHP 5.4 or lower, you’ll need to install an extra library via Composer. No problem! Head to your terminal and use the composer require command and pass it ircmaxell/password-compat:
php composer.phar require ircmaxell/password-compat
When it asks, use the ~1.0.3 version. By the way, this require command is just a shortcut that updates our composer.json for us and then runs the Composer update:
"require": {
"...": "..."
"ircmaxell/password-compat": "~1.0.3"
},
Now for the Jedi magic! In security.yml, remove the single providers entry and replace it with a new one:
# app/config/security.yml
security:
# ...
providers:
our_database_users:
entity: { class: UserBundle:User, property: username }
I’m just inventing the our_database_users part, that can be anything. But the entity key is a special built-in provider that knows how to load users via a Doctrine entity.
Yea, and that’s really it! Ok, let’s try it.
When you refresh, you may get an error:
There is no user provider for user "Symfony\Component\Security\Core\User\User".
Don’t panic, this is just because we’re still logged in as one of the hard-coded users... even though we just deleted them from security.yml. It’s a one-time error - just refresh and it’ll go away.
Hey Diego!
Actually, this is a big topic and something that's been changing! Though you don't see it (yet) in the Symfony series, the recommendation now (unless you're building code to be shared between projects) is to just have one bundle. You're of course free to add more if you feel like one feature is *really* different than everything else and it *feels* better to have it in a different bundle, but I would push you towards less bundles. Bundles are basically just directories, and having less directories and depth will make your project easier to navigate. In case you haven't seen it, here's a bit about the *why* behind the AppBundle: http://knpuniversity.com/bl...
Cheers!
Nice article, for the moment I'll follow the AppBundle structure for simplicity
Thanks for your time!
// composer.json
{
"require": {
"php": ">=5.3.3",
"symfony/symfony": "~2.4", // v2.4.2
"doctrine/orm": "~2.2,>=2.2.3", // v2.4.2
"doctrine/doctrine-bundle": "~1.2", // v1.2.0
"twig/extensions": "~1.0", // v1.0.1
"symfony/assetic-bundle": "~2.3", // v2.3.0
"symfony/swiftmailer-bundle": "~2.3", // v2.3.5
"symfony/monolog-bundle": "~2.4", // v2.5.0
"sensio/distribution-bundle": "~2.3", // v2.3.4
"sensio/framework-extra-bundle": "~3.0", // v3.0.0
"sensio/generator-bundle": "~2.3", // v2.3.4
"incenteev/composer-parameter-handler": "~2.0", // v2.1.0
"doctrine/doctrine-fixtures-bundle": "~2.2.0", // v2.2.0
"ircmaxell/password-compat": "~1.0.3", // 1.0.3
"phpunit/phpunit": "~4.1" // 4.1.0
}
}
Hey there!
I have a question about creating bundles. What kind of logic should we follow in order to determine if is necessary to create a new bundle or not ?
Thanks in advance.