laravel-best-practices/spanish.md at master · alexeymezenin/laravel-best-practices
Extracto
Laravel best practices. Contribute to alexeymezenin/laravel-best-practices development by creating an account on GitHub.
Resumen
Resumen Principal
El documento "Laravel Best Practices" constituye una guía esencial para desarrolladores que buscan implementar standards profesionales en sus aplicaciones Laravel. Esta recopilación de buenas prácticas aborda aspectos fundamentales del desarrollo web moderno, enfocándose en la arquitectura limpia, mantenibilidad del código y eficiencia operativa. Las recomendaciones presentadas promueven un enfoque estructurado que favorece la separación de responsabilidades mediante el uso adecuado de Service Containers, Repositories y Service Classes. La guía enfatiza la importancia de mantener controladores delgados, delegando lógica compleja a capas especializadas, lo que resulta crucial para proyectos escalables. Además, se destacan prácticas relacionadas con optimización de consultas Eloquent, gestión eficiente de relaciones y implementación correcta de validaciones. El contenido también aborda aspectos de seguridad, testing y documentación, estableciendo un marco completo para el desarrollo profesional de aplicaciones Laravel que cumplan con estándares industriales contemporáneos.
Elementos Clave
- Arquitectura basada en capas: Implementación de Service Classes y Repositories para separar lógica de negocio de controladores, promoviendo código reutilizable y fácil de mantener mediante principios SOLID
- Optimización de consultas Eloquent: Uso eficiente de eager loading, lazy loading y query constraints para prevenir problemas de N+1 queries y mejorar el rendimiento de bases de datos relacionales
- Validación y autorización estructurada: Implementación de Form Request para validaciones complejas y políticas de autorización que aseguran el acceso controlado a recursos específicos dentro de la aplicación
- Testing y documentación sistemática: Enfoque integral en pruebas unitarias, feature tests y documentación de código que garantiza la estabilidad y comprensión del proyecto por parte de equipos de desarrollo
Análisis e Implicaciones
La adopción de estas prácticas tiene un impacto transformacional en la calidad del software desarrollado con Laravel, reduciendo significativamente el technical debt y facilitando el onboarding de nuevos desarrolladores en proyectos existentes. La implementación consistente de estos principios no solo mejora la mantenibilidad a largo plazo, sino que también optimiza los tiempos de desarrollo y reduce la incidencia de errores en producción, estableciendo una base sólida para aplicaciones empresariales robustas.
Contexto Adicional
Este repositorio GitHub representa una contribución comunitaria significativa al ecosistema Laravel, reflejando la experiencia práctica de desarrolladores senior y proporcionando un recurso actualizado que evoluciona con las nuevas versiones del framework. La naturaleza colaborativa del proyecto permite su constante enriquecimiento con casos de uso reales y soluciones probadas en entornos de producción.
Contenido
Traducciones:
Nederlands (por Protoqol)
Indonesia (por P0rguy, Doni Ahmad)
한국어 (por cherrypick)
ภาษาไทย (por kongvut sangkla)
বাংলা (por Anowar Hossain)
فارسی (por amirhossein baghaie)
Українська (por Tenevyk)
Tiếng Việt (por Chung Nguyễn)
Español (por César Escudero)
Français (por Mikayil S.)
Polski (by Karol Pietruszka)
Deutsch (por Sujal Patel)
Italiana (por Sujal Patel)
Azərbaycanca (por Maharramoff)
العربية (por ahmedsaoud31)
اردو (by RizwanAshraf1)
No se trata de una adaptación a Laravel de los principios SOLID ni de patrones, etcétera. Aquí encontrarás las mejores prácticas que, por lo general, son ignoradas en proyectos Laravel de la vida real.
Índice de contenido
Modelos gordos, controladores delgados
La lógica de negocios debe estar en una clase de servicio
No ejecutes consultas en las plantillas Blade y utiliza el cargado prematuro (Problema N + 1))
No coloques JS ni CSS en las plantillas Blade y no coloques HTML en clases de PHP
Utiliza los archivos de configuración y lenguaje en lugar de texto en el código
Utiliza las herramientas estándar de Laravel aceptadas por la comunidad
Sigue la convención de Laravel para los nombres
Utiliza sintaxis cortas y legibles siempre que sea posible
Utiliza contenedores IoC o fachadas en lugar de new Class
No saques información directamente del archivo .env
Principio de propósito único
Las clases y los métodos deben tener un solo propósito.
Malo:
public function getFullNameAttribute() { if (auth()->user() && auth()->user()->hasRole('client') && auth()->user()->isVerified()) { return 'Mr. ' . $this->first_name . ' ' . $this->middle_name . ' ' . $this->last_name; } else { return $this->first_name[0] . '. ' . $this->last_name; } }
Bueno:
public function getFullNameAttribute() { return $this->isVerifiedClient() ? $this->getFullNameLong() : $this->getFullNameShort(); } public function isVerifiedClient() { return auth()->user() && auth()->user()->hasRole('client') && auth()->user()->isVerified(); } public function getFullNameLong() { return 'Mr. ' . $this->first_name . ' ' . $this->middle_name . ' ' . $this->last_name; } public function getFullNameShort() { return $this->first_name[0] . '. ' . $this->last_name; }
Modelos gordos, controladores delgados
Coloca toda la lógica relacionada a la base de datos en los modelos de Eloquent o en una clase Repositorio si estás utilizando el constructor de consultas o consultas SQL puras.
Malo:
public function index() { $clients = Client::verified() ->with(['orders' => function ($q) { $q->where('created_at', '>', Carbon::today()->subWeek()); }]) ->get(); return view('index', ['clients' => $clients]); }
Bueno:
public function index() { return view('index', ['clients' => $this->client->getWithNewOrders()]); } class Client extends Model { public function getWithNewOrders() { return $this->verified() ->with(['orders' => function ($q) { $q->where('created_at', '>', Carbon::today()->subWeek()); }]) ->get(); } }
Validación
Quita las validaciones de los controladores y colócalas en clases Request
Malo:
public function store(Request $request) { $request->validate([ 'title' => 'required|unique:posts|max:255', 'body' => 'required', 'publish_at' => 'nullable|date', ]); .... }
Bueno:
public function store(PostRequest $request) { .... } class PostRequest extends Request { public function rules() { return [ 'title' => 'required|unique:posts|max:255', 'body' => 'required', 'publish_at' => 'nullable|date', ]; } }
La lógica de negocios debe estar en una clase de servicio
Un controlador solo debe tener un propósito, así que mueve la lógica de negocio fuera de los controladores y colócala en clases de servicio.
Malo:
public function store(Request $request) { if ($request->hasFile('image')) { $request->file('image')->move(public_path('images') . 'temp'); } .... }
Bueno:
public function store(Request $request) { $this->articleService->handleUploadedImage($request->file('image')); .... } class ArticleService { public function handleUploadedImage($image) { if (!is_null($image)) { $image->move(public_path('images') . 'temp'); } } }
No te repitas (DRY)
Reutiliza código cada vez que puedas. El SRP (Principio de Propósito Único) te ayuda a evitar la duplicación. Reutiliza también las plantillas Blade, utiliza scopes de Eloquent, etcétera.
Malo:
public function getActive() { return $this->where('verified', 1)->whereNotNull('deleted_at')->get(); } public function getArticles() { return $this->whereHas('user', function ($q) { $q->where('verified', 1)->whereNotNull('deleted_at'); })->get(); }
Bueno:
public function scopeActive($q) { return $q->where('verified', 1)->whereNotNull('deleted_at'); } public function getActive() { return $this->active()->get(); } public function getArticles() { return $this->whereHas('user', function ($q) { $q->active(); })->get(); }
Prioriza el uso de Eloquent por sobre el constructor de consultas y consultas puras. Prioriza las colecciones sobre los arreglos
Eloquent te permite escribir código legible y mantenible. Eloquent también tiene muy buenas herramientas preconstruidas como los borrados leves, eventos, scopes, etcétera.
Malo:
SELECT * FROM `articles` WHERE EXISTS (SELECT * FROM `users` WHERE `articles`.`user_id` = `users`.`id` AND EXISTS (SELECT * FROM `profiles` WHERE `profiles`.`user_id` = `users`.`id`) AND `users`.`deleted_at` IS NULL) AND `verified` = '1' AND `active` = '1' ORDER BY `created_at` DESC
Bueno:
Article::has('user.profile')->verified()->latest()->get();
Asignación en masa
Malo:
$article = new Article; $article->title = $request->title; $article->content = $request->content; $article->verified = $request->verified; // Add category to article $article->category_id = $category->id; $article->save();
Bueno:
$category->article()->create($request->validated());
No ejecutes consultas en las plantillas Blade y utiliza el cargado prematuro (Problema N + 1)
Malo (Para 100 usuarios, se ejecutarán 101 consultas):
@foreach (User::all() as $user) {{ $user->profile->name }} @endforeach
Bueno (Para 100 usuarios, se ejecutarán 2 consultas):
$users = User::with('profile')->get(); ... @foreach ($users as $user) {{ $user->profile->name }} @endforeach
Comenta tu código, pero prioriza los métodos y nombres de variables descriptivas por sobre los comentarios
Malo:
if (count((array) $builder->getQuery()->joins) > 0)
Mejor:
// Determina si hay alguna unión if (count((array) $builder->getQuery()->joins) > 0)
Bueno:
No coloques JS ni CSS en las plantillas Blade y no coloques HTML en clases de PHP
Malo:
let article = `{{ json_encode($article) }}`;Mejor:
<input id="article" type="hidden" value='@json($article)'> Or <button class="js-fav-article" data-article='@json($article)'>{{ $article->name }}<button>
En un archivo JavaScript:
let article = $('#article').val();
La mejor ruta es utilizar algún paquete especializado para transferir información de PHP a JS.
Utiliza los archivos de configuración y lenguaje en lugar de texto en el código
Malo:
public function isNormal() { return $article->type === 'normal'; } return back()->with('mensaje', '¡Su artículo ha sido agregado!');
Bueno:
public function isNormal() { return $article->type === Article::TYPE_NORMAL; } return back()->with('mensaje', __('app.articulo_agregado'));
Utiliza las herramientas estandar de Laravel aceptadas por la comunidad
Prioriza la utilización de funcionalidades integradas y los paquetes de la comunidad en lugar de utilizar paquetes o herramientas de terceros ya que cualquier desarrollador que vaya a trabajar a futuro en tu aplicación necesitará aprender a utilizar nuevas herramientas. También, las probabilidades de recibir ayuda de la comunidad son significativamente más bajas cuando utilizas herramientas o paquetes de terceros. No hagas que tu cliente pague por ello.
| Tarea | Herramienta estándar | Herramientas de terceras personas |
|---|---|---|
| Autorización | Policies | Entrust, Sentinel y otros paquetes |
| Compilar assets | Laravel Mix | Grunt, Gulp, paquetes de terceros |
| Entorno de desarrollo | Laravel Sail, Homestead | Docker |
| Deployment | Laravel Forge | Deployer y otras soluciones |
| Unit testing | PHPUnit, Mockery | Phpspec |
| Testeo en el navegador | Laravel Dusk | Codeception |
| Base de datos | Eloquent | SQL, Doctrine |
| Plantillas | Blade | Twig |
| Trabajar con data | Laravel collections | Arreglos |
| Validación de formularios | Clases Request | Paquetes de terceros, validación en el controlador |
| Autenticación | Integrada | Paquetes de terceros, solución propia |
| Autenticación para API's | Laravel Passport, Laravel Sanctum | Paquetes oAuth y JWT de terceros |
| Creación de API's | Integrado | Dingo API y paquetes similares |
| Estructura de la base de datos | Migraciones | Trabajar directamente con la estructura |
| Localización | Integrada | Paquetes de terceros |
| Interfaces en tiempo real | Laravel Echo, Pusher | Paquetes de terceros y trabajar directamente con WebSockets. |
| Generación de información de prueba | Clases Seeder, Fábricas de modelos, Faker | Crear la información manualmente |
| Programación de tareas | Laravel Task Scheduler | Scripts y paquetes de terceros |
| Base de datos | MySQL, PostgreSQL, SQLite, SQL Server | MongoDB |
Sigue la convención de Laravel para los nombres
Sigue los estándares PSR.
También, sigue la convención aceptada por la comunidad:
| Qué | Cómo | Bueno | Malo |
|---|---|---|---|
| Controlador | singular | ControladorArticulo | |
| Ruta | plural | articulos/1 | |
| Nombres de rutas | snake_case con notación de puntos | usuarios.mostrar_activos | |
| Modelo | singular | Usuario | |
| Relaciones hasOne o belongsTo | singular | comentarioArticulo | |
| Cualquier otra relación | plural | comentariosArticulo | |
| Tabla | plural | comentarios_articulo | |
| Tabla de pivote | Nombres de modelos en singular y en orden alfabético | articulo_usuario | |
| Columna de tabla | snake_case sin el nombre del modelo | meta_titulo | |
| Propiedad de modelo | snake_case | $model->created_at | |
| Clave foránea | Nombre en singular del modelo con el sufijo _id | articulo_id | |
| Clave primaria | - | id | |
| Migración | - | 2017_01_01_000000_create_articles_table | |
| Método | camelCase | traerTodo | |
| Método en controlador de recursos | table | guardar | |
| Método en clase de pruebas | camelCase | testGuestCannotSeeArticle | |
| Variable | camelCase | $articulosConAutor | |
| Colección | descriptiva, plural | $usuariosActivos = Usuario::active()->get() | |
| Objeto | descriptivo, singular | $usuarioActivo = Usuario::active()->first() | |
| Índice de archivos de configuración y lenguaje | snake_case | articulos_habilitados | |
| Vistas | kebab-case | show-filtered.blade.php | |
| Configuración | snake_case | google_calendar.php | |
| Contrato (interface) | adjetivo o sustantivo | AuthenticationInterface | |
| Trait | adjetivo | Notifiable |
Utiliza sintaxis cortas y legibles siempre que sea posible
Malo:
$request->session()->get('cart'); $request->input('name');
Bueno:
session('cart'); $request->name;
Más ejemplos
| Sintaxis común | Sintaxis corta y legible |
|---|---|
Session::get('cart') |
session('cart') |
$request->session()->get('cart') |
session('cart') |
Session::put('cart', $data) |
session(['cart' => $data]) |
$request->input('name'), Request::get('name') |
$request->name, request('name') |
return Redirect::back() |
return back() |
is_null($object->relation) ? null : $object->relation->id |
optional($object->relation)->id |
return view('index')->with('title', $title)->with('client', $client) |
return view('index', compact('title', 'client')) |
$request->has('value') ? $request->value : 'default'; |
$request->get('value', 'default') |
Carbon::now(), Carbon::today() |
now(), today() |
App::make('Class') |
app('Class') |
->where('column', '=', 1) |
->where('column', 1) |
->orderBy('created_at', 'desc') |
->latest() |
->orderBy('age', 'desc') |
->latest('age') |
->orderBy('created_at', 'asc') |
->oldest() |
->select('id', 'name')->get() |
->get(['id', 'name']) |
->first()->name |
->value('name') |
Utiliza contenedores IoC o fachadas en lugar de new Class
La sintaxis new Class crea acoplamientos estrechos y complica las pruebas. Utiliza contenedores IoC o fachadas en lugar de ello.
Malo:
$user = new User; $user->create($request->validated());
Bueno:
public function __construct(User $user) { $this->user = $user; } .... $this->user->create($request->validated());
No saques información directamente del archivo .env
En lugar de ello, pasa la información a un archivo de configuración y luego utiliza el ayudante config() para obtener la información en tu aplicación.
Malo:
$apiKey = env('API_KEY');
Bueno:
// config/api.php 'key' => env('API_KEY'), // Utiliza la información $apiKey = config('api.key');
Guarda las fechas en los formatos estándares. Utiliza los accessors y mutators para modificar el formato
Malo:
{{ Carbon::createFromFormat('Y-d-m H-i', $object->ordered_at)->toDateString() }}
{{ Carbon::createFromFormat('Y-d-m H-i', $object->ordered_at)->format('m-d') }}Bueno:
// Modelo protected $dates = ['ordered_at', 'created_at', 'updated_at']; public function getSomeDateAttribute($date) { return $date->format('m-d'); } // Vista {{ $object->ordered_at->toDateString() }} {{ $object->ordered_at->some_date }}
Otras buenas prácticas
No coloques ningún tipo de lógica en los archivos de rutas.
Minimiza el uso de PHP vanilla en las plantillas Blade.
Fuente: GitHub
