Saltar al contenido principal
Un paquete Laravel no acaba con su primer release. Para mantenerlo durante años acompañando las actualizaciones del propio Laravel, PHP y sus dependencias, necesitas ejecutar de forma continua no solo los tests, sino también el análisis estático.
Esta página complementa Desarrollo de paquetes Laravel, Probar paquetes Laravel con Orchestra Testbench y Gestión de la compatibilidad de versiones de paquetes. Añadir análisis estático a tu estrategia de implementación, tests y versiones te dará un paquete mucho más fácil de mantener a largo plazo.

Qué es el análisis estático

El análisis estático detecta incoherencias de tipos y bugs potenciales sin ejecutar el código. En los paquetes Laravel, con tantos mecanismos dinámicos (contenedor de servicios, facades, Eloquent), permite capturar problemas antes de que aparezcan en tiempo de ejecución. Introducir análisis estático aporta principalmente estos beneficios:
  • Mayor seguridad de tipos — detectas argumentos y retornos incorrectos antes de la review.
  • Detección temprana de bugs — llamadas a métodos inexistentes o nullables olvidados salen antes de los tests.
  • Mejor autocompletado del IDE — ajustar PHPDoc y generics eleva la precisión.
  • Mantenimiento estable a largo plazo — es más fácil identificar qué se rompe al actualizar Laravel o PHP.

Configuración de PHPStan

Empieza montando la base de análisis con PHPStan solo. PHPStan 2.x es más estricto con mixed y los nullables que antes, lo que también es una buena excusa para pulir la API pública de tu paquete.
1

Añade PHPStan como dependencia de desarrollo

composer require --dev phpstan/phpstan:^2.0
2

Primero, analiza directamente los directorios objetivo

En los paquetes suele empezarse con src y tests.
vendor/bin/phpstan analyse src tests
3

Fíjalo en un script de Composer

Para usar el mismo comando en CI y en local, define un script en composer.json.
{
    "scripts": {
        "analyse": "phpstan analyse"
    }
}

Configuración de Larastan

Con solo PHPStan, en un paquete Laravel no capturas del todo mecanismos específicos como la resolución vía contenedor, los facades o las relaciones de Eloquent. Por eso se combina con Larastan, la extensión de PHPStan para Laravel.
1

Añade Larastan

Su nombre actual es larastan/larastan. En artículos antiguos verás nunomaduro/larastan.
composer require --dev larastan/larastan:^3.0
2

Si desarrollas un paquete, instala también Testbench

Larastan arranca el contenedor de aplicación de Laravel para resolver tipos. Cuando analizas un paquete Laravel aislado, puede que necesites orchestra/testbench.
composer require --dev orchestra/testbench
3

Carga el extension.neon

En phpstan.neon incluye la configuración de Larastan.
includes:
    - vendor/larastan/larastan/extension.neon

Configuración de phpstan.neon

phpstan.neon es el archivo central donde declaras el nivel de análisis, los paths a analizar, los excluidos y las reglas de excepción. En un paquete Laravel, es más realista empezar con una configuración que puedas ir endureciendo poco a poco que apuntar a la perfección desde el primer día. Ejemplo de phpstan.neon:
includes:
    - vendor/larastan/larastan/extension.neon

parameters:
    level: 5

    paths:
        - src
        - tests

    excludePaths:
        analyse:
            - vendor
            - workbench
            - bootstrap/cache/*

    ignoreErrors:
        -
            message: '#Call to an undefined method Illuminate\\Support\\HigherOrderCollectionProxy::#'
            path: tests/Fixtures/*

Cómo elegir el nivel 0–9

PHPStan permite ir subiendo poco a poco. Es más seguro empezar por el nivel más alto que puedas ejecutar limpiamente y elevarlo a medida que arreglas problemas.
NivelFase adecuada
0–3Reducir primero llamadas a clases, funciones o métodos desconocidos.
4–6Cuadrar los argumentos, retornos y propiedades de la API pública.
7–9Fase de mantenimiento a largo plazo con nullables, uniones y mixed bajo control.
PHPStan 2.x incluye el nivel 10, pero en paquetes Laravel es realista estabilizar primero 5–7 y luego pasar a 8–9. En un paquete nuevo puedes arrancar en un nivel más alto para no tener que volver atrás.

paths

En paths declara explícitamente los directorios a analizar. En los paquetes es útil incluir no solo src, sino también tests, donde suele romperse la tipificación.

excludePaths

Excluye solo lo que no aporta al análisis (archivos generados, caché, el Workbench de verificación…). Si excluyes demasiado, dejarás fuera errores que sí querías detectar.

ignoreErrors

ignoreErrors es el último recurso. Restringe el mensaje con una expresión regular y usa también path para limitar el alcance. Así podrás retirar la excepción con facilidad si Laravel o Larastan mejoran en el futuro.

Anotaciones de tipo habituales

La precisión de PHPStan y Larastan depende mucho de cómo escribas los PHPDoc. En un paquete Laravel merece especialmente la pena cuidar @param, @return, @var y los generics (@template).
<?php

use Illuminate\Database\Eloquent\Model;
use Illuminate\Support\Collection;

/**
 * @template TModel of Model
 */
final class ModelRepository
{
    /**
     * @param class-string<TModel> $modelClass
     */
    public function __construct(private string $modelClass)
    {
    }

    /**
     * @return TModel|null
     */
    public function find(int $id): ?Model
    {
        return $this->modelClass::query()->find($id);
    }

