gstreamer0.10-ffmpeg
gstreamer0.10-plugins-good
packages.
Now matter how your users will login, the first step to creating an authentication system is to create a User
class. And we just did that with the handy make:user
command.
Go check out that class: src/Entity/User.php
:
... lines 1 - 2 | |
namespace App\Entity; | |
use Doctrine\ORM\Mapping as ORM; | |
use Symfony\Component\Security\Core\User\UserInterface; | |
/** | |
* @ORM\Entity(repositoryClass="App\Repository\UserRepository") | |
*/ | |
class User implements UserInterface | |
{ | |
/** | |
* @ORM\Id() | |
* @ORM\GeneratedValue() | |
* @ORM\Column(type="integer") | |
*/ | |
private $id; | |
/** | |
* @ORM\Column(type="string", length=180, unique=true) | |
*/ | |
private $email; | |
/** | |
* @ORM\Column(type="json") | |
*/ | |
private $roles = []; | |
public function getId(): ?int | |
{ | |
return $this->id; | |
} | |
public function getEmail(): ?string | |
{ | |
return $this->email; | |
} | |
public function setEmail(string $email): self | |
{ | |
$this->email = $email; | |
return $this; | |
} | |
/** | |
* A visual identifier that represents this user. | |
* | |
* @see UserInterface | |
*/ | |
public function getUsername(): string | |
{ | |
return (string) $this->email; | |
} | |
/** | |
* @see UserInterface | |
*/ | |
public function getRoles(): array | |
{ | |
$roles = $this->roles; | |
// guarantee every user at least has ROLE_USER | |
$roles[] = 'ROLE_USER'; | |
return array_unique($roles); | |
} | |
public function setRoles(array $roles): self | |
{ | |
$this->roles = $roles; | |
return $this; | |
} | |
/** | |
* @see UserInterface | |
*/ | |
public function getPassword() | |
{ | |
// not needed for apps that do not check user passwords | |
} | |
/** | |
* @see UserInterface | |
*/ | |
public function getSalt() | |
{ | |
// not needed for apps that do not check user passwords | |
} | |
/** | |
* @see UserInterface | |
*/ | |
public function eraseCredentials() | |
{ | |
// If you store any temporary, sensitive data on the user, clear it here | |
// $this->plainPassword = null; | |
} | |
} |
Two important things. First, because we chose "yes" to storing user info in the database, the command created an entity class with the normal annotations and id
property. It also added an email
property, a roles
property - that we'll talk about later - and the normal getter and setter methods. Yep, this User
class is just a normal, boring entity class.
Now look back at the top of the class. Ah, it implements a UserInterface
:
... lines 1 - 5 | |
use Symfony\Component\Security\Core\User\UserInterface; | |
... lines 7 - 10 | |
class User implements UserInterface | |
{ | |
... lines 13 - 99 | |
} |
This is the second important thing make:user
did. Our User
class can look however we want. The only rule is that it must implement this interface... which is actually pretty simple. It just means that you need a few extra methods. The first is getUsername()
... which is a bad name... because your users do not need to have a username. This method should just return a visual identifier for the user. In our case: email
:
... lines 1 - 10 | |
class User implements UserInterface | |
{ | |
... lines 13 - 46 | |
/** | |
* A visual identifier that represents this user. | |
* | |
* @see UserInterface | |
*/ | |
public function getUsername(): string | |
{ | |
return (string) $this->email; | |
} | |
... lines 56 - 99 | |
} |
And actually, this method is only used by Symfony to display who is currently logged in on the web debug toolbar. It's not important.
Next is getRoles()
:
... lines 1 - 10 | |
class User implements UserInterface | |
{ | |
... lines 13 - 56 | |
/** | |
* @see UserInterface | |
*/ | |
public function getRoles(): array | |
{ | |
$roles = $this->roles; | |
// guarantee every user at least has ROLE_USER | |
$roles[] = 'ROLE_USER'; | |
return array_unique($roles); | |
} | |
... lines 68 - 99 | |
} |
This is related to user permissions, and we'll talk about it later.
The last 3 are getPassword()
, getSalt()
and eraseCredentials()
. And all 3 of these are only needed if your app is responsible for storing and checking user passwords. Because our app will not check user passwords - well, not yet - these can safely be blank:
... lines 1 - 10 | |
class User implements UserInterface | |
{ | |
... lines 13 - 75 | |
/** | |
* @see UserInterface | |
*/ | |
public function getPassword() | |
{ | |
// not needed for apps that do not check user passwords | |
} | |
/** | |
* @see UserInterface | |
*/ | |
public function getSalt() | |
{ | |
// not needed for apps that do not check user passwords | |
} | |
/** | |
* @see UserInterface | |
*/ | |
public function eraseCredentials() | |
{ | |
// If you store any temporary, sensitive data on the user, clear it here | |
// $this->plainPassword = null; | |
} | |
} |
So, for us: we basically have a normal entity class that also has a getUsername()
method and a getRoles()
method. It's really, pretty boring.
The other file that was modified was config/packages/security.yaml
. Go back to your terminal and run:
git diff
to see what changed. Ah, it updated this providers
key:
security: | |
# https://symfony.com/doc/current/security.html#where-do-users-come-from-user-providers | |
providers: | |
# used to reload user from session & other features (e.g. switch_user) | |
app_user_provider: | |
entity: | |
class: App\Entity\User | |
property: email | |
... lines 9 - 29 |
This is called a "user provider". Each User
class - and you'll almost definitely only need one User
class - needs a corresponding "user provider". And actually, it's not that important. I'll tell you what it does later.
But before we get there, forget about security and remember that our User
class is a Doctrine entity. Let's add another field to it, generate a migration & add some dummy users to the database. Then, to authentication!
Hey Ozornick
It's good to see you again :)
About your question, I don't fully get your point. The command make:user
does not generate anything on src/Security
folder. I just tried it out on Sf4.3 and it creates/updates the same files as shown on the video. Could you clarify things a bit?
Cheers!
Hey Ozornick!
Indeed, maker:user
might create something in src/Entity
OR in src/Security
. The deciding point is this question:
Do you want to store user data in the database (via Doctrine)?
If you answer yes to that, we create an entity in src/Entity
. If you say not to that, we create a "plain old PHP object" in src/Security
. It's made to work for both use-cases.
Does that clarify things?
Cheers!
Ohh interesting. I've never said no to "Store user data in the database" - I learnt something new today :)
Cheers!
I've hit an issue with type=json for the roles. This is not supported in MariaDB 5.5.60. Is there an easy solution, other than upgrading the DB version?
Thanks
Hi Steve D.
You can try to set doctrine.dbal.server_version
like:
doctrine:
dbal:
server_version: 'mariadb-5.5.60'
Cheers!
Thank you Vladimir. I'm actually importing the database from my local dev to the remote dev and the user table won't import as the remote DB is version 5.5.6 and doesn't like the field type JSON.
I'm going to either refactor the roles to be an entity or get the DB version on the remote server upgraded.
Thanks again
Steve
Hey ahmadmayahi!
Hmm, great question! This comes from MakerBundle... but I implemented this code :). And I don't exactly remember why I decided to do it this way. I mean, I "kind of" know. I decided to add the `:string` return type... which is just nice to have. Because of that, I needed to guarantee that the method returns a string. Because it's possible for email to be null (not in the database, but before it's saved, it's possible), I case it to a string to avoid a type error.
I hope that clarifies! It's not too important of a detail :).
Cheers!
// composer.json
{
"require": {
"php": "^7.1.3",
"ext-iconv": "*",
"composer/package-versions-deprecated": "^1.11", // 1.11.99
"knplabs/knp-markdown-bundle": "^1.7", // 1.7.0
"knplabs/knp-paginator-bundle": "^2.7", // v2.8.0
"knplabs/knp-time-bundle": "^1.8", // 1.8.0
"nexylan/slack-bundle": "^2.0,<2.2.0", // v2.0.0
"php-http/guzzle6-adapter": "^1.1", // v1.1.1
"sensio/framework-extra-bundle": "^5.1", // v5.2.0
"stof/doctrine-extensions-bundle": "^1.3", // v1.3.0
"symfony/asset": "^4.0", // v4.1.4
"symfony/console": "^4.0", // v4.1.4
"symfony/flex": "^1.0", // v1.17.6
"symfony/framework-bundle": "^4.0", // v4.1.4
"symfony/lts": "^4@dev", // dev-master
"symfony/orm-pack": "^1.0", // v1.0.6
"symfony/security-bundle": "^4.0", // v4.1.4
"symfony/serializer-pack": "^1.0", // v1.0.1
"symfony/twig-bundle": "^4.0", // v4.1.4
"symfony/web-server-bundle": "^4.0", // v4.1.4
"symfony/yaml": "^4.0", // v4.1.4
"twig/extensions": "^1.5" // v1.5.2
},
"require-dev": {
"doctrine/doctrine-fixtures-bundle": "^3.0", // 3.0.2
"easycorp/easy-log-handler": "^1.0.2", // v1.0.7
"fzaninotto/faker": "^1.7", // v1.8.0
"symfony/debug-bundle": "^3.3|^4.0", // v4.1.4
"symfony/dotenv": "^4.0", // v4.1.4
"symfony/maker-bundle": "^1.0", // v1.7.0
"symfony/monolog-bundle": "^3.0", // v3.3.0
"symfony/phpunit-bridge": "^3.3|^4.0", // v4.1.4
"symfony/profiler-pack": "^1.0", // v1.0.3
"symfony/var-dumper": "^3.3|^4.0" // v4.1.4
}
}
Again I guys) In Symfony 4.3.3 make:user creates the essence of src/Security/User.php. Next, you say edit User make:entity and oops ..! A NEW class is created in src/Entity/User.php Can make a note or edit the code under 4.3.3?