Hola que tal? Les hago una consulta, resulta que estoy trabajando con las versiones más actuales de doctrine y symfony2, y resulta que a la hora de hacer un login, obtengo el siguiente mensaje de error:
Attempted to call an undefined method named "getRole" of class "Doctrine\ORM\PersistentCollection".
Acá mi código:
composer.json
"require": { "php": ">=5.5", "symfony/symfony": "2.*", "doctrine/orm": "~2,>=2.2.3", "doctrine/doctrine-bundle": "1.2.*", "twig/extensions": "1.0.*", "symfony/assetic-bundle": "2.3.*", "symfony/swiftmailer-bundle": "2.3.*", "symfony/monolog-bundle": "2.3.*", "sensio/distribution-bundle": "2.3.*", "sensio/framework-extra-bundle": "2.3.*", "sensio/generator-bundle": "2.3.*", "incenteev/composer-parameter-handler": "~2.0" },
Security.yml
security: encoders: FindPlaces\UsuariosBundle\Entity\Usuario: plaintext providers: usuarios_db: entity: { class: UsuariosBundle:Usuario, property: username } firewalls: secured_area: anonymous: true pattern: ^/ security: true form_login: login_path: login check_path: login_check logout: path: /logout target: login access_control: - { path: ^/login, roles: IS_AUTHENTICATED_ANONYMOUSLY } - { path: ^/, roles: IS_AUTHENTICATED_ANONYMOUSLY }
Usuario.php
class Usuario implements UserInterface, \Serializable { /** * @ORM\ManyToOne(targetEntity="Rol", inversedBy="usuarios") * @ORM\JoinColumn(name="rol_id", referencedColumnName="idRol") */ private $rol; ... public function getRol() { return $this->rol; } public function getRole() { $rol = array($this->rol); return $rol; } public function getRoles() { return $this->getRole(); } }
Rol.php
class Rol implements \Serializable, RoleInterface { ... public function getRole() { return $this->nombre; } }
dev.log
[2015-11-03 12:20:45] php.DEBUG: Undefined variable: name {"type":8,"file":"D:\\Laburo\\!Web\\Lugares de interes\\Repositorio\\FindPlaces\\app\\cache\\dev\\classes.php","line":3373,"level":28928} [] [2015-11-03 12:20:45] php.INFO: Defining the initRuntime() method in an extension is deprecated. Use the `needs_environment` option to get the Twig_Environment instance in filters, functions, or tests; or explicitly implement Twig_Extension_InitRuntimeInterface if needed (not recommended). {"type":16384,"file":"D:\\Laburo\\!Web\\Lugares de interes\\Repositorio\\FindPlaces\\app\\cache\\dev\\classes.php","line":3373,"level":28928,"stack":[{"function":"handleError","class":"Symfony\\Component\\Debug\\ErrorHandler","type":"->"},{"file":"D:\\Laburo\\!Web\\Lugares de interes\\Repositorio\\FindPlaces\\app\\cache\\dev\\classes.php","line":3373,"function":"trigger_error"},{"file":"D:\\Laburo\\!Web\\Lugares de interes\\Repositorio\\FindPlaces\\app\\cache\\dev\\classes.php","line":3219,"function":"initRuntime","class":"Twig_Environment","type":"->"},{"file":"D:\\Laburo\\!Web\\Lugares de interes\\Repositorio\\FindPlaces\\app\\cache\\dev\\classes.php","line":3187,"function":"loadTemplate","class":"Twig_Environment","type":"->"},{"file":"D:\\Laburo\\!Web\\Lugares de interes\\Repositorio\\FindPlaces\\vendor\\symfony\\symfony\\src\\Symfony\\Bundle\\TwigBundle\\Controller\\ExceptionController.php","line":74,"function":"render","class":"Twig_Environment","type":"->"},{"function":"showAction","class":"Symfony\\Bundle\\TwigBundle\\Controller\\ExceptionController","type":"->"},{"file":"D:\\Laburo\\!Web\\Lugares de interes\\Repositorio\\FindPlaces\\app\\bootstrap.php.cache","line":3109,"function":"call_user_func_array"},{"file":"D:\\Laburo\\!Web\\Lugares de interes\\Repositorio\\FindPlaces\\app\\bootstrap.php.cache","line":3071,"function":"handleRaw","class":"Symfony\\Component\\HttpKernel\\HttpKernel","type":"->"},{"file":"D:\\Laburo\\!Web\\Lugares de interes\\Repositorio\\FindPlaces\\app\\bootstrap.php.cache","line":3222,"function":"handle","class":"Symfony\\Component\\HttpKernel\\HttpKernel","type":"->"},{"file":"D:\\Laburo\\!Web\\Lugares de interes\\Repositorio\\FindPlaces\\vendor\\symfony\\symfony\\src\\Symfony\\Component\\HttpKernel\\EventListener\\ExceptionListener.php","line":50,"function":"handle","class":"Symfony\\Component\\HttpKernel\\DependencyInjection\\ContainerAwareHttpKernel","type":"->"},{"function":"onKernelException","class":"Symfony\\Component\\HttpKernel\\EventListener\\ExceptionListener","type":"->"},{"file":"D:\\Laburo\\!Web\\Lugares de interes\\Repositorio\\FindPlaces\\vendor\\symfony\\symfony\\src\\Symfony\\Component\\EventDispatcher\\Debug\\WrappedListener.php","line":61,"function":"call_user_func"},{"function":"__invoke","class":"Symfony\\Component\\EventDispatcher\\Debug\\WrappedListener","type":"->"},{"file":"D:\\Laburo\\!Web\\Lugares de interes\\Repositorio\\FindPlaces\\app\\cache\\dev\\classes.php","line":1834,"function":"call_user_func"},{"file":"D:\\Laburo\\!Web\\Lugares de interes\\Repositorio\\FindPlaces\\app\\cache\\dev\\classes.php","line":1763,"function":"doDispatch","class":"Symfony\\Component\\EventDispatcher\\EventDispatcher","type":"->"},{"file":"D:\\Laburo\\!Web\\Lugares de interes\\Repositorio\\FindPlaces\\vendor\\symfony\\symfony\\src\\Symfony\\Component\\EventDispatcher\\Debug\\TraceableEventDispatcher.php","line":124,"function":"dispatch","class":"Symfony\\Component\\EventDispatcher\\EventDispatcher","type":"->"},{"file":"D:\\Laburo\\!Web\\Lugares de interes\\Repositorio\\FindPlaces\\app\\bootstrap.php.cache","line":3141,"function":"dispatch","class":"Symfony\\Component\\EventDispatcher\\Debug\\TraceableEventDispatcher","type":"->"},{"file":"D:\\Laburo\\!Web\\Lugares de interes\\Repositorio\\FindPlaces\\app\\bootstrap.php.cache","line":3089,"function":"handleException","class":"Symfony\\Component\\HttpKernel\\HttpKernel","type":"->"},{"function":"terminateWithException","class":"Symfony\\Component\\HttpKernel\\HttpKernel","type":"->"},{"file":"D:\\Laburo\\!Web\\Lugares de interes\\Repositorio\\FindPlaces\\vendor\\symfony\\symfony\\src\\Symfony\\Component\\Debug\\ExceptionHandler.php","line":138,"function":"call_user_func"},{"function":"handle","class":"Symfony\\Component\\Debug\\ExceptionHandler","type":"->"},{"file":"D:\\Laburo\\!Web\\Lugares de interes\\Repositorio\\FindPlaces\\vendor\\symfony\\symfony\\src\\Symfony\\Component\\Debug\\ErrorHandler.php","line":505,"function":"call_user_func"},{"file":"D:\\Laburo\\!Web\\Lugares de interes\\Repositorio\\FindPlaces\\vendor\\symfony\\symfony\\src\\Symfony\\Component\\Debug\\ErrorHandler.php","line":565,"function":"handleException","class":"Symfony\\Component\\Debug\\ErrorHandler","type":"->"},{"function":"handleFatalError","class":"Symfony\\Component\\Debug\\ErrorHandler","type":"::"}]} [] [2015-11-03 12:20:45] php.DEBUG: Undefined variable: name {"type":8,"file":"D:\\Laburo\\!Web\\Lugares de interes\\Repositorio\\FindPlaces\\app\\cache\\dev\\classes.php","line":3656,"level":28928} [] [2015-11-03 12:20:45] php.INFO: Defining the getGlobals() method in an extension is deprecated without explicitly implementing Twig_Extension_GlobalsInterface. {"type":16384,"file":"D:\\Laburo\\!Web\\Lugares de interes\\Repositorio\\FindPlaces\\app\\cache\\dev\\classes.php","line":3656,"level":28928,"stack":[{"function":"handleError","class":"Symfony\\Component\\Debug\\ErrorHandler","type":"->"},{"file":"D:\\Laburo\\!Web\\Lugares de interes\\Repositorio\\FindPlaces\\app\\cache\\dev\\classes.php","line":3656,"function":"trigger_error"},{"file":"D:\\Laburo\\!Web\\Lugares de interes\\Repositorio\\FindPlaces\\app\\cache\\dev\\classes.php","line":3617,"function":"initGlobals","class":"Twig_Environment","type":"->"},{"file":"D:\\Laburo\\!Web\\Lugares de interes\\Repositorio\\FindPlaces\\app\\cache\\dev\\classes.php","line":3623,"function":"getGlobals","class":"Twig_Environment","type":"->"},{"file":"D:\\Laburo\\!Web\\Lugares de interes\\Repositorio\\FindPlaces\\app\\cache\\dev\\classes.php","line":4869,"function":"mergeGlobals","class":"Twig_Environment","type":"->"},{"file":"D:\\Laburo\\!Web\\Lugares de interes\\Repositorio\\FindPlaces\\app\\cache\\dev\\classes.php","line":4876,"function":"display","class":"Twig_Template","type":"->"},{"file":"D:\\Laburo\\!Web\\Lugares de interes\\Repositorio\\FindPlaces\\app\\cache\\dev\\classes.php","line":3187,"function":"render","class":"Twig_Template","type":"->"},{"file":"D:\\Laburo\\!Web\\Lugares de interes\\Repositorio\\FindPlaces\\vendor\\symfony\\symfony\\src\\Symfony\\Bundle\\TwigBundle\\Controller\\ExceptionController.php","line":74,"function":"render","class":"Twig_Environment","type":"->"},{"function":"showAction","class":"Symfony\\Bundle\\TwigBundle\\Controller\\ExceptionController","type":"->"},{"file":"D:\\Laburo\\!Web\\Lugares de interes\\Repositorio\\FindPlaces\\app\\bootstrap.php.cache","line":3109,"function":"call_user_func_array"},{"file":"D:\\Laburo\\!Web\\Lugares de interes\\Repositorio\\FindPlaces\\app\\bootstrap.php.cache","line":3071,"function":"handleRaw","class":"Symfony\\Component\\HttpKernel\\HttpKernel","type":"->"},{"file":"D:\\Laburo\\!Web\\Lugares de interes\\Repositorio\\FindPlaces\\app\\bootstrap.php.cache","line":3222,"function":"handle","class":"Symfony\\Component\\HttpKernel\\HttpKernel","type":"->"},{"file":"D:\\Laburo\\!Web\\Lugares de interes\\Repositorio\\FindPlaces\\vendor\\symfony\\symfony\\src\\Symfony\\Component\\HttpKernel\\EventListener\\ExceptionListener.php","line":50,"function":"handle","class":"Symfony\\Component\\HttpKernel\\DependencyInjection\\ContainerAwareHttpKernel","type":"->"},{"function":"onKernelException","class":"Symfony\\Component\\HttpKernel\\EventListener\\ExceptionListener","type":"->"},{"file":"D:\\Laburo\\!Web\\Lugares de interes\\Repositorio\\FindPlaces\\vendor\\symfony\\symfony\\src\\Symfony\\Component\\EventDispatcher\\Debug\\WrappedListener.php","line":61,"function":"call_user_func"},{"function":"__invoke","class":"Symfony\\Component\\EventDispatcher\\Debug\\WrappedListener","type":"->"},{"file":"D:\\Laburo\\!Web\\Lugares de interes\\Repositorio\\FindPlaces\\app\\cache\\dev\\classes.php","line":1834,"function":"call_user_func"},{"file":"D:\\Laburo\\!Web\\Lugares de interes\\Repositorio\\FindPlaces\\app\\cache\\dev\\classes.php","line":1763,"function":"doDispatch","class":"Symfony\\Component\\EventDispatcher\\EventDispatcher","type":"->"},{"file":"D:\\Laburo\\!Web\\Lugares de interes\\Repositorio\\FindPlaces\\vendor\\symfony\\symfony\\src\\Symfony\\Component\\EventDispatcher\\Debug\\TraceableEventDispatcher.php","line":124,"function":"dispatch","class":"Symfony\\Component\\EventDispatcher\\EventDispatcher","type":"->"},{"file":"D:\\Laburo\\!Web\\Lugares de interes\\Repositorio\\FindPlaces\\app\\bootstrap.php.cache","line":3141,"function":"dispatch","class":"Symfony\\Component\\EventDispatcher\\Debug\\TraceableEventDispatcher","type":"->"},{"file":"D:\\Laburo\\!Web\\Lugares de interes\\Repositorio\\FindPlaces\\app\\bootstrap.php.cache","line":3089,"function":"handleException","class":"Symfony\\Component\\HttpKernel\\HttpKernel","type":"->"},{"function":"terminateWithException","class":"Symfony\\Component\\HttpKernel\\HttpKernel","type":"->"},{"file":"D:\\Laburo\\!Web\\Lugares de interes\\Repositorio\\FindPlaces\\vendor\\symfony\\symfony\\src\\Symfony\\Component\\Debug\\ExceptionHandler.php","line":138,"function":"call_user_func"},{"function":"handle","class":"Symfony\\Component\\Debug\\ExceptionHandler","type":"->"},{"file":"D:\\Laburo\\!Web\\Lugares de interes\\Repositorio\\FindPlaces\\vendor\\symfony\\symfony\\src\\Symfony\\Component\\Debug\\ErrorHandler.php","line":505,"function":"call_user_func"},{"file":"D:\\Laburo\\!Web\\Lugares de interes\\Repositorio\\FindPlaces\\vendor\\symfony\\symfony\\src\\Symfony\\Component\\Debug\\ErrorHandler.php","line":565,"function":"handleException","class":"Symfony\\Component\\Debug\\ErrorHandler","type":"->"},{"function":"handleFatalError","class":"Symfony\\Component\\Debug\\ErrorHandler","type":"::"}]} []
¿Alguna idea de cual puede ser el error?
Gracias! :)
Respuestas
En primer lugar creo que estás complicando muchísimo la aplicación con la idea de definir tus propios roles y los roles de Symfony a la vez en la misma clase de usuario. Salvo que los requerimientos de tu aplicación sean complejos y muy especiales, hacer esto es totalmente innecesario y muy contraproducente.
Dicho esto, el error dice que no puedes ejecutar el método getRole()
sobre una variable de tipo PersistentCollection
. Una posible causa es que en alguna parte has hecho una consulta de Doctrine y en vez de quedarte con el primer resultado, estás usando el "array" entero que te devuelve Doctrine.
@javiereguiluz
Gracias por responder javier, en realidad mi intención no es usar los roles de symfony, si ves que los estoy usando, por favor dime como puedo hacer para dejar de hacerlo, en cuanto a lo otro, voy a revisar más a fondo a ver si encuentro el error.
@MrXXX0323
En realidad mi consejo es usar los roles de Symfony. No solo son muy fáciles de usar, sino que te van a ahorrar muchos problemas de seguridad en tu aplicación.
@javiereguiluz
El problema que le veo al usar los roles de symfony es que no son dinámicos es decir, si me limitara a usar los roles de symfony, mi cliente no podría crear roles para asignar a los empleados de su empresa con diferentes permisos y eso es un requerimiento que me están haciendo mucho hoy en día.
Pero si conoces una mejor manera de resolverlo, soy todo oidos.
@MrXXX0323
Precisamente los roles de Symfony son lo más dinámico que hay: cualquier cadena de texto que empiece por ROLE_
es un rol. No hay que definir nada, ni configurar nada, ni instanciar nada. Si quieres 100 roles, crea 100 cadenas de texto que empiecen por ROLE_
.
Y si quieres crear por ejemplo un rol por cada usuario o empresa, entonces lo que tienes que hacer es olvidarte de los roles personalizados y usar los "voters". En un "voter" puedes comprobar que el usuario tenga un rol mínimo (por ejemplo ROLE_CLIENTE
) y luego ya haces todas las comprobaciones que necesites (que el usuario esté al día en el pago; que el usuario pertenezca a la empresa cuya información quiere modificar; etc.)
@javiereguiluz
Genial, no sabía que se podía hacer eso.
Donde podría investigar el correcto uso de los votters ? tenes algún link que me puedas pasar ?
Con lo que me comentas arriba, puedo tener esta estructura ?
class Usuario implements UserInterface, \Serializable { /** * @var integer * * @ORM\Column(name="idUsuario", type="integer") * @ORM\Id * @ORM\GeneratedValue(strategy="AUTO") */ private $id; /** * @var string * * @ORM\Column(name="username", type="string", length=100, unique=true) * @Assert\NotBlank(message = "Éste campo no puede quedar vacio.") * @Assert\Length( * min = 4, * max = 50, * minMessage = "El nombre de usuario debe tener un mínimo de {{ limit }} caracteres", * maxMessage = "El nombre deusuario no puede superar los {{ limit }} caracteres" * ) */ protected $username; /** * @var string * * @ORM\Column(name="password", type="string", length=255, nullable=false) * @Assert\NotBlank(message = "Éste campo no puede quedar vacio.") */ private $password; /** * @ORM\ManyToOne(targetEntity="Rol", inversedBy="usuarios") * @ORM\JoinColumn(name="rol_id", referencedColumnName="idRol") */ private $rol; }
class Rol implements \Serializable, RoleInterface { /** * @var integer * * @ORM\Column(name="idRol", type="integer") * @ORM\Id * @ORM\GeneratedValue(strategy="AUTO") */ private $id; /** * @var string * * @ORM\Column(name="nombre", type="string", length=100, unique=true) * @Assert\NotBlank(message = "Éste campo no puede quedar vacio.") */ private $nombre; /** * @var string * * @ORM\Column(name="descripcion", type="string", length=255, nullable=true) */ private $descripcion; /** * @ORM\ManyToOne(targetEntity="FindPlaces\AdministracionBundle\Entity\Estado", inversedBy="roles") * @ORM\JoinColumn(name="estado_id", referencedColumnName="idEstado", nullable=false) * @Assert\NotBlank(message = "Debe seleccionar 1 Estado.") */ private $estado; /** * @ORM\OneToMany(targetEntity="PermisosPorRol", mappedBy="rol") */ private $permisos; /** * @ORM\OneToMany(targetEntity="Usuario", mappedBy="rol") */ private $usuarios; }
class Permiso implements \Serializable { /** * @var integer * * @ORM\Column(name="idPermiso", type="integer") * @ORM\Id * @ORM\GeneratedValue(strategy="AUTO") */ private $id; /** * @var string * * @ORM\Column(name="nombre", type="string", length=100, unique=true) * @Assert\NotBlank(message = "Éste campo no puede quedar vacio.") */ private $nombre; /** * @var string * * @ORM\Column(name="clave", type="string", length=100, unique=true) */ private $clave; /** * @var string * * @ORM\Column(name="descripcion", type="string", length=255, nullable=true) */ private $descripcion; /** * @ORM\ManyToOne(targetEntity="FindPlaces\AdministracionBundle\Entity\Estado", inversedBy="permisos") * @ORM\JoinColumn(name="estado_id", referencedColumnName="idEstado", nullable=false) * @Assert\NotBlank(message = "Debe seleccionar 1 Estado.") */ private $estado; /** * @ORM\OneToMany(targetEntity="PermisosPorRol", mappedBy="permiso") */ private $roles; }
class PermisosPorRol implements \Serializable { /** * @var integer * * @ORM\Column(name="id", type="integer") * @ORM\Id * @ORM\GeneratedValue(strategy="AUTO") */ private $id; /** * @ORM\ManyToOne(targetEntity="Rol", inversedBy="permisos") * @ORM\JoinColumns({ * @ORM\JoinColumn(name="rol_id", referencedColumnName="idRol", nullable=false) * }) */ private $rol; /** * @ORM\ManyToOne(targetEntity="Permiso", inversedBy="roles") * @ORM\JoinColumns({ * @ORM\JoinColumn(name="permiso_id", referencedColumnName="idPermiso", nullable=false) * }) */ private $permiso; // Estado: 1 (Habilitado), 0 (Inhabilitado) /** * @var integer * * @ORM\Column(name="estado", type="integer") */ private $estado; }
O como sería lo correcto para administrar, permisos por rol (con roles dinámicos) ?
@MrXXX0323
Lo mejor para aprender a usar los "voters" es leerse este artículo de la documentación de Symfony: How to Use Voters to Check User Permissions
También puedes ver esta presentación (aunque es de 2013).
En el libro de Buenas Prácticas Oficiales de Symfony también se mencionan brevemente los voters de Symfony.
@javiereguiluz