Разработка на Laravel становится максимально эффективной, если использовать автоматизацию на каждом этапе: от настройки окружения до проверки кода и тестирования. В этой статье я покажу, как построить процесс разработки, который снижает количество ручной работы, повышает качество кода и ускоряет выпуск функционала.

Материал рассчитан на разработчиков с опытом работы с Laravel, которые хотят внедрить автоматические проверки, статический анализ, единый стиль кода и готовую сборку Docker Compose для быстрого старта проектов. Я поделюсь своим опытом, конкретными инструментами и практическими примерами.

Настройка Docker compose

Любой проект на Laravel у меня начинается с тщательной настройки Docker Compose. Это позволяет сразу получить полностью рабочее и изолированное окружение для разработки, тестирования и мониторинга. Такой подход минимизирует конфликты зависимостей, ускоряет старт проекта и обеспечивает стабильную работу всех сервисов.

В моей сборке обычно используются следующие сервисы:

Сервис

Назначение

php-fpm

Обработка PHP-запросов приложения

PostgreSQL

Реляционная база данных

Grafana

Визуализация метрик и логов

Loki

Централизованное логирование

pgAdmin

Веб-интерфейс для управления PostgreSQL

Redis

Кэш и очереди Laravel

RedisInsight

Веб-мониторинг Redis

Queue

Обработка фоновых задач через php artisan queue:work

Каждый сервис разворачивается в отдельном контейнере, что позволяет:

  • гибко настраивать окружение;

  • управлять зависимостями независимо друг от друга;

  • обновлять компоненты без простоя остальных сервисов.

Такой подход делает разработку, поддержку и масштабирование проекта удобными и безопасными.

Совет: для быстрого старта можно использовать готовый docker-compose.yml, который сразу поднимает все сервисы в изолированном окружении и настраивает базовые параметры для Laravel.

services:
    app:
        build: .
        container_name: pet
        user: root
        depends_on:
            - pgdb
            - redis
            - loki
        env_file:
            - .env
        working_dir: /var/www/
        volumes:
            - .:/var/www
        networks:
            - pet
        dns:
            - 8.8.8.8
            - 1.1.1.1

    pgdb:
        container_name: pgdb
        image: postgres
        tty: true
        restart: always
        environment:
            - POSTGRES_DB=${DB_DATABASE}
            - POSTGRES_USER=${DB_USERNAME}
            - POSTGRES_PASSWORD=${DB_PASSWORD}
        ports:
            - ${PGDB_PORT}
        volumes:
            - ./docker/postgres:/var/lib/postgresql/data
        networks:
            - pet

    nginx:
        image: nginx:latest
        container_name: nginx
        restart: unless-stopped
        ports:
            - ${NGINX_PORT}
            - "443:443"
        volumes:
            - .:/var/www
            - ./docker/nginx:/etc/nginx/conf.d
            - /etc/letsencrypt:/etc/letsencrypt:ro
        environment:
            - TZ=${SYSTEM_TIMEZONE}
        depends_on:
            - pgdb
            - app
            - pgadmin
        networks:
            - pet

    pgadmin:
        image: dpage/pgadmin4:latest
        restart: always
        depends_on:
            - pgdb
        environment:
            - PGADMIN_DEFAULT_EMAIL=${PGADMIN_EMAIL}
            - PGADMIN_DEFAULT_PASSWORD=${PGADMIN_PASSWORD}
        ports:
            - ${PGADMIN_PORT}
        networks:
            - pet

    redis:
        image: redis:latest
        container_name: redis
        restart: always
        ports:
            - ${REDIS_PORT}
        environment:
            - REDIS_PASSWORD=${REDIS_PASSWORD}
        command: ["redis-server", "--requirepass", "${REDIS_PASSWORD}"]
        networks:
            - pet

    redisinsight:
        image: redislabs/redisinsight:latest
        container_name: redisinsight
        ports:
            - ${REDISINSIGHT_PORT}
        volumes:
            - ./docker/redisinsight:/db
        restart: always
        networks:
            - pet

    grafana:
        image: grafana/grafana:latest
        container_name: grafana
        user: "472"
        ports:
            - ${GRAFANA_PORT}
        environment:
            - GF_SECURITY_ADMIN_USER=${GRAFANA_USER}
            - GF_SECURITY_ADMIN_PASSWORD=${GRAFANA_PASSWORD}
        volumes:
            - ./docker/grafana:/var/lib/grafana
        depends_on:
            - loki
        networks:
            - pet

    queue:
        build: .
        image: docker_template:latest
        container_name: laravel_queue
        restart: always
        depends_on:
            - app
            - redis
        env_file:
            - .env
        working_dir: /var/www
        volumes:
            - .:/var/www
        command: php artisan queue:work --sleep=3 --tries=3 --timeout=90
        networks:
            - pet
        dns:
            - 8.8.8.8
            - 1.1.1.1

    loki:
        image: grafana/loki:latest
        container_name: loki
        ports:
            - ${LOKI_PORT}
        networks:
            - pet

