Guía completa de PHP moderno para desarrolladores
Esta guía reúne las prácticas, herramientas y patrones esenciales para escribir aplicaciones PHP seguras, mantenibles y rápidas usando PHP 8+. Incluye estructura de proyecto, ejemplos reales, seguridad, rendimiento, pruebas y despliegue.
1. Filosofía: por qué PHP moderno
PHP ya no es solo scripts rápidos; hoy es un lenguaje orientado a objetos con tipado, atributos, manejo mejorado de errores y ecosistema robusto (Composer, PSR, frameworks, herramientas de análisis estático). Adopta estas prácticas para reducir bugs y mejorar la velocidad de desarrollo.
2. Estructura mínima recomendada
my-app/
├─ src/ # código fuente (PSR-4)
│ ├─ Controller/
│ ├─ Service/
│ ├─ Repository/
│ └─ Domain/
├─ public/ # entrypoint (index.php)
├─ config/
├─ tests/
├─ docker/
├─ composer.json
└─ .env
Por qué: separación clara entre capa HTTP y dominio facilita testing y mantenimiento.
3. Composer y PSR-4 - ejemplo mínimo
{
"name": "acme/my-app",
"autoload": {
"psr-4": { "App\\": "src/" }
},
"require": {
"php": "^8.0"
}
}
4. Ejemplo de código: controlador, servicio y repositorio
Objetivo: mostrar inyección de dependencias, tipado, DTO y manejo de excepciones.
<?php
namespace App\Controller;
use App\Service\UserService;
use Psr\Http\Message\ServerRequestInterface;
use Psr\Http\Message\ResponseInterface;
use Nyholm\Psr7\Response;
final class UserController
{
public function __construct(private UserService $userService) {}
public function getUser(ServerRequestInterface $req): ResponseInterface
{
$id = (int) $req->getQueryParams()['id'] ?? 0;
try {
$dto = $this->userService->findUserById($id);
$body = json_encode($dto);
return new Response(200, ['content-type' => 'application/json'], $body);
} catch (\InvalidArgumentException $e) {
return new Response(400, [], json_encode(['error' => $e->getMessage()]));
}
}
}
<?php
namespace App\Service;
use App\Repository\UserRepository;
use App\Domain\UserDTO;
final class UserService
{
public function __construct(private UserRepository $repo) {}
public function findUserById(int $id): UserDTO
{
if ($id <= 0) {
throw new \InvalidArgumentException('ID inválido');
}
$row = $this->repo->find($id);
if ($row === null) {
throw new \RuntimeException('Usuario no encontrado');
}
return new UserDTO(id: $row['id'], name: $row['name'], email: $row['email']);
}
}
<?php
namespace App\Domain;
readonly class UserDTO
{
public function __construct(
public int $id,
public string $name,
public string $email
) {}
}
5. Acceso a base de datos seguro: PDO y consultas preparadas
<?php
// Repository simple con PDO
final class UserRepository
{
public function __construct(private \PDO $pdo) {}
public function find(int $id): ?array
{
$stmt = $this->pdo->prepare('SELECT id, name, email FROM users WHERE id = :id');
$stmt->execute([':id' => $id]);
$row = $stmt->fetch(PDO::FETCH_ASSOC);
return $row === false ? null : $row;
}
}
Por qué: evita inyección SQL y permite statements reusables.
6. Seguridad esencial
- Escapa siempre la salida: htmlspecialchars(..., ENT_QUOTES | ENT_SUBSTITUTE, 'UTF-8').
- Protege contra CSRF: tokens por sesión/usuario para formularios/requests state-changing.
- Contraseñas: password_hash() y password_verify(), nunca MD5/SHA sin salt manual.
- No uses unserialize() en datos no confiables; prefiere json_decode() o libs seguras.
- Valida entradas en la capa de dominio usando allowlists y filtros; no confíes en el front-end.
- Cabeceras: Content-Security-Policy, X-Frame-Options, X-Content-Type-Options.
- Desactiva display_errors en producción; usa logging y monitoreo.
7. Manejo de errores y logging
Usa excepciones específicas y un middleware/global handler que transforme excepciones a respuestas HTTP seguras. Registra con Monolog y centraliza la política de niveles (DEBUG, INFO, WARNING, ERROR).
8. Testing
Unit tests con PHPUnit y pruebas funcionales/HTTP con frameworks o herramientas como Symfony HttpClient o PestPHP.
// Ejemplo básico PHPUnit
final class UserServiceTest extends \PHPUnit\Framework\TestCase
{
public function testFindUserByIdThrowsOnInvalid(): void
{
$repo = $this->createMock(\App\Repository\UserRepository::class);
$service = new \App\Service\UserService($repo);
$this->expectException(\InvalidArgumentException::class);
$service->findUserById(0);
}
}
9. Rendimiento y optimización
- Activa OPcache en producción (valor más efectivo por bajo coste).
- Cachea respuestas y consultas frecuentes con Redis o Memcached.
- Evita N+1 en consultas; usa joins o repositorios optimizados.
- Profiling: Xdebug, Blackfire o Tideways para localizar hotspots.
- Reduce latencia externa con timeouts, retries y circuit breakers.
- Considera precompilar rutas/mapeos y usar JIT solo si tu carga lo beneficia.
10. CI/CD y análisis estático
- PHPStan o Psalm para análisis estático. Nivel 7+ en PHPStan atrapa muchos bugs antes de ejecutar.
- PHPCS/PSR-12 para estilo. Git hooks con pre-commit para formateo automático.
- Pipeline básico: composer install --no-dev, phpstan analyse, phpunit, build artefacto y desplegar.
11. Despliegue: Docker minimal
# docker/php-fpm/Dockerfile
FROM php:8.1-fpm
RUN apt-get update && apt-get install -y zlib1g-dev git zip unzip
RUN docker-php-ext-install pdo_mysql
COPY --from=composer:2 /usr/bin/composer /usr/bin/composer
WORKDIR /var/www
# docker-compose.yml (extracto)
version: '3.8'
services:
app:
build: ./docker/php-fpm
volumes:
- ./:/var/www
environment:
- APP_ENV=production
web:
image: nginx:alpine
ports:
- '80:80'
volumes:
- ./public:/var/www/public:ro
- ./docker/nginx/conf.d/default.conf:/etc/nginx/conf.d/default.conf:ro
db:
image: mysql:8
environment:
MYSQL_ROOT_PASSWORD: root
12. Buenas prácticas rápidas
- Prefiere tipos (param/return) y readonly cuando tenga sentido.
- Usa DTOs/Value Objects para modelar datos, no arrays asociativos en todo el código.
- Inyección de dependencias en constructor; evita Singletons globales.
- Aplica principio SOLID y separación de responsabilidades.
- Centraliza configuración por entorno (.env + secrets manager en producción).
- Documenta contratos (interfaces) y escribe tests para ellos.
- Monitorea métricas: latencia, errores 5xx, tasa de respuestas cacheadas.
13. Herramientas recomendadas
- Composer, PHPStan, Psalm, PHPUnit, PHPCS, Rector (refactorización automática).
- Monolog para logging; Sentry o Bugsnag para error tracking.
- Redis para caching, queues y sessions cuando haga falta.
- Docker + docker-compose para entornos reproducibles.
Empieza migrando una pieza a la vez: añade tipado, luego análisis estático, después tests y finalmente caching. Si estás listo para el siguiente paso, integra PHPStan/ Psalm en tu pipeline y sube el nivel gradualmente: cada fallo que corriges ahora evita problemas en producción.
Consejo avanzado: complementa PHPStan con un conjunto de reglas personalizadas que reflejen tus invariantes de dominio (por ejemplo: ciertos servicios siempre devuelven DTOs, ciertas clases no deben depender de infraestructura). Esto detecta violaciones arquitectónicas antes de que lleguen al main branch.
¿Quieres comentar?
Inicia sesión con Telegram para participar en la conversación