سپهر محسنی

Senior Full Stack Software Engineer

strict-types: مدیریت declare(strict_types=1) در PHP — نمونه‌کار×
AddressC:\PROJECTS\strict-types

strict-types: مدیریت declare(strict_types=1) در PHP

ابزار خط فرمان که declare(strict_types=1) را به فایل‌های PHP اضافه یا از آن‌ها حذف می‌کند و فایل‌های بدون آن را گزارش می‌دهد.

نقش من
نویسنده
فناوری‌ها
PHP
نشانی
packagist.org/packages/sepehr-mohseni/strict-types
کد
github.com/sepehrmohseni/strict-types

PHP به‌طور پیش‌فرض نوع‌های ساده را خودش تبدیل می‌کند. اگر تابعی int بخواهد و شما رشتهٔ "5" به آن بدهید، PHP آن را به عدد تبدیل می‌کند. وقتی بالای یک فایل declare(strict_types=1); بنویسید، فراخوانی‌هایی که از داخل آن فایل انجام می‌شوند این تبدیل را انجام نمی‌دهند و به‌جای آن TypeError می‌دهند. برای روشن کردن این حالت در یک پروژهٔ موجود باید این خط را به صدها فایل اضافه کنید و بعد هم مراقب باشید فایل‌های تازه آن را داشته باشند. strict-types این دو کار را با سه فرمان انجام می‌دهد: add، remove و check.

strict-types یک ابزار خط فرمان PHP است و به Laravel یا فریم‌ورک دیگری وابسته نیست. طبق composer.json با PHP 7.4 و PHP 8 کار می‌کند و وابستگی دیگری ندارد.

برای چه کسانی مناسب است

  • پروژه‌هایی که می‌خواهند strict typing را روی کد موجود روشن کنند.
  • تیم‌هایی که می‌خواهند در CI جلوی اضافه شدن فایل بدون declaration را بگیرند.
  • پروژه‌های Laravel، Symfony، WordPress، Drupal، Yii و CodeIgniter که پوشه‌های vendor و cache و قالب‌هایشان باید کنار گذاشته شوند. برای هر کدام یک preset آماده هست.

پیش از اجرا یک نکته را در نظر بگیرید: کدی که به تبدیل خودکار نوع تکیه کرده، بعد از افزودن declaration ممکن است TypeError بدهد. بعد از add تست‌های پروژه را اجرا کنید.

نصب

composer require --dev sepehr-mohseni/strict-types

فایل اجرایی در vendor/bin/strict-types قرار می‌گیرد.

مثال‌ها

# Add strict_types to files
./vendor/bin/strict-types add src/

# Check coverage
./vendor/bin/strict-types check .

# Remove strict_types
./vendor/bin/strict-types remove src/

# Generate config file
./vendor/bin/strict-types init

فرمان add خط declare(strict_types=1); را درست بعد از تگ <?php در ابتدای فایل می‌گذارد. فایل‌هایی که از قبل این declaration را دارند دست نمی‌خورند. در پایان، جدولی از تعداد فایل‌های پردازش‌شده، تغییرکرده و فایل‌هایی که از قبل declaration داشتند نشان داده می‌شود.

# Laravel project with backup
./vendor/bin/strict-types add --preset=laravel --backup .

# Dry run to preview (-v lists the files)
./vendor/bin/strict-types add --dry-run -v app/

# Exclude files by pattern
./vendor/bin/strict-types add --exclude="*.test.php" src/

# Check and output JSON for CI
./vendor/bin/strict-types check --json src/ > report.json

# Only list files that are missing the declaration
./vendor/bin/strict-types check --missing-only app/

گزینه‌های اصلی:

  • --dry-run تغییرات را نشان می‌دهد ولی فایلی را عوض نمی‌کند.
  • --backup پیش از تغییر هر فایل یک نسخهٔ .bak کنار آن می‌سازد.
  • --no-recursive فقط فایل‌های همان پوشه را پردازش می‌کند و وارد زیرپوشه‌ها نمی‌شود.
  • --preset=<name> یکی از presetهای فریم‌ورک را به کار می‌برد.
  • --exclude=<pattern> فایل‌هایی را که با الگو جور درمی‌آیند کنار می‌گذارد و می‌توانید آن را چند بار تکرار کنید.
  • --json خروجی check را JSON می‌کند.
  • -v جزئیات بیشتر و -q خروجی کمتر نشان می‌دهد.

استفاده در CI

# GitHub Actions
- name: Check strict_types
  run: ./vendor/bin/strict-types check --json . > coverage.json