volumes:
    pgdata:
networks:
    pet:
        driver: bridge

Под каждый сервис я разворачиваю отдельный контейнер, что позволяет гибко настраивать окружение, управлять зависимостями и обновлять компоненты независимо друг от друга. Такой подход делает разработку и поддержку проекта более удобной и масштабируемой.

Поддержка Code Style с Laravel Pint

Для соблюдения PSR-12 стандартов в проектах на Laravel я использую пакет laravel/pint. Этот инструмент выполняет статический анализ кода и автоматически форматирует файлы PHP в соответствии с установленными правилами.

Pint интегрируется в процесс разработки и позволяет:

  • запускать проверки при коммитах;

  • автоматически исправлять ошибки форматирования;

  • ускорять приведение кода к единому стилю.

Такой подход снижает вероятность появления разрозненного или неаккуратного кода и экономит время на ручные исправления.

Пример конфигурации pint.json для проекта:

{
    "preset": "psr12",
    "exclude": [
        "vendor",
        "storage",
        "node_modules",
        "bootstrap/cache"
    ],
    "rules": {
        "array_syntax": {
            "syntax": "short"
        },
        "binary_operator_spaces": {
            "default": "single_space"
        },
        "braces": true,
        "class_attributes_separation": {
            "elements": {
                "const": "one",
                "method": "one",
                "property": "one"
            }
        },
        "no_unused_imports": true,
        "ordered_imports": true,
        "phpdoc_separation": true,
        "phpdoc_align": true,
        "single_quote": true,
        "ternary_to_null_coalescing": true,
        "trailing_comma_in_multiline": {
            "after_heredoc": true
        },
        "types_spaces": {
            "space": "none"
        },
        "phpdoc_no_empty_return": false,
        "no_superfluous_phpdoc_tags": false,
        "concat_space": {
            "spacing": "one"
        }
    }
}

Эта конфигурация позволяет:

  • поддерживать чистоту и единый стиль кода;

  • стандартизировать форматирование массивов, операторов, скобок, импортов и PHPDoc;

  • исключать лишние импорты и автоматически выравнивать документацию.

Совет: запуск Pint перед коммитом помогает всегда поддерживать код в правильном стиле, не тратя время на исправления после ревью.

Статический анализ кода с PHPStan и Larastan

Для выявления ошибок и потенциальных багов в проектах на Laravel я использую комбинацию phpstan/phpstan и nunomaduro/larastan. Эти инструменты выполняют статический анализ кода и помогают обнаруживать:

  • неправильное использование типов;

  • недостающие проверки;

  • потенциальные ошибки на ранней стадии разработки.

Пример конфигурации phpstan.neon для проекта:

parameters:
    level: 6
    paths:
        - app
        - routes
    excludePaths:
        - vendor
        - storage
        - bootstrap

    errorFormat: table
    checkMissingVarTagTypehint: false
    inferPrivatePropertyTypeFromConstructor: true

    ignoreErrors:
        - identifier: missingType.iterableValue
        - identifier: missingType.generics
        - '#referenced with incorrect case#'

includes:
    - vendor/phpstan/phpstan/conf/bleedingEdge.neon

Основные преимущества использования PHPStan + Larastan:

  • ошибки выявляются ещё до запуска приложения;

  • повышается стабильность и качество кода;

  • интеграция в процесс разработки минимизирует риск появления багов в продакшене.

Автоматические проверки с Git Hooks и Shell-скриптами

Для поддержания качества кода я использую Git Hooks, которые автоматически проверяют код перед коммитом и пушем. Все проверки вынесены в отдельные shell-скрипты, что позволяет гибко настраивать их для разных проектов.

Основные подходы:

1. Pre-commit: проверка изменённых файлов

  • Проверяются только новые или изменённые файлы, что ускоряет процесс;

  • Скрипты запускают Pint и PHPStan, автоматически исправляют стиль и выявляют ошибки;

  • Если проблем нет, коммит продолжается без задержек.

2. Постепенное исправление старых ошибок

  • Для старых проектов скрипты проверяют, что количество ошибок в файле уменьшилось хотя бы на 1–2 по сравнению с предыдущим коммитом;

  • Такой подход позволяет внедрять проверки без блокировки разработки.

