<?xml version="1.0" encoding="UTF-8"?><rss version="2.0"
	xmlns:content="http://purl.org/rss/1.0/modules/content/"
	xmlns:wfw="http://wellformedweb.org/CommentAPI/"
	xmlns:dc="http://purl.org/dc/elements/1.1/"
	xmlns:atom="http://www.w3.org/2005/Atom"
	xmlns:sy="http://purl.org/rss/1.0/modules/syndication/"
	xmlns:slash="http://purl.org/rss/1.0/modules/slash/"
	>

<channel>
	<title>PHP Archives - fuga_digital</title>
	<atom:link href="https://fugadigital.com/tag/php/feed/" rel="self" type="application/rss+xml" />
	<link>https://fugadigital.com/tag/php/</link>
	<description>Naturalmente digital</description>
	<lastBuildDate>Sat, 08 Apr 2017 06:20:24 +0000</lastBuildDate>
	<language>es</language>
	<sy:updatePeriod>
	hourly	</sy:updatePeriod>
	<sy:updateFrequency>
	1	</sy:updateFrequency>
	<generator>https://wordpress.org/?v=6.8.3</generator>
	<item>
		<title>Introducción práctica a Laravel: Tutorial para sistema de usuarios</title>
		<link>https://fugadigital.com/introduccion-practica-laravel-tutorial-sistema-de-usuarios/</link>
					<comments>https://fugadigital.com/introduccion-practica-laravel-tutorial-sistema-de-usuarios/#respond</comments>
		
		<dc:creator><![CDATA[Arturo]]></dc:creator>
		<pubDate>Fri, 20 Jun 2014 01:47:37 +0000</pubDate>
				<category><![CDATA[Uncategorized]]></category>
		<category><![CDATA[Blade]]></category>
		<category><![CDATA[bundle]]></category>
		<category><![CDATA[framework]]></category>
		<category><![CDATA[Laravel]]></category>
		<category><![CDATA[paquete]]></category>
		<category><![CDATA[PHP]]></category>
		<category><![CDATA[Sentry]]></category>
		<guid isPermaLink="false">http://fugadigital.com/?p=56</guid>

					<description><![CDATA[<p>De algo de tiempo para acá Laravel ha empezado a hacerse realmente popular dentro del mundo de los frameworks en PHP. Esto se debe a la accesibilidad para usarlo ya que es funcional prácticamente después de instalarlo permitiendo construir de manera rápida una aplicación estándar. Además, al utilizar componentes que han probado su robustez (Oh &#8230; </p>
<p class="link-more"><a href="https://fugadigital.com/introduccion-practica-laravel-tutorial-sistema-de-usuarios/" class="more-link">Continuar leyendo<span class="screen-reader-text"> "Introducción práctica a Laravel: Tutorial para sistema de usuarios"</span></a></p>
<p>La entrada <a href="https://fugadigital.com/introduccion-practica-laravel-tutorial-sistema-de-usuarios/">Introducción práctica a Laravel: Tutorial para sistema de usuarios</a> se publicó primero en <a href="https://fugadigital.com">fuga_digital</a>.</p>
]]></description>
										<content:encoded><![CDATA[<p><a href="https://fugadigital.com/wp-content/uploads/2014/06/laravel_arrived.png"><img fetchpriority="high" decoding="async" src="https://fugadigital.com/wp-content/uploads/2014/06/laravel_arrived.png" alt="Laravel: You have arrived." width="300" height="506" class="aligncenter size-full wp-image-57" srcset="https://fugadigital.com/wp-content/uploads/2014/06/laravel_arrived.png 300w, https://fugadigital.com/wp-content/uploads/2014/06/laravel_arrived-177x300.png 177w" sizes="(max-width: 300px) 100vw, 300px" /></a></p>
<p>De algo de tiempo para acá Laravel ha empezado a hacerse realmente popular dentro del mundo de los frameworks en PHP. Esto se debe a la accesibilidad para usarlo ya que es <a href="https://laravel.com/docs/installation#configuration">funcional prácticamente después de instalarlo</a> permitiendo construir de manera rápida una aplicación estándar. Además, al utilizar componentes que han probado su robustez (Oh gran Symfony) en lugar de reinventar la rueda, Laravel ofrece un entorno de desarrollo sencillo montado sobre bases sólidas.</p>
<p>Normalmente los tutoriales para aprender a usar un framework se enfocan en la creación del obligado blog. Evidentemente necesitaremos un mecanismo de acceso para algo así de modo que esta guía contiene las instrucciones para crear el sistema de logueo y registro que podremos usar más adelante al crear nuestro blog, CMS, o cualquier otra aplicación.</p>
<p><span id="more-56"></span></p>
<p>Si has empezado a leer las introducciones en la documentación oficial verás que desde el inicio te envían a crear rutas, generar alguna vista y ver resultados. Más rápido no podría ser, sin embargo si ya has usado otros frameworks todo esto ya empezará a sonar algo raro. No temas, Laravel 4 permite nativamente la creación de paquetes (llamados anteriormente bundles) para mantener nuestro código con el acoplamiento y cohesión adecuados.</p>
<p>Para que nuestro primer paquete sea realmente útil en un futuro lo implementaremos usando una gran librería para la tarea: <a href="https://cartalyst.com/manual/sentry">Cartalyst Sentry</a>. Sentry implementa de muy buena manera un sistema de usuarios. Aún cuando en este tutorial no haremos nada avanzado, nos facilitará implementar mejoras conforme nuestras necesidades lo requieran (grupos, permisos, bloqueos, etc.).</p>
<p>Nota: Se da por hecho que tienes un entorno listo: servidor local, PHP>=5.4, RDBMS, Composer y Laravel accesible desde tu navegador (You have arrived.). Puedes seguir la guía de instalación para trabajar con Vagrant y Homestead, que es una máquina virtual con todo lo necesario para Laravel.</p>
<p>Antes de comenzar necesitamos configurar el creador de paquetes editando <em>&#8230;/app/config/workbench.php</em> agregando nombre y dirección electrónica, y el acceso a la base de datos que usará el framework, editando <em>&#8230;/app/config/database.php</em>.</p>
<p>Para trabajar en Homestead lo siguiente puede ser útil:</p>
<p></p><pre class="urvanov-syntax-highlighter-plain-tag">'mysql' =&gt; array(
			'driver'    =&gt; 'mysql',
			'host'      =&gt; '127.0.0.1',
			'port'      =&gt; 33060,
			'database'  =&gt; 'homestead',
			'username'  =&gt; 'homestead',
			'password'  =&gt; 'secret',
			'charset'   =&gt; 'utf8',
			'collation' =&gt; 'utf8_unicode_ci',
			'prefix'    =&gt; '',
		),

	mysql -h127.0.0.1 homestead -P33060 -uhomestead -p</pre><p></p>
<p>Ya que tenemos configurado el acceso a nuestra base, la prepararemos para migrar (crear tablas e importar datos), esto lo haremos ejectuando:</p>
<p></p><pre class="urvanov-syntax-highlighter-plain-tag">php artisan migrate:install</pre><p></p>
<p>que nos creará la tabla de control de migraciones.</p>
<p>Comenzaremos creando el esqueleto de nuestro paquete de autenticación:</p>
<p></p><pre class="urvanov-syntax-highlighter-plain-tag">php artisan workbench allegro/auth</pre><p></p>
<p>Ejecutando la línea anterior desde la terminal en la ruta base donde está Laravel, <code>artisan</code> creará una <a href="http://laravel.com/docs/packages#creating-a-package">estructura estandar</a> para un paquete. Si revisamos el directorio raiz de Laravel veremos la siguiente estructura de directorios creada:</p>
<p></p><pre class="urvanov-syntax-highlighter-plain-tag">laravel_root
    ...
    workbench/
        allegro/
            auth/
                ...</pre><p></p>
<p>Dentro de <em>workbench</em> se encontrarán todos los paquetes que hagamos. <em>allegro</em> es el vendor que puedes/debes cambiarlo por tu nickname, usuario de Git, compañía, palabra impactante, etc; <em>auth</em> es el paquete en específico que estamos creando.</p>
<p>Si revisas el contenido en <em>&#8230;/workbench/allegro/auth/</em> verás que la estructura del paquete es realmente sencilla, más adelante iremos agregando algunas cosas.</p>
<p>Lo primero que haremos será configurar el composer.json de nuestro paquete:</p>
<p></p><pre class="urvanov-syntax-highlighter-plain-tag">{
    "name": "allegro/auth",
    "description": "A simple authentication implementation using Sentry",
    "license": "MIT",
    "authors": [
        {
            "name": "John Doe",
            "email": "john.doe@domain.com"
        }
    ],
    "require": {
        "php": ">=5.4.0",
        "illuminate/support": "4.2.*",
        "cartalyst/sentry": "2.1.*"
    },
    "autoload": {
        "psr-0": {
            "Allegro\\Auth": "src/"
        }
    },
    "minimum-stability": "stable"
}</pre><p></p>
<p>Agregamos una licencia, una descripción e indicamos que vamos a utilizar el paquete Sentry. Luego de guardar los cambios actualizaremos las dependencias ejecutando en el directorio raiz de nuestro paquete (&#8230;/workbench/allegro/auth/)</p>
<p></p><pre class="urvanov-syntax-highlighter-plain-tag">composer update</pre><p></p>
<p>Una vez que se haya descargado Sentry publicamos su configuración en Laravel y creamos la base de  datos.</p>
<p>Para la parte de configuración abriremos el archivo <em>&#8230;/app/config/app.php</em> y agregaremos el nombre de la clase de <em>Sentry</em> al arreglo de proveedores y su alias al arreglo de aliases:</p>
<p></p><pre class="urvanov-syntax-highlighter-plain-tag">'providers' =&gt; array(
		...
		'Cartalyst\Sentry\SentryServiceProvider',
	...
	'aliases' =&gt; array(
		...
		'Sentry'          =&gt; 'Cartalyst\Sentry\Facades\Laravel\Sentry',</pre><p></p>
<p>después debemos exponer el archivo de configuración. Como estamos agregando la dependencia a un paquete y no directamente al framework debemos definir explícitamente su ubicación.</p>
<p></p><pre class="urvanov-syntax-highlighter-plain-tag">php artisan config:publish --path=&quot;workbench/allegro/auth/vendor/cartalyst/sentry/src/config&quot; cartalyst/sentry</pre><p></p>
<p>Ya sólo queda generar las tablas en la base:</p>
<p></p><pre class="urvanov-syntax-highlighter-plain-tag">php artisan migrate --path=&quot;workbench/allegro/auth/vendor/cartalyst/sentry/src/migrations/&quot; --force</pre><p></p>
<p>Con lo anterior tenemos la dependencia lista para ser usada, de lujo eh. Vayamos ahora a configurar nuestro paquete.</p>
<p>Primero vamos a editar la clase <em>&#8230;/workbench/allegro/auth/src/Allegro/Auth/AuthServiceProvider.php</em> que es la clase de arranque agregando el método <code>boot()</code>:</p>
<p></p><pre class="urvanov-syntax-highlighter-plain-tag">...
    protected $defer = false;

    /**
     * Bootstrap the application events.
     *
     * @return void
     */
    public function boot()
    {
        $this->package('allegro/auth');
        include __DIR__.'/../../routes.php';
    }
    ...
}</pre><p></p>
<p>La primera línea en <code>boot()</code> establece como identificará el framework nuestro paquete, la segunda carga el archivo (inexistente hasta este momento) que contendrá la definición de nuestras rutas.</p>
<p>El paquete ya puede ser cargado por Laravel y sólo falta registrarlo en él para activarlo, esto se hace abriendo el archivo <em>&#8230;/app/config/app.php</em> y agregando al arreglo de proveedores como lo hicimos con Sentry:</p>
<p></p><pre class="urvanov-syntax-highlighter-plain-tag">'providers' =&gt; array(
		...
		'Cartalyst\Sentry\SentryServiceProvider',
		'Allegro\Auth\AuthServiceProvider',</pre><p></p>
<p>Ahora vamos a crear el archivo de rutas <em>routes.php</em> dentro de la raiz de nuestro paquete (<em>&#8230;/workbench/allegro/auth</em>) y definir las rutas de acceso al paquete. Esto nos dará una visión general de que tiene que existir.</p>
<p></p><pre class="urvanov-syntax-highlighter-plain-tag"><?php

$sessionController = 'Allegro\Auth\Controller\SessionController';
$userController    = 'Allegro\Auth\Controller\UserController';

/**
* Checks if the user is logged
*/
Route::filter('sentry.auth', function()
{
    if (!Sentry::check()) {
        Session::put('loginRedirect', Request::url());
        return Redirect::route('user_login_show');
    }
});

// // // // // // // // // // // // // // // // // // // // // // Session routes

Route::get('login', [
    'as'         => 'user_login_show',
    'uses'       => "$sessionController@create"
]);

Route::post('login', [
    'as'         => 'user_login_action',
    'uses'       => "$sessionController@store"
]);

Route::get('logout', [
    'as'         => 'user_logout',
    'uses'       => "$sessionController@destroy"
]);

// // // // // // // // // // // // // // // // // // // // // // // User routes

Route::get('register', [
    'as'         => 'user_register_show',
    'uses'       => "$userController@create"
]);

Route::post('register', [
    'as'         => 'user_register_action',
    'uses'       => "$userController@store"
]);

Route::get('user/edit', [
    'before'     => 'sentry.auth',
    'as'         => 'user_edit_show',
    'uses'       => "$userController@edit"
]);

Route::post('user/edit', [
    'before'     => 'sentry.auth',
    'as'         => 'user_edit_action',
    'uses'       => "$userController@update"
]);

Route::get('user/delete', [
    'before'     => 'sentry.auth',
    'as'         => 'user_delete',
    'uses'       => "$userController@destroy"
]);</pre><p></p>
<p>Si has leído un poco de <a href="http://laravel.com/docs/routing">enrutamiento en Laravel</a> sabrás que hay diferentes <a href="http://laravel.com/docs/controllers">estrategias disponibles</a> para acceder a los controladores, sin embargo las estrategias automáticas (<a href="http://philsturgeon.co.uk/blog/2013/07/beware-the-route-to-evil">REST</a>, <a href="http://forumsarchive.laravel.io/viewtopic.php?id=6769">recursos</a>) limitan el manejo de las rutas al usar enrutamientos inversos y hacen menos evidentes los accesos a nuestro sistema, así que usaremos el método básico para definir explícitamente todos nuestros accesos y tener un control total, de cualquier manera usaremos los nombres estándar para <a href="laravel.com/docs/controllers#resource-controllers">las acciones</a>.</p>
<p>Personalmente uso namespaces siempre que puedo. Si no los usas hay <a href="http://daylerees.com/php-namespaces-explained">buenas razones</a> para empezar a hacerlo, aunque esto significa que necesitaremos agregar <em>\</em> como prefijo a cada fachada usada para evitar que sea interpretada como parte de nuestro paquete (ej. <code>\BaseController::</code> en lugar de <code>BaseController::</code>, <code>\Input::</code> en lugar de <code>Input::</code>, etc). Siendo consistentes también con la propuesta de uso de namespaces que en <em>AuthServiceProvider.php</em> usaremos controladores con namespaces.</p>
<p>Podríamos también realizar el trabajo de los controladores dentro de las propias funciones de enrutamiento agregando clousures, sin embargo por razones de separación de concernimientos usaremos <em>routes.php</em> para definir los accessos y <em>SessionController.php / UserController.php</em> para la acciones correspondientes al acceso y gestión.</p>
<p>Ahora vamos a crear los controladores que procesarán las solicitudes que acabamos de definir.</p>
<p></p><pre class="urvanov-syntax-highlighter-plain-tag">php artisan controller:make --bench allegro/auth UserController --only=create,store,edit,update,destroy</pre><p></p>
<p>creará el controlador de usuarios y</p>
<p></p><pre class="urvanov-syntax-highlighter-plain-tag">php artisan controller:make --bench allegro/auth SessionController --only=create,store,destroy</pre><p></p>
<p>creará el controlador de sesiones.</p>
<p>Como no usaremos todas las acciones REST indicamos de manera explícita el subconjunto de métodos que nos interesa que sean maquetados. Nuestros nuevos controladores han sido creados y depositados dentro del (nuevo) subdirectorio <em>controllers</em>.</p>
<p>La clase <em>UserController</em> necesita el siguiente código:</p>
<p></p><pre class="urvanov-syntax-highlighter-plain-tag">&lt;?php namespace Allegro\Auth\Controller;

class UserController extends \BaseController {

    /**
     * Show the form for creating a new resource.
     *
     * @return Response
     */
    public function create()
    {
        return \View::make('auth::user.register');
    }


    /**
     * Store a newly created resource in storage.
     *
     * @return Response
     */
    public function store()
    {
        try {
            $p1 = \Input::get('password');
            $p2 = \Input::get('password_r');
            if ('' === $p1 || '' === $p2 || $p1 !== $p2) {
                throw new \Exception('Passwords don\'t match');
            }

            \Sentry::createUser([
                'email'         =&gt; \Input::get('email'),
                'password'      =&gt; \Input::get('password'),
                'activated'     =&gt; true,
                'created_at'    =&gt; date('Y-m-d H:i:s'),
                'updated_at'    =&gt; date('Y-m-d H:i:s'),
            ]);
        }
        /* @var $ex \Exception */
        catch (\Exception $ex) {
            return \Redirect::route('user_register_show')
                -&gt;withInput()
                -&gt;with('error', $ex-&gt;getMessage());
        }

        return \Redirect::route('user_login_show')
                -&gt;with('notice', 'User has been created');
    }


    /**
     * Show the form for editing the specified resource.
     *
     * @return Response
     */
    public function edit()
    {
        return \View::make('auth::user.edit');
    }


    /**
     * Update the specified resource in storage.
     *
     * @return Response
     */
    public function update()
    {
        try {
            /* @var $user \Cartalyst\Sentry\Users\Eloquent\User */
            $user = \Sentry::getUser();

            $user-&gt;first_name = \Input::get('first_name');
            $user-&gt;last_name = \Input::get('last_name');

            if ('' !== \Input::get('password') . \Input::get('password_r')) {
                $p1 = \Input::get('password');
                $p2 = \Input::get('password_r');
                if ($p1 !== $p2) {
                    throw new \Exception('Passwords don\'t match');
                }
                $user-&gt;password = $p1;
                $logout = true;
            }
            if (!$user-&gt;update()) {
                throw new \Exception('Unable to update values');
            }
        }
        /* @var $ex \Exception */
        catch (\Exception $ex) {
            return \Redirect::route('user_edit_show')
                -&gt;withInput()
                -&gt;with('error', $ex-&gt;getMessage());
        }

        if (isset($logout)) {
            \Sentry::logout();
            return \Redirect::route('user_login_show')
                    -&gt;with('notice', 'User has been updated, please log in again');
        }

        return \Redirect::back()
                -&gt;with('notice', 'User has been updated');
    }

    /**
     * Remove the specified resource from storage.
     *
     * @return Response
     */
    public function destroy()
    {
        /* @var $user \Cartalyst\Sentry\Users\Eloquent\User */
        $user = \Sentry::getUser();
        $user-&gt;delete();
        \Sentry::logout();
        return \Redirect::route('user_login_show')
                -&gt;with('notice', 'User has been deleted. Wanna check?');
    }
}</pre><p></p>
<p>y la clase <em>SessionController</em>:</p>
<p></p><pre class="urvanov-syntax-highlighter-plain-tag">&lt;?php namespace Allegro\Auth\Controller;

class SessionController extends \BaseController {

    /**
     * Show the login page
     * @return View
     */
    public function create()
    {
        return \View::make('auth::session.login');
    }

    /**
    * Store a newly created resource in storage.
    *
    * @return Response
    */
    public function store()
    {
        $credentials = [
            'email'        =&gt; \Input::get('email'),
            'password'     =&gt; \Input::get('password'),
        ];
        $remember = \Input::get('remember') === '1';

        try {
            \Sentry::authenticate($credentials, $remember);
        }
        catch (\Exception $ex) {
            $msg = $ex instanceof \Cartalyst\Sentry\Throttling\UserSuspendedException
                    ? $ex-&gt;getMessage()
                    : 'Invalid credentials';

            return \Redirect::back()
                    -&gt;withInput()
                    -&gt;with('error', $msg);
        }

        // Extract requested page from session
        $redirect = \Session::get('loginRedirect', \URL::route('user_edit_show'));
        \Session::forget('loginRedirect');

        return \Redirect::to($redirect);
    }

    /**
    * Remove the specified resource from storage.
    *
    * @return Response
    */
    public function destroy()
    {
        \Sentry::logout();
        \Session::flash('notice', 'Session closed');
        return \Redirect::back();
    }

}</pre><p></p>
<p>Algo a notar es que los parámetros que recibían algunos métodos se han removido pues no son útiles en nuestro caso.</p>
<p>Laravel usa Composer para la carga automática de clases, así que para que nuestros controladores sean usables debemos hacer dos cosas: agregar al <em>composer.json</em> del paquete la sección <code>classmap</code> con el directorio <em>controllers</em> y luego ejecutar desde la raiz de nuestro paquete el comando de actualización de carga automática de clase.</p>
<p></p><pre class="urvanov-syntax-highlighter-plain-tag">&quot;autoload&quot;: {
        &quot;classmap&quot;: [
            &quot;src/controllers&quot;
        ],
        &quot;psr-0&quot;: {
            &quot;Allegro\\Auth&quot;: &quot;src/&quot;
        }
    },</pre><p></p>
<p></p><pre class="urvanov-syntax-highlighter-plain-tag">composer dump-autoload</pre><p></p>
<p><a href="http://laravel.com/docs/packages#creating-a-package">Nota</a>: Si el proveedor de servicios no se puede encontrar, ejecuta el comando <code>php artisan dump-autoload</code> desde el directorio raiz de tu aplicación.</p>
<p>Con lo que hemos hecho hasta aquí ya tenemos el back-end prácticamente listo y sólo necesitamos crear algunas vistas para nuestro front-end.</p>
<p>El motor de plantillas de Laravel es bastante sencillo pero lo suficientemente potente para crear templates reutilizables y extensibles permitiéndonos reciclar código. Revisando los controladores vemos que tenemos algunas líneas como <code>return \View::make('auth::user.register');</code> que podemos interpretar como «Hey amigo, carga la vista auth/views/user/register[.blade].php».</p>
<p>La estructura de plantillas que corresponde a los llamados de los controladores (y que debemos crear) es la siguiente:</p>
<p></p><pre class="urvanov-syntax-highlighter-plain-tag">auth/
    public/
        style.css
    src/
        views/
            base.blade.php
            user_box.blade.php
            session/
                login.blade.php
            user/
                edit.blade.php
                register.blade.php
    ...</pre><p></p>
<p><em>base.blade.php</em> contendrá la plantilla general para todas las vistas, excepto <em>user_box.blade.php</em> (que será importada por otros paquetes para mostrar una cajita que despliegue al usuario activo y ligas de inicio y cierre de sesión). Los demás archivos no necesitan más explicación.</p>
<p>base.blade.php:</p>
<p></p><pre class="urvanov-syntax-highlighter-plain-tag"><!doctype html>
<html lang="en">
    <head>
        <meta charset="utf-8">
        <title>Userland</title>
        <link rel="stylesheet" type="text/css" href="{{ URL::asset('packages/allegro/auth/style.css') }}" />
    </head>
    <body>
        @if(Session::has('error'))
            <p class="alert {{ Session::get('alert-class', 'alert-error') }}">{{ Session::get('error') }}</p>
        @endif
        @if(Session::has('notice'))
            <p class="alert {{ Session::get('alert-class', 'alert-info') }}">{{ Session::get('notice') }}</p>
        @endif

        <div class="container">
            <div id="login">
                @yield('main')
            </div>
        </div>
    </body>
</html></pre><p></p>
<p>user_box.blade.php:</p>
<p></p><pre class="urvanov-syntax-highlighter-plain-tag"><div class="auth-user-box">
    @if (!Sentry::check())
    <a class="brand" href="{{ URL::route('user_login_show') }}">Sign in</a>
    @else
    <a href="{{ URL::route('user_edit_show') }}">{{ Sentry::getUSer()->getLogin() }}</a> / <a href="{{ URL::route('user_logout') }}">Sign out</a>
    @endif
</div></pre><p></p>
<p>register.blade.php:</p>
<p></p><pre class="urvanov-syntax-highlighter-plain-tag">@extends('auth::base')

@section('main')
{{ Form::open() }}
    <div class="title"><div>
        Register
    </div></div>

    <div class="control-group">
        <div class="label">
            {{ Form::label('email', 'Email') }}
        </div>
        <div class="field">
            {{ Form::text('email') }}
        </div>
    </div>

    <div class="control-group">
        <div class="label">
            {{ Form::label('password', 'Password') }}
        </div>
        <div class="field">
            {{ Form::password('password') }}
        </div>
    </div>

    <div class="control-group">
        <div class="label">
            {{ Form::label('password_r', 'Repeat pwd') }}
        </div>
        <div class="field">
            {{ Form::password('password_r') }}
        </div>
    </div>

    <div class="form-actions">
        <a href="{{ URL::route('user_login_show') }}" class="btn">Sign in</a>
        &nbsp;
        {{ Form::submit('Register', array('class' => 'btn')) }}
    </div>
{{ Form::close() }}
@stop</pre><p></p>
<p>edit.blade.php:</p>
<p></p><pre class="urvanov-syntax-highlighter-plain-tag">@extends('auth::base')

@section('main')
<?php
    function parseDate($date)
    {
        $dateTime = DateTime::createFromFormat('Y-m-d H:i:s', $date);
        return $dateTime->format('D d, M Y, H:i');
    }
?>
{{ Form::open() }}
    <div class="title"><div>
        Edit
    </div></div>

    <div class="control-group">
        <div class="label">
            {{ Form::label('login', 'Login') }}
        </div>
        <div class="field"><div>
            {{ Sentry::getUser()->getLogin() }}
        </div></div>
    </div>

    <div class="control-group">
        <div class="label">
            {{ Form::label('firs_name', 'first name') }}
        </div>
        <div class="field">
            {{ Form::text('first_name', Sentry::getUser()['attributes']['first_name']) }}
        </div>
    </div>

    <div class="control-group">
        <div class="label">
            {{ Form::label('last_name', 'last name') }}
        </div>
        <div class="field">
            {{ Form::text('last_name', Sentry::getUser()['attributes']['last_name']) }}
        </div>
    </div>

    <div class="control-group">
        <div class="label">
            {{ Form::label('password', 'Password') }}
        </div>
        <div class="field">
            {{ Form::password('password') }}
        </div>
    </div>

    <div class="control-group">
        <div class="label">
            {{ Form::label('password_r', 'Repeat pwd') }}
        </div>
        <div class="field">
            {{ Form::password('password_r') }}
        </div>
    </div>

    <div class="form-actions">
        <a href="{{ URL::previous() }}" class="btn">Cancel</a>
        &nbsp;
        {{ Form::submit('Update', array('class' => 'btn')) }}
        <p>Registered at: {{ parseDate(Sentry::getUser()['attributes']['created_at']) }}</p>
        <p>Last login: {{ parseDate(Sentry::getUser()['attributes']['last_login']) }}</p>
        <p><a href="{{ URL::route('user_logout') }}" class="btn">Sign out</a></p>
        <br>
        <p><a href="{{ URL::route('user_delete') }}" class="btn">Delete account</a></p>
    </div>
{{ Form::close() }}
@stop</pre><p></p>
<p>login.blade.php:</p>
<p></p><pre class="urvanov-syntax-highlighter-plain-tag">@extends('auth::base')

@section('main')
    @if (!Sentry::check())
        {{ Form::open() }}
        <div class="title"><div>
            Sign in
        </div></div>
        <div class="control-group">
            <div class="label">
                {{ Form::label('email', 'Email') }}
            </div>
            <div class="field">
                {{ Form::text('email') }}
            </div>
        </div>

        <div class="control-group">
            <div class="label">
                {{ Form::label('password', 'Password') }}
            </div>
            <div class="field">
                {{ Form::password('password') }}
            </div>
        </div>

        <div class="control-group">
            <div class="label">
            {{ Form::label('remember', 'remember') }}
            </div>
            <div class="field"><div>
                {{ Form::checkbox('remember') }}
            </div></div>
        </div>

        <div class="form-actions">
            <a href="{{ URL::route('user_register_show') }}" class="btn">Register</a>
            &nbsp;
            <input type="submit" class="btn" name="login" value="Sign in">
        </div>
        {{ Form::close() }}
    @else
        <div class="center-text">
            You already signed in as
            <br><br>
            {{ Sentry::getUSer()->getLogin() }}
            <br><br>
            <a href="{{ URL::route('user_edit_show') }}" class="btn">Edit</a>
            &nbsp;
            <a href="{{ URL::route('user_logout') }}" class="btn">Sign out</a>
        </div>
    @endif
@stop</pre><p></p>
<p>style.css:</p>
<p></p><pre class="urvanov-syntax-highlighter-plain-tag">html {
    height: 100%;
}
body {
    background-color: #eee;
    margin: 0;
    padding: 0;
    font-family: sans-serif;
    height: 100%;
}
p.alert {
    text-align: center;
    font-weight: bold;
    padding: 10pt;
    margin: 0;
    background-color: #888;
    color: #fff;
}
p.alert-error {
    background-color: #ff3333;
}
.center-text {
    text-align: center;
}
.container {
}
#login-block {
    background-color: #f9f9f9;
    margin: 0 auto;
    padding: 10pt;
    max-width: 375px;
    border-radius: 0 0 10px 10px;
    border: 1px solid #ddd;
    border-top: 0;
}
.title {
    background: #85cf85;
    margin: -10pt -10pt 20pt -10pt;
    font-size: 200%;
    text-align: center;
}
.title > * {
    padding: 5pt;
}
.control-group {
    clear: both;
    margin: 10pt 0;
    overflow: hidden;
}
.label {
    float: left;
    vertical-align: middle;
}
.field {
    float:right;
    width: 65%;
    overflow: hidden;
    text-align: right;
}
.field > * {
    width: 97%;
    font-size: 100%;
    text-align: left;
}
.form-actions {
    text-align: right;
}
.btn {
    border-radius: 4pt;
    background: #ddd;
    color: #444;
    font-weight: bold;
    font-size: 100%;
    padding: 2px 8px;
}</pre><p></p>
<p>Ahora solo resta hacer público nuestro archivo de estilos. Para eso nos vamos a nuestra consola y ejecutamos en la raiz de nuestro proyecto:</p>
<p></p><pre class="urvanov-syntax-highlighter-plain-tag">php artisan asset:publish --bench allegro/auth</pre><p></p>
<p>Ahora si, tenemos todo listo.</p>
<p>Como has visto la creación de paquetes en Laravel no tiene complicaciones. Al ser éste un tutorial práctico introductorio, he tratado de mostrar una estructura adecuada y a la vez sencilla, que sirva de ejemplo y que además sea útil en un futuro y no sólo como un ejercicio, sin embargo hay algunas modificaciones que podrían venir bien, como mover el filtro que se encuentra en el controlador a un archivo de filtros y cargarlo junto con <em>routes.php</em> en la clase proveedora, o seccionar de mejor manera las vistas, etc.</p>
<p>El paquete completo puedes <a href="https://github.com/fugadigital/SimpleLaravel4AuthPackage">descargarlo desde Github</a></p>
<p>Feliz Laraveleo.</p>
<p>La entrada <a href="https://fugadigital.com/introduccion-practica-laravel-tutorial-sistema-de-usuarios/">Introducción práctica a Laravel: Tutorial para sistema de usuarios</a> se publicó primero en <a href="https://fugadigital.com">fuga_digital</a>.</p>
]]></content:encoded>
					
					<wfw:commentRss>https://fugadigital.com/introduccion-practica-laravel-tutorial-sistema-de-usuarios/feed/</wfw:commentRss>
			<slash:comments>0</slash:comments>
		
		
			</item>
		<item>
		<title>Agregar campos informativos en formularios de Sonata Admin</title>
		<link>https://fugadigital.com/agregar-campos-informativos-en-formularios-de-sonata-admin/</link>
					<comments>https://fugadigital.com/agregar-campos-informativos-en-formularios-de-sonata-admin/#respond</comments>
		
		<dc:creator><![CDATA[Arturo]]></dc:creator>
		<pubDate>Mon, 12 May 2014 14:45:20 +0000</pubDate>
				<category><![CDATA[Uncategorized]]></category>
		<category><![CDATA[PHP]]></category>
		<category><![CDATA[Sonata]]></category>
		<category><![CDATA[SonataAdminBundle]]></category>
		<category><![CDATA[Symfony2]]></category>
		<category><![CDATA[Twig]]></category>
		<guid isPermaLink="false">http://fugadigital.com/?p=34</guid>

					<description><![CDATA[<p>No es sorpresa la gran popularidad que tiene el bundle Sonata Admin, siendo como dicen ellos «El generador de admistradores faltante en Symfony2» (énfasis mío). Al generar los adminstradores de nuestras entidades es común necesitar desplegar junto con los campos a editar, campos informativos del objeto (fecha de creación, último acceso, autor, etc.). Se puede &#8230; </p>
<p class="link-more"><a href="https://fugadigital.com/agregar-campos-informativos-en-formularios-de-sonata-admin/" class="more-link">Continuar leyendo<span class="screen-reader-text"> "Agregar campos informativos en formularios de Sonata Admin"</span></a></p>
<p>La entrada <a href="https://fugadigital.com/agregar-campos-informativos-en-formularios-de-sonata-admin/">Agregar campos informativos en formularios de Sonata Admin</a> se publicó primero en <a href="https://fugadigital.com">fuga_digital</a>.</p>
]]></description>
										<content:encoded><![CDATA[<p>No es sorpresa la gran popularidad que tiene el bundle <em>Sonata Admin</em>, siendo como dicen ellos «El generador de admistradores faltante en Symfony2» (énfasis mío).</p>
<p>Al generar los adminstradores de nuestras entidades es común necesitar desplegar junto con los campos a editar, campos informativos del objeto (fecha de creación, último acceso, autor, etc.). Se puede fácilmente agregar los campos y hacerlos no editables y/o desactivarlos, pero visualmente el resultado no es realmente bueno.</p>
<p></p><pre class="urvanov-syntax-highlighter-plain-tag">// simple but ugly
->add('created', null, array(
    'read_only' => true,
    'disabled' => true,
    'widget' => 'single_text',
    'format' => 'EEE, MMM FF yyyy HH:mm'
))</pre><p></p>
<p>Podemos utilizar CSS para mejorar considerablemente la imagen, sin embargo esto no funciona si queremos desplegar estructuras más complejas.</p>
<p><span id="more-34"></span></p>
<p>Tratándose de <em>Symfony2</em> y <em>SonataAdmin</em> es posible adaptar los administradores generados para nuestro bundle en la medida que necesitemos sin impactar en otras áreas. En esta ocasión lo aprovecharemos para crear etiquetas informativas insertables como campos dentro de los formularios.</p>
<p><a href="https://fugadigital.com/wp-content/uploads/2014/05/labeled_cat.jpg"><img decoding="async" src="https://fugadigital.com/wp-content/uploads/2014/05/labeled_cat-300x224.jpg" alt="label in cat" width="300" height="224" class="aligncenter size-medium wp-image-35" srcset="https://fugadigital.com/wp-content/uploads/2014/05/labeled_cat-300x224.jpg 300w, https://fugadigital.com/wp-content/uploads/2014/05/labeled_cat.jpg 500w" sizes="(max-width: 300px) 100vw, 300px" /></a></p>
<p>Probado con:<br />
Symfony 2.4<br />
Sonata Admin Bundle 2.2</p>
<p>Dependiendo de los objetivos, lo que hacer es:<br />
&#8211; <a href="#type-class">Crear la clase para el nuevo tipo de despliegue</a><br />
&#8211; <a href="#transformer">Crear un transformador para hacer desplegable el objeto</a><br />
&#8211; <a href="#admin-config">Configurar las clases de administración con el objeto y el nuevo tipo</a><br />
&#8211; <a href="#twig">Crear los bloques en Twig con el marcado para el despliegue</a></p>
<p>Lo siguiente mostrará como desplegar un objecto DateTime, pero la guía es fácilmente adaptable para otros tipos, como números, hipervínculos, entidades, colecciones o cualquier cosa.</p>
<h3 id="type-class">Crear la clase para el nuevo tipo de despliegue</h3>
<p>Primero necesitamos crear una clase para el nuevo tipo</p>
<p></p><pre class="urvanov-syntax-highlighter-plain-tag">/* Acme/DemoBundle/Form/StaticTextType.php */
<?php

namespace Acme\DemoBundle\Form;

use Symfony\Component\Form\AbstractType;
use Symfony\Component\OptionsResolver\OptionsResolverInterface;

class StaticTextType extends AbstractType
{
    public function getParent()
    {
        return 'text';
    }

    public function getName()
    {
        return 'acme_static_text';
    }
}</pre><p></p>
<p>Para usar el nuevo tipo podemos incluir la clase e instanciarla en el administrador</p><pre class="urvanov-syntax-highlighter-plain-tag">/* Acme/DemoBundle/Admin/EntityAdmin.php */
<?php
namespace Acme\DemoBundle\Admin;

...
use Acme\DemoBundle\Form\StaticTextType;
... 

...
    ->add('id', new StaticTextType())
...</pre><p></p>
<p>o crear crear un servicio y registrarlo como nuevo tipo de formulario. Esto también permite hacer uso de él a través de su etiqueta.</p>
<p></p><pre class="urvanov-syntax-highlighter-plain-tag"># Acme/DemoBundle/Resources/config/services.yml
services:
    ...
    acme_demo.form.type.info:
        class: Acme\DemoBundle\Form\StaticTextType
        tags:
            - { name: form.type, alias: acme_static_text }</pre><p></p><pre class="urvanov-syntax-highlighter-plain-tag">/* Acme/DemoBundle/Admin/EntityAdmin.php */
    ...
    ->add('id', 'acme_static_text')</pre><p></p>
<p>Para desplegar información básica como números o cadenas no necesitamos crear el transformador pues nuestro tipo hereda las características del tipo &#8216;text&#8217;.</p>
<h3 id="transformer">Crear un transformador para hacer desplegable el objeto</h3>
<p>Para desplegar la fecha y hora necesitamos un transformador que genere la cadena que represente a la instancia <code>DateTime</code>.</p>
<p></p><pre class="urvanov-syntax-highlighter-plain-tag">/* Acme/DemoBundle/Form/DataTransformer/DateTimeToStringTransformer.php */
<?php
namespace Acme\DemoBundle\Form\DataTransformer;

use Symfony\Component\Form\DataTransformerInterface;
use Symfony\Component\Form\Exception\TransformationFailedException;

class DateTimeToStringTransformer implements DataTransformerInterface
{
    /**
     * The format for our DateTime object
     * @var string
     */
    private $format;
    
    public function __construct($format)
    {
        $this->format = $format;
    }
    
    /**
     * Transforms a datetime object to a string.
     *
     * @param  DateTime|null $date
     * @return string
     */
    public function transform($dateTime)
    {
        return null === $dateTime
	    ? return ''
	    : $dateTime->format($this->format);
    }

    /**
     * Transforms a string date to a DateTime object.
     *
     * @param  string $dateTime
     *
     * @return DateTime|null
     *
     * @throws TransformationFailedException if cannot transform to DateTime.
     */
    public function reverseTransform($dateTime)
    {
        if (empty($dateTime)) {
            return null;
        }

        $dt = DateTime::createFromFormat($this->format, $dateTime);
        if (null === $dt) {
            throw new TransformationFailedException(sprintf(
                "Unable to transform '%s' to a DateTime object",
                $dateTime
            ));
        }

        return $dt;
    }
}</pre><p></p>
<h3 id="admin-config">Configurar las clases de administración con el objeto y el nuevo tipo</h3>
<p>Para mostrar los datos de los campos de manera que no sean procesados por el formulario, podemos agregar el campo configurándolo como no mapeado y el dato manualmente agregado o configurarlo como desactivado.</p>
<p></p><pre class="urvanov-syntax-highlighter-plain-tag"><?php
    // correct display, error on save
    ->add('id', 'acme_static_text', array(
        'required' => false
    ))

    // correct display, ignored on save
    ->add('id', 'acme_static_text', array(
        'required' => false,
        'disabled' => true,
    ))

    $entity = $this->getSubject();
    ->add('id', 'acme_static_text', array(
        'required' => false,
        'mapped' => false,
        'data' => $entity->getId(),
    ))</pre><p></p>
<p>Tip: Como el método <code>$this->getSubject()</code> de la clase de administración regresa la entidad que se está gestionando, tambíen se puede aprovechar para desplegar diferentes campos dependiendo de si se está creando una nueva instancia o editando una existente.</p>
<p>Nota: Al utilizar subadminstradores <code>getSubject()</code> regresa siempre la primera entidad.</p>
<p>Para usar el transformador al estructurar nuestro formulario en el administrador no agregamos directamente nuestro objeto con el tipo, sino que creamos un objeto <code>FormBuilder</code> al que le agregaremos una instancia del transformador.</p>
<p></p><pre class="urvanov-syntax-highlighter-plain-tag">/* Acme/DemoBundle/Admin/EntityAdmin.php */
<?php
    namespace Acme\DemoBundle\Admin;

    ...
    use Acme\DemoBundle\Form\DataTransformer\DateTimeToStringTransformer;
    ...

        // Static text showing the DateTime object
        ->add(
            $formMapper->create('created', 'acme_static_text', array(
                // data_class set to null to avoid object type errors
                'data_class' => null,
                'required' => false,
                'disablex' => true,
            ))
            ->addViewTransformer(new DateTimeToStringTransformer('D, M d Y H:i:s'))
        )</pre><p></p>
<p>Algunos otros ejemplos de configuración:</p><pre class="urvanov-syntax-highlighter-plain-tag">// 1-to-N collection would be something like this
        ->add('pages', 'acme_static_text_collection', array(
            'label' => false,
            'type' => 'entity',
                'options' => array(
                'class' => 'AcmeDemoBundle:Foo',
            ),
        ))

        // An entity would look like
        ->add(
            $formMapper->create('author', 'info', array(
                'data_class' => null,
                'required' => false,
                'mapped' => false,
                'data' => $subject->getAuthor(),
                'property_path' => null
            ))
            ->addViewTransformer(new UserToStringTransformer())
        )</pre><p></p>
<h3 id="twig">Crear los bloques en Twig con el marcado para el despliegue</h3>
<p>Finalmente vamos a construir los widgets con el HTML para mostrar la información. Como nuestro tipo se llama <em>acme_static_text</em> el bloque lo debemos crear con el nombre <em>acme_static_text_widget</em>.</p>
<p></p><pre class="urvanov-syntax-highlighter-plain-tag">{# Acme/DemoBundle/Resources/views/Form/widgets.html.twig #}
{% block acme_static_text_widget %}
    {% spaceless %}
        <div class="sonata-ba-field sonata-ba-field-standard-natural">{{ value }}</div>
    {% endspaceless %}
{% endblock %}</pre><p></p>
<p>Ahora debemos agregar nuestra plantilla con widgets en el administrador para que pueda ser usada. Una manera sencilla es sobrecargando el método <code>getFormTheme()</code> de nuestra clase de administración.</p>
<p></p><pre class="urvanov-syntax-highlighter-plain-tag">/* Acme/DemoBundle/Admin/EntityAdmin.php */
<?php
namespace Allegro\SitesBundle\Admin;

...
use Allegro\SitesBundle\Form\DataTransformer\DateTimeToStringTransformer;
...

class EntityAdmin extends Admin
{
    /**
     * Adds a custom collection template.
     * @return array
     */
    public function getFormTheme()
    {
        return array_merge(
            parent::getFormTheme(),
            array('AcmeDemoBundle:Form:widgets.html.twig')
        );
    }</pre><p></p>
<p>Y con esto terminamos. Si bien la implementación no es tan rápida, no es compleja y es valiosa porque muestra una manera para personalizar cualquier campo en los administradores de nuestros bundles.</p>
<p>La entrada <a href="https://fugadigital.com/agregar-campos-informativos-en-formularios-de-sonata-admin/">Agregar campos informativos en formularios de Sonata Admin</a> se publicó primero en <a href="https://fugadigital.com">fuga_digital</a>.</p>
]]></content:encoded>
					
					<wfw:commentRss>https://fugadigital.com/agregar-campos-informativos-en-formularios-de-sonata-admin/feed/</wfw:commentRss>
			<slash:comments>0</slash:comments>
		
		
			</item>
	</channel>
</rss>