    /**
     * @return Collection<int, TModel>
     */
    public function all(): Collection
    {
        /** @var Collection<int, TModel> $models */
        $models = $this->modelClass::query()->get();

        return $models;
    }
}
En este ejemplo, se le pasa a PHPStan la siguiente información:
  • @param class-string<TModel> — indica que la cadena no es cualquiera, sino el nombre de una clase Model.
  • @return TModel|null — comunica que find() devuelve el tipo concreto del modelo.
  • @var Collection<int, TModel> — explicita los tipos de clave y valor de la colección.
  • @template TModel of Model — expresa un repositorio genérico reutilizable.

Consideraciones específicas de Laravel

La «magia» cómoda de Laravel no se transmite tal cual al análisis estático. Debes añadir información de tipos desde el paquete para que el analizador pueda entenderla.

Facades

En facades personalizadas, describir con PHPDoc los métodos que el usuario invocará mejora tanto el IDE como el análisis estático.
<?php

namespace Vendor\Package\Facades;

use Illuminate\Support\Facades\Facade;

/**
 * @method static string issueToken(int $userId)
 */
class Tokenizer extends Facade
{
    protected static function getFacadeAccessor(): string
    {
        return 'package.tokenizer';
    }
}

Métodos mágicos

Las APIs basadas en __call() o Macroable son cómodas, pero rompen fácilmente los tipos. Si van a formar parte de tu API pública, es más seguro añadir métodos wrapper explícitos con tipos claros de argumentos y retorno.
Si engordas ignoreErrors solo para satisfacer al analizador, ocultarás cómo se rompen realmente tus facades y macros. Prioriza soluciones basadas en métodos explícitos, value objects tipados o mejor PHPDoc.

Tipar modelos Eloquent

Para las relaciones Eloquent y las propiedades dinámicas, combinar @property con generics de relación funciona muy bien.
<?php

namespace Vendor\Package\Models;

use Illuminate\Database\Eloquent\Model;
use Illuminate\Database\Eloquent\Relations\BelongsTo;

/**
 * @property-read Team $team
 */
class Member extends Model
{
    /**
     * @return BelongsTo<Team, self>
     */
    public function team(): BelongsTo
    {
        return $this->belongsTo(Team::class);
    }
}

Ejecución automática en CI

Ejecuta el análisis estático no solo en local, sino también en CI. Sobre todo si el paquete soporta varias versiones de Laravel, aplica la misma idea que la test matrix descrita en Gestión de la compatibilidad de versiones de paquetes: así vigilas compatibilidad y seguridad de tipos a la vez. Ejemplo de .github/workflows/static-analysis.yml:
name: Static analysis

on:
  push:
  pull_request:

jobs:
  static-analysis:
    runs-on: ubuntu-latest

    strategy:
      fail-fast: false
      matrix:
        php: [8.2, 8.3, 8.4]
        laravel: ["^12.0", "^13.0"]
        include:
          - laravel: "^13.0"
            testbench: "^10.0"
          - laravel: "^12.0"
            testbench: "^9.0"

    name: PHP ${{ matrix.php }} - Laravel ${{ matrix.laravel }} - PHPStan

    steps:
      - uses: actions/checkout@v4

      - name: Setup PHP
        uses: shivammathur/setup-php@v2
        with:
          php-version: ${{ matrix.php }}
          extensions: dom, curl, libxml, mbstring, zip
          coverage: none

      - name: Install dependencies
        run: |
          composer require "laravel/framework:${{ matrix.laravel }}" \
                           "orchestra/testbench:${{ matrix.testbench }}" \
                           --no-interaction --no-update
          composer update --prefer-dist --no-interaction

      - name: Run static analysis
        run: vendor/bin/phpstan analyse --error-format=github
En este ejemplo se usa la misma tabla de compatibilidad Laravel / Testbench que la matriz de tests. Monitorizar la misma combinación en tests y análisis estático te ayuda a que no pase desapercibido el caso «se ejecuta pero los tipos están rotos».

Falsos positivos frecuentes y cómo tratarlos

Ante un warning de análisis estático, sospecha primero de la información de tipos: aunque parezca un falso positivo, muchas veces es simplemente PHPDoc insuficiente.

@phpstan-ignore-next-line

Sirve como parche puntual, pero úsalo solo en líneas donde puedas explicar el motivo.
// @phpstan-ignore-next-line El macro de Laravel se registra en tiempo de ejecución
$builder->whereLike('name', $keyword);

ignoreErrors

Considéralo solo cuando el mismo error aparece en varios sitios. Restringe siempre el path y no uses expresiones regulares laxas que silencien todo.
parameters:
    ignoreErrors:
        -
            message: '#Call to an undefined method Illuminate\\Database\\Eloquent\\Builder::whereLike\(\)#'
            path: tests/Fixtures/*

Prioridades al resolver un aviso

  1. Añadir PHPDoc.
  2. Declarar el tipo de retorno de facades y relaciones.
  3. Sustituir mixed por tipos concretos.
  4. Solo entonces, ignorar los falsos positivos que puedas explicar.

Páginas relacionadas

Desarrollo de paquetes Laravel

Fundamentos de implementación de paquetes, incluyendo service providers y recursos publicados.

Probar paquetes Laravel con Orchestra Testbench

Base de pruebas ideal para combinar con análisis estático.

Gestión de la compatibilidad de versiones de paquetes

Estrategia para la tabla de compatibilidad Laravel / PHP y la matriz de GitHub Actions.
Última modificación el 13 de julio de 2026