3. Проверка наличия тестов для классов

4. Проверка работы Docker-сборки

Совет: интегрируйте эти скрипты с самого начала проекта, чтобы автоматизация стала частью привычного рабочего процесса.

Все актуальные скрипты и примеры можно посмотреть в репозитории:
https://github.com/prog-time/git-hooks

Shell скрипт для работы с PHPStan

Пример работы PHPStan
Пример работы PHPStan
#!/bin/bash

COMMAND="$1"  # commit или push

# ПРОВЕРЯЕМ НОВЫЕ ФАЙЛЫ
if [ "$COMMAND" = "commit" ]; then
    # только новые файлы (статус A = Added)
    NEW_FILES=$(git diff --cached --name-only --diff-filter=A | grep '\.php$')

    if [ -z "$NEW_FILES" ]; then
        echo "✅ Нет новых PHP-файлов. Пропускаем проверку PHPStan для новых файлов."
    else
        echo "? Проверка PHPStan только для новых файлов..."
        ./vendor/bin/phpstan analyse --no-progress --error-format=table $NEW_FILES
        if [ $? -ne 0 ]; then
          echo "❌ НОВЫЕ ФАЙЛЫ! PHPStan нашёл ошибки типизации (ОБЯЗАТЕЛЬНО)"
          exit 1
        fi
    fi
fi
# ===============

# ===============
# ПРОВЕРЯЕМ ИЗМЕНЕННЫЕ ФАЙЛЫ

BASELINE_FILE=".phpstan-error-count.json"
BLOCK_COMMIT=0

if [ "$COMMAND" = "commit" ]; then
    ALL_FILES=$(git diff --cached --name-only --diff-filter=ACM | grep '\.php$' || true)
elif [ "$COMMAND" = "push" ]; then
    BRANCH=$(git rev-parse --abbrev-ref HEAD)
    ALL_FILES=$(git diff --name-only origin/$BRANCH --diff-filter=ACM | grep '\.php$' || true)
else
    echo "Неизвестная команда: $COMMAND"
    exit 1
fi

if [ ! -f "$BASELINE_FILE" ]; then
    echo "{}" > "$BASELINE_FILE"
fi

if [ -z "$ALL_FILES" ]; then
  echo "✅ [PHPStan] Нет PHP-файлов для проверки."
  exit 0
fi

echo "? [PHPStan] Проверка файлов"

for FILE in $ALL_FILES; do
    echo "? Проверка: $FILE"

    ERR_NEW=$(vendor/bin/phpstan analyse --error-format=raw --no-progress "$FILE" 2>/dev/null | grep -c '^')
    ERR_OLD=$(jq -r --arg file "$FILE" '.[$file] // empty' "$BASELINE_FILE")

    if [ -z "$ERR_OLD" ]; then
        echo "? Файл ранее не проверялся. В нём $ERR_NEW ошибок."
        ERR_OLD=$ERR_NEW
    fi

    TARGET=$((ERR_OLD - 1))
    [ "$TARGET" -lt 0 ] && TARGET=0

    if [ "$ERR_NEW" -le "$TARGET" ]; then
        echo "✅ Улучшено: было $ERR_OLD, стало $ERR_NEW"
        jq --arg file "$FILE" --argjson errors "$ERR_NEW" '.[$file] = $errors' "$BASELINE_FILE" > "$BASELINE_FILE.tmp" && mv "$BASELINE_FILE.tmp" "$BASELINE_FILE"
    else
        echo "❌ Ошибок: $ERR_NEW (нужно ≤ $TARGET)"
        vendor/bin/phpstan analyse --no-progress --error-format=table "$FILE"
        jq --arg file "$FILE" --argjson errors "$ERR_OLD" '.[$file] = $errors' "$BASELINE_FILE" > "$BASELINE_FILE.tmp" && mv "$BASELINE_FILE.tmp" "$BASELINE_FILE"
        BLOCK_COMMIT=1
    fi

    echo "------------------"
done

if [ "$BLOCK_COMMIT" -eq 1 ]; then
    echo "⛔ Коммит остановлен. Уменьши количество ошибок по сравнению с предыдущей версией."
    exit 1
fi

echo "✅ [PHPStan] Проверка завершена успешно."

# ===============

exit 0

Shell скрипт для работы с Pint

Пример работы Pint
Пример работы Pint
#!/bin/bash

COMMAND="$1"  # commit или push

if [ "$COMMAND" = "commit" ]; then
    ALL_FILES=$(git diff --cached --name-only --diff-filter=ACM | grep '\.php$' || true)
