PHP_CodeSniffer ile PHP Kod Kalitesi Denetimi

Kod kalitesi meselesi her zaman biraz felsefi bir tartışmaya dönüşür ekiplerde. “Çalışıyor mu? Çalışıyorsa tamam.” diyenler vardır, bir de “çalışması yetmez, okunabilir ve tutarlı olması lazım” diyenler. Ben ikinci kamptayım ve yıllarca PHP projelerinde yaşadığım acılar beni bu noktaya getirdi. Bu yazıda PHP_CodeSniffer’ı hem kurulum hem de gerçek dünya kullanımı açısından ele alacağım.

PHP_CodeSniffer Nedir ve Neden Kullanmalısınız?

PHP_CodeSniffer, PHP kodunuzun belirli kodlama standartlarına uyup uymadığını denetleyen bir statik analiz aracıdır. İki temel binary ile gelir: phpcs (PHP Code Sniffer, denetim yapar) ve phpcbf (PHP Code Beautifier and Fixer, otomatik düzeltme yapar).

Aracın temel gücü, birden fazla kodlama standardını desteklemesinden gelir. PSR-1, PSR-2, PSR-12, PEAR, Squiz, Zend gibi hazır standartları kullanabilir ya da kendi özel kurallarınızı tanımlayabilirsiniz. Bir ekiple çalışıyorsanız ve herkesin farklı bir editör, farklı bir format anlayışı varsa, PHP_CodeSniffer bu kaosun önüne geçen en pratik araçtır.

Bir projede 6 geliştiricinin 6 farklı stil kullandığını, bazılarının tab kullandığını bazılarının 2 boşluk bazılarının 4 boşluk kullandığını, kiminin açılış parantezini aynı satıra koyduğunu kiminin yeni satıra koyduğunu gördüm. Bu tür projelerde git diff çıktısı gerçek değişikliklerden çok format farklarıyla dolu olur. PHP_CodeSniffer bu problemi kökten çözer.

Kurulum

Composer ile Kurulum (Önerilen Yöntem)

Proje bazlı kurulum için Composer kullanmak en temiz yaklaşım:

# Sadece geliştirme bağımlılığı olarak ekle
composer require --dev squizlabs/php_codesniffer

# Kurulumu doğrula
./vendor/bin/phpcs --version
./vendor/bin/phpcbf --version

Global kullanım için:

composer global require squizlabs/php_codesniffer

# Global Composer bin dizininin PATH'te olduğundan emin ol
export PATH="$PATH:$HOME/.composer/vendor/bin"

# Kalıcı hale getirmek için
echo 'export PATH="$PATH:$HOME/.composer/vendor/bin"' >> ~/.bashrc
source ~/.bashrc

PHAR ile Kurulum

Bazı durumlarda Composer kullanmak istemezsiniz, örneğin CI/CD pipeline’larında bağımlılıkları minimize etmek istiyorsanız:

# PHAR dosyasını indir
curl -OL https://squizlabs.github.io/PHP_CodeSniffer/phpcs.phar
curl -OL https://squizlabs.github.io/PHP_CodeSniffer/phpcbf.phar

# Çalıştırılabilir yap
chmod +x phpcs.phar phpcbf.phar

# Sistem geneline taşı
sudo mv phpcs.phar /usr/local/bin/phpcs
sudo mv phpcbf.phar /usr/local/bin/phpcbf

# Test et
phpcs --version

Temel Kullanım

Kurulum tamamlandıktan sonra en basit haliyle bir dosyayı veya dizini tarayabilirsiniz:

# Tek dosya denetle
phpcs --standard=PSR12 src/MyClass.php

# Tüm src dizinini denetle
phpcs --standard=PSR12 src/

# Mevcut standartları listele
phpcs -i

# Daha ayrıntılı çıktı için
phpcs --standard=PSR12 -v src/

# Hata sayısını görmek için özet rapor
phpcs --standard=PSR12 --report=summary src/

Çıktı şöyle görünür:

