درآمدی بر آنالیز استاتیک PHP و کاربرد لایبرری اپن‌سورس Phan


Static Analysis به نوعی آنالیز سورس‌کد اشاره دارد که در آن هیچ کدی اجرا نمی‌شود بلکه ابزار مورد استفاده صرفاً به دنبال مشکلات احتمالی سینتکسی و ... می‌گردد. یکی از فیچرهای PHP 7 که آنالیز استاتیک سورس‌کد را تسهیل می‌کند Abstract Syntactic Tree یا به اختصار AST نام دارد که بواسطهٔ آن شمایی منسجم از سورس‌کد مد نظر ترسیم شده سپس به سادگی می‌توان به دنبال الگوهای مشکل‌زا در آن گشت.

اولین کسی باشید که به این سؤال پاسخ می‌دهید

Phan یک لایبرری اپن‌سورس مبتنی بر اِکستنشن AST برای آنالیز استاتیک سورس‌کدهای پی‌اچ‌پی است که توسط خالق اصلی این زبان یعنی Rasmus Lerdorf توسعه داده شده است اما در حال حاضر کانتریبوترهای دیگری نیز به این پروژه ملحق شده‌اند.

راهنمای نصب Phan

به منظور استفاده از این ابزار، نیاز به برخورداری از نسخهٔ PHP 7 و همچنین اِکستنشن AST داریم. ابتدا جهت اطمینان حاصل کردن از نسخهٔ پی‌اچ‌پی خود، کامند زیر را در محیط ترمینال وارد می‌کنیم:

$ php -v
PHP 7.1.28-1+ubuntu18.04.1+deb.sury.org+3 (cli) (built: Apr 10 2019 10:50:29) ( NTS )
Copyright (c) 1997-2018 The PHP Group
Zend Engine v3.1.0, Copyright (c) 1998-2018 Zend Technologies
    with Zend OPcache v7.1.28-1+ubuntu18.04.1+deb.sury.org+3, Copyright (c) 1999-2018, by Zend Technologies

حال نوبت به نصب اِکستنشن AST می‌رسد که برای این منظور نیز می‌توانیم کامند زیر را وارد کنیم:

$ sudo apt-get install php-ast

پس از نصب موفقیت‌آمیز این اِکستنشن، نیاز است تا آن را در فایل php.ini فعال سازیم. به طور مثال، در محیط گنو/لینوکس این فایل در مسیر etc/php/7.1/apache2/ قرار دارد. داخل پوشهٔ apache2 فایلی است تحت عنوان php.ini که پس از باز کردن آن، می‌باید دستور extension=ast.so را داخلش درج نموده و آن را ذخیره ساخت.

اکنون نوبت به نصب خودِ ابزار Phan می‌رسد که برای نصب آن نیاز به ابزار Git داریم. جهت نصب، کامند زیر را در مسیری وارد می‌نماییم که می‌خواهیم پروژه را ایجاد کنیم:

$ git clone https://github.com/phan/phan.git

به طور مثال، در مسیر var/www/ آن را دانلود می‌‌کنیم و خواهیم دید که فولدری تحت عنوان phan در مسیر مذکور ساخته می‌شود. حال با استفاده از دستور cd phan وارد این فولدر شده سپس با استفاده از Composer، وابستگی‌های این ابزار را نصب می‌کنیم:

$ sudo composer install

در صورتی که پروسهٔ‌ نصب با موفقیت پایان پذیرد، حال نیاز داریم تا تنظیمات مد نظر خود را اِعمال کنیم که برای این منظور، فولدری مخفی تحت عنوان phan. داخل این پروژه وجود دارد که داخلش فایلی است تحت عنوان config.php که کلیهٔ تنظیمات مربوط به این لایبرری داخل آن نوشته می‌شود.

اکنون جهت تست،‌ داخل روت پروژه فایلی با نامی دلخواه مثلاً static-analyzer.php ساخته و کدهای زیر را داخل آن درج می‌کنیم:

<?php
$name = 'Behzad';
$lastname;
echo "Hello $name";

سپس دستور زیر را جهت اجرای این ابزار وارد می‌کنیم:

$ php phan static-analyzer.php 

چنانچه در حین تحلیل کردن سورس‌کد هیچ مشکلی رخ ندهد، خروجی زیر در معرض دیدمان قرار خواهد گرفت:

