G1 - Guía de estándar de codificación en laravel
#
Objetivo(s)Generar una guía que permita entender con ejemplos los estandartes a utilizar en la codificación de laravel
#
Pre-requisitosN/A
#
Pasos a seguir#
Tabla de contenidoNo 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
#
Principio de propósito únicoLas 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 delgadosColoca 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ónQuita 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 servicioUn 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.
Como estándar el uso de scopes para funciones específicas o de grado mayor seguido de las de menor
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 arreglosEloquent 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 masaMalo:
$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 comentariosMalo:
if (count((array) $builder->getQuery()->joins) > 0)
Mejor:
// Determina si hay alguna uniónif (count((array) $builder->getQuery()->joins) > 0)
Bueno:
if ($this->hasJoins())
#
No coloques JS ni CSS en las plantillas Blade y no coloques HTML en clases de PHPMalo:
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 las herramientas estándar de Laravel aceptadas por la comunidadPrioriza 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 |
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 |
Estructura de la base de datos | Migraciones | Trabajar directamente con la estructura |
Generación de información de prueba | Clases Seeder, Fábricas de modelos, Faker | Crear la información manualmente |
Base de datos | MySQL, PostgreSQL, SQLite, SQL Server | MongoDB |
#
Sigue la convención de Laravel para los nombresSigue 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 |
Sintaxis recomendadas
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 ClassLa 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());
.env
#
No saques información directamente del archivo 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 formatoMalo:
{{ 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:
// Modeloprotected $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ácticasNo coloques ningún tipo de lógica en los archivos de rutas.
Minimiza el uso de PHP vanilla en las plantillas Blade.
#
Autores- Juan Manuel Amador Perez Flores
- Sebastian Gonzalez Tafoya
- José Carlos Pacheco Sánchez
#
Auditoría- Adolfo Acosta Castro
Bitácora de cambios
#
Versión 1.0- Se creó la guía