FILE: /var/www/html/src/UserController.php
----------------------------------------------------------------------
FOUND 3 ERRORS AND 2 WARNINGS AFFECTING 4 LINES
----------------------------------------------------------------------
  15 | ERROR   | [x] Expected 1 space after closing parenthesis;
     |         |     found 0
  23 | ERROR   | [ ] Line exceeds 120 characters; contains 145 characters
  31 | WARNING | [ ] Possible parse error: function defined
----------------------------------------------------------------------
PHPCBF CAN FIX THE 1 MARKED SNIFF VIOLATIONS AUTOMATICALLY
----------------------------------------------------------------------

[x] işareti, phpcbf tarafından otomatik düzeltilebilecek sorunları gösterir. [ ] işareti ise manuel müdahale gerektiren sorunları belirtir.

phpcs.xml ile Proje Konfigürasyonu

Her seferinde komut satırında parametre vermek zahmetli ve hata yapmaya açık. Proje kökünde bir phpcs.xml ya da .phpcs.xml dosyası oluşturmak çok daha temiz bir yaklaşım:

<?xml version="1.0"?>
<ruleset name="Proje Kodlama Standartları">
    <description>Şirket içi PHP kodlama standartları</description>

    <!-- Taranacak dizinler -->
    <file>src</file>
    <file>tests</file>
    <file>app</file>

    <!-- Hariç tutulacak dizinler -->
    <exclude-pattern>*/vendor/*</exclude-pattern>
    <exclude-pattern>*/node_modules/*</exclude-pattern>
    <exclude-pattern>*/storage/*</exclude-pattern>
    <exclude-pattern>*/bootstrap/cache/*</exclude-pattern>
    <exclude-pattern>*.blade.php</exclude-pattern>

    <!-- Temel standart -->
    <rule ref="PSR12"/>

    <!-- Satır uzunluğu: PSR-12 120 karakter önerir -->
    <rule ref="Generic.Files.LineLength">
        <properties>
            <property name="lineLimit" value="120"/>
            <property name="absoluteLineLimit" value="0"/>
        </properties>
    </rule>

    <!-- Yorum bloklarını kontrol et -->
    <rule ref="Generic.Commenting.Todo.CommentFound">
        <message>TODO yorum bulundu: %s</message>
        <type>warning</type>
    </rule>

    <!-- Bu kuralı devre dışı bırak -->
    <rule ref="PSR1.Methods.CamelCapsMethodName.NotCamelCaps">
        <exclude-pattern>*/tests/*</exclude-pattern>
    </rule>

    <!-- PHP sürümü belirt -->
    <config name="php_version" value="80100"/>

    <!-- Rapor formatı -->
    <arg name="colors"/>
    <arg value="sp"/>
</ruleset>

Bu dosyayı oluşturduktan sonra proje kökünden sadece phpcs yazmanız yeterli, otomatik olarak konfigürasyonu okur.

Otomatik Düzeltme ile phpcbf Kullanımı

phpcbf, otomatik düzeltilebilen sorunları sizin yerinize giderir. Özellikle mevcut bir projeye standart uygulamaya başlarken çok işe yarar:

# Tek dosyayı düzelt
phpcbf --standard=PSR12 src/UserController.php

# Tüm dizini düzelt
phpcbf --standard=PSR12 src/

# Konfigürasyon dosyasını kullanarak
phpcbf

# Kuru çalıştırma (ne yapacağını göster ama değiştirme)
# phpcbf'nin --dry-run seçeneği yok, önce phpcs çalıştır
phpcs --report=diff src/UserController.php

Şunu söylemek lazım: phpcbf sihirli bir araç değil. Kodunuzun mantığını değiştirmez, sadece formatlama ve boşluk gibi mekanik sorunları çözer. Manuel düzeltme gerektiren sorunlar için yine kodunuza bakmanız gerekecek.

Mevcut büyük bir projeye standart uygulamaya başlıyorsanız şu yaklaşımı öneririm:

# Önce ne kadar sorun var görelim
phpcs --standard=PSR12 --report=summary src/ 2>&1 | tee phpcs_initial_report.txt

# Otomatik düzeltilebilenleri temizle
phpcbf --standard=PSR12 src/

# Kalan sorunları görüntüle
phpcs --standard=PSR12 --report=full src/ 2>&1 | tee phpcs_remaining.txt

# Kaç sorun kaldı?
phpcs --standard=PSR12 --report=summary src/

Özel Kurallar ve Standart Genişletme

Projenizin ihtiyaçlarına göre kendi kodlama standartlarınızı oluşturabilirsiniz. Bu, özellikle birden fazla projenin aynı standartlara uyması gerektiğinde kullanışlı:

# Özel standart dizini oluştur
mkdir -p company-standard/Sniffs/Commenting

# Özel bir sniff yazalım
cat > company-standard/Sniffs/Commenting/FunctionCommentSniff.php << 'EOF'
<?php

class Company_Sniffs_Commenting_FunctionCommentSniff implements PHP_CodeSniffer_Sniff
{
    public function register()
    {
        return [T_FUNCTION];
    }

    public function process(PHP_CodeSniffer_File $phpcsFile, $stackPtr)
    {
        $tokens = $phpcsFile->getTokens();
        $find = PHP_CodeSniffer_Tokens::$methodPrefixes;
        $find[] = T_WHITESPACE;

        $commentEnd = $phpcsFile->findPrevious($find, ($stackPtr - 1), null, true);

        if ($tokens[$commentEnd]['code'] !== T_DOC_COMMENT_CLOSE_TAG) {
            $phpcsFile->addError(
                'Fonksiyon için DocBlock yorumu eksik',
                $stackPtr,
                'Missing'
            );
        }
    }
}
EOF

Standartları miras almak da mümkün. ruleset.xml dosyasında birden fazla standardı birleştirebilirsiniz:

<?xml version="1.0"?>
<ruleset name="CompanyStandard">
    <description>Şirket standardı - PSR-12 tabanlı</description>

    <!-- PSR-12'yi miras al -->
    <rule ref="PSR12"/>

    <!-- Squiz'den bazı kuralları ekle -->
    <rule ref="Squiz.Arrays.ArrayDeclaration"/>
    <rule ref="Squiz.Strings.ConcatenationSpacing">
        <properties>
            <property name="spacing" value="1"/>
            <property name="ignoreNewlines" value="true"/>
        </properties>
    </rule>

    <!-- Generic'ten bazı kurallar -->
    <rule ref="Generic.PHP.NoSilencedErrors"/>
    <rule ref="Generic.CodeAnalysis.UnusedFunctionParameter"/>

    <!-- Belirli bir kuralı sadece uyarı olarak işaretle -->
    <rule ref="Generic.Metrics.CyclomaticComplexity">
        <properties>
            <property name="complexity" value="10"/>
            <property name="absoluteComplexity" value="20"/>
        </properties>
        <type>warning</type>
    </rule>
</ruleset>

CI/CD Entegrasyonu

PHP_CodeSniffer’ın gerçek gücü CI/CD pipeline’larıyla entegre edildiğinde ortaya çıkar. Her pull request’te otomatik denetim yapmak, kod kalitesini sürdürülebilir kılar.

GitHub Actions ile Entegrasyon

# .github/workflows/code-quality.yml
name: PHP Code Quality

on:
  push:
    branches: [ main, develop ]
  pull_request:
    branches: [ main, develop ]

jobs:
  phpcs:
    name: PHP CodeSniffer
    runs-on: ubuntu-latest

    steps:
      - name: Kodu çek
        uses: actions/checkout@v3

      - name: PHP kur
        uses: shivammathur/setup-php@v2
        with:
          php-version: '8.1'
          tools: phpcs

      - name: Composer bağımlılıklarını yükle
        run: composer install --prefer-dist --no-progress

      - name: PHP_CodeSniffer çalıştır
        run: ./vendor/bin/phpcs --standard=phpcs.xml --report=checkstyle --report-file=phpcs-report.xml || true

      - name: Sonuçları yükle
        uses: actions/upload-artifact@v3
        if: always()
        with:
          name: phpcs-report
          path: phpcs-report.xml

      - name: Hata durumunda başarısız say
        run: ./vendor/bin/phpcs --standard=phpcs.xml

GitLab CI ile Entegrasyon

# .gitlab-ci.yml içine ekle
code-quality:phpcs:
  stage: test
  image: php:8.1-cli
  before_script:
    - curl -sS https://getcomposer.org/installer | php
    - php composer.phar install --no-dev
    - composer global require squizlabs/php_codesniffer
  script:
    - ~/.composer/vendor/bin/phpcs
      --standard=phpcs.xml
      --report=junit
      --report-file=phpcs-junit.xml
      src/
  artifacts:
    when: always
    reports:
      junit: phpcs-junit.xml
    expire_in: 1 week
  allow_failure: false

Git Pre-commit Hook

CI/CD dışında, yerel geliştirme ortamında commit öncesi kontrol yapmak için pre-commit hook kullanabilirsiniz:

#!/bin/bash
# .git/hooks/pre-commit

echo "PHP_CodeSniffer kontrolü çalışıyor..."

# Staged PHP dosyalarını al
STAGED_FILES=$(git diff --cached --name-only --diff-filter=ACM | grep ".php$")

if [ -z "$STAGED_FILES" ]; then
    echo "Kontrol edilecek PHP dosyası yok."
    exit 0
fi

PHPCS_BIN="./vendor/bin/phpcs"

if [ ! -f "$PHPCS_BIN" ]; then
    echo "phpcs bulunamadı. 'composer install' çalıştırın."
    exit 1
fi

PASS=true

for FILE in $STAGED_FILES; do
    $PHPCS_BIN --standard=phpcs.xml "$FILE"

    if [ $? -ne 0 ]; then
        echo "HATA: $FILE dosyasında kodlama standardı ihlali var"
        PASS=false
    fi
done

if ! $PASS; then
    echo ""
    echo "Commit reddedildi. 'phpcbf' çalıştırarak otomatik düzeltilebilir sorunları giderin."
    echo "Kalan sorunları manuel olarak düzeltin."
    exit 1
fi

echo "Tüm dosyalar standardı karşılıyor."
exit 0

Hook’u aktif hale getirmek için:

chmod +x .git/hooks/pre-commit

Tüm ekip için otomatik kurulum istiyorsanız Composer’ın scripts bölümünü kullanabilirsiniz:

# composer.json'a ekle
# "scripts": {
#   "post-install-cmd": [
#     "cp .github/hooks/pre-commit .git/hooks/pre-commit",
#     "chmod +x .git/hooks/pre-commit"
#   ]
# }
composer run-script post-install-cmd

Gerçek Dünya: Laravel Projesi Örneği

Laravel projelerinde PHP_CodeSniffer kullanırken bazı özel durumlarla karşılaşırsınız. Blade template dosyaları, migration dosyalarındaki özel metot isimleri, test sınıflarındaki snake_case metot isimleri bunlardan bazıları:

<?xml version="1.0"?>
<ruleset name="Laravel Projesi">

    <file>app</file>
    <file>database/seeders</file>
    <file>tests</file>
    <file>routes</file>

    <exclude-pattern>*/vendor/*</exclude-pattern>
    <exclude-pattern>*/storage/*</exclude-pattern>
    <exclude-pattern>*_ide_helper*</exclude-pattern>
    <exclude-pattern>*/database/migrations/*</exclude-pattern>

    <!-- *.blade.php dosyaları hariç tut -->
    <exclude-pattern>*.blade.php</exclude-pattern>

    <rule ref="PSR12">
        <!-- Laravel'de sıkça kullanılan magic metotlar için -->
        <exclude name="PSR1.Methods.CamelCapsMethodName"/>
    </rule>

    <!-- Test metotlarında snake_case izin ver -->
    <rule ref="PSR1.Methods.CamelCapsMethodName.NotCamelCaps">
        <exclude-pattern>*/tests/*</exclude-pattern>
    </rule>

    <!-- Laravel helpers -->
    <rule ref="Generic.PHP.ForbiddenFunctions">
        <properties>
            <property name="forbiddenFunctions" type="array">
                <element key="var_dump" value="null"/>
                <element key="print_r" value="null"/>
                <element key="dd" value="null"/>
                <element key="dump" value="null"/>
            </property>
        </properties>
    </rule>

    <arg name="colors"/>
    <arg value="sp"/>
    <arg name="parallel" value="8"/>
</ruleset>

Dikkat: dd() ve dump() gibi debug fonksiyonlarını forbidden olarak işaretlemek, geliştirme sırasında can sıkıcı olabilir. Bu kuralı sadece main branch için aktif etmek daha pragmatik bir yaklaşım olabilir.

Performans ve Büyük Projeler

Büyük projelerde PHP_CodeSniffer yavaş çalışabilir. Bunu hızlandırmanın birkaç yolu var:

# Paralel işleme aktif et (--parallel seçeneği)
phpcs --parallel=8 src/

# Sadece değişen dosyaları tara (git ile entegre)
git diff --name-only HEAD~1 HEAD | grep '.php$' | xargs phpcs --standard=phpcs.xml

# Cache kullan (tekrar çalıştırmalarda hızlanır)
phpcs --cache --cache-file=.phpcs-cache src/

# Cache dosyasını .gitignore'a ekle
echo ".phpcs-cache" >> .gitignore

Cache kullanımı özellikle CI/CD pipeline’larında fark yaratır. Eğer Composer bağımlılıklarını cache’liyorsanız, phpcs cache dosyasını da dahil edin.

SonarQube ile Entegrasyon

PHP_CodeSniffer çıktısını SonarQube’a beslemek mümkün. Bunun için checkstyle formatında rapor oluşturmanız yeterli:

# Checkstyle formatında rapor oluştur
phpcs --standard=phpcs.xml 
      --report=checkstyle 
      --report-file=build/logs/phpcs.xml 
      src/

# SonarQube scanner konfigürasyonu
cat > sonar-project.properties << 'EOF'
sonar.projectKey=my-php-project
sonar.projectName=My PHP Project
sonar.sources=src
sonar.php.coverage.reportPaths=build/logs/clover.xml
sonar.php.phpcs.reportPaths=build/logs/phpcs.xml
EOF

# SonarQube scanner çalıştır
sonar-scanner

Sonuç

PHP_CodeSniffer, PHP ekosisteminin olmazsa olmaz araçlarından biri. Kurulumu basit, konfigürasyonu esnek, CI/CD entegrasyonu ise oldukça yönetilebilir.

Benim önerim şu: Mevcut bir projeye standart uygulamak için “hepsini bir anda düzeltelim” yaklaşımı çoğu zaman çalışmaz. Önce phpcbf ile otomatik düzeltilebilenleri temizleyin, sonra kalan sorunları --warning-severity=0 ile sadece hataları görerek kademeli olarak çözün. Yeni projelerde ise baştan bir konfigürasyon dosyası oluşturun ve pre-commit hook’u ekiple birlikte aktif hale getirin.

PHP_CodeSniffer tek başına yeterli değil elbette. PHPStan veya Psalm gibi tip kontrolü araçlarıyla, PHP Mess Detector gibi karmaşıklık analizi araçlarıyla bir arada kullanmak, gerçek anlamda kapsamlı bir kod kalitesi sürecini oluşturur. Ama her şeyin başlangıcı tutarlı bir format standardı. Oradan başlayın.

Bir yanıt yazın

E-posta adresiniz yayınlanmayacak. Gerekli alanlar * ile işaretlenmişlerdir