elif [ "$COMMAND" = "push" ]; then
    BRANCH=$(git rev-parse --abbrev-ref HEAD)
    ALL_FILES=$(git diff --name-only origin/$BRANCH --diff-filter=ACM | grep '\.php$' || true)
else
    echo "Неизвестная команда: $COMMAND"
    exit 1
fi


if [ -z "$ALL_FILES" ]; then
  echo "✅ [Pint] Нет PHP-файлов для проверки."
  exit 0
fi

echo "? [Pint] Проверка code style..."

vendor/bin/pint --test $ALL_FILES

RESULT=$?

if [ $RESULT -ne 0 ]; then
  echo "❌ Pint нашёл ошибки. Автоисправление..."
  vendor/bin/pint $ALL_FILES
  echo "$ALL_FILES" | xargs git add
  echo "✅ [Pint] Code style исправлен. Перезапусти коммит."
  exit 1
fi

echo "✅ [Pint] Всё чисто."
exit 0

Проверка наличия тестов для классов

Для достижения этой цели я использую скрипт, который проверяет наличие тестов для каждого PHP-класса, добавленного или изменённого в коммите. 

Скрипт получает список изменённых и добавленных PHP-файлов и ищет соответствующий тестовый файл в директории tests

Например, если в проекте есть класс app/Services/UserService.php, скрипт потребует создать файл теста tests/Unit/Services/UserServiceTest.php. Таким образом, любой новый или изменённый класс обязательно должен иметь соответствующий тест, что помогает поддерживать качество и надёжность кода.

Это скрипт, который постоянно дополняется, поэтому актуальную версию вы можете посмотреть здесь - https://github.com/prog-time/git-hooks

Поиск наличия теста для класса
Поиск наличия теста для класса

Проверка работы Docker сборки

Не менее важно регулярно проверять работу Docker сборки. Для этого я создаю отдельный shell-скрипт, который перезапускает все контейнеры и проверяет, что они успешно запустились. Такой подход позволяет убедиться, что изменения в конфигурации или коде не нарушили работу сервисов и приложение корректно поднимается в локальной среде.

Скрипт может автоматически останавливать текущие контейнеры, заново собирать их и запускать в фоне. После запуска выполняется проверка состояния через docker ps или docker compose ps, чтобы убедиться, что все контейнеры находятся в статусе healthy или up.

#!/bin/bash

echo "=== Остановка всех контейнеров ==="
docker-compose down

echo "=== Сборка контейнеров ==="
docker-compose build

echo "=== Запуск контейнеров в фоне ==="
docker-compose up -d

# Пауза для запуска сервисов
echo "=== Ждем 5 секунд для старта сервисов ==="
sleep 5

echo "=== Проверка состояния контейнеров ==="
# Получаем статус всех контейнеров
STATUS=$(docker-compose ps --services --filter "status=running")

if [ -z "$STATUS" ]; then
  echo "Ошибка: ни один контейнер не запущен!"
  exit 1
else
  echo "Запущенные контейнеры:"
  docker-compose ps
fi

# Дополнительно можно проверять HEALTHCHECK каждого контейнера
echo "=== Проверка состояния HEALTH ==="
docker ps --filter "health=unhealthy" --format "table {{.Names}}\t{{.Status}}"

echo "=== Скрипт завершен ==="

exit 0

Таким образом, перед деплоем или важными изменениями можно убедиться, что сборка полностью работоспособна и готова к развёртыванию.

Итог

Автоматизация процесса разработки на Laravel позволяет значительно повысить эффективность команды и качество проекта. Ключевые моменты, которые делают процесс максимально продуктивным:

  • Настройка окружения через Docker Compose — быстрое и стабильное поднятие всех сервисов для разработки, тестирования и мониторинга.

  • Автоматические проверки стиля кода (Pint) — поддержка единого PSR-12 стандарта без ручной работы.

  • Статический анализ кода (PHPStan + Larastan) — выявление ошибок и потенциальных багов на ранних этапах разработки.

  • Git Hooks и shell-скрипты — автоматическая проверка изменённых файлов, контроль наличия тестов и проверка работы Docker-сборки.

  • Покрытие тестами — обязательное тестирование новых и изменённых классов повышает надёжность приложения.

Следуя этим практикам, вы сможете:

  • сократить время на исправление ошибок;

  • поддерживать единый стиль кода;

  • обеспечить стабильность и предсказуемость работы приложения;

  • ускорить внедрение новых функций.

В итоге автоматизация превращает рутинные задачи в прозрачный процесс, позволяя разработчикам сосредоточиться на создании полезного функционала и развитии проекта.

Комментарии (0)