Skip to main content

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-requisitos#

N/A

Pasos a seguir#

Tabla de contenido#

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;}

🔝 Volver al índice

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();    }}

🔝 Volver al índice

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',        ];    }}

🔝 Volver al índice

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');        }    }}

🔝 Volver al índice

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();}

🔝 Volver al índice

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();

🔝 Volver al índice

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());

🔝 Volver al índice

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

🔝 Volver al índice

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ónif (count((array) $builder->getQuery()->joins) > 0)

Bueno:

if ($this->hasJoins())

🔝 Volver al índice

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.

🔝 Volver al índice

Utiliza las herramientas estándar 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.

TareaHerramienta estándarHerramientas de terceras personas
AutorizaciónPoliciesEntrust, Sentinel y otros paquetes
Compilar assetsLaravel MixGrunt, Gulp, paquetes de terceros
DeploymentLaravel ForgeDeployer y otras soluciones
Unit testingPHPUnit, MockeryPhpspec
Testeo en el navegadorLaravel DuskCodeception
Base de datosEloquentSQL, Doctrine
PlantillasBladeTwig
Trabajar con dataLaravel collectionsArreglos
Validación de formulariosClases RequestPaquetes de terceros, validación en el controlador
AutenticaciónIntegradaPaquetes de terceros, solución propia
Estructura de la base de datosMigracionesTrabajar directamente con la estructura
Generación de información de pruebaClases Seeder, Fábricas de modelos, FakerCrear la información manualmente
Base de datosMySQL, PostgreSQL, SQLite, SQL ServerMongoDB

🔝 Volver al índice

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ómoBuenoMalo
ControladorsingularControladorArticuloControladorArticulos
Rutapluralarticulos/1articulo/1
Nombres de rutassnake_case con notación de puntosusuarios.mostrar_activosusuarios.mostrar-activos, mostrar-usuarios-activos
ModelosingularUsuarioUsuarios
Relaciones hasOne o belongsTosingularcomentarioArticulocomentariosArticulo, comentario_articulo
Cualquier otra relaciónpluralcomentariosArticulocomentarioArticulo, comentarios_articulo
Tablapluralcomentarios_articulocomentario_articulo, comentariosArticulo
Tabla de pivoteNombres de modelos en singular y en orden alfabéticoarticulo_usuariousuario_articulo, articulos_usuarios
Columna de tablasnake_case sin el nombre del modelometa_tituloMetaTitulo; articulo_meta_titulo
Propiedad de modelosnake_case$model->created_at$model->createdAt
Clave foráneaNombre en singular del modelo con el sufijo _idarticulo_idarticuloId, id_articulo, articulos_id
Clave primaria-idid_personalizado
Migración-2017_01_01_000000_create_articles_table2017_01_01_000000_articles
MétodocamelCasetraerTodotraer_todo
Método en controlador de recursostableguardarguardarArticulo
Método en clase de pruebascamelCasetestGuestCannotSeeArticletest_guest_cannot_see_article
VariablecamelCase$articulosConAutor$articulos_con_autor
Coleccióndescriptiva, plural$usuariosActivos = Usuario::active()->get()$activo, $data
Objetodescriptivo, singular$usuarioActivo = Usuario::active()->first()$usuarios, $obj
Índice de archivos de configuración y lenguajesnake_casearticulos_habilitadosarticulosHabilitados; articulos-habilitados
Vistaskebab-caseshow-filtered.blade.phpshowFiltered.blade.php, show_filtered.blade.php
Configuraciónsnake_casegoogle_calendar.phpgoogleCalendar.php, google-calendar.php
Contrato (interface)adjetivo o sustantivoAuthenticationInterfaceAuthenticatable, IAuthentication
TraitadjetivoNotifiableNotificationTrait

🔝 Volver al índice

Sintaxis recomendadas

Sintaxis comúnSintaxis 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->idoptional($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')

🔝 Volver al índice

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());

🔝 Volver al índice

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');

🔝 Volver al índice

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:

// 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 }}

🔝 Volver al índice

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.

🔝 Volver al índice

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