21.7 C
Caracas
miércoles, agosto 18, 2021
Inicio Blog Página 5

Crear funciones php en twig

0
Crear extensiones personalizadas en twig

La principal motivación para escribir una función personalizada para twig, es que al migrar a este potente motor de plantillas integrado en Symfony2, me di cuenta de que en las mismas no se puede usar código php, lo que resulta fantástico en la teoría, ya que en las vistas solo hay unas cuantas condiciones y código html, sin embargo en la práctica y después de venir trabajando con Symfony 1.4, resulta bastante laborioso y tardío hacer todas las funciones en el controlador.

Bien sea por el factor tiempo, o por no salir de la zona de confort muchos optan por rechazar twig y seguir usando php como motor de plantillas.

En este post te enseñare como Crear funciones php en twig, y posteriormente usarlas a través de la Inyección de dependencias.

Creando la clase de la extensión

Para obtener la funcionalidad personalizada primero debes crear la clase para la extensión Twig, que use la clase EntityManager de symfony. Como ejemplo vas a crear una consulta que traiga todos las provincias de un país seleccionado

// src/Acme/DemoBundle/DependencyInjection/FunctionsExtension.php
namespace Acme\DemoBundle\DependencyInjection;
use Doctrine\ORM\EntityManager;

class FunctionsExtension extends \Twig_Extension
{

	protected $em;

	public function __construct(EntityManager $em) {
	$	this->em = $em;
	}

	/**
	* {@inheritdoc}
	*/
	public function getFunctions()
	{
		return array(
			'estadosFilter' => new \Twig_Function_Method($this, 'estadosFilter'),
		);
	}

	public function estadosFilter($pais)
	{
		$provincias = $this->em->getRepository('AcmeDemoBundle:Provincias')->findBy(array('paisid'=>$pais));

		return $provincias;
	}

	public function getName()
	{
		return 'acme_funtions';
	}
}

Registrando una extensión como servicio

Ahora hay que dejar que el contenedor de servicios sepa de su nueva extensión declarando la misma:

YAML:
# src/Acme/DemoBundle/Resources/config/services.yml
services:
acme.twig.acme_funtions:
class: Acme\DemoBundle\DependencyInjection\FunctionsExtension
tags:
- { name: twig.extension }
arguments: [ @doctrine.orm.default_entity_manager, @doctrine.orm.entity_manager, @kernel ]
XML:
<!-- src/Acme/DemoBundle/Resources/config/services.xml -->
<services>
<service id="acme.twig.acme_funtions" class="Acme\DemoBundle\DependencyInjection\FunctionsExtension">
<tag name="twig.extension" />
<@doctrine.orm.default_entity_manager>
<@doctrine.orm.entity_manager>
<@kernel>
</service>
</services>
PHP:
// src/Acme/DemoBundle/Resources/config/services.php
use Symfony\Component\DependencyInjection\Definition;

$container
->register('acme.twig.acme_funtions', '\Acme\DemoBundle\DependencyInjection\FunctionsExtension')
->addTag('twig.extension')
->addArgument('@doctrine.orm.default_entity_manager')
->addArgument('@doctrine.orm.entity_manager')
->addArgument('@kernel');

Usando la extensión personalizada

Usar tu recién creada extensión de Twig no es diferente a cualquier otra, en este ejemplo se estaria llenando selects de acuerdo al objeto paises:

{% for p in paises %}
	{% set provincias = estadosFilter(p.id) %}
	<option value="">Seleccione una Provincia</option>
	{% for prov in provincias %}
		<option value="{{ prov.id }}">{{ prov.nombre }</option>
	{% endfor %}
{% endfor %}

De esta forma ya puedes crear cualquier otra función php como una inyección de dependencias y usarlas en cualquier momento en la plantilla twig

Notas

Ten en cuenta que las extensiones Twig no se cargan de manera diferida. Esto significa que hay una mayor probabilidad de que obtengas una CircularReferenceException o ScopeWideningInjectionException si cualquier servicio (o tu extensión Twig —en este caso—) es dependiente del servicio Petición. Para obtener más información, échale un vistazo a Cómo Trabajar con Ámbitos

Listado de Países y Provincias Sql en Español

4

Hasta hace poco tiempo estuve buscando como un loco alguna base de datos con el Listado de Países y Provincias Sql en Español de todo el mundo, a pesar de conseguir innumerables inclusive desde la misma página de mysql (que ya he tenido la oportunidad de usar en proyectos anteriores), obviamente todas estaban en ingles, pero, está vez la necesitaba específicamente en español. Luego de tanta búsqueda y de ayuda de mi compañero de trabajo, logramos dar con un artículo, en donde se encuentra la lista de países con sus estados y provincias en 8 idiomas, no era exactamente lo que queríamos, por problemas de codificación, pero, al menos nos aligero bastante el trabajo.

Proceso

El proceso para usar esta lista fue algo laboriosa pero se resume en lo siguiente:

  • Primero importamos toda la lista en una nueva base de datos
  • Luego exportamos de la lista los países y ciudades que estuvieran en el idioma español
  • Editamos manualmente los acentos, diéresis, eñes, etc, que estaban dañados por la codificación
  • Importamos este resultado en una nueva base de datos y exportamos nuevamente solo lo que necesitábamos (en país id y nombre, y en estado id, pais_id y nombre)

En este momento ya teníamos dos tablas relacionadas en español y con codificación utf8 que luego importamos dentro de nuestra base de datos en producción.

Me parece bastante extraño no conseguir una lista de países en español o bien no supe buscar.

Acá les dejo el enlace del repositorio para que puedan tener su tabla de pais / estado en español

Repositorio

Y este es el atículo donde esta la lista en 8 idiomas por si alguen la quiere completa

Tutorial: mini-backend de usuarios con Doctrine

0
Mysql y Doctrine en Symfony

Uno de los aspectos más importantes en el desarrollo de cualquier aplicación es la Seguridad de acceso, para ello Symfony 2 dispone de una moderna librería que se encarga de las validaciones de acceso y seguridad.
Una de las opciones más tentadoras es utilizar Doctrine como proveedor de los Usuarios, con el cual podamos crear Roles y Usuarios desde CRUD’s elaborados por el mismo framework y crear nuestro propio esquema de seguridad, debo resaltar que existen muchos Bundles prefabricados como el FOSUserBundle que facilitan enormemente ésta tarea, pero si quieres profundizar puedes seguir el siguiente Tutorial: mini-backend de usuarios con Doctrine, para conocer a fondo como se hace desde 0 .

Paso 1: Crea las entidades básicas

Antes de empezar debemos de definir las entidades básicas para ser utilizadas como proveedor de usuarios y roles en Sf2, dichas entidades User y Role deben de implementar las interfaces Symfony\Component\Security\Core\User\UserInterface y Symfony\Component\Security\Core\Role\RoleInterface respectivamente, así que añade estas 2 entidades a tu directorio “loginbundle/src/Test/LoginBundle/Entity;”:

User.php:
<?php
namespace Test\LoginBundle\Entity;
use Symfony\Component\Security\Core\User\UserInterface;
use Doctrine\Common\Collections\ArrayCollection;
use Doctrine\ORM\Mapping as ORM;
/**
* @ORM\Entity
* @ORM\Table(name=”admin_user”)
*/
class User implements UserInterface
{
/**
* @var integer
*
* @ORM\Column(name=”id”, type=”integer”, nullable=false)
* @ORM\Id
* @ORM\GeneratedValue(strategy=”IDENTITY”)
*/
private $id;

/**
* @var string
*
* @ORM\Column(name=”username”, type=”string”, length=255, nullable=true)
*/
protected $username;

/**
* @var string
*
* @ORM\Column(name=”password”, type=”string”, length=255, nullable=true)
*/
protected $password;

/**
* @var string
*
* @ORM\Column(name=”salt”, type=”string”, length=255, nullable=true)
*/
protected $salt;

/**
* se utilizó user_roles para no hacer conflicto al aplicar ->toArray en getRoles()
* @ORM\ManyToMany(targetEntity=”Role”)
* @ORM\JoinTable(name=”user_role”,
* joinColumns={@ORM\JoinColumn(name=”user_id”, referencedColumnName=”id”)},
* inverseJoinColumns={@ORM\JoinColumn(name=”role_id”, referencedColumnName=”id”)}
* )
*/
protected $user_roles;

public function __construct()
{
$this->user_roles = new ArrayCollection();
}

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

/**
* Set username
*
* @param string $username
*/
public function setUsername($username)
{
$this->username = $username;
}

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

/**
* Set password
*
* @param string $password
*/
public function setPassword($password)
{
$this->password = $password;
}

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

/**
* Set salt
*
* @param string $salt
*/
public function setSalt($salt)
{
$this->salt = $salt;
}

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

/**
* Add user_roles
*
* @param Test\LoginBundle\Entity\Role $userRoles
*/
public function addRole(\Test\LoginBundle\Entity\Role $userRoles)
{
$this->user_roles[] = $userRoles;
}

public function setUserRoles($roles) {
$this->user_roles = $roles;
}

/**
* Get user_roles
*
* @return Doctrine\Common\Collections\Collection
*/
public function getUserRoles()
{
return $this->user_roles;
}

/**
* Get roles
*
* @return Doctrine\Common\Collections\Collection
*/
public function getRoles()
{
//return $this->user_roles->toArray(); //IMPORTANTE: el mecanismo de seguridad de Sf2 requiere ésto como un array
$roles = array();
foreach ($this->user_roles as $role) {
$roles[] = $role->getRole();
}

return $roles;
}

/**
* Compares this user to another to determine if they are the same.
*
* @param UserInterface $user The user
* @return boolean True if equal, false othwerwise.
*/
public function equals(UserInterface $user) {
return md5($this->getUsername()) == md5($user->getUsername());

}

/**
* Erases the user credentials.
*/
public function eraseCredentials() {

}
}
Role.php:
<?php
namespace Test\LoginBundle\Entity;

use Symfony\Component\Security\Core\Role\RoleInterface;
use Doctrine\ORM\Mapping as ORM;

/**
* @ORM\Entity
* @ORM\Table(name=”admin_roles”)
*/
class Role implements RoleInterface
{
/**
* @var integer
*
* @ORM\Column(name=”id”, type=”integer”, nullable=false)
* @ORM\Id
* @ORM\GeneratedValue(strategy=”AUTO”)
*/
protected $id;

/**
* @var string
*
* @ORM\Column(name=”nombre”, type=”string”, length=255, nullable=true)
*/
protected $name;

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

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

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

public function getRole() {
return $this->getName();
}

public function __toString() {
return $this->getRole();
}
}

Una vez creadas nuestras entidades, accedemos a la consola de Symfony2 y generamos las tablas en Base de Datos:

$ app/console doctrine:schema:update –force

Paso 2: Generando los CRUD’s

Una vez creadas las entidades en DB, procedemos a crear los CRUD’s desde la consola de symfony:

$ app/console doctrine:generate:crud

Seguimos los pasos colocando TestLoginBundle:Role, luego nos solicita si deseamos crear las opciones de escritura, le decimos “yes” (sí), formato del CRUD: annotation, y finalmente en el Routes prefix colocamos /admin/role, este paso es importante porque a la ruta le asignamos el prefijo /admin para que nos permita empatar luego con el access_control, confirmamos y aparecerá el mensaje “You can now start using the generated code!”

Procedemos a aplicar lo mismo pero en este caso con TestLoginBundle:User y en Routes prefix colocamos /admin/user

Ahora añadiremos las rutas a nuestro archivo de rutas (loginbundle/src/Test/LoginBundle/Resources/Config/routing.yml),

TestAnnotations:
resource: “@TestLoginBundle/Controller/”
prefix: /
type: annotation

porque al crearlas como Anotaciones las mismas no se añaden automáticamente:

De ésta forma añadiremos todas las rutas definidas por anotaciones del directorio Controller, ésta técnica forma parte del SensioFrameworkExtraBundle y nos permite definir las rutas directamente en nuestros controladores. si utilizas la Versión estándar de Symfony2 este Bundle viene por defecto.

Ya con esto podemos acceder a nuestros crud’s desde localhost/loginbundle/web/app_dev.php/admin/user, pero aún debemos modificar ciertos aspectos en el controlador User para codificar el hash de contraseña.

Primero añadiremos la siguiente función en el controlador de usuarios:

Luego modificamos las funciones de las acciones correspondientes a create y update, añadiendo la llamada al la función anterior para establecer el hash de la contraseña con el algoritmo SHA512:

// loginbundle/src/Test/LoginBundle/Controller/UserController.php

//funcion createAction:
public function createAction(Request $request)
{
$entity = new User();
$form = $this->createCreateForm($entity);
$form->handleRequest($request);

if ($form->isValid()) {

//establecemos la contraseña: --------------------------
$this->setSecurePassword($entity);

$em = $this->getDoctrine()->getManager();
$em->persist($entity);
$em->flush();

return $this->redirect($this->generateUrl('admin_user_show', array('id' => $entity->getId())));
}

return array(
'entity' => $entity,
'form' => $form->createView(),
);
}

//...

//funcion updateAction:
public function updateAction(Request $request, $id)
{
$em = $this->getDoctrine()->getManager();

$entity = $em->getRepository('TestLoginBundle:User')->find($id);

//obtiene la contraseña actual -----------------------
$current_pass = $entity->getPassword();

if (!$entity) {
throw $this->createNotFoundException('Unable to find User entity.');
}

$deleteForm = $this->createDeleteForm($id);
$editForm = $this->createEditForm($entity);
$editForm->handleRequest($request);

if ($editForm->isValid()) {
//evalua si la contraseña fue modificada: ------------------------
if ($current_pass != $entity->getPassword()) {
$this->setSecurePassword($entity);
}
$em->persist($entity);
$em->flush();

return $this->redirect($this->generateUrl('admin_user_edit', array('id' => $id)));
}

return array(
'entity' => $entity,
'edit_form' => $editForm->createView(),
'delete_form' => $deleteForm->createView(),
);
}

Luego dentro de este mismo controlado UserController.php agregas la función setSecurePassword:private

private function setSecurePassword(&$entity)
{
$entity->setSalt(md5(time()));
$encoder = new \Symfony\Component\Security\Core\Encoder\MessageDigestPasswordEncoder('sha512', true, 10);
$password = $encoder->encodePassword($entity->getPassword(), $entity->getSalt());
$entity->setPassword($password);
}

Por último sólo nos queda eliminar del formulario (src/Test/LoginBundle/Form/UserType.php) el campo salt el cual no debe ser modificado por el usuario:

<?php
// loginbundle/src/Test/LoginBundle/Form/UserType.php
namespace Test\loginBundle\Form;
// funcion buildform:
public function buildForm(FormBuilderInterface $builder, array $options)
{
$builder
->add('username')
->add('password')
//->add('salt') //No necesitamos que salt sea mostrado ---------------
->add('user_roles')
;
}

Ahora puedes proceder a registrar usuarios y roles, es muy importante que al menos crees los roles “ROLE_ADMIN” y “ROLE_USER” y dos usuarios (uno con un rol diferente) antes de que procedas en aplicar el esquema de seguridad, de lo contrario no tendrás usuario con que loguearte .

Paso 3: Creando el esquema de seguridad

Ahora procedemos a sobreescribir nuestro esquema de seguridad (loginBundle/app/config/security.yml), recomiendo que antes de hacerlo guardes una copia del security.yml.

# loginBundle /app/config/security.yml
security:
encoders:
Test\LoginBundle\Entity\User:
algorithm: sha512
encode-as-base64: true
iterations: 10

providers:
user_db:
entity: { class: Test\LoginBundle\Entity\User, property: username }

firewalls:
dev:
pattern: ^/(_(profiler|wdt)|css|images|js)/
security: false

login:
pattern: ^/admin/login$
security: false

secured_area:
pattern: ^/admin/
# http_basic:
# realm: "Introduzca Usuario y Contraseña"
form_login:
login_path: /admin/login
check_path: /admin/login_check
logout:
invalidate_session: false
path: /admin/logout
target: /

access_control:
- { path: ^/admin, roles: ROLE_ADMIN }

Como puedes apreciar en “encoders” se ha definido un codificador especifico para la entidad User, utilizando el algoritmo SHA512, además codificandolo en Base64 con 10 iteracciones, tal cual se apreció en la función setSecurePassword del controlador.

En “providers” se estableció nuestra entidad User de Doctrine, especificando el campo correspondiente al username, es cual es el mismo username en nuestra entidad.

En “firewalls” se ha añadido la nueva regla (o firewall) login desde la cual se aplica el parametro security: false lo que permite acceder a la misma sin autenticarse, de lo contrario el formulario de login nunca lo podremos visializar.

Además en “secured_area” se ha eliminado anonymous, se ha establecido “form_login” donde definimos la ruta para el login del sistema, y se definió una ruta personalizada para el “log_out“, donde en “target” podemos definir el path hacia donde redirigir cuando los usuarios cierren sesión.

Para culminar sólo necesitamos crear el controlador y vista para nuestro login, por lo que debes de crear el archivo SecurityController.php en el directorio (loginBundle/src/MDW/BlogBundle/Controller):

SecurityController.php
<?php
// loginBundle/src/Test/LoginBundle/Controller/SecurityController.php
namespace Test\LoginBundle\Controller;

use Symfony\Bundle\FrameworkBundle\Controller\Controller;
use Sensio\Bundle\FrameworkExtraBundle\Configuration\Method;
use Sensio\Bundle\FrameworkExtraBundle\Configuration\Route;
use Sensio\Bundle\FrameworkExtraBundle\Configuration\Template;
use Symfony\Component\Security\Core\SecurityContext;

/**
* Security controller.
*
* @Route("/admin")
*/
class SecurityController extends Controller
{
/**
* Definimos las rutas para el login:
* @Route("/login", name="login")
* @Route("/login_check", name="login_check")
*/
public function loginAction()
{
$request = $this->getRequest();
$session = $request->getSession();
// obtiene el error de inicio de sesión si lo hay
if ($request->attributes->has(SecurityContext::AUTHENTICATION_ERROR)) {
$error = $request->attributes->get(SecurityContext::AUTHENTICATION_ERROR);
} else {
$error = $session->get(SecurityContext::AUTHENTICATION_ERROR);
}
return $this->render('TestLoginBundle:Security:login.html.twig', array(
// el último nombre de usuario ingresado por el usuario
'last_username' => $session->get(SecurityContext::LAST_USERNAME),
'error' => $error,
));
}

/**
* Definimos las rutas para el logout:
* @Route("/logout", name="logout")
*/
public function logoutAction()
{

}
}

Ahora crea el directorio “Security” dentro de (loginBundle/src/MDW/BlogBundle/Resources/views) y procede a crear el archivo de vista:

login.html.twig
{# loginbundle/src/Test/loginBundle/Resources/views/Security/login.html.twig #}
{% if error %}
<div>{{ error.message }}</div>

{% endif %}
<form action=”{{ path(‘login_check’) }}” method=”post”><label for=”username”>Username:</label>
<input id=”username” type=”text” name=”_username” value=”{{ last_username }}” />
<label for=”password”>Password:</label>
<input id=”password” type=”password” name=”_password” />

<input type=”submit” name=”login” />
</form>

Y con ello ya puedes intentar acceder a localhost/loginBundle/web/admin/user y probar el sistema de seguridad de Symfony2 (vaciar la caché en el caso de entrar al entorno de producción), si creaste previamente 2 usuarios, intenta acceder con el usuario que no tiene el rol “ROLE_ADMIN” y verás como te niega el acceso, en cambio si pruebas con un usuario con dicho rol, puedes entrar perfectamente.

Resumen Final

En esta ocasión apreciamos el complejo sistema de seguridad de Symfony2, en donde unfirewall verifica si el usuario está o no logueado, y un access control vigila que dicho usuario no pueda acceder a contenido del cual no se le ha dado acceso, también conocimos que se pueden definir providers diferentes para contener a nuestros usuarios y encoderspara personalizar el HASH de la contraseña.

Interactuamos con dicho sistema a través de un “rápido” tutorial que nos permitió resolver las inquietudes más directas en cuanto a creación de un básico RBAC (Role-based Access Control), reitero que no es la única forma de hacerlo y que existen muchos Bundles Prefabricados como el FOSUserBundle que nos facilita enormemente ésta tarea, pero si no se conoce debidamente la base puede resultar una verdadera caja negra el usar un Bundle sin el previo conocimiento de como Symfony2 implementa tales mecanismos.

Información Adicional

Cree un repositorio con la funcionalidad y con estilos de bootstrap 3, puedes revisarlo haciendo clic en el siguiente enlace:

Repositorio

Usuario Administrador: admin
Password Usuario Administrador: uprueba1
Usuario: user
Password Usuario: uprueba2

Popular

Programación: La psicología de las computadoras

Programación: La psicología de las computadoras

0
Programación: La psicología de las computadoras En un post anterior te mostramos porque todo el mundo debería de saber programar y es que todos nos...
Optimizar Sitios Wordpress

Optimizar Sitios WordPress

0
Hoy en día un atractivo aspecto de su sitio web o un contenido perfecto puede no ser suficiente para atraer a los usuarios potenciales....
Ponceelrelajado - Tips y Trucos Sublie Text

Trucos y Tips Sublime Text

0
Sublime text es uno de los editores de texto preferidos por los desarrolladores por la gran simplicidad, estética y cantidad de plugins disponibles en la comunidad, hoy dejamos un recopilatorio de Trucos y Tips Sublime Text.

SPOILER Temporada 7 de Game of Thrones + Trailer Oficial

0
¡Alerta de Spoiler! Si eres uno de esos fans impacientes de Game of Thrones y no puedes esperar hasta Julio para averiguar lo que sucede en...
Footer siempre abajo

Footer siempre abajo

8
Uno de los principales problemas a la hora de desarrollar o diseñar un sitio o sistema web es la ubicación del footer. Cuando se tiene...

Videojuegos

10 videojuegos que deberían convertirse en películas por sus historias

10 videojuegos que deberían convertirse en películas por sus historias

0
En posts anteriores ya te hemos hemos escrito de videojuegos, aunque no me considero una gamer, si no mas bien una jugadora ocasional, la verdad es que he tenido la oportunidad de probar algunos...
videojuegos de sociedades distópicas

5 videojuegos de sociedades distópicas que muestran el futuro decadente de la humanidad

0
En un post anterior te listamos lo que a nuestro parecer, son juegos que deberían de ir la gran pantalla, sin embargo, pesar de la gran lista de títulos que existen, hemos notado que...

Más Leído