Inicio / PHP / PHP Profesional: De Intermedio a Avanzado / Estándares PSR

Estándares PSR

PSR-1, PSR-4, PSR-7, PSR-11, PSR-12, PSR-15, PSR-18 y PHP-FIG.

Intermedio
🔒 Solo lectura
📖

Estás en modo lectura

Puedes leer toda la lección, pero para marcar progreso, hacer ejercicios y ganar XP necesitas una cuenta Pro.

Desbloquear por $9/mes

Estándares PSR en PHP

Los PSR (PHP Standards Recommendations) son estándares definidos por PHP-FIG (Framework Interoperability Group) que promueven la interoperabilidad entre frameworks y librerías PHP. Seguir estos estándares hace tu código compatible con el ecosistema PHP moderno.


¿Qué es PHP-FIG?

PHP-FIG es un grupo de representantes de proyectos PHP importantes (Laravel, Symfony, Drupal, Slim, etc.) que colaboran para definir estándares comunes. Los PSR aceptados son recomendaciones que cualquier proyecto puede adoptar.


PSR-1: Basic Coding Standard

Define las reglas básicas de estilo para asegurar interoperabilidad:

<?php
// ✓ Solo etiquetas <?php o <?=
// ✓ Codificación UTF-8 sin BOM
// ✓ Un archivo debe declarar símbolos O ejecutar efectos secundarios, NO ambos

// ✓ Namespaces y clases siguen PSR-4
namespace App\Servicios;

// ✓ Nombres de clase en PascalCase
class GestorUsuarios
{
    // ✓ Constantes en MAYUSCULAS_CON_GUIONES
    const VERSION_MAYOR = 1;
    const TAMANO_PAGINA = 25;

    // ✓ Métodos en camelCase
    public function buscarPorEmail(string $email): ?Usuario
    {
        // implementación
    }

    public function crearUsuario(array $datos): Usuario
    {
        // implementación
    }
}

Reglas principales de PSR-1

Regla Ejemplo correcto
Clases en PascalCase class MiClase
Métodos en camelCase public function miMetodo()
Constantes en UPPER_SNAKE_CASE const MI_CONSTANTE = 1;
Archivos UTF-8 sin BOM Configurar en el editor

PSR-12: Extended Coding Style Guide

PSR-12 extiende PSR-1 con reglas detalladas de formato (reemplaza al antiguo PSR-2):

<?php

declare(strict_types=1);

namespace App\Controladores;

use App\Modelos\Usuario;
use App\Servicios\{Mailer, Logger};
use Psr\Http\Message\ResponseInterface;
use Psr\Http\Message\ServerRequestInterface;

class ControladorUsuario extends ControladorBase implements
    ControladorInterface,
    LogeableInterface
{
    // Visibilidad SIEMPRE declarada
    private const LIMITE = 100;

    // Propiedades con visibilidad explícita
    private Logger $logger;
    protected Mailer $mailer;

    public function __construct(Logger $logger, Mailer $mailer)
    {
        $this->logger = $logger;
        $this->mailer = $mailer;
    }

    // Llave de apertura en nueva línea para clases, en misma línea para métodos NO
    // En PSR-12: llave de apertura en nueva línea para CLASES
    //            llave de apertura en MISMA línea para estructuras de control
    public function listar(
        ServerRequestInterface $request,
        ResponseInterface $response,
        int $pagina = 1
    ): ResponseInterface {
        // Estructuras de control: llave en la misma línea
        if ($pagina < 1) {
            $pagina = 1;
        } elseif ($pagina > 100) {
            $pagina = 100;
        }

        // switch
        switch ($request->getMethod()) {
            case 'GET':
                $usuarios = $this->obtenerUsuarios($pagina);
                break;
            case 'POST':
                $usuarios = $this->crearUsuario($request);
                break;
            default:
                throw new \RuntimeException('Método no soportado');
        }

        // foreach, while, for
        foreach ($usuarios as $usuario) {
            $this->logger->info("Procesando: {$usuario->nombre}");
        }

        return $response;
    }

    // Closures y funciones flecha
    public function filtrar(array $usuarios): array
    {
        return array_filter(
            $usuarios,
            function (Usuario $usuario): bool {
                return $usuario->estaActivo();
            }
        );
    }

    // Try-catch
    public function ejecutar(): void
    {
        try {
            $this->proceso();
        } catch (ExcepcionA | ExcepcionB $e) {
            $this->logger->error($e->getMessage());
        } finally {
            $this->limpiar();
        }
    }
}

