Laravel 8 desde cero Parte VIII - Agregando Roles y Perfiles
Introducción
Es necesario que cuando un usuario se registre, cree un perfil para el usuario, con el roll más básico, en la Parte VI, podemos ver que dicho roll es:
'1 = Estudiante'.
Además para reiniciar el proyecto, con nuevos campos, tablas, semilleros, modelos, vistas, etc.
Eliminamos las tablas y ejecutamos:
php artisan migrate
php artisan db:seed
Creando modelo y migración para perfiles
Para lo cual ejecutamos el comando:
php artisan make:model Perfil -m
En el archivo "App\Models\Perfil.php" antes del final de la clase agregamos el código:
protected $fillable = [
'user_name', 'nombres', 'apellidos', 'conocimientos', 'roll', 'roll_name', 'email',
];
protected $hidden = [
'user_id',
];
Y en la migración "Database\Migrations\AAAA_MM_DD_CODIGO_create_perfils_table.php", escribimos en la función up:
Schema::create('perfils', function (Blueprint $table) {
$table->id('user_id')->references('id')->on('users');
$table->string('user_name')->default('');
$table->string('nombres')->default('');
$table->string('apellidos')->default('');
$table->string('email')->references('email')->on('users')->default('')->unique();
$table->text('conocimientos')->default('');
$table->unsignedBigInteger('roll')->default('1');
$table->string('roll_name')->default('Estudiante');
$table->timestamps();
});
Nota:
- Obsérvese el roll por default 1 = Estudiante, los demás default, y los references on users: 'user_id', y 'email'.
Para crear el perfil al registrarse el usuario
Vamos al archivo: "App\Http\Controllers\Auth\RegisterController.php"
Agregamos:
use App\Models\Perfil;
Antes de la function create agregamos la siguiente función:
public function createperfil(array $data){
return Perfil::create([
'user_name' => $data['name'],
'email' => $data['email'],
]);
}
Y en la function create agregamos al inicio:
$this->createperfil($data);
Modificación al semillero de usuarios, para crear perfiles
Vamos al archivo: "Database\Seeders\UsersTableSeeder.php"
Agregamos:
use App\Models\Perfil;
Y antes del cierre de la función run añadimos:
Perfil::create(['user_id' => 1, 'user_name' => 'Luis Gabriel Hernández Valderrama', 'nombres' => 'Luis Gabriel', 'apellidos' => 'Hernandez Valderrama', 'email' => "luisga@email.com", 'conocimientos' => "html, css, php, mysql, javascript, visual", 'roll' => 5, 'roll_name' => 'Superusuario']);
Perfil::create(['user_id' => 2, 'user_name' => 'LuisN', 'nombres' => 'LuisN', 'apellidos' => 'LuisN', 'email' => "email@email.com", 'conocimientos' => "html, css, php, mysql, javascript, visual", 'roll' => 1, 'roll_name' => 'Estudiante']);
Perfil::create(['user_id' => 3, 'user_name' => 'LuisN2', 'nombres' => 'LuisN2', 'apellidos' => 'LuisN2', 'email' => "email2@email.com", 'conocimientos' => "html, css, php, mysql, javascript, visual", 'roll' => 3, 'roll_name' => 'Administrador de Roles']);
Observaciones:
- Todos los perfiles requieren verificación de correo, por lo que si ingresamos por primera vez, con un usuario no registrado, que ha sido creado con un semillero, exigirá la verificación de correo al momento de ingresar, para lo cual ya se ha dispuesto un link, o enlace, para enviar dicho correo.
- Si el usuario se registra de forma normal, el correo es enviado automáticamente.
Archivo Database\Seeders\DatabaseSeeder.php
Primero que todo hay que saber que es el archivo donde se registran los semilleros a usar.
En la función run agregamos como segunda linea, o después de:
$this->truncateTables(['users']);
$this->truncateTables(['perfils']);
Hay que tener en cuenta que estos semilleros son para las pruebas, sin embargo solo temas y rolls, son las semillas que requiere el programa al inicio.
Es por lo cual solo quedan esos dos semilleros, una vez realizadas las pruebas y publiquemos nuestro proyecto.
Creando el controlador y las vistas para los perfiles
En este punto vamos a crear para los perfiles, el controlador (PerfilController), la solicitud o request (PerfilFormRequest, validaciones de formulario), las vistas (Resources\Views\Perfil), y el registro de las rutas (Routes\web.php).
Para crear el controlador de los perfiles ejecutamos:
php artisan make:controller PerfilController --resource --model=Perfil
Vamos al archivo creado 'App\Http\Controllers\PerfilController.php' y agregamos:
use App\Models\User;
use App\Models\Roll;
use App\Http\Requests\PerfilFormRequest;
use App\Models\Roll;
use App\Http\Requests\PerfilFormRequest;
El archivo resaltado en aguamarina App\Http\Requests\PerfilFormRequest, lo crearemos después.
Modificamos la función index así y agregamos una función auxiliar getrolluseractive:
public function index(Request $request)
{
$perfil = $this->getrolluseractive($request);
if ($perfil->roll >= 3){
$perfils = Perfil::all();
return view('perfil/index', compact('perfils'));
} else {
return redirect()
->route('perfils.my')
->with('message', 'lblonlymyperfil');
}
}
public function getrolluseractive(Request $request){
$idlcluser = $request->user()->id;
$allperfils = Perfil::all();
$perfils = $allperfils->where('id', $idlcluser);
$perfil = $perfils->get($idlcluser-1);
return $perfil;
}
Notas: La función getrolluseractive devuelve el perfil del usuario activo (nótese la resta del $idlcluser-1, al asignar el perfil del usuario.), el cual toma la función index para dirigir el acceso según el roll, en este caso mayor o igual a tres (>=3).
La función show:
public function show(Perfil $perfil)
{
return view('perfil/show', compact('perfil'));
}
La función edit queda:
public function edit(Request $request, Perfil $perfil)
{
$perfilact = $this->getrolluseractive($request);
if ($perfilact->roll >= 3){
$rolls = Roll::all();
return view('perfil\editroll', compact("perfil"), compact("rolls"));
} else {
return view('perfil\edit', compact("perfil"));
}
}
Nota: Se usa de nuevo la función getrolluseractive, para el ejemplo permitiendo o no la edición de roles.
La función update:
public function update(PerfilFormRequest $request, Perfil $perfil)
{
$perfilact = $this->getrolluseractive($request);
->route('home')
->with('message', 'lblactualizado');
}
{
$perfilact = $this->getrolluseractive($request);
if ($perfilact->roll >= 3){
$perfil->nombres = $request->input('nombres');
$perfil->apellidos = $request->input('apellidos');
$perfil->conocimientos = $request->input('conocimientos');
$perfil->roll_name = $request->input('roll_name');
$perfil->roll = $this->getidroll($perfil->roll_name);
if ($perfil->roll == 0){
$rolls = Roll::all();
return view('perfil\editroll', compact("perfil"), compact("rolls"));
}
} else {
$perfil->nombres = $request->input('nombres');
$perfil->apellidos = $request->input('apellidos');
$perfil->conocimientos = $request->input('conocimientos');
}
$perfil->save();
return redirect()->route('home')
->with('message', 'lblactualizado');
}
Como tendremos roles que permiten o restringen la modificación de los perfiles incluido el roll de los usuarios, por lo cual vemos tanto en index como en edit dos (2) vistas según el acceso que permita el roll, al igual que dos opciones de actualización según el roll del usuario activo.
La función auxiliar getidroll de momento implementada, se usa para obtener el id del nombre del roll entregado (más adelante debemos modificarla, para ampliar un proyecto, con creación de roles, de momento estos son estáticos, mientras sean estáticos se puede usar de la forma aquí mostrada.):
public function getidroll(String $nameroll){
if ($nameroll == 'Superusuario'){
return 5;
} elseif ($nameroll == 'Administrador de proyectos'){
return 4;
} elseif ($nameroll == 'Administrador de Roles'){
return 3;
} elseif ($nameroll == 'Editor'){
return 2;
} elseif ($nameroll == 'Estudiante'){
return 1;
} else { return 0;}
}
Por último tenemos la función my (encargada de mostrar mi perfil) :
public function my(Request $request)
{
$idlcluser = $request->user()->id;
$allperfils = Perfil::all();
$perfils = $allperfils->where('id', $idlcluser);
$perfil = $perfils->get($idlcluser-1);
return view('perfil/show', ['perfil' => $perfil]);
}
Observaciones:
- Vemos cómo se limita el acceso a la vista de todos los perfiles, a quienes tengan un roll, mayor o igual a 3 (tres). De lo contrario solo muestra el perfil del usuario.
- Este pequeño detalle, no me funcionaba en principio, ya que al id del usuario, hay que restarle 1, para acceder al perfil del usuario, y luego evaluar el roll del usuario.
- Es de notar que la función my, usa la vista show.
- El amarillo para PerfilController, variables y funciones destacadas.
- El aguamarina para el archivo que crearemos a continuación, PerfilFormRequest.
- El verde para las vistas y mensajes personalizados en las mismas.
Creando archivo de validación (Request o Solicitud) del formulario perfil
Para crear el archivo de respuestas, y exigencias al editar un perfil: PerfilFormRequest. Ejecutamos:
php artisan make:request PerfilFormRequest
Vamos al archivo creado "App\Http\Request\PerfilFormRequest"
Agregamos:
use Illuminate\Support\Facades\App;
En rules para el ejemplo colocamos:
return [
'nombres' => 'min:2|max:100',
'apellidos' => 'min:2|max:100',
'conocimientos' => 'min:2|max:10000',
];
Y para mensajes en inglés y español se agrega la función:
public function messages(){
$locale = str_replace('_', '-', app()->getLocale());
if ($locale == 'es'){
return[
'nombres.min' => 'El nombre(s) debe tener al menos 2 caracteres',
'nombres.max' => 'El nombre(s) no debe tener mas de 100 caracteres',
'apellidos.min' => 'El apellido(s) debe tener al menos 2 caracteres',
'apellidos.max' => 'El apellido(s) no debe tener mas de 100 caracteres',
'conocimientos.min' => 'Conocimientos debe tener al menos 2 caracteres',
'conocimientos.max' => 'Conocimientos no debe tener mas de 10000 caracteres',
];
} else {
return[
'nombres.min' => 'Name (s) must be at least 2 characters long',
'nombres.max' => 'The name (s) must not be more than 100 characters',
'apellidos.min' => 'Last name (s) must be at least 2 characters long',
'apellidos.max' => 'The last name (s) must not be more than 100 characters',
'conocimientos.min' => 'Knowledge must have at least 2 characters',
'conocimientos.max' => 'Knowledge must not be more than 10,000 characters',
];
}
}
Observaciones:
- En la función messages, usamos para $locale, app()->getLocale(), para lo cual incluimos al inicio, use Illuminate\Support\Facades\App;. De lo contrario falla la función.
- Dicha función permite declarar los mensajes, en múltiples idiomas.
Creando las vistas en la carpeta Resources\Views\Perfil
En el archivo PerfilFormRequest, vemos las vistas resaltadas en verde, junto con las etiquetas de mensaje que recibe la vista, tenemos pues, las siguientes vistas:
index, show (lblonlymyperfil), editroll, edit, y en la vista ya creada home, agregamos la opción para el mensaje: lblactualizado.
Las vistas del ejemplo se basan en el modelo y estilos ya vistos.
index.blade.php
@extends('layouts.compulg')
@section('title')
@lang('main.lblttlindxprfl')
@endsection
@section('mainttl')
@endsection
@section('content')
@if(Session::has('message'))
@if(Session::get('message') == 'lblactualizado')
<div class="container alert alert-success">
@lang('main.lblactualizado')
</div>
@else
<div class="container alert alert-success">
{{ Session::get('message') }}
</div>
@endif
@endif
<div>
<div>
<div>
@foreach($perfils as $perfil)
<div class="card">
<div class="card-header"><span class="smplbl">@lang('main.lblname') </span>{{ $perfil->user_name }}</div>
<div class="card-footer"><span class="smplbl">@lang('main.lblperfilnames') </span>{{ $perfil->nombres }}</div>
<div class="card-header"><span class="smplbl">@lang('main.lblperfilapell') </span>{{ $perfil->apellidos }}</div>
<div class="card-footer smplbl">@lang('main.lblperfilconoce') </div>
<textarea class="spmlgcardin" cols="30" rows="10" readonly>{{ $perfil->conocimientos }}</textarea>
<div class="card-footer"><span class="smplbl">@lang('main.lbluserol') </span>{{ $perfil->roll_name }}</div>
<div class="card-header"><span class="smplbl">@lang('main.lblmail') </span>{{ $perfil->email }}</div>
<div class="card-footer">
<spam class="d-flex justify-content-end ml-2">
<a href="{{ route('perfils.edit',['perfil' => $perfil]) }}" class="btn btn-primary ml-2">@lang('main.lblbtnedit')</a>
</spam>
</div>
</div><br>
@endforeach
</div>
</div>
</div>
@endsection
Observaciones:
- He resaltado en verde, las etiquetas relacionadas con texto multi-idioma, por lo cual debe verificarse la existencia de dichas etiquetas en los archivos Resources\Lang\En\main.php, y Resources\Lang\Es\main.php.
- De igual forma vemos el mensaje lblactualizado, que al ser recibido, muestra el texto correspondiente a dicha etiqueta creada con el mismo nombre para mayor facilidad, etiqueta que debe estar en los archivos previamente mencionados.
show.blade.php
@extends('layouts.compulg')
@section('title')
@lang('main.lblperfil')
@endsection
@section('mainttl')
@endsection
@section('content')
@if(Session::has('message'))
@if(Session::get('message') == 'lblonlymyperfil')
<div class="container alert alert-success">
@lang('main.lblonlymyperfil')
</div>
@else
<div class="container alert alert-success">
{{ Session::get('message') }}
</div>
@endif
@endif
<div>
<div class="card">
<div class="card-header"><span class="smplbl">@lang('main.lblname') </span>{{ $perfil->user_name }}</div>
<div class="card-footer"><span class="smplbl">@lang('main.lblperfilnames') </span>{{ $perfil->nombres }}</div>
<div class="card-header"><span class="smplbl">@lang('main.lblperfilapell') </span>{{ $perfil->apellidos }}</div>
<div class="card-footer smplbl">@lang('main.lblperfilconoce') </div>
<textarea class="spmlgcardin" cols="30" rows="10" readonly>{{ $perfil->conocimientos }}</textarea>
<div class="card-footer"><span class="smplbl">@lang('main.lbluserol') </span>{{ $perfil->roll_name }}</div>
<div class="card-header"><span class="smplbl">@lang('main.lblmail') </span>{{ $perfil->email }}</div>
<div class="card-footer"><a href="{{ route('perfils.edit',['perfil' => $perfil]) }}" class="btn btn-primary ml-2">@lang('main.lblbtnedit')</a></div>
</div><br>
</div>
@endsection
La etiqueta: lblonlymyperfil, esta definida en main.php.
Para español: 'lblonlymyperfil' => 'Su nivel de acceso solo le permite acceder a su perfil',
Para inglés: 'lblonlymyperfil' => 'Your access level only allows you to access your profile',
Para español: 'lblonlymyperfil' => 'Su nivel de acceso solo le permite acceder a su perfil',
Para inglés: 'lblonlymyperfil' => 'Your access level only allows you to access your profile',
edit.blade.php
@extends('layouts.compulg')
@section('title')
@lang('main.lblbtnedit')
@endsection
@section('mainttl')
@endsection
@section('content')
@if(Session::has('message'))
<div class="container alert alert-success">
{{ Session::get('message') }}
</div>
@endif
@if($errors->any())
<div class="alert alert-danger">
<ul>
@foreach($errors->all() as $error)
<li>{{ $error }}</li>
@endforeach
</ul>
</div>
@endif
<form action="{{ route('perfils.update',['perfil' => $perfil]) }}" method="post">
@method('PUT')
@csrf
<div class="card">
<div class="form-group">
<div class="card-header"><span class="smplbl">@lang('main.lblname') </span>: {{ $perfil->user_name }}</div>
<div class="card-header"><span class="smplbl">@lang('main.lblperfilnames') </span></div>
<input type="text" class="form-control" name="nombres" id="nombres" value="{{ $perfil->nombres }}">
<div class="card-header"><span class="smplbl">@lang('main.lblperfilapell') </span></div>
<input type="text" class="form-control" name="apellidos" id="apellidos" value="{{ $perfil->apellidos }}">
<div class="card-header smplbl">@lang('main.lblperfilconoce') </div>
<textarea class="txtpost form-control" name="conocimientos" id="conocimientos" cols="30" rows="10">{{ $perfil->conocimientos }}</textarea>
<div class="card-header"><span class="smplbl">@lang('main.lblmail') </span>{{ $perfil->email }}</div>
<div class="card-header"><span class="smplbl">@lang('main.lblmail') </span>{{ $perfil->roll_name }}</div>
<button class="btn btn-primary btn-block" type="submit">@lang('main.lblupdate')</button>
</div>
<div class="card"><br><b><center>@lang('main.lblneedmoreacces')</center></b><br></div>
</div>
</form>
@endsection
editroll.blade.php
@extends('layouts.compulg')
@section('title')
@lang('main.lblbtnedit')
@endsection
@section('mainttl')
@endsection
@section('content')
@if(Session::has('message'))
<div class="container alert alert-success">
{{ Session::get('message') }}
</div>
@endif
@if($errors->any())
<div class="alert alert-danger">
<ul>
@foreach($errors->all() as $error)
<li>{{ $error }}</li>
@endforeach
</ul>
</div>
@endif
<form action="{{ route('perfils.update',['perfil' => $perfil]) }}" method="post">
@method('PUT')
@csrf
<div class="card">
<div class="form-group">
<div class="card-header"><span class="smplbl">@lang('main.lblname') </span>: {{ $perfil->user_name }}</div>
<div class="card-header"><span class="smplbl">@lang('main.lblperfilnames') </span></div>
<input type="text" class="form-control" name="nombres" id="nombres" value="{{ $perfil->nombres }}">
<div class="card-header"><span class="smplbl">@lang('main.lblperfilapell') </span></div>
<input type="text" class="form-control" name="apellidos" id="apellidos" value="{{ $perfil->apellidos }}">
<div class="card-header smplbl">@lang('main.lblperfilconoce') </div>
<textarea class="txtpost form-control" name="conocimientos" id="conocimientos" cols="30" rows="10">{{ $perfil->conocimientos }}</textarea>
<div class="card-header"><span class="smplbl">@lang('main.lblmail') </span>{{ $perfil->email }}</div>
<div class="card-header txtwhtbkblack"><center>@lang('main.lblrolldelista')</center></div>
<div class="card-header"><span class="smplbl">@lang('main.lbluserol'): </span>
<input class="txtwhtbkblack" list="rolls" name="roll_name" placeholder="{{ $perfil->roll_name }}" value="{{ $perfil->roll_name }}">
<datalist id="rolls">
@foreach($rolls as $roll)
<option value="{{ $roll->roll_name }}">
@endforeach
</datalist>
</div>
<button class="btn btn-primary btn-block" type="submit">@lang('main.lblupdate')</button>
</div>
<div class="card"><br><b><center>@lang('main.lblneedmoreacces')</center></b><br></div>
</div>
</form>
@endsection
Notas:
- La etiqueta lblrolldelista,
- Queda en español: 'lblrolldelista' => 'El roll es obligatorio, se debe borrar y seleccionar de la lista para cambiar, de lo contrario permanece aquí sin guardar cambios',
- Y en ingles: 'lblrolldelista' => 'The roll is mandatory, it must be deleted and selected from the list to change, otherwise it remains here without saving changes',
- la etiqueta lblneedmoreacces,
- Queda en español: 'lblneedmoreacces' => 'Si necesita modificar otros datos de su perfil, favor enviar correo a compulgh@gmail.com, detallando sus necesidades.',
- Y en ingles: 'lblneedmoreacces' => 'If you need to modify other information on your profile, please send an email to compulgh@gmail.com, detailing your needs.',
Modificaciones a Resources\Views\home.blade.php
La sección de mensajes queda así:
@if(Session::has('message'))
@if(Session::get('message') == 'lblupdateok')
<div class="container alert alert-success">
@lang('main.lblupdateok')
</div>
@elseif(Session::get('message') == 'lblactualizado')
<div class="container alert alert-success">
@lang('main.lblactualizado')
</div>
@else
<div class="container alert alert-success">
{{ Session::get('message') }}
</div>
@endif
@endif
Y antes del final agregamos el vinculo a los perfiles:
<a href="{{route('perfil/index')}}" class="btn btn-primary btn-block">@lang('main.lblttlindxprfl')</a>
Registrando las rutas en Routes\web.php
Añadimos en nuestro grupo de verificados, el código:
Route::resource('perfils', PerfilController::class);
Route::get('/perfil/edit', [App\Http\Controllers\PerfilController::class, 'edit'])->name('perfil/edit');
Route::get('/perfil/editroll', [App\Http\Controllers\PerfilController::class, 'editroll'])->name('perfil/editroll');
Route::get('/perfil/index', [App\Http\Controllers\PerfilController::class, 'index'])->name('perfil/index');
Route::get('/perfil/show', [App\Http\Controllers\PerfilController::class, 'show'])->name('perfil/show');
Route::get('/perfil/my', [App\Http\Controllers\PerfilController::class, 'my'])->name('perfils.my');
Importante. En ocasiones no reconoce algunas rutas, y es necesario ejecutar el siguiente comando para que lo haga:
composer dump-autoload
Hecho esto, debe reconocer las rutas sin inconvenientes.
Dejo enlace para descargar la carpeta 'Resources\Lang', actualizada, sea para uso o para consulta.
Click aquí para descargar.
Click aquí para descargar.
Observaciones:
- Si se ha realizado, la lectura e implementación de este grupo de guías "Laravel 8 desde cero", tendremos un sistema con, registro, ingreso y acceso libre a las publicaciones.
- También se ha implementado el registro, con la exigencia de verificación de correo.
- Además de el ingreso, y la creación de un perfil, con roles prefijados, que incluyen la modificación de perfiles, para usuarios cuyo roll sea mayor o igual a tres (3).
- Incluye un aviso, para solicitar mayor acceso mediante correo electrónico.
Notas adicionales:
- Se han realizado modificaciones en las vistas, parciales y/o diseños (layouts), con el objetivo de utilizar etiquetas multi-idioma.
- Dichas modificaciones se aprecian en el presente artículo, tanto en index, show, edit, como en editroll. De igual manera que vemos como en los perfiles, se han agregado títulos a los campos, esto se pueden implementar en las demás plantillas, para entregar la información al usuario en pantalla, el título que identifica el dato, seguido datos que ya hemos implementado, en todas las vistas.
- Como ejercicio queda la modificación de las vistas anteriores, incluyendo títulos que identifiquen los campos.
- Lo más relevante hasta el momento es lo que en el presente artículo trata sobre dirigir al usuario según su nivel de acceso, el cual está determinado por el roll del usuario, a la vista y/o función (para el presente caso index, edit y update), con el propósito de limitar determinadas funciones a los usuarios de acuerdo a su nivel de acceso.
Si ya tenemos funcionando un sistema como el implementado aquí, es posible crear sistemas con mas tablas, permitiendo o restringiendo el acceso según el roll asignado al usuario.
En próximos artículos se realizará un resumen de las ocho (8) partes, de la presente guía.
Y se creará un nuevo proyecto, con bases de datos nuevas.
Comentarios
Publicar un comentario