اگر حتی یک فایل declaration نداشته باشد، check با کد خروج 1 تمام می‌شود و این مرحله در CI شکست می‌خورد. خروجی JSON یک بخش summary با تعداد کل فایل‌ها، تعداد فایل‌های با و بدون declaration، تعداد خطاها و درصد پوشش دارد، و یک فهرست files که وضعیت هر فایل را نشان می‌دهد.

پیکربندی

init فایل .strict-types.json را می‌سازد. اگر این فایل در پوشه‌ای باشد که ابزار را از آن اجرا می‌کنید، خودکار خوانده می‌شود. با --config=path هم می‌توانید فایل دیگری بدهید. همهٔ کلیدها در فایل نمونهٔ .strict-types.json.example مخزن آمده‌اند:

{
    "exclude_patterns": ["*.blade.php", "*.test.php"],
    "exclude_directories": ["vendor", "node_modules", ".git", "storage", "cache"],
    "exclude_files": [],
    "dry_run": false,
    "backup": true,
    "backup_extension": ".bak",
    "recursive": true,
    "declaration_style": "standard",
    "preset": null
}
  • پوشه‌های vendor، node_modules، .git، storage و cache همیشه کنار گذاشته می‌شوند و exclude_directories به این فهرست اضافه می‌شود.
  • declaration_style با مقدار standard خط declare(strict_types=1); و با مقدار spaced خط declare( strict_types = 1 ); را می‌نویسد. همین را با گزینهٔ --style هم می‌توانید بدهید.
  • exclude_files نام فایل‌هایی است که باید کنار گذاشته شوند.

presetها

اگر --preset ندهید، add و check نوع پروژه را از روی فایل‌های پوشه‌ای که به فرمان داده‌اید تشخیص می‌دهند. مثلاً وجود artisan و bootstrap/app.php در آن پوشه یعنی Laravel، پس این تشخیص وقتی کار می‌کند که ریشهٔ پروژه را بدهید. هر preset این موارد را کنار می‌گذارد:

  • preset laravel فایل‌های *.blade.php و پوشه‌های storage، bootstrap/cache و public را کنار می‌گذارد.
  • preset symfony پوشه‌های var و public را کنار می‌گذارد.
  • preset wordpress پوشه‌های wp-admin و wp-includes را کنار می‌گذارد.
  • preset drupal پوشهٔ core را کنار می‌گذارد.
  • preset yii پوشه‌های runtime و web/assets را کنار می‌گذارد.
  • preset codeigniter پوشه‌های system و writable را کنار می‌گذارد.

تفاوت با گزینه‌های دیگر

PHP CS Fixer قانونی به نام declare_strict_types دارد. در مستندات آن، این قانون risky علامت خورده است و گزینهٔ strategy آن سه مقدار add_when_missing، enforce (پیش‌فرض) و remove را می‌پذیرد. اگر PHP CS Fixer را از قبل در پروژه دارید، روشن کردن همین قانون احتمالاً کافی است.

strict-types ابزار جداگانه‌ای است که فقط همین یک کار را انجام می‌دهد و به PHP 7.4 یا بالاتر نیاز دارد. presetهای فریم‌ورک، پشتیبان .bak، گزارش درصد پوشش و خروجی JSON با کد خروج مناسب برای CI را هم دارد.

پرسش‌های پرتکرار

فایل‌هایی که از قبل declaration دارند چه می‌شوند؟

دست نمی‌خورند. add آن‌ها را رد می‌کند و در جدول پایانی جداگانه می‌شمارد.

اجرای add روی پروژه امن است؟

پیش از هر چیز با --dry-run -v ببینید کدام فایل‌ها تغییر می‌کنند و با --backup از فایل‌ها نسخهٔ پشتیبان بگیرید. اگر فایلی با <?php شروع نشود، مثلاً قالبی که با HTML شروع می‌شود، ابزار یک <?php و declaration به ابتدای آن اضافه می‌کند. این‌جور فایل‌ها را با --exclude یا exclude_patterns کنار بگذارید.

چطور تغییرات add را برگردانم؟

اگر با --backup اجرا کرده باشید، نسخهٔ قبلی هر فایل با پسوند .bak کنارش هست. فرمان remove هم declaration را حذف می‌کند، ولی از همهٔ فایل‌های آن مسیر، از جمله فایل‌هایی که پیش از اجرای add هم آن را داشتند. برای برگرداندن دقیق، نسخه‌های .bak یا git را به کار ببرید.

با کدام نسخه‌های PHP کار می‌کند؟

طبق composer.json، PHP 7.4 و همهٔ نسخه‌های PHP 8.

لینک‌ها

‹ برگشت به نمونه‌کارها

SEPEHR.SYS — همه‌چیز در همین مرورگر اجرا می‌شود.