A future major version of Phan will require php-ast 1.0.1+ for AST version 70. php-ast 0.1.6 is installed.
(Set PHAN_SUPPRESS_AST_UPGRADE_NOTICE=1 to suppress this message)
bm.php:3 PhanNoopVariable Unused variable

خط اول و دوم مربوط به هشداری است که در ارتباط با نسخهٔ نصب‌شدهٔ AST اعلام شده است که می‌توان آن را نادیده گرفت اما خط سوم از خروجی فوق مرتبط با مشکلی است که در سورس‌کدمان وجود دارد و آن هم اینکه در کدمان در خط سوم متغیری تعریف شده اما هرگز از آن استفاده نکرده‌ایم.

همچنین جهت آشنایی بیشتر با نحوه‌ٔ کاربرد این ابزار، می‌تواند کامند php phan --help را وارد نمود به طوری که با خروجی زیر مواجه خواهیم شد:

Usage: phan [options] [files...]
 -f, --file-list <filename>
  A file containing a list of PHP files to be analyzed

 -l, --directory <directory>
  A directory that should be parsed for class and
  method information. After excluding the directories
  defined in --exclude-directory-list, the remaining
  files will be statically analyzed for errors.

  Thus, both first-party and third-party code being used by
  your application should be included in this list.

  You may include multiple `--directory DIR` options.

 --exclude-file <file>
  A file that should not be parsed or analyzed (or read
  at all). This is useful for excluding hopelessly
  unanalyzable files.

 -3, --exclude-directory-list <dir_list>
  A comma-separated list of directories that defines files
  that will be excluded from static analysis, but whose
  class and method information should be included.
  (can be repeated, ignored if --include-analysis-directory-list is used)

  Generally, you'll want to include the directories for
  third-party code (such as "vendor/") in this list.

 -I, --include-analysis-file-list <file_list>
  A comma-separated list of files that will be included in
  static analysis. All others won't be analyzed.
  (can be repeated)

  This is primarily intended for performing standalone
  incremental analysis.

 -d, --project-root-directory </path/to/project>
  Hunt for a directory named `.phan` in the provided directory
  and read configuration file `.phan/config.php` from that path.

 -r, --file-list-only
  A file containing a list of PHP files to be analyzed to the
  exclusion of any other directories or files passed in. This
  is unlikely to be useful.

 -k, --config-file
  A path to a config file to load (instead of the default of
  `.phan/config.php`).

 -m <mode>, --output-mode
  Output mode from 'text', 'json', 'csv', 'codeclimate', 'checkstyle', or 'pylint'

 -o, --output <filename>
  Output filename

 --init
   [--init-level=3]
   [--init-analyze-dir=path/to/src]
   [--init-analyze-file=path/to/file.php]
   [--init-no-composer]

  Generates a `.phan/config.php` in the current directory
  based on the project's composer.json.
  The logic used to generate the config file is currently very simple.
  Some third party classes (e.g. in vendor/)
  will need to be manually added to 'directory_list' or excluded,
  and you may end up with a large number of issues to be manually suppressed.
  See https://github.com/phan/phan/wiki/Tutorial-for-Analyzing-a-Large-Sloppy-Code-Base

  [--init-level] affects the generated settings in `.phan/config.php`
    (e.g. null_casts_as_array).
    `--init-level` can be set to 1 (strictest) to 5 (least strict)
  [--init-analyze-dir] can be used as a relative path alongside directories
    that Phan infers from composer.json's "autoload" settings
  [--init-analyze-file] can be used as a relative path alongside files
    that Phan infers from composer.json's "bin" settings
  [--init-no-composer] can be used to tell Phan that the project
    is not a composer project.
    Phan will not check for composer.json or vendor/,
    and will not include those paths in the generated config.
  [--init-overwrite] will allow 'phan --init' to overwrite .phan/config.php.

 -C, --color
  Add colors to the outputted issues. Tested in Unix.
  This is recommended for only the default --output-mode ('text')

 -p, --progress-bar
  Show progress bar

 -q, --quick
  Quick mode - doesn't recurse into all function calls

 -b, --backward-compatibility-checks
  Check for potential PHP 5 -> PHP 7 BC issues

 --target-php-version {7.0,7.1,7.2,7.3,7.4,native}
  The PHP version that the codebase will be checked for compatibility against.
  For best results, the PHP binary used to run Phan should have the same PHP version.
  (Phan relies on Reflection for some param counts
   and checks for undefined classes/methods/functions)

 -i, --ignore-undeclared
  Ignore undeclared functions and classes

 -y, --minimum-severity <level in {0,5,10}>
  Minimum severity level (low=0, normal=5, critical=10) to report.
  Defaults to 0.

 -c, --parent-constructor-required
  Comma-separated list of classes that require
  parent::__construct() to be called

 -x, --dead-code-detection
  Emit issues for classes, methods, functions, constants and
  properties that are probably never referenced and can
  be removed. This implies `--unused-variable-detection`.

 --unused-variable-detection
  Emit issues for variables, parameters and closure use variables
  that are probably never referenced.
  This has a few known false positives, e.g. for loops or branches.

 -j, --processes <int>
  The number of parallel processes to run during the analysis
  phase. Defaults to 1.

 -z, --signature-compatibility
  Analyze signatures for methods that are overrides to ensure
  compatibility with what they're overriding.

 --disable-cache
  Don't cache any ASTs from the polyfill/fallback.

  ASTs from the native parser (php-ast) don't need to be cached.

  This is useful if Phan will be run only once and php-ast is unavailable (e.g. in Travis)

 --disable-plugins
  Don't run any plugins. Slightly faster.

 -P, --plugin <pluginName|path/to/Plugin.php>
  Add a plugin to run. This flag can be repeated.
  (Either pass the name of the plugin or a relative/absolute path to the plugin)

 --strict-method-checking
  Warn if any type in a method invocation's object is definitely not an object,
  or any type in an invoked expression is not a callable.
  (Enables the config option `strict_method_checking`)

 --strict-param-checking
  Warn if any type in an argument's union type cannot be cast to
  the parameter's expected union type.
  (Enables the config option `strict_param_checking`)

 --strict-property-checking
  Warn if any type in a property assignment's union type
  cannot be cast to a type in the property's declared union type.
  (Enables the config option `strict_property_checking`)

 --strict-return-checking
  Warn if any type in a returned value's union type
  cannot be cast to the declared return type.
  (Enables the config option `strict_return_checking`)

 -S, --strict-type-checking
  Equivalent to
  `--strict-method-checking --strict-param-checking --strict-property-checking --strict-return-checking`.

 --use-fallback-parser
  If a file to be analyzed is syntactically invalid
  (i.e. "php --syntax-check path/to/file" would emit a syntax error),
  then retry, using a different, slower error tolerant parser to parse it.
  (And phan will then analyze what could be parsed).
  This flag is experimental and may result in unexpected exceptions or errors.
  This flag does not affect excluded files and directories.

 --allow-polyfill-parser
  If the `php-ast` extension isn't available or is an outdated version,
  then use a slower parser (based on tolerant-php-parser) instead.
  Note that https://github.com/Microsoft/tolerant-php-parser
  has some known bugs which may result in false positive parse errors.

 --force-polyfill-parser
  Use a slower parser (based on tolerant-php-parser) instead of the native parser,
  even if the native parser is available.
  Useful mainly for debugging.

 -s, --daemonize-socket </path/to/file.sock>
  Unix socket for Phan to listen for requests on, in daemon mode.

 --daemonize-tcp-host
  TCP hostname for Phan to listen for JSON requests on, in daemon mode.
  (e.g. 'default', which is an alias for host 127.0.0.1, or `0.0.0.0` for
  usage with Docker). `phan_client` can be used to communicate with the Phan Daemon.

 --daemonize-tcp-port <default|1024-65535>
  TCP port for Phan to listen for JSON requests on, in daemon mode.
  (e.g. 'default', which is an alias for port 4846.)
  `phan_client` can be used to communicate with the Phan Daemon.

 -v, --version
  Print Phan's version number

 -h, --help
  This help information

 --extended-help
  This help information, plus less commonly used flags
  (E.g. for daemon mode)

همان‌طور که ملاحظه می‌شود، آپشن‌های کاربردی در این ابزار امکانی را در اختیار توسعه‌دهندگان می‌گذارند تا بتوانند بسته به نیاز خود نحوهٔ آنالیز سورس‌کد را مدیریت کنند.



بهزاد مرادی