Mariano Fuentes
Máximo Calderon
Muchísimas gracias Mariano... Invaluable su aporte 🙏
Juan Pablo Campuzano Monsalve
Excelente aporte! Me sirvió mucho y me ahorró mucho tiempo de verdad.
Cuando trabajamos con TDD (Test-Driven Development), existe una fase que muchos subestiman pero que marca la diferencia en la calidad del código: la refactorización. Después de obtener el rojo con una prueba fallida y el verde al escribir el código que la hace pasar, llega el momento opcional pero valioso de mejorar la estructura sin alterar el funcionamiento. Aquí es donde entran las políticas de acceso en Laravel como una herramienta poderosa para limpiar nuestros controladores.
¿Qué son las políticas de acceso y por qué usarlas?
Las políticas de acceso (policies) en Laravel permiten aislar la lógica de autorización que normalmente vive dentro de los controladores. Ese if que valida si un usuario tiene permiso sobre un recurso puede —y debe— separarse en su propio archivo para que el código sea más legible y mantenible.
El proceso comienza generando la política desde el terminal [1:00]:
php artisan make:policy seguido del nombre, que por convención termina en Policy.pass, edit, create o update.En este caso, se crea un único método llamado pass porque la condicional es la misma para todas las operaciones: verificar que el ID del usuario autenticado coincida con el user_id del repositorio [2:30].
¿Cómo se configura y registra una política?
Una vez creado el archivo, hay tres pasos esenciales:
AuthServiceProvider para que Laravel asocie el modelo con su política correspondiente.En el AuthServiceProvider se descomenta el ejemplo que Laravel provee y se vincula la entidad (el modelo de repositorio) con la política recién creada [3:15]. Esta asociación es lo que permite usar el método authorize dentro del controlador.
¿Cómo se aplica la política en el controlador?
Dentro del controlador de repositorios, la sustitución es directa [3:50]:
if por una llamada a $this->authorize('pass', $repository).Esto reduce significativamente las líneas de código y mejora la legibilidad. Cada método del controlador queda más limpio al delegar la responsabilidad de autorización a la política.
¿Por qué los tests son fundamentales durante la refactorización?
Al realizar cambios estructurales, los tests funcionan como una red de seguridad. Mientras la refactorización no esté completa, las pruebas lo señalan con errores claros [4:40]:
$request del controlador pero no se actualiza el acceso al usuario, los tests fallan.auth()->user() en lugar de $request->user()), los tests confirman que todo sigue funcionando.Al ejecutar php artisan test después de completar todos los cambios, todas las pruebas pasan exitosamente [5:50]. La esencia del testing queda demostrada: una modificación amplia que no altera el funcionamiento del sistema.
¿Qué pasa si no tenemos tests al refactorizar?
Imagina un escenario real: necesitas presentar tu proyecto a un cliente mañana y decides hacer una mejora importante hoy. Sin tests, no hay forma de saber con certeza si alguna parte del proyecto se dañó durante el cambio. La presentación se convierte en un momento de inseguridad.
Con tests, en cambio, cada modificación queda respaldada por validaciones automáticas que confirman que el sistema funciona correctamente. Esto es especialmente relevante en entornos laborales donde las refactorizaciones son frecuentes y los equipos necesitan confianza al desplegar cambios.
La combinación de políticas de acceso para organizar la autorización y TDD para garantizar estabilidad representa una práctica profesional que vale la pena incorporar en cualquier proyecto Laravel. ¿Has implementado políticas en tus proyectos? Comparte tu experiencia en los comentarios.
Mariano Fuentes
Máximo Calderon
Muchísimas gracias Mariano... Invaluable su aporte 🙏
Juan Pablo Campuzano Monsalve
Excelente aporte! Me sirvió mucho y me ahorró mucho tiempo de verdad.
Kenneth Angulo L
Para Agosto 2024 hay CAMBIOS importantes para (jugar) con los permisos.
Se introdujo el concepto de GATES y se mantiene POLICIES.
Una Policy sigue usandose para dar permisos a los metodos del controlador.
Ahora se registra asi, pero dentro de AppServiceProvider:
public function boot(): void { Gate::policy(Repository::class, RepositoryPolicy::class); }
Y dentro del controlador se usa asi:
public function edit(Repository $repository) { Gate::authorize('pass', $repository); return view('repositories.edit', compact('repository')); }
Edinson David Natera Acosta
Gracias por el dato amigo.
Jorge Bou-saad
Muy interesante...excelente curso. Aunque tengo mucho tiempo trabajando con Laravel, sabia que existian las Policies pero nunca las habia usado...me gusto muchisimo y efectivamente ayuda a que el codigo sea mas limpio...Laravel es quizas el framework de desarrollo web mas completo...al menos de php...
Les dejo mi commit de la clase https://github.com/jlbousing/practica-tdd/commit/6f7f881171a8fc812151cf2483a7b6b0e378b417
Angel Hernández Nava
Juan Camilo Randazzo
¿La instancia de User es singleton? Es que no entiendo por qué la función pass recibe un usuario pero cuando la llamamos en el controlador del Repositorio no se lo pasamos, como sí hacemos con la variable $repository
Carlos Eduardo Gomez García
Esto es por algo llamado "inyección de dependencias", básicamente tú le dices a Laravel qué dependencias necesitas y ya Laravel lo resuelve para pasarte esas dependencias, en este caso, en la Política, Laravel es capaz de pasarte el usuario directamente para que puedas hacer tus validaciones. En los contrladores, Laravel nos pasa los Requests (e implicitamente el usuario ya viene dentro del Request), así que en ese caso no es necesario pedirlo, pero digamos que Laravel ya tiene internamente al usuario identificado y simplemente te lo proporciona cuando lo necesites :D
Abraham Gonzalez
Para Laravel 11 deben agregar el trait ** AuthorizesRequests*.*
Queda de esta forma
namespace App\Http\Controllers; use Illuminate\Foundation\Auth\Access\AuthorizesRequests; class RepositoryController extends Controller { use AuthorizesRequests; ```*use* Illuminate\Foundation\Auth\Access\AuthorizesRequests;
Santiago Velásquez Serna
La autorización de las politicas se puede hacer directamente desde el formRequest para dejar aun mas limpio el controlador:
Acá un ejemplo donde dependiendo de si vamos a actualizar o a crear le paso diferentes valores, pero podria ser tranquilamente una sola condición como en la clase:
public function authorize(): bool { $response = false; switch ($this->method()) { case 'PUT': $response = $this->user()->can("update", $this->repository); break; case 'POST': $response = $this->user()->can("create", Repository::class); break; default: $response = false; break; } return $response; ; } ```Lo hice de esta manera porque para crear le voy a pedir solamente que este logueado, pero para actualizar debe ser el dueño de el repositorio.
En la versión más reciente de Laravel, específicamente Laravel 11.10.0., ha habido algunos cambios significativos en la estructura y la configuración del framework. Uno de esos cambios incluye la forma en que se maneja la autenticación y la autorización. Les comparto como lo realice por si alguno se encuentra en el mismo punto que yo.<?php<?phpuse Illuminate\Foundation\Auth\Access\AuthorizesRequests;use AuthorizesRequests;<?php namespace App\Http\Controllers; use App\Http\Controllers\Controller;use App\Http\Requests\RepositoryRequest;use App\Models\Repository;use Illuminate\Foundation\Auth\Access\AuthorizesRequests; class RepositoryController extends Controller{ use AuthorizesRequests; public function index() { return view('repositories.index', [ 'repositories' => auth()->user()->repositories ]); } public function show(Repository $repository) { $this->authorize('pass', $repository); return view('repositories.show', compact('repository')); } public function create() { return view('repositories.create'); } public function store(RepositoryRequest $request) { $request->user()->repositories()->create($request->all()); return redirect()->route('repositories.index'); } public function edit(Repository $repository) { $this->authorize('pass', $repository); return view('repositories.edit', compact('repository')); } public function update(RepositoryRequest $request, Repository $repository) { $this->authorize('pass', $repository); $repository->update($request->all()); return redirect()->route('repositories.edit', $repository); } public function destroy(Repository $repository) { $this->authorize('pass', $repository); $repository->delete(); return redirect()->route('repositories.index'); }}
namespace App\Providers; use Illuminate\Support\ServiceProvider;use Illuminate\Support\Facades\Gate;use App\Models\Repository;use App\Policies\RepositoryPolicy; class AppServiceProvider extends ServiceProvider{ /** * Register any application services. */ public function register(): void { // } /** * Bootstrap any application services. */ public function boot(): void { // Registrar la política para el modelo Repository Gate::policy(Repository::class, RepositoryPolicy::class); }}
namespace App\Policies; use App\Models\Repository;use App\Models\User;use Illuminate\Auth\Access\HandlesAuthorization; class RepositoryPolicy{ use HandlesAuthorization; /** * Determine if the given repository can be accessed by the user. * * @param \App\Models\User $user * @param \App\Models\Repository $repository * @return bool */ public function pass(User $user, Repository $repository) { // Define tu lógica de autorización aquí return $user->id === $repository->user_id; }}
Despues de que crees RepositoryPolicy.php debes de realizar algunas importanciones, quedaría asi el archivo final:
<?php namespace App\Policies; use App\Models\Repository; use App\Models\User; use Illuminate\Auth\Access\HandlesAuthorization; class RepositoryPolicy { use HandlesAuthorization; /** * Determine if the given repository can be accessed by the user. * * @param \App\Models\User $user * @param \App\Models\Repository $repository * @return bool */ public function pass(User $user, Repository $repository) { // Define tu lógica de autorización aquí return $user->id === $repository->user_id; } } ```Luego en el AppServiceProvider.php debes manejar otras importaciones y Registrar la política para el modelo Repository: ```js <?php namespace App\Providers; use Illuminate\Support\ServiceProvider; use Illuminate\Support\Facades\Gate; use App\Models\Repository; use App\Policies\RepositoryPolicy; class AppServiceProvider extends ServiceProvider { /** * Register any application services. */ public function register(): void { // } /** * Bootstrap any application services. */ public function boot(): void { // Registrar la política para el modelo Repository Gate::policy(Repository::class, RepositoryPolicy::class); } } ```Luego el archivo RepositoryController.php debes importar  ```js use Illuminate\Foundation\Auth\Access\AuthorizesRequests; ```y luego agregar dentro de la clase: ```js use AuthorizesRequests; ```quedando de la siguiente forma el archivo RepositoryController.php, con los cambios que también realizo el profesor Ítalo: ```js <?php namespace App\Http\Controllers; use App\Http\Controllers\Controller; use App\Http\Requests\RepositoryRequest; use App\Models\Repository; use Illuminate\Foundation\Auth\Access\AuthorizesRequests; class RepositoryController extends Controller { use AuthorizesRequests; public function index() { return view('repositories.index', [ 'repositories' => auth()->user()->repositories ]); } public function show(Repository $repository) { $this->authorize('pass', $repository); return view('repositories.show', compact('repository')); } public function create() { return view('repositories.create'); } public function store(RepositoryRequest $request) { $request->user()->repositories()->create($request->all()); return redirect()->route('repositories.index'); } public function edit(Repository $repository) { $this->authorize('pass', $repository); return view('repositories.edit', compact('repository')); } public function update(RepositoryRequest $request, Repository $repository) { $this->authorize('pass', $repository); $repository->update($request->all()); return redirect()->route('repositories.edit', $repository); } public function destroy(Repository $repository) { $this->authorize('pass', $repository); $repository->delete(); return redirect()->route('repositories.index'); } } ```Espero haber ayudado en este paso.