Reglas clave de PSR-12

  • 4 espacios de indentación (no tabs).
  • Líneas de máximo 120 caracteres (recomendado 80).
  • Una línea en blanco después de namespace y después del bloque use.
  • Llaves de apertura de clases/métodos en nueva línea.
  • Llaves de apertura de estructuras de control en la misma línea.
  • Visibilidad siempre declarada en propiedades y métodos.

PSR-4: Autoloading Standard

PSR-4 define cómo los nombres de clases completamente cualificados se mapean a rutas de archivo:

Nombre completo: App\Modelos\Usuario
Prefijo:         App\
Directorio base: src/
Ruta del archivo: src/Modelos/Usuario.php

Configuración en composer.json

{
    "autoload": {
        "psr-4": {
            "App\\": "src/",
            "App\\Tests\\": "tests/"
        }
    }
}

Ejemplos de mapeo

Clase completamente cualificada Prefijo Base Archivo
App\Modelos\Usuario App\ src/ src/Modelos/Usuario.php
App\Http\Controladores\HomeController App\ src/ src/Http/Controladores/HomeController.php
App\Tests\Unit\UsuarioTest App\Tests\ tests/ tests/Unit/UsuarioTest.php

PSR-7: HTTP Message Interface

PSR-7 define interfaces para mensajes HTTP (request y response). Es la base de muchos frameworks:

use Psr\Http\Message\ServerRequestInterface;
use Psr\Http\Message\ResponseInterface;

function manejarPeticion(
    ServerRequestInterface $request,
    ResponseInterface $response
): ResponseInterface {
    // Acceder a datos del request
    $metodo = $request->getMethod();              // GET, POST, etc.
    $uri = $request->getUri()->getPath();          // /api/usuarios
    $query = $request->getQueryParams();           // ?page=1
    $cuerpo = $request->getParsedBody();           // Datos POST
    $headers = $request->getHeaders();             // Todos los headers
    $contentType = $request->getHeaderLine('Content-Type');

    // Los objetos son INMUTABLES: withX() devuelve una nueva instancia
    $response = $response
        ->withStatus(200)
        ->withHeader('Content-Type', 'application/json');

    $response->getBody()->write(json_encode([
        'mensaje' => 'Hola mundo',
        'metodo' => $metodo,
    ]));

    return $response;
}

Interfaces principales de PSR-7

Interface Descripción
MessageInterface Base para request y response
RequestInterface Petición HTTP del cliente
ServerRequestInterface Petición del lado del servidor
ResponseInterface Respuesta HTTP
StreamInterface Cuerpo del mensaje
UriInterface URI del request
UploadedFileInterface Archivo subido

PSR-11: Container Interface

PSR-11 define una interfaz estándar para contenedores de inyección de dependencias:

use Psr\Container\ContainerInterface;

class MiContenedor implements ContainerInterface
{
    private array $servicios = [];
    private array $fabricas = [];

    public function registrar(string $id, callable $fabrica): void
    {
        $this->fabricas[$id] = $fabrica;
    }

    public function get(string $id): mixed
    {
        if (!$this->has($id)) {
            throw new ServiceNotFoundException("Servicio '{$id}' no encontrado");
        }

        if (!isset($this->servicios[$id])) {
            $this->servicios[$id] = ($this->fabricas[$id])($this);
        }

        return $this->servicios[$id];
    }

    public function has(string $id): bool
    {
        return isset($this->fabricas[$id]);
    }
}

// Uso
$contenedor = new MiContenedor();
$contenedor->registrar(Logger::class, fn() => new Logger('/var/log/app.log'));
$contenedor->registrar(Mailer::class, fn(ContainerInterface $c) => new Mailer(
    $c->get(Logger::class)
));

$mailer = $contenedor->get(Mailer::class);

PSR-15: HTTP Server Request Handlers

PSR-15 define interfaces para manejadores y middleware HTTP:

use Psr\Http\Message\ResponseInterface;
use Psr\Http\Message\ServerRequestInterface;
use Psr\Http\Server\RequestHandlerInterface;
use Psr\Http\Server\MiddlewareInterface;

