symfony authentication autenticacion

autenticacion es el proceso por el que reconocemos si el que quiere ingresar a nuestro sitio es quien dice que es.
en esta etapa realizaremos lo que se conoce como logueo, o login.

vamos a usar authenfication de tipo Guard que aunque es mas trabajosa de implementar es mas flexible (a comparacion de sistemas built-in como form_login)

vamos a necesitar crear:

  • SecurityController.php : tendrá el loginAction
  • User.php : esta entidad implementará UserInterface
  • LoginFom.php : creará el formulario para el ingreso de username y password
  • LoginFormAuthenticator.php : aqui se realizará la obtencion de los datos de login y su validación.
  • login.html.twig


y también deberemos configurar:

  • services.yml : para registrar como servicio a LoginFormAuthenticator
  • security.yml : para indicar que la seguridad será manejada por el service ligada a LoginFormAuthenticator

class SecurityController extends Controller
{
/**
* @Route("/login", name="security_login")
*/
public function loginAction()
{
$authenticationUtils = $this->get('security.authentication_utils');
// get the login error if there is one
$error = $authenticationUtils->getLastAuthenticationError();
// last username entered by the user
$lastUsername = $authenticationUtils->getLastUsername();
$form = $this->createForm(LoginForm::class, [
'_username' => $lastUsername,
]);
return $this->render(
'security/login.html.twig',
array(
'form' => $form->createView(),
'error' => $error,
)
);
}
}
**********************************************
/**
 * User
 *
 * @ORM\Table(name="user")
 * @ORM\Entity(repositoryClass="AppBundle\Repository\UserRepository")
 */
class User implements UserInterface
{
    /**
     * @var int
     *
     * @ORM\Column(name="id", type="integer")
     * @ORM\Id
     * @ORM\GeneratedValue(strategy="AUTO")
     */
    private $id;

    /**
     * @ORM\Column(type="string", unique=true)
     */
    private $email;

    /**
     * Get id
     *
     * @return integer 
     */
    public function getId()
    {
        return $this->id;
    }
/**
* {@inheritDoc}
* @see \Symfony\Component\Security\Core\User\UserInterface::getRoles()
*/
public function getRoles() {
// TODO: Auto-generated method stub
return ['ROLE_USER'];
}



public function getUsername() {
// TODO: Auto-generated method stub
return $this->getEmail();
}


public function getEmail() {
return $this->email;
}
public function setEmail($email) {
$this->email = $email;
return $this;
}

}

notar que aun no estamos manejando un password por usuario
(la validación se hará luego contra una cadena cualquiera ,p.e: "123123")
el método getUsername, sólo devuelve el nombre de usuario para mostrar, en nuestro caso el nombre de usuario será el email.

**************
class LoginForm extends AbstractType {
public function buildForm(FormBuilderInterface $builder, array $options)
{
$builder
->add('_username')
->add('_password', PasswordType::class)
;
//_username for us, it's an email.
}
}
***************
class LoginFormAuthenticator extends AbstractFormLoginAuthenticator {
private $formFactory;
private $em;
private $router;
public function __construct(FormFactoryInterface $formFactory, EntityManager $em, RouterInterface $router)
{
$this->formFactory = $formFactory;
$this->em = $em;
$this->router = $router;

}

public function getCredentials(Request $request) {
$isLoginSubmit = $request->getPathInfo() == '/login' && $request->isMethod('POST');
if (!$isLoginSubmit) {
// skip authentication
return;
}
$form = $this->formFactory->create(LoginForm::class);
$form->handleRequest($request);
$data = $form->getData();

$request->getSession()->set(
Security::LAST_USERNAME,
$data['_username']
);

return $data;
}
public function getUser($credentials, UserProviderInterface $userProvider) {
$username = $credentials['_username'];
return $this->em->getRepository('AppBundle:User')
->findOneBy(['email' => $username]);
}
public function checkCredentials($credentials, UserInterface $user) {
$password = $credentials['_password'];
if ($password == '123123') {
return true;
}
return false;
}
protected function getLoginUrl()
{
//nos determina a donde ir si el login falla
return $this->router->generate('security_login');
}
protected function getDefaultSuccessRedirectUrl()
{
//cuando el logueo es exitoso se regresará a la pagina desde la que se solicitó la authentication
//si no existiera esa pagina se retornorá por default a la home
return $this->router->generate('homepage');
}
}