// Handler: maneja una petición y produce una respuesta
class HomeHandler implements RequestHandlerInterface
{
    public function handle(ServerRequestInterface $request): ResponseInterface
    {
        $response = new Response();
        $response->getBody()->write('<h1>Bienvenido</h1>');
        return $response;
    }
}

// Middleware: procesa la petición antes/después del handler
class AuthMiddleware implements MiddlewareInterface
{
    public function process(
        ServerRequestInterface $request,
        RequestHandlerInterface $handler
    ): ResponseInterface {
        $token = $request->getHeaderLine('Authorization');

        if (empty($token)) {
            return new Response(401, [], 'No autorizado');
        }

        // Pasar al siguiente handler/middleware
        return $handler->handle($request);
    }
}

class LogMiddleware implements MiddlewareInterface
{
    public function __construct(private Logger $logger) {}

    public function process(
        ServerRequestInterface $request,
        RequestHandlerInterface $handler
    ): ResponseInterface {
        $inicio = microtime(true);

        // Antes del handler
        $this->logger->info("Petición: {$request->getMethod()} {$request->getUri()}");

        $response = $handler->handle($request);

        // Después del handler
        $duracion = microtime(true) - $inicio;
        $this->logger->info("Respuesta: {$response->getStatusCode()} en {$duracion}s");

        return $response;
    }
}

PSR-18: HTTP Client

PSR-18 define una interfaz estándar para clientes HTTP:

use Psr\Http\Client\ClientInterface;
use Psr\Http\Message\RequestInterface;
use Psr\Http\Message\ResponseInterface;

class ServicioApi
{
    public function __construct(
        private ClientInterface $httpClient,
        private RequestFactoryInterface $requestFactory
    ) {}

    public function obtenerUsuarios(): array
    {
        $request = $this->requestFactory
            ->createRequest('GET', 'https://api.ejemplo.com/usuarios')
            ->withHeader('Accept', 'application/json');

        $response = $this->httpClient->sendRequest($request);

        if ($response->getStatusCode() !== 200) {
            throw new \RuntimeException('Error al obtener usuarios');
        }

        return json_decode(
            $response->getBody()->getContents(),
            true
        );
    }
}

PSR-3: Logger Interface

PSR-3 define una interfaz común para loggers:

use Psr\Log\LoggerInterface;
use Psr\Log\LogLevel;

class MiServicio
{
    public function __construct(private LoggerInterface $logger) {}

    public function procesar(): void
    {
        $this->logger->info('Iniciando procesamiento');

        try {
            // Lógica...
            $this->logger->debug('Paso intermedio completado', [
                'registros_procesados' => 150,
            ]);
        } catch (\Exception $e) {
            $this->logger->error('Error en procesamiento', [
                'excepcion' => $e->getMessage(),
                'traza' => $e->getTraceAsString(),
            ]);
        }

        $this->logger->info('Procesamiento finalizado');
    }
}

// Niveles de log (de más a menos severo):
// emergency, alert, critical, error, warning, notice, info, debug

Resumen

  • PHP-FIG define estándares PSR para interoperabilidad entre proyectos PHP.
  • PSR-1 y PSR-12 establecen convenciones de codificación (PascalCase, camelCase, indentación).
  • PSR-4 estandariza el autoloading, mapeando namespaces a directorios.
  • PSR-7 provee interfaces inmutables para mensajes HTTP.
  • PSR-11 define una interfaz común para contenedores de dependencias.
  • PSR-15 estandariza handlers y middleware HTTP del servidor.
  • PSR-18 y PSR-3 homogeneizan los clientes HTTP y loggers respectivamente.
  • Adoptar los PSR hace tu código compatible con el ecosistema PHP y facilita la integración con frameworks y librerías.
🔒

Ejercicio práctico disponible

Mini PSR-7 HTTP Messages y PSR-11 Container

Desbloquear ejercicios
// Mini PSR-7 HTTP Messages y PSR-11 Container
// Desbloquea Pro para acceder a este ejercicio
// y ganar +50 XP al completarlo

function ejemplo() {
    // Tu código aquí...
}

¿Te gustó esta lección?

Con Pro puedes marcar progreso, hacer ejercicios, tomar quizzes, ganar XP y obtener tu constancia.

Ver planes desde $9/mes