esta clase será creada dentro de la nueva carpeta Security.
este authenticator se comunica con el SecurityController a travez de almacenar cosas en la session
por ejemplo guardando el ultimo username que falló en el logueo.

$request->getSession()->set(
Security::LAST_USERNAME,
$data['_username']
);

**********************************************

{# app/Resources/views/security/login.html.twig #} {# ... you will
probably extend your base template, like base.html.twig #}
{% extends 'base.html.twig' %}

{% block body %}
<div class="container">


<div class="row">
<div class="col-xs-12">
<h1>Login!</h1>
            {% if error %}
                <div class="alert alert-danger">
                    {{ error.messageKey|trans(error.messageData, 'security') }}
                </div>
            {% endif %}
{{ form_start(form) }}
                {{ form_row(form._username) }}
                {{ form_row(form._password) }}
<button type="submit" class="btn btn-success">Login <span class="fa fa-lock"></span></button>
            {{ form_end(form) }}


</form>
</div>
</div>
</div>
{% endblock %}

*********************************

services.yml

services:
     app.security.login_form_authenticator:
        class: AppBundle\Security\LoginFormAuthenticator
        autowire: true

**********************************

security.yml

security:

    providers:
        our_users:
            entity: { class: AppBundle\Entity\User, property: email }

    firewalls:
         main:
            guard:
                authenticators:
                    - app.security.login_form_authenticator


vamos ahora a hacer una verdadera validacion de password, entonces debemos agregar el campo en nuestra entidad User, password será el campo encodeado que se guardará en la tabla y plainPassword
el que guardara temporalmente el valor que se ingrese antes de encodearlo. Este campo no se persiste

/**
     * The encoded password
     *
     * @ORM\Column(type="string")
     */
    private $password;
 /**
     * campo non-persisted que se usará para crear el encoded password.
     *
     * @var string
     */
    private $plainPassword;


public function eraseCredentials() {
$this->plainPassword = null;
/*
* Symfony calls this after logging in,
* and it's just a minor  security measure
* to prevent the plain-text password from being accidentally saved anywhere.
*/

}
public function setPlainPassword($plainPassword) {
$this->plainPassword = $plainPassword;
// forces the object to look "dirty" to Doctrine. Avoids
// Doctrine *not* saving this entity, if only plainPassword changes
$this->password = null;
}
+++++++++++++

ahora vamos a implementar un listener que tendrá la tarea de disparar eventos para cuando se intente crear una entidad del tipo User (prePersist) o cuando se la intente actualizar (preUpdated)
este listener luego se lo registrará como servicio

lo crearemos dentro de una nueva carpeta llamada Doctrine:

class HashPasswordListener implements EventSubscriber {
/*
* Symfony comes with a built-in service that's really good at encoding passwords. 
* It's called security.password_encoder 
* and  its class is UserPasswordEncoder
*/
private $passwordEncoder;
public function __construct(UserPasswordEncoder $passwordEncoder)
{
$this->passwordEncoder = $passwordEncoder;
}
public function getSubscribedEvents() {
/* These are two event names that Doctrine makes available. 
* prePersist is called right before an entity is originally inserted.
* preUpdate is called right before an entity is updated. */
return ['prePersist', 'preUpdate'];
}
public function prePersist(LifecycleEventArgs $args)
{
$entity = $args->getEntity();//obtenemos que entidad esta siendo salvada
/*
 * Now, if this is not an instanceof User, just return and do nothing
*/
if (!$entity instanceof User) {
return;
}
$this->encodePassword($entity);
}
public function preUpdate(LifecycleEventArgs $args)
{
$entity = $args->getEntity();
if (!$entity instanceof User) {
return;
}
$this->encodePassword($entity);
// necessary to force the update to see the change
$em = $args->getEntityManager();
$meta = $em->getClassMetadata(get_class($entity));
$em->getUnitOfWork()->recomputeSingleEntityChangeSet($meta, $entity);
}
private function encodePassword(User $entity)
{
if (!$entity->getPlainPassword()) {
return;
}
$encoded = $this->passwordEncoder->encodePassword(
$entity,
$entity->getPlainPassword()
);
$entity->setPassword($encoded);
}
}


en services.yml

services:
  app.doctrine.hash_password_listener:
        class: AppBundle\Doctrine\HashPasswordListener
        autowire: true
        tags:
            - { name: doctrine.event_subscriber }


en LoginFormAuthenticator.php

en el constructor agregamos un parametro extra para hacer referencia al encoder del password:
asi lo podemos usar para la validacion en checkCredentials

private $passwordEncoder;
public function __construct(FormFactoryInterface $formFactory, EntityManager $em, RouterInterface $router, UserPasswordEncoder $passwordEncoder)
{
$this->formFactory = $formFactory;
$this->em = $em;
$this->router = $router;
$this->passwordEncoder = $passwordEncoder;
}

public function checkCredentials($credentials, UserInterface $user) {
$password = $credentials['_password'];
if ($this->passwordEncoder->isPasswordValid($user, $password)) {
return true;
}
return false;
}


finalmente, en security.yml,
indicamos que encoder vamos a usar para encriptar los passwords

security:
encoders:
AppBundle\Entity\User: bcrypt

eclipse trabajar un proyecto php pdt

para facilitarte enormemente el trabajo en el desarrollo de un proyecto PHP,
hay que instalarle el plugin de extensiones PDT(PHP Development Tools)

Ir a Help -> Install New Software -> agregar la url: "http://p2.pdt-extensions.org" -> select PDT Extensions de la lista.



symfony formularios DateType



ahora vamos a agregar a nuestra entidad Employee el campo de su nacimiento

use Symfony\Component\Form\Extension\Core\Type\DateType;

/**
     * @ORM\Column(type="date",nullable=true)
     */
    private $birthday;

 /**
     * birthday
     * @return date
     */
    public function getBirthday(){
        return $this->birthday;
    }

    /**
     * birthday
     * @param date $birthday
     * @return Employee
     */
    public function setBirthday($birthday){
        $this->birthday = $birthday;
        return $this;
    }



ahora en el EmployeeType si le agregamos lo siguiente en el buildForm:

...

->add('birthday', DateType::class, [
    'widget' => 'single_text'
    ])


vamos a obtener un campo de texto con un formato de input dd/mm/yyyy que valida la fecha ingresada mientras se ingresa mas un datepicker desplegable.




symfony formularios validacion email

las reglas de validaciones de los campos se pueden hacer desde la entidad,
a traves de asserts,

use Symfony\Component\Validator\Constraints as Assert;

/**
     * @ORM\Column(type="string",nullable=true,length=100)
     * @Assert\Email()
     */
    private $email;

hará una validación de formato simple de lo que se ingrese. (que exista una @ y texto a ambos lados de ella) se muestra como tooltip.

Podemos cambiar el mensaje de direccion invalida (este mensaje se muestra como texto debajo del cuadro de input)

y checkMX en true, analiza el dns del servidor, si esto no estuviera podriamos ingresar jaja@cualquierfruta.com

/**
     * @ORM\Column(type="string",nullable=true,length=100)
     * @Assert\Email( message = "El email '{{ value }}' no es una dirección válida.",checkMX = true)
     */
    private $email;

symfony doctrine tipos de datos de mapeo

  • string: Type that maps a SQL VARCHAR to a PHP string.
  • integer: Type that maps a SQL INT to a PHP integer.
  • smallint: Type that maps a database SMALLINT to a PHP integer.
  • bigint: Type that maps a database BIGINT to a PHP string.
  • boolean: Type that maps a SQL boolean or equivalent (TINYINT) to a PHP boolean.
  • decimal: Type that maps a SQL DECIMAL to a PHP string.
  • date: Type that maps a SQL DATETIME to a PHP DateTime object.
  • time: Type that maps a SQL TIME to a PHP DateTime object.
  • datetime: Type that maps a SQL DATETIME/TIMESTAMP to a PHP DateTime object.
  • datetimetz: Type that maps a SQL DATETIME/TIMESTAMP to a PHP DateTime object with timezone.
  • text: Type that maps a SQL CLOB to a PHP string.
  • object: Type that maps a SQL CLOB to a PHP object using serialize() and unserialize()
  • array: Type that maps a SQL CLOB to a PHP array using serialize() and unserialize()
  • simple_array: Type that maps a SQL CLOB to a PHP array using implode() and explode(), with a comma as delimiter. IMPORTANT Only use this type if you are sure that your values cannot contain a ”,”.
  • json_array: Type that maps a SQL CLOB to a PHP array using json_encode() and json_decode()
  • float: Type that maps a SQL Float (Double Precision) to a PHP double. IMPORTANT: Works only with locale settings that use decimal points as separator.
  • guid: Type that maps a database GUID/UUID to a PHP string. Defaults to varchar but uses a specific type if the platform supports it.
  • blob: Type that maps a SQL BLOB to a PHP resource stream

All Date types assume that you are exclusively using the default timezone set by date_default_timezone_set() or by the php.ini configuration date.timezone. Working with different timezones will cause troubles and unexpected behavior.
If you need specific timezone handling you have to handle this in your domain, converting all the values back and forth from UTC. There is also a cookbook entry on working with datetimes that gives hints for implementing multi timezone applications

symfony formularios EntityType

supongamos que tenemos la entidad Employee y otra llamada Nacionality :D
un empleado tiene una nacionalidad y una misma nacionalidad la pueden tener muchos empleados
la relacion es de manytoone. (y se modela en Employee ya que es quien en la tabla tendrá el foreign_key)

la idea es que en el formulario de creacion de empleado, podamos seleccionar su nacionalidad de una lista de nacionalidades, esto se lografacilmente con EntityType, veamos:

use Symfony\Bridge\Doctrine\Form\Type\EntityType;
...

class EmployeeType extends AbstractType
{
 
    public function buildForm(FormBuilderInterface $builder, array $options)
    {
..

)->add('nacionality', EntityType::class, [
    'class' => 'AppBundle:Nacionality',
    // use the Nacionality.name property as the visible option string
    'choice_label' => 'name',
   
    // used to render a select box, check boxes or radios
    // 'multiple' => true,
    // 'expanded' => true,
])

por default nos va a ordenar la lista por name alfabeticamente ASCendente.
que pasaría si quisiera cambiar el orden o filtrar la lista por alguna otra condicion?

usamos el atributo query_builder

use Doctrine\ORM\EntityRepository;

->add('nacionality', EntityType::class, [
    'class' => 'AppBundle:Nacionality',
    // use the User.username property as the visible option string
    'choice_label' => 'name',
    'query_builder' => function (EntityRepository $er) {
                return $er->createQueryBuilder('u')
            ->orderBy('u.name', 'DESC');
    },
    // used to render a select box, check boxes or radios
    // 'multiple' => true,
    // 'expanded' => true,
    ])

una mejor manera es crear la query en el repository correspondiente a la entity,
entonces en nuestro caso en NacionalityRepository, creamos:

public function createAlphabeticalQueryBuilder()
{
return $this->createQueryBuilder('nacionality')
->orderBy('nacionality.name', 'DESC');
}

entonces nuestro query_builder en el form type quedaría asi:

'query_builder' => function (EntityRepository $er) {
    return $er->createAlphabeticalQueryBuilder()
    ;
    }


si queremos que la primera opción de la lista sea un texto del tipo "Seleccione una nacionalidad"
agregamos estas lineas

->add('nacionality', EntityType::class, [
    'class' => 'AppBundle:Nacionality',
    // use the User.username property as the visible option string
    'choice_label' => 'name',
    'query_builder' => function (EntityRepository $er) {
    return $er->createQueryBuilder('u')
    ->orderBy('u.name', 'DESC');
    },
    'placeholder' => 'Seleccione la nacionalidad',
    'empty_data'  => null
    ]);

previamente tendremos que haber creado, la entidad Nacionality:

<?php

namespace AppBundle\Entity;

use Doctrine\ORM\Mapping as ORM;

/**
 * Nacionality
 *
 * @ORM\Table(name="nacionality")
 * @ORM\Entity(repositoryClass="AppBundle\Repository\NacionalityRepository")
 */
class Nacionality
{
    /**
     * @var int
     *
     * @ORM\Column(name="id", type="integer")
     * @ORM\Id
     * @ORM\GeneratedValue(strategy="AUTO")
     */
    private $id;

    /**
     * @var string
     *
     * @ORM\Column(name="name", type="string", length=255, unique=true)
     */
    private $name;


    /**
     * Get id
     *
     * @return integer
     */
    public function getId()
    {
        return $this->id;
    }

    /**
     * Set name
     *
     * @param string $name
     * @return Nacionality
     */
    public function setName($name)
    {
        $this->name = $name;

        return $this;
    }

    /**
     * Get name
     *
     * @return string
     */
    public function getName()
    {
        return $this->name;
    }
}

y en Employee generar la relacion:

/**
     * @ORM\ManyToOne(targetEntity="Nacionality")
     * @ORM\JoinColumn(nullable=true)
     */
    private $nacionality;

    /**
     * nacionality
     * @return Nacionality
     */
    public function getNacionality(){
        return $this->nacionality;
    }

    /**
     * nacionality
     * @param Nacionality $nacionality
     * @return Employee
     */
    public function setNacionality($nacionality){
        $this->nacionality = $nacionality;
        return $this;
    }

symfony ejecutar un up / down de un archivo de migration

php app/console doctrine:migration:execute YYYYMMDDHHMMSS --up



php app/console doctrine:migration:execute YYYYMMDDHHMMSS --down


tambien podemos saber el estado de las migraciones, cual fue la ultima, la previa etc

a través del siguiente comando:

php app/console doctrine:migrations:status

s  == Configuration

    >> Name:                                               Application Migrations
    >> Database Driver:                                    pdo_mysql
    >> Database Name:                                      test_56
    >> Configuration Source:                               manually configured
    >> Version Table Name:                                 migration_versions
    >> Version Column Name:                                version
    >> Migrations Namespace:                               Application\Migrations
    >> Migrations Directory:                               C:\Users\ibazan\workspace-zennovia-56\test\app/DoctrineMigrations
    >> Previous Version:                                   2017-04-28 15:57:13 (20170428155713)
    >> Current Version:                                    2017-04-28 16:01:26 (20170428160126)
    >> Next Version:                                       Already at latest version
    >> Latest Version:                                     2017-04-28 16:01:26 (20170428160126)
    >> Executed Migrations:                                9
    >> Executed Unavailable Migrations:                    0
    >> Available Migrations:                               9

    >> New Migrations:                                     0

symfony formularios choiceType

sirve para armar una lista de opciones aplicables a un determinado campo del formulario
por ejemplo, para el campo isActivated los valores que puede tomar son true(1) o false(0).

entonces, en el form de employee...

use Symfony\Component\Form\Extension\Core\Type\ChoiceType;

class EmployeeType extends AbstractType
{
 public function buildForm(FormBuilderInterface $builder, array $options)
    {
...
)->add('isActivated', ChoiceType::class, [
    'choices' => [
    true => 'Si',
    false => 'No',
    ]
    ]);

esto mostrará en el formulario (por default) un combox con las opciones Si y No.

se puede variar la presentación según como combinemos los atributos expanded y multiple:

Element TypeExpandedMultiple
select tagfalsefalse
select tag (with multiple attribute)falsetrue
radio buttonstruefalse
checkboxestruetrue
en nuestro caso podriamos optar por una presentación del tipo radio button, por lo que, escribiremos:

->add('isActived', ChoiceType::class, [
    'choices' => [
    true => 'Si',
    false => 'No',
    ],
    'expanded' => true,'multiple' => false,
         'label' => 'Está activo?'
    ]);

nota:
a partir de symfony 3,
las opciones de choices se deberán ingresar de manera inversa
add(‘isPublished’, ChoiceType::class, [ ‘choices’ => [ ‘Yes’ => true, ‘No’ => false, ] ])

mas info en
http://symfony.com/doc/current/reference/forms/types/choice.html#multiple




symfony entity field default value

  /**
     * @ORM\Column(type="boolean", options={"default"=true})
     */
    private $isActived ;

symfony formularios formType

vamos a usar nuestra entidades employee, para crear un formulario de creacion de empleados:

nos audaremos de el sgt comando de consola:

php app/console doctrine:generate:form AppBundle:Employee --no-interaction

esto generará el archivo appBundle/Form/EmployeeForm.php

al archivo generado le vamos a tocar la parte para que se pueda mostrar el conjunto de roles como checkboxes

<?php

namespace AppBundle\Form;

use Symfony\Component\Form\AbstractType;
use Symfony\Component\Form\FormBuilderInterface;
use Symfony\Component\OptionsResolver\OptionsResolver;

class EmployeeType extends AbstractType
{
    /**
     * {@inheritdoc}
     */
    public function buildForm(FormBuilderInterface $builder, array $options)
    {
    $builder->add('name')->add('address')->add('dni')->add(
    'rolesCollection', 'entity',
    [
    'class' => 'AppBundle\Entity\Role',
    'property' => 'name',
    'multiple' => TRUE,
    'expanded' => TRUE,
    'label' => 'Roles',
    ]
    );
    }
   
    /**
     * {@inheritdoc}
     */
    public function configureOptions(OptionsResolver $resolver)
    {
        $resolver->setDefaults(array(
            'data_class' => 'AppBundle\Entity\Employee'
        ));
    }

    /**
     * {@inheritdoc}
     */
    public function getBlockPrefix()
    {
        return 'appbundle_employee';
    }


}


si expanded es False, los roles se mostrarán en un combobox

las validaciones se definen en los atributos de la entidad

en configureOptions...el array con la key data_class define la relacion con la entidad.

ahora ya podemos crear el action en el controller para llamar al template que contendrá el formulario,
en este mismo action vamos a manejar el submit que se hará desde el formulario.
el formulario no tiene un action definido por lo que el evento submit llamará al mismo action.




/**
* @Route("/new-employee")
*/
public function newemployeeAction(Request $request)
{
$form = $this->createForm(EmployeeType::class);
// only handles data on POST
$form->handleRequest($request);
if ($form->isSubmitted() && $form->isValid()) {
//dump($form->getData());die;
$employee = $form->getData();
$em = $this->getDoctrine()->getManager();
$em->persist($employee);
$em->flush();
$this->addFlash('success', 'Empleado creado!');
return $this->redirectToRoute('admin_employee_list');
}
return $this->render('admin/employee/new.html.twig', [
'employeeForm' => $form->createView()
]);
}
crear
en app/Resources/views/admin/empolyee/new.htm.twig

{% extends 'base.html.twig' %}
{% block body %}
    <div class="container">
        <div class="row">
            <div class="col-xs-12">
                <h1>Nuevo Empleado</h1>
                {{ form_start(employeeForm) }}
                    {{ form_widget(employeeForm) }}
                    <button type="submit" class="btn btn-primary">Guardar</button>
                {{ form_end(employeeForm) }}
            </div>
        </div>
    </div>
{% endblock %}

para mostrar el mensaje flash, editamos el archivo base.html.twig

....
<body>
        {% for msg in app.session.flashBag.get('success') %}
                <div class="alert alert-success">
                    {{ msg }}
                </div>
        {% endfor %}
....

para que el formulario tenga un mejor aspecto vamos a habilitar bootstrap en config.yml


twig:
   form:
        resources: ['bootstrap_3_horizontal_layout.html.twig']

symfony relaciones manyToMany - employee - role

supongamos que tenemos las tablas employee y role
donde el empleado puede tener varios roles y un rol puede estar asignado a varios empleados.

en una de las entidades se define la relacion, por ejemplo en Employee


para ayudarnos en la creación de la entidad vamos a usar la consola:

php app/console doctrine:generate:entity

donde podremos ir agregando el nombre, bajo el formato AppBundle:Employee
y los atributos de la entidad.

esto generará 2 archivos por entidad: entity/Employee.php y repository/EmployeeRepository.php
     


<?php

namespace AppBundle\Entity;

use Doctrine\ORM\Mapping as ORM;
use AppBundle\Entity\Role;
use Doctrine\Common\Collections\ArrayCollection;

/**
 * Employee
 *
 * @ORM\Table(name="employee")
 * @ORM\Entity(repositoryClass="AppBundle\Repository\EmployeeRepository")
 */
class Employee
{
    /**
     * @var int
     *
     * @ORM\Column(name="id", type="integer")
     * @ORM\Id
     * @ORM\GeneratedValue(strategy="AUTO")
     */
    private $id;

    /**
     * @var string
     *
     * @ORM\Column(name="name", type="string", length=255)
     */
    private $name;

    /**
     * @var string
     *
     * @ORM\Column(name="address", type="string", length=255, nullable=true)
     */
    private $address;

    /**
     * @var int
     *
     * @ORM\Column(name="dni", type="integer", unique=true)
     */
    private $dni;


    /**
     * @var ArrayCollection
     *
     * @ORM\ManyToMany(targetEntity="AppBundle\Entity\Role")
     * @ORM\JoinTable(name="employees_roles",
     *    joinColumns={@ORM\JoinColumn(name="employee_id", referencedColumnName="id")},
     *    inverseJoinColumns={@ORM\JoinColumn(name="role_id", referencedColumnName="id")}
     * )
     */
    protected $roles;
   
   
    /**
     * Get id
     *
     * @return integer
     */
    public function getId()
    {
        return $this->id;
    }

    /**
     * Set name
     *
     * @param string $name
     * @return Employee
     */
    public function setName($name)
    {
        $this->name = $name;

        return $this;
    }

    /**
     * Get name
     *
     * @return string
     */
    public function getName()
    {
        return $this->name;
    }

    /**
     * Set address
     *
     * @param string $address
     * @return Employee
     */
    public function setAddress($address)
    {
        $this->address = $address;

        return $this;
    }

    /**
     * Get address
     *
     * @return string
     */
    public function getAddress()
    {
        return $this->address;
    }

    /**
     * Set dni
     *
     * @param integer $dni
     * @return Employee
     */
    public function setDni($dni)
    {
        $this->dni = $dni;

        return $this;
    }

    /**
     * Get dni
     *
     * @return integer
     */
    public function getDni()
    {
        return $this->dni;
    }
   
    /**
     * Get roles as array of strings.
     *
     * @return array
     */
    public function getRoles() {
    $roleNames = [];
    foreach ($this->roles as $role) {
    $roleNames[] = $role->getName();
    }
    return $roleNames;
    }
   
    /**
     * Get roles ass ArrayCollection.
     *
     * @return ArrayCollection
     */
    public function getRolesCollection() {
    return $this->roles;
    }
   
    /**
     * Returns TRUE if employee has ROLE_ADMIN.
     *
     * @return bool
     */
    public function isAdmin() {
    return in_array('ROLE_ADMIN', $this->getRoles(), TRUE);
    }
   
    /**
     * Set roles.
     *
     * @param ArrayCollection $roles
     * @return Employee
     */
    public function setRolesCollection($roles) {
    $this->roles = $roles;
    return $this;
    }
   
    /**
     * Add roles
     *
     * @param Role $roles
     * @return Employee
     */
    public function addRolesCollection(Role $roles) {
    $this->roles[] = $roles;
   
    return $this;
    }
   
    /**
     * Remove roles
     *
     * @param Role $roles
     */
    public function removeRolesCollection(Role $roles) {
    $this->roles->removeElement($roles);
    }
   
    /**
     * Constructor.
     */
    public function __construct() {
   
    $this->roles = new ArrayCollection();
    }
}


<?php

namespace AppBundle\Entity;

use Doctrine\ORM\Mapping as ORM;

/**
 * Role
 *
 * @ORM\Table(name="role")
 * @ORM\Entity(repositoryClass="AppBundle\Repository\RoleRepository")
 */
class Role
{
    /**
     * @var int
     *
     * @ORM\Column(name="id", type="integer")
     * @ORM\Id
     * @ORM\GeneratedValue(strategy="AUTO")
     */
    private $id;

    /**
     * @var string
     *
     * @ORM\Column(name="name", type="string", length=255, unique=true)
     */
    private $name;


    /**
     * Get id
     *
     * @return integer 
     */
    public function getId()
    {
        return $this->id;
    }

    /**
     * Set name
     *
     * @param string $name
     * @return Role
     */
    public function setName($name)
    {
        $this->name = $name;

        return $this;
    }

    /**
     * Get name
     *
     * @return string 
     */
    public function getName()
    {
        return $this->name;
    }
}




linux ubuntu mint actualizar chrome

 desde una terminal: $ sudo apt update $ sudo apt install google-chrome-stable