├── .gitignore ├── .styleci.yml ├── .travis.yml ├── LICENSE ├── README.md ├── README_ZH.md ├── bin └── phpmig ├── composer.json ├── doc ├── Migrating from PHP 5.2.x to PHP 5.3.x.md ├── Migrating from PHP 5.3.x to PHP 5.4.x.md ├── Migrating from PHP 5.4.x to PHP 5.5.x.md ├── Migrating from PHP 5.5.x to PHP 5.6.x.md └── migration70.yml ├── phpunit.xml ├── src ├── App.php ├── Changes │ ├── AbstractChange.php │ ├── AbstractIntroduced.php │ ├── AbstractKeywordReserved.php │ ├── AbstractRemoved.php │ ├── ClassTree.php │ ├── RemoveTableItemTrait.php │ ├── v5dot3 │ │ ├── Deprecated.php │ │ ├── IncompByReference.php │ │ ├── IncompCallFromGlobal.php │ │ ├── IncompMagic.php │ │ ├── IncompMagicInvoked.php │ │ ├── IncompMisc.php │ │ ├── IncompReserved.php │ │ ├── Introduced.php │ │ └── Removed.php │ ├── v5dot4 │ │ ├── Deprecated.php │ │ ├── IncompBreakContinue.php │ │ ├── IncompByReference.php │ │ ├── IncompHashAlgo.php │ │ ├── IncompMisc.php │ │ ├── IncompParamName.php │ │ ├── IncompRegister.php │ │ ├── IncompReserved.php │ │ ├── Introduced.php │ │ └── Removed.php │ ├── v5dot5 │ │ ├── Deprecated.php │ │ ├── IncompCaseInsensitive.php │ │ ├── IncompPack.php │ │ ├── Introduced.php │ │ └── Removed.php │ ├── v5dot6 │ │ ├── Deprecated.php │ │ ├── IncompMisc.php │ │ ├── IncompPropertyArray.php │ │ ├── Introduced.php │ │ └── Removed.php │ └── v7dot0 │ │ ├── Deprecated.php │ │ ├── ExceptionHandle.php │ │ ├── ForeachLoop.php │ │ ├── FuncList.php │ │ ├── FuncParameters.php │ │ ├── IntegerOperation.php │ │ ├── Introduced.php │ │ ├── KeywordReserved.php │ │ ├── ParseDifference.php │ │ ├── Removed.php │ │ ├── StringOperation.php │ │ └── SwitchMultipleDefaults.php ├── CheckVisitor.php ├── Logger.php ├── ReduceVisitor.php ├── Sets │ ├── classtree.json │ ├── to53.json │ ├── to54.json │ ├── to55.json │ ├── to56.json │ ├── to70.json │ ├── v53.json │ ├── v54.json │ ├── v55.json │ ├── v56.json │ └── v70.json ├── SymbolTable.php └── Utils │ ├── FunctionListExporter.php │ ├── Logging.php │ ├── Packager.php │ └── ParserHelper.php └── tests ├── Changes ├── AbstractChangeTest.php ├── AbstractIntroducedTest.php ├── AbstractRemovedTest.php ├── v5dot3 │ ├── DeprecatedTest.php │ ├── IncompByReferenceTest.php │ ├── IncompCallFromGlobalTest.php │ ├── IncompMagicInvokedTest.php │ ├── IncompMagicTest.php │ ├── IncompMiscTest.php │ ├── IntroducedTest.php │ └── RemovedTest.php ├── v5dot4 │ ├── DeprecatedTest.php │ ├── IncompBreakContinueTest.php │ ├── IncompByReferenceTest.php │ ├── IncompHashAlgoTest.php │ ├── IncompMiscTest.php │ ├── IncompParamNameTest.php │ ├── IncompRegisterTest.php │ ├── IntroducedTest.php │ └── RemovedTest.php ├── v5dot5 │ ├── DeprecatedTest.php │ ├── IncompCaseInsensitiveTest.php │ ├── IncompPackTest.php │ ├── IntroducedTest.php │ └── RemovedTest.php ├── v5dot6 │ ├── DeprecatedTest.php │ ├── IncompMiscTest.php │ ├── IncompPropertyArrayTest.php │ ├── IntroducedTest.php │ └── RemovedTest.php └── v7dot0 │ ├── DeprecatedTest.php │ ├── ExceptionHandleTest.php │ ├── ForeachLoopTest.php │ ├── FuncListTest.php │ ├── FuncParametersTest.php │ ├── IntegerOperationTest.php │ ├── IntroducedTest.php │ ├── KeywordReservedTest.php │ ├── ParseDifferenceTest.php │ ├── ParserTest.php │ ├── RemovedTest.php │ ├── StringOperationTest.php │ └── SwitchMultipleDefaultsTest.php ├── SymbolTableTest.php ├── Utils └── TestHelper.php └── bootstrap.php /.gitignore: -------------------------------------------------------------------------------- 1 | *.phar 2 | /vendor 3 | composer.lock 4 | php_manual_en.html 5 | php_manual_en.html.gz 6 | src/Changes/Dev.php 7 | src/Sets/dev.json 8 | testspace 9 | -------------------------------------------------------------------------------- /.styleci.yml: -------------------------------------------------------------------------------- 1 | preset: recommended 2 | 3 | risky: false 4 | 5 | disabled: 6 | - align_double_arrow 7 | - blank_line_before_return 8 | - phpdoc_summary 9 | - phpdoc_to_comment 10 | 11 | finder: 12 | name: 13 | - "*.php" 14 | not-name: 15 | - "IncompByReference.php" 16 | -------------------------------------------------------------------------------- /.travis.yml: -------------------------------------------------------------------------------- 1 | language: php 2 | php: 3 | - 5.5 4 | - 5.6 5 | - 7.0 6 | install: 7 | composer install 8 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | Copyright (c) 2014 Yuchen Wang 2 | 3 | Permission is hereby granted, free of charge, to any person obtaining a copy 4 | of this software and associated documentation files (the "Software"), to deal 5 | in the Software without restriction, including without limitation the rights 6 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 7 | copies of the Software, and to permit persons to whom the Software is 8 | furnished to do so, subject to the following conditions: 9 | 10 | The above copyright notice and this permission notice shall be included in all 11 | copies or substantial portions of the Software. 12 | 13 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 14 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 15 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 16 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 17 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 18 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 19 | SOFTWARE. 20 | -------------------------------------------------------------------------------- /bin/phpmig: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env php 2 | = 5.3.0, not '.PHP_VERSION."\n"; 6 | exit(1); 7 | } 8 | 9 | $baseDir = dirname(__DIR__); 10 | $vendorDir = $baseDir . '/vendor'; 11 | 12 | if (!file_exists($vendorDir . '/autoload.php')) { 13 | $vendorDir = dirname(dirname($baseDir)); 14 | } 15 | 16 | require $vendorDir . '/autoload.php'; 17 | 18 | // Make it pass syntax parsing when PHP doesn't support namespace 19 | class_alias('PhpMigration\App', 'PhpMigration_App'); 20 | 21 | $app = new PhpMigration_App(); 22 | $app->run(); 23 | -------------------------------------------------------------------------------- /composer.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "monque/php-migration", 3 | "description": "A static analyzer for PHP version migration and compatibility", 4 | "keywords": ["migration", "analyzer", "compatibility", "version"], 5 | "homepage": "http://github.com/monque/PHP-Migration", 6 | "license": "MIT", 7 | "authors": [ 8 | { 9 | "name": "Yuchen Wang", 10 | "email": "phobosw@gmail.com" 11 | } 12 | ], 13 | "require": { 14 | "php": ">=5.4.0", 15 | "nikic/php-parser": "^2.1.0", 16 | "psr/log": "^1.0" 17 | }, 18 | "autoload": { 19 | "psr-4": { 20 | "PhpMigration\\": "src/" 21 | } 22 | }, 23 | "bin": [ 24 | "bin/phpmig" 25 | ] 26 | } 27 | -------------------------------------------------------------------------------- /doc/Migrating from PHP 5.3.x to PHP 5.4.x.md: -------------------------------------------------------------------------------- 1 | Changes are collected from 2 | - [PHP Manual > Migrating from PHP 5.3.x to PHP 5.4.x](http://php.net/manual/en/migration54.php) 3 | 4 | **NOT ALL** changes will be checked, because some can not be check by a program. 5 | 6 | Lists below describes which will be check and not. 7 | 8 | 9 | ## Overview 10 | - [x] [Backward Incompatible Changes](http://php.net/manual/en/migration54.incompatible.php) 11 | - [~~Ignore~~] [New features](http://php.net/manual/en/migration54.new-features.php) 12 | - [~~Ignore~~] [Changes in SAPI modules](http://php.net/manual/en/migration54.sapi.php) 13 | - [x] [Deprecated features](http://php.net/manual/en/migration54.deprecated.php) 14 | - [~~Ignore~~] [Changed Functions](http://php.net/manual/en/migration54.parameters.php) 15 | - [x] [New Functions](http://php.net/manual/en/migration54.functions.php) 16 | - [x] [New Classes and Interfaces](http://php.net/manual/en/migration54.classes.php) 17 | - [x] [New Methods](http://php.net/manual/en/migration54.methods.php) 18 | - [~~Ignore~~] [Removed Extensions](http://php.net/manual/en/migration54.removed-extensions.php) 19 | - [~~Ignore~~] [Other changes to extensions](http://php.net/manual/en/migration54.extensions-other.php) 20 | - [x] [New Global Constants](http://php.net/manual/en/migration54.global-constants.php) 21 | - [~~Ignore~~] [Changes to INI file handling](http://php.net/manual/en/migration54.ini.php) 22 | - [*Todo*] [Other changes](http://php.net/manual/en/migration54.other.php) 23 | 24 | 25 | ## Backward Incompatible Changes [link](http://php.net/manual/en/migration53.incompatible.php) 26 | 27 | Although most existing PHP 5 code should work without changes, please take 28 | note of some backward incompatible changes: 29 | 30 | #### {~~Ignore~~} [Safe mode](http://php.net/manual/en/features.safe-mode.php) is no longer supported 31 | 32 | Any applications that rely on safe mode may need adjustment, in terms of 33 | security. 34 | 35 | #### {~~Ignore~~} [Magic quotes](http://php.net/manual/en/security.magicquotes.php) has been removed 36 | 37 | Applications relying on this feature may need to be updated, to avoid security 38 | issues. 39 | 40 | [get_magic_quotes_gpc()](http://php.net/manual/en/function.get-magic-quotes-gpc.php) 41 | and 42 | [get_magic_quotes_runtime()](http://php.net/manual/en/function.get-magic-quotes-runtime.php) 43 | now always return `FALSE`. 44 | [set_magic_quotes_runtime()](http://php.net/manual/en/function.set-magic-quotes-runtime.php) 45 | raises an `E_CORE_ERROR` level error on trying to enable 46 | [Magic quotes](http://php.net/manual/en/security.magicquotes.php). 47 | 48 | #### {~~Ignore~~} [register_globals](http://php.net/manual/en/ini.core.php#ini.register-globals) and [register_long_arrays](http://php.net/manual/en/ini.core.php#ini.register-long-arrays) php.ini directives have been removed 49 | > Only `register_long_arrays` will be checked 50 | 51 | #### {~~Ignore~~} mbstring.script_encoding directives have been removed 52 | 53 | Use [zend.script_encoding](http://php.net/manual/en/ini.core.php#ini.zend.script-encoding) instead. 54 | 55 | #### {**Done**} [Call-time pass by reference](http://php.net/manual/en/language.references.pass.php) has been removed 56 | 57 | #### {**Done**} [break](http://php.net/manual/en/control-structures.break.php) and [continue](http://php.net/manual/en/control-structures.continue.php) statements no longer accept variable arguments 58 | 59 | (e.g., `break 1 + foo() * $bar;`). Static arguments still work, such as break 60 | 2;. As a side effect of this change `break 0;` and `continue 0;` are no longer 61 | allowed. 62 | 63 | #### {*Todo*} In the [date and time extension](http://php.net/manual/en/book.datetime.php), the timezone can no longer be set using the TZ environment variable. 64 | 65 | Instead you have to specify a timezone using the 66 | [date.timezone](http://php.net/manual/en/datetime.configuration.php#ini.date.timezone) 67 | php.ini option or 68 | [date_default_timezone_set()](http://php.net/manual/en/function.date-default-timezone-set.php) 69 | function. PHP will no longer attempt to guess the timezone, and will instead 70 | fall back to "UTC" and issue a `E_WARNING`. 71 | 72 | #### {*Todo*} Non-numeric string offsets 73 | 74 | (e.g. `$a['foo']` where $a is a string) 75 | 76 | - return `false` on [isset()](http://php.net/manual/en/function.isset.php) 77 | - return `true` on [empty()](http://php.net/manual/en/function.empty.php), and produce a `E_WARNING` 78 | 79 | if you try to use them. Offsets of types double, bool and null produce a 80 | `E_NOTICE`. Numeric strings (e.g. $a['2']) still work as before. Note that 81 | offsets like '12.3' and '5 foobar' are considered non-numeric and produce a 82 | `E_WARNING`, but are converted to 12 and 5 respectively, for backward 83 | compatibility reasons. 84 | 85 | Note: Following code returns different result. 86 | ``` 87 | $str='abc'; 88 | var_dump(isset($str['x'])); // false for PHP 5.4 or later, but true for 5.3 or less 89 | ``` 90 | 91 | #### {~~Ignore~~} Converting array to string will generate an `E_NOTICE` level error 92 | 93 | but the result of the cast will still be the string "Array". 94 | 95 | #### {~~Ignore~~} Turning `NULL`, `FALSE`, or an empty string into an object by adding a property will now emit an `E_WARNING` level error, instead of `E_STRICT` 96 | 97 | #### {**Done**} Parameter names that shadow super globals now cause a fatal error 98 | 99 | This prohibits code like `function foo($_GET, $_POST) {}`. 100 | 101 | #### {**Done**} The Salsa10 and Salsa20 [hash algorithms](http://php.net/manual/en/book.hash.php) have been removed 102 | 103 | #### {**Done**} [array_combine()](http://php.net/manual/en/function.array-combine.php) now returns array() instead of `FALSE` when two empty arrays are provided as parameters 104 | 105 | #### {**Done**} If you use [htmlentities()](http://php.net/manual/en/function.htmlentities.php) with asian character sets, it works like [htmlspecialchars()](http://php.net/manual/en/function.htmlspecialchars.php) 106 | 107 | this has always been the case in previous versions of PHP, but now an 108 | `E_STRICT` level error is emitted. 109 | 110 | #### {**Done**} The third parameter of [ob_start()](http://php.net/manual/en/function.ob-start.php) has changed 111 | 112 | changed from [boolean](http://php.net/manual/en/language.types.boolean.php) 113 | erase to [integer](http://php.net/manual/en/language.types.integer.php) flags. 114 | Note that code that explicitly set erase to `FALSE` will no longer behave as 115 | expected in PHP 5.4: please follow [this 116 | example](http://php.net/manual/en/function.ob-start.php#function.ob-start.flags-bc) 117 | to write code that is compatible with PHP 5.3 and 5.4. 118 | 119 | #### {**Done**} The following keywords are now [reserved](http://php.net/manual/en/reserved.php) 120 | 121 | and may not be used as names by functions, classes, etc. 122 | 123 | - [trait](http://php.net/manual/en/language.oop5.traits.php) 124 | - [callable](http://php.net/manual/en/language.types.callable.php) 125 | - [insteadof](http://php.net/manual/en/language.oop5.traits.php) 126 | 127 | #### {**Done**} The following functions have been removed from PHP: 128 | 129 | - [define_syslog_variables()](http://php.net/manual/en/function.define-syslog-variables.php) 130 | - [import_request_variables()](http://php.net/manual/en/function.import-request-variables.php) 131 | - [session_is_registered()](http://php.net/manual/en/function.session-is-registered.php) 132 | - [session_register()](http://php.net/manual/en/function.session-register.php) 133 | - [session_unregister()](http://php.net/manual/en/function.session-unregister.php). 134 | 135 | The aliases 136 | - [mysqli_bind_param()](http://php.net/manual/en/function.mysqli-bind-param.php) 137 | - [mysqli_bind_result()](http://php.net/manual/en/function.mysqli-bind-result.php) 138 | - [mysqli_client_encoding()](http://php.net/manual/en/function.mysqli-client-encoding.php) 139 | - [mysqli_fetch()](http://php.net/manual/en/function.mysqli-fetch.php) 140 | - [mysqli_param_count()](http://php.net/manual/en/function.mysqli-param-count.php) 141 | - [mysqli_get_metadata()](http://php.net/manual/en/function.mysqli-get-metadata.php) 142 | - [mysqli_send_long_data()](http://php.net/manual/en/function.mysqli-send-long-data.php) 143 | - mysqli::client_encoding() 144 | - mysqli_stmt::stmt() 145 | -------------------------------------------------------------------------------- /doc/Migrating from PHP 5.4.x to PHP 5.5.x.md: -------------------------------------------------------------------------------- 1 | Changes are collected from 2 | - [PHP Manual > Migrating from PHP 5.4.x to PHP 5.5.x](http://php.net/manual/en/migration55.php) 3 | 4 | **NOT ALL** changes will be checked, because some can not be check by a program. 5 | 6 | Lists below describes which will be check and not. 7 | 8 | 9 | ## Overview 10 | - [x] [Backward Incompatible Changes](http://php.net/manual/en/migration55.incompatible.php) 11 | - [~~Ignore~~] [New features](http://php.net/manual/en/migration55.new-features.php) 12 | - [x] [Deprecated features](http://php.net/manual/en/migration55.deprecated.php) 13 | - [~~Ignore~~] [Changed Functions](http://php.net/manual/en/migration55.changed-functions.php) 14 | - [x] [New Functions](http://php.net/manual/en/migration55.new-functions.php) 15 | - [x] [New Classes and Interfaces](http://php.net/manual/en/migration55.classes.php) 16 | - [x] [New Methods](http://php.net/manual/en/migration55.new-methods.php) 17 | - [~~Ignore~~] [Other changes to extensions](http://php.net/manual/en/migration55.extensions-other.php) 18 | - [x] [New Global Constants](http://php.net/manual/en/migration55.global-constants.php) 19 | - [~~Ignore~~] [Changes to INI file handling](http://php.net/manual/en/migration55.ini.php) 20 | - [~~Ignore~~] [Changes to PHP Internals](http://php.net/manual/en/migration55.internals.php) 21 | 22 | 23 | ## Backward Incompatible Changes [link](http://php.net/manual/en/migration55.incompatible.php) 24 | 25 | Although most existing PHP 5 code should work without changes, please take 26 | note of some backward incompatible changes: 27 | 28 | #### {~~Ignore~~} Windows XP and 2003 support dropped 29 | 30 | Support for Windows XP and 2003 has been dropped. Windows builds of PHP 31 | now require Windows Vista or newer. 32 | 33 | #### {*Todo*} Case insensitivity no longer locale specific 34 | 35 | All case insensitive matching for function, class and constant names is 36 | now performed in a locale independent manner according to ASCII rules. 37 | This improves support for languages using the Latin alphabet with unusual 38 | collating rules, such as Turkish and Azeri. 39 | 40 | 41 | This may cause issues for code that uses case insensitive matches for 42 | non-ASCII characters in multibyte character sets (including UTF-8), such 43 | as accented characters in many European languages. If you have a 44 | non-English, non-ASCII code base, then you will need to test that you are 45 | not inadvertently relying on this behaviour before deploying PHP 5.5 to 46 | production systems. 47 | 48 | #### {**Done**} [pack()](http://php.net/manual/en/function.pack.php) and [unpack()](http://php.net/manual/en/function.unpack.php) changes 49 | 50 | Changes were made to [pack()](http://php.net/manual/en/function.pack.php) and 51 | [unpack()](http://php.net/manual/en/function.unpack.php) to make them more 52 | compatible with Perl: 53 | 54 | - [pack()](http://php.net/manual/en/function.pack.php) now supports the "Z" 55 | format code, which behaves identically to "a". 56 | - [unpack()](http://php.net/manual/en/function.unpack.php) now support the "Z" 57 | format code for NULL padded strings, and behaves as "a" did in previous 58 | versions: it will strip trailing NULL bytes. 59 | - [unpack()](http://php.net/manual/en/function.unpack.php) now keeps trailing 60 | NULL bytes when the "a" format code is used. 61 | - [unpack()](http://php.net/manual/en/function.unpack.php) now strips all 62 | trailing ASCII whitespace when the "A" format code is used. 63 | 64 | Writing backward compatible code that uses the "a" format code with 65 | [unpack()](http://php.net/manual/en/function.unpack.php) requires the use of 66 | [version_compare()](http://php.net/manual/en/function.version-compare.php), due 67 | to the backward compatibility break. 68 | 69 | For example: 70 | ```php 71 | =')) { 77 | $data = unpack('Z5', $packed); 78 | } else { 79 | $data = unpack('a5', $packed); 80 | } 81 | ?> 82 | ``` 83 | 84 | #### {**Done**} self, parent and static are now always case insensitive 85 | 86 | Prior to PHP 5.5, cases existed where the 87 | [self](http://php.net/manual/en/language.oop5.paamayim-nekudotayim.php), 88 | [parent](http://php.net/manual/en/language.oop5.paamayim-nekudotayim.php), and 89 | [static](http://php.net/manual/en/language.oop5.late-static-bindings.php) 90 | keywords were treated in a case sensitive fashion. These have now been 91 | resolved, and these keywords are always handled case insensitively: 92 | `SELF::CONSTANT` is now treated identically to `self::CONSTANT`. 93 | 94 | #### {**Done**} PHP logo GUIDs removed 95 | 96 | The GUIDs that previously resulted in PHP outputting various logos have 97 | been removed. This includes the removal of the functions to return those 98 | GUIDs. The removed functions are: 99 | 100 | - [php_logo_guid()](http://php.net/manual/en/function.php-logo-guid.php) 101 | - php_egg_logo_guid() 102 | - php_real_logo_guid() 103 | - [zend_logo_guid()](http://php.net/manual/en/function.zend-logo-guid.php) 104 | 105 | #### {~~Ignore~~} Internal execution changes 106 | 107 | Extension authors should note that the zend_execute() function can no longer be 108 | overridden, and that numerous changes have been made to the execute_data struct 109 | and related function and method handling opcodes. 110 | 111 | Most extension authors are unlikely to be affected, but those writing 112 | extensions that hook deeply into the Zend Engine should read [the notes on 113 | these changes](http://php.net/manual/en/migration55.internals.php). 114 | 115 | 116 | ## Deprecated features [link](http://php.net/manual/en/migration55.deprecated.php) 117 | 118 | #### {**Done**} [ext/mysql](http://php.net/manual/en/book.mysql.php) deprecation 119 | 120 | The [original MySQL extension](http://php.net/manual/en/book.mysql.php) is now 121 | deprecated, and will generate `E_DEPRECATED` errors when connecting to a 122 | database. Instead, use the [MySQLi](http://php.net/manual/en/book.mysqli.php) 123 | or [PDO_MySQL](http://php.net/manual/en/ref.pdo-mysql.php) extensions. 124 | 125 | #### {**Done**} [preg_replace()](http://php.net/manual/en/function.preg-replace.php) /e modifier 126 | 127 | The [preg_replace()](http://php.net/manual/en/function.preg-replace.php) /e 128 | modifier is now deprecated. Instead, use the 129 | [preg_replace_callback()](http://php.net/manual/en/function.preg-replace-callback.php) 130 | function. 131 | 132 | #### {**Done**} [intl](http://php.net/manual/en/book.intl.php) deprecations 133 | 134 | [IntlDateFormatter::setTimeZoneID()](http://php.net/manual/en/intldateformatter.settimezoneid.php) 135 | and 136 | [datefmt_set_timezone_id()](http://php.net/manual/en/intldateformatter.settimezoneid.php) 137 | are now deprecated. Instead, use the 138 | [IntlDateFormatter::setTimeZone()](http://php.net/manual/en/intldateformatter.settimezone.php) 139 | method and 140 | [datefmt_set_timezone()](http://php.net/manual/en/intldateformatter.settimezone.php) 141 | function, respectively. 142 | 143 | #### {**Done**} [mcrypt](http://php.net/manual/en/book.mcrypt.php) deprecations 144 | 145 | The following functions have been deprecated: 146 | - [mcrypt_cbc()](http://php.net/manual/en/function.mcrypt-cbc.php) 147 | - [mcrypt_cfb()](http://php.net/manual/en/function.mcrypt-cfb.php) 148 | - [mcrypt_ecb()](http://php.net/manual/en/function.mcrypt-ecb.php) 149 | - [mcrypt_ofb()](http://php.net/manual/en/function.mcrypt-ofb.php) 150 | -------------------------------------------------------------------------------- /doc/migration70.yml: -------------------------------------------------------------------------------- 1 | # Migrating from PHP 5.6.x to PHP 7.0.x 2 | # http://php.net/migration70 3 | --- 4 | 5 | Backward incompatible changes: 6 | 7 | # Changes to error and exception handling 8 | - 9 | name: set_exception_handler() is no longer guaranteed to receive Exception objects 10 | status: CHECKED 11 | check: v7dot0/ExceptionHandle 12 | - 13 | name: Internal constructors always throw exceptions on failure 14 | status: IGNORED 15 | - 16 | name: Parse errors throw ParseError 17 | status: IGNORED 18 | - 19 | name: E_STRICT notices severity changes 20 | status: TODO 21 | 22 | # Changes to variable handling 23 | - 24 | name: Changes to the handling of indirect variables, properties, and methods 25 | status: CHECKED 26 | check: v7dot0/ParseDifference 27 | - 28 | name: list() no longer assigns variables in reverse order 29 | status: CHECKED 30 | check: v7dot0/FuncList 31 | - 32 | name: Empty list() assignments have been removed 33 | status: CHECKED 34 | check: v7dot0/FuncList 35 | - 36 | name: list() cannot unpack strings 37 | status: IGNORED 38 | - 39 | name: Array ordering when elements are automatically created during by reference assignments has changed 40 | status: IGNORED 41 | - 42 | name: global only accepts simple variables 43 | status: PARSER 44 | - 45 | name: Parentheses around function arguments no longer affect behaviour 46 | status: IGNORED 47 | 48 | # Changes to foreach 49 | - 50 | name: foreach no longer changes the internal array pointer 51 | status: CHECKED 52 | check: v7dot0/ForeachLoop 53 | - 54 | name: foreach by-value operates on a copy of the array 55 | status: IGNORED 56 | - 57 | name: foreach by-reference has improved iteration behaviour 58 | status: IGNORED 59 | - 60 | name: Iteration of non-Traversable objects 61 | status: IGNORED 62 | 63 | # Changes to integer handling 64 | - 65 | name: Invalid octal literals 66 | status: PARSER 67 | - 68 | name: Negative bitshifts 69 | status: CHECKED 70 | check: v7dot0/IntegerOperation 71 | - 72 | name: Out of range bitshits 73 | status: IGNORED 74 | - 75 | name: Changes to Division By Zero 76 | status: IGNORED 77 | - 78 | name: Changes to Modulus By Zero 79 | status: CHECKED 80 | check: v7dot0/IntegerOperation 81 | 82 | # Changes to string handling 83 | - 84 | name: Hexadecimal strings are no longer considered numeric 85 | status: CHECKED 86 | check: v7dot0/StringOperation 87 | - 88 | name: \u{ may cause errors 89 | status: IGNORED 90 | 91 | # Removed functions 92 | - 93 | name: call_user_method() and call_user_method_array() 94 | status: CHECKED 95 | check: v7dot0/Removed 96 | - 97 | name: All ereg* functions 98 | status: CHECKED 99 | check: v7dot0/Removed 100 | - 101 | name: mcrypt aliases 102 | status: CHECKED 103 | check: v7dot0/Removed 104 | - 105 | name: All ext/mysql functions 106 | status: CHECKED 107 | check: v7dot0/Removed 108 | - 109 | name: All ext/mssql functions 110 | status: CHECKED 111 | check: v7dot0/Removed 112 | - 113 | name: intl aliases 114 | status: CHECKED 115 | check: v7dot0/Removed 116 | - 117 | name: set_magic_quotes_runtime() 118 | status: CHECKED 119 | check: v7dot0/Removed 120 | - 121 | name: set_socket_blocking() 122 | status: CHECKED 123 | check: v7dot0/Removed 124 | - 125 | name: dl() in PHP-FPM 126 | status: CHECKED 127 | check: v5dot3/Removed 128 | - 129 | name: GD Type1 functions 130 | status: CHECKED 131 | check: v7dot0/Removed 132 | 133 | # Removed INI directives 134 | - 135 | name: Removed INI directives 136 | status: INI 137 | 138 | # Other backward incompatible changes 139 | - 140 | name: New objects cannot be assigned by reference 141 | status: PARSER # @see https://github.com/nikic/PHP-Parser/issues/295 142 | - 143 | name: Invalid class, interface and trait names 144 | status: CHECKED 145 | check: v7dot0/KeywordReserved 146 | - 147 | name: ASP and script PHP tags removed 148 | status: PARSER 149 | - 150 | name: Calls from incompatible context removed 151 | status: IGNORED 152 | - 153 | name: yield is now a right associative operator 154 | status: CHECKED 155 | check: v7dot0/ParseDifference 156 | - 157 | name: Functions cannot have multiple parameters with the same name 158 | status: CHECKED 159 | check: v7dot0/FuncParameters 160 | - 161 | name: Functions inspecting arguments report the current parameter value 162 | status: TODO 163 | - 164 | name: Switch statements cannot have multiple default blocks 165 | status: CHECKED 166 | check: v7dot0/SwitchMultipleDefaults 167 | - 168 | name: $HTTP_RAW_POST_DATA removed 169 | status: CHECKED 170 | check: v7dot0/Removed 171 | - 172 | name: \# comments in INI files removed 173 | status: INI 174 | - 175 | name: JSON extension replaced with JSOND 176 | status: IGNORED 177 | - 178 | name: Internal function failure on overflow 179 | status: IGNORED 180 | - 181 | name: Fixes to custom session handler return values 182 | status: IGNORED 183 | 184 | 185 | Deprecated: 186 | - 187 | name: PHP 4 style constructors 188 | status: CHECKED 189 | check: v7dot0/Deprecated 190 | - 191 | name: Static calls to non-static methods 192 | status: TODO 193 | - 194 | name: password_hash() salt option 195 | status: CHECKED 196 | check: v7dot0/Deprecated 197 | - 198 | name: capture_session_meta SSL context option 199 | status: IGNORED 200 | - 201 | name: LDAP deprecations 202 | status: CHECKED 203 | check: v7dot0/Deprecated 204 | 205 | 206 | Changed functions: 207 | - 208 | status: IGNORED 209 | 210 | 211 | New functions, classes, interfaces, constants: 212 | - 213 | status: CHECKED 214 | check: v7dot0/Introduced 215 | 216 | 217 | Changes in SAPI Modules: 218 | - 219 | name: Unqualified listen ports now listen on both IPv4 and IPv6 220 | status: IGNORED 221 | 222 | 223 | Removed Extensions and SAPIs: 224 | - 225 | status: IGNORED 226 | 227 | 228 | Other Changes: 229 | - 230 | name: Loosening Reserved Word Restrictions 231 | status: IGNORED 232 | - 233 | name: Removal of date.timezone Warning 234 | status: IGNORED 235 | 236 | 237 | # vim: set shiftwidth=2 expandtab softtabstop=2: 238 | -------------------------------------------------------------------------------- /phpunit.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | tests 7 | 8 | 9 | 10 | -------------------------------------------------------------------------------- /src/Changes/AbstractChange.php: -------------------------------------------------------------------------------- 1 | visitor = $visitor; 23 | } 24 | 25 | /** 26 | * Quick method to add spot to visitor. 27 | */ 28 | public function addSpot($cate, $identified, $message, $line = null, $file = null) 29 | { 30 | $this->visitor->addSpot( 31 | $cate, 32 | $identified, 33 | $message, 34 | static::$version, 35 | $line, 36 | $file 37 | ); 38 | } 39 | 40 | /** 41 | * Initialization of properties. 42 | */ 43 | public function __construct() 44 | { 45 | } 46 | 47 | /** 48 | * Called before any processing, after __construct(). 49 | */ 50 | public function prepare() 51 | { 52 | } 53 | 54 | /** 55 | * Called after all file have been parsed 56 | * Usually process data collected in traversing, and return. 57 | */ 58 | public function finish() 59 | { 60 | } 61 | 62 | /** 63 | * De-initialization of properties, after finish(). 64 | */ 65 | public function __destruct() 66 | { 67 | } 68 | 69 | /** 70 | * Called before Traverser woking. 71 | */ 72 | public function beforeTraverse(array $nodes) 73 | { 74 | } 75 | 76 | /** 77 | * Called after Traverser woking done. 78 | */ 79 | public function afterTraverse(array $nodes) 80 | { 81 | } 82 | 83 | /** 84 | * Called when Traverser enter a node. 85 | */ 86 | public function enterNode($node) 87 | { 88 | } 89 | 90 | /** 91 | * Called when Traverser leave a node. 92 | */ 93 | public function leaveNode($node) 94 | { 95 | } 96 | } 97 | -------------------------------------------------------------------------------- /src/Changes/AbstractIntroduced.php: -------------------------------------------------------------------------------- 1 | funcTable)) { 30 | $this->funcTable = new SymbolTable($this->funcTable, SymbolTable::IC); 31 | } 32 | if (isset($this->methodTable)) { 33 | $this->methodTable = new SymbolTable($this->methodTable, SymbolTable::IC); 34 | } 35 | if (isset($this->classTable)) { 36 | $this->classTable = new SymbolTable($this->classTable, SymbolTable::IC); 37 | } 38 | if (isset($this->constTable)) { 39 | $this->constTable = new SymbolTable($this->constTable, SymbolTable::CS); 40 | } 41 | } 42 | 43 | public function enterNode($node) 44 | { 45 | // Support the simplest conditional declaration 46 | if ($this->isConditionalFunc($node)) { 47 | $this->condFunc = $this->getConditionalName($node); 48 | } elseif ($this->isConditionalConst($node)) { 49 | $this->condConst = $this->getConditionalName($node); 50 | } 51 | } 52 | 53 | public function leaveNode($node) 54 | { 55 | // Function 56 | if ($this->isNewFunc($node)) { 57 | $this->addSpot('FATAL', true, sprintf('Cannot redeclare %s()', $node->migName)); 58 | 59 | // Method 60 | } elseif ($this->isNewMethod($node, $method_name)) { 61 | $this->addSpot('WARNING', true, sprintf( 62 | 'Method %s::%s() will override built-in method %s()', 63 | $this->visitor->getClassName(), 64 | $node->migName, 65 | $method_name 66 | )); 67 | 68 | // Class, Interface, Trait 69 | } elseif ($this->isNewClass($node)) { 70 | $this->addSpot('FATAL', true, sprintf('Cannot redeclare class "%s"', $node->migName)); 71 | 72 | // Constant 73 | } elseif ($this->isNewConst($node)) { 74 | $constname = $node->args[0]->value->value; 75 | $this->addSpot('WARNING', true, sprintf('Constant "%s" already defined', $constname)); 76 | 77 | // Parameter 78 | } elseif ($this->isNewParam($node)) { 79 | $advice = $this->paramTable->get($node->migName); 80 | $this->addSpot('NEW', false, sprintf('Function %s() has new parameter, %s', $node->migName, $advice)); 81 | } 82 | 83 | // Conditional declaration clear 84 | if ($this->isConditionalFunc($node)) { 85 | $this->condFunc = null; 86 | } elseif ($this->isConditionalConst($node)) { 87 | $this->condConst = null; 88 | } 89 | } 90 | 91 | protected function isNewFunc($node) 92 | { 93 | if (!isset($this->funcTable) || !$node instanceof Stmt\Function_ || !is_string($node->migName)) { 94 | return; 95 | } 96 | 97 | return $this->funcTable->has($node->migName) && 98 | (is_null($this->condFunc) || !ParserHelper::isSameFunc($node->migName, $this->condFunc)); 99 | } 100 | 101 | protected function isNewMethod($node, &$mname = null) 102 | { 103 | if (!isset($this->methodTable) || !$node instanceof Stmt\ClassMethod) { 104 | return false; 105 | } 106 | 107 | $class = $this->visitor->getClass(); 108 | if (!$class instanceof Stmt\Class_ || !$class->migExtends) { 109 | return false; 110 | } 111 | 112 | $mname = $class->migExtends.'::'.$node->migName; 113 | 114 | return $this->methodTable->has($mname); 115 | } 116 | 117 | protected function isNewClass($node) 118 | { 119 | if (!isset($this->classTable) || !$node instanceof Stmt\ClassLike || is_null($node->migName)) { 120 | return false; 121 | } 122 | 123 | return $this->classTable->has($node->migName); 124 | } 125 | 126 | protected function isNewConst($node) 127 | { 128 | if (!isset($this->constTable) || 129 | !$node instanceof Expr\FuncCall || 130 | !ParserHelper::isSameFunc($node->migName, 'define') || 131 | !$node->args[0]->value instanceof Scalar\String_) { 132 | return false; 133 | } 134 | 135 | $name = $node->args[0]->value->value; 136 | 137 | return $this->constTable->has($name) && 138 | (is_null($this->condConst) || $name != $this->condConst); 139 | } 140 | 141 | protected function isNewParam($node) 142 | { 143 | return $node instanceof Expr\FuncCall && isset($this->paramTable) && 144 | $this->paramTable->has($node->migName); 145 | } 146 | 147 | /** 148 | * Conditional checking. 149 | */ 150 | protected function isConditionalDeclare($node, $testfunc) 151 | { 152 | if (!$node instanceof Stmt\If_ || !$node->cond instanceof Expr\BooleanNot) { 153 | return false; 154 | } 155 | 156 | $expr = $node->cond->expr; 157 | 158 | return $expr instanceof Expr\FuncCall && ParserHelper::isSameFunc($expr->migName, $testfunc); 159 | } 160 | 161 | protected function isConditionalFunc($node) 162 | { 163 | return $this->isConditionalDeclare($node, 'function_exists'); 164 | } 165 | 166 | protected function isConditionalConst($node) 167 | { 168 | return $this->isConditionalDeclare($node, 'defined'); 169 | } 170 | 171 | protected function getConditionalName($node) 172 | { 173 | if ($node->cond->expr->args[0]->value instanceof Scalar\String_) { 174 | return $node->cond->expr->args[0]->value->value; 175 | } else { 176 | return; 177 | } 178 | } 179 | } 180 | -------------------------------------------------------------------------------- /src/Changes/AbstractKeywordReserved.php: -------------------------------------------------------------------------------- 1 | wordTable = new SymbolTable($this->wordTable, SymbolTable::IC); 17 | } 18 | 19 | public function leaveNode($node) 20 | { 21 | /** 22 | * {Description} 23 | * These words have special meaning in PHP. Some of them represent 24 | * things which look like functions, some look like constants, and so 25 | * on - but they're not, really: they are language constructs. You 26 | * cannot use any of the following words as constants, class names, 27 | * function or method names. Using them as variable names is generally 28 | * OK, but could lead to confusion. 29 | * 30 | * {Reference} 31 | * http://php.net/manual/en/reserved.keywords.php 32 | */ 33 | $name = null; 34 | if ($node instanceof Stmt\ClassLike || 35 | $node instanceof Stmt\Function_ || $node instanceof Stmt\ClassMethod || 36 | $node instanceof Expr\MethodCall || $node instanceof Expr\StaticCall || 37 | $node instanceof Expr\ConstFetch || 38 | ($node instanceof Expr\FuncCall && !ParserHelper::isDynamicCall($node))) { 39 | $name = $node->migName; 40 | } 41 | if (!is_null($name) && $this->wordTable->has($name)) { 42 | $this->addSpot('FATAL', true, 'Keyword "'.$name.'" is reserved'); 43 | } 44 | } 45 | } 46 | -------------------------------------------------------------------------------- /src/Changes/AbstractRemoved.php: -------------------------------------------------------------------------------- 1 | funcTable)) { 19 | $this->funcTable = new SymbolTable($this->funcTable, SymbolTable::IC); 20 | } 21 | if (isset($this->constTable)) { 22 | $this->constTable = new SymbolTable($this->constTable, SymbolTable::CS); 23 | } 24 | if (isset($this->varTable)) { 25 | $this->varTable = new SymbolTable($this->varTable, SymbolTable::CS); 26 | } 27 | } 28 | 29 | public function leaveNode($node) 30 | { 31 | // Function 32 | if ($this->isRemovedFunc($node)) { 33 | $this->addSpot('FATAL', true, sprintf('Function %s() is removed', $node->migName)); 34 | 35 | // Constant 36 | } elseif ($this->isRemovedConst($node)) { 37 | $this->addSpot('WARNING', true, sprintf('Constant %s is removed', $node->migName)); 38 | 39 | // Variable 40 | } elseif ($this->isRemovedVar($node)) { 41 | $this->addSpot('WARNING', true, sprintf('Variable $%s is removed', $node->migName)); 42 | } 43 | } 44 | 45 | protected function isRemovedFunc($node) 46 | { 47 | return $node instanceof Expr\FuncCall && isset($this->funcTable) && 48 | $this->funcTable->has($node->migName); 49 | } 50 | 51 | protected function isRemovedConst($node) 52 | { 53 | return $node instanceof Expr\ConstFetch && isset($this->constTable) && 54 | $this->constTable->has($node->migName); 55 | } 56 | 57 | protected function isRemovedVar($node) 58 | { 59 | return $node instanceof Expr\Variable && isset($this->varTable) && 60 | $this->varTable->has($node->migName); 61 | } 62 | } 63 | -------------------------------------------------------------------------------- /src/Changes/ClassTree.php: -------------------------------------------------------------------------------- 1 | classTable = []; 15 | } 16 | 17 | public function leaveNode($node) 18 | { 19 | if ($node instanceof Stmt\Class_) { 20 | $name = $node->migName; 21 | $parent_name = $node->migExtends; 22 | 23 | if (isset($this->classTable[$name])) { 24 | Logging::notice('Found a duplicated class '.$name.' in '.$this->visitor->getFile()); 25 | } 26 | 27 | $this->classTable[$name] = [ 28 | 'parent' => $parent_name, 29 | 'children' => [], 30 | 'topentry' => true, 31 | ]; 32 | } 33 | } 34 | 35 | public function finish() 36 | { 37 | // Find parent 38 | foreach ($this->classTable as $name => &$self) { 39 | if (is_null($self['parent'])) { 40 | continue; 41 | } 42 | $parent_name = $self['parent']; 43 | if (!isset($this->classTable[$parent_name])) { 44 | continue; 45 | } 46 | 47 | $self['topentry'] = false; 48 | $this->classTable[$parent_name]['children'][$name] = &$self; 49 | } 50 | 51 | // Output 52 | if ($this->classTable) { 53 | $this->outputTree($this->classTable); 54 | } else { 55 | echo "No class found\n"; 56 | } 57 | } 58 | 59 | protected function outputTree($data, $depth = 0, $last_status = []) 60 | { 61 | if (!is_array($data) || empty($data)) { 62 | return; 63 | } 64 | 65 | ksort($data); 66 | 67 | // Record last name 68 | foreach ($data as $name => $node) { 69 | if ($depth == 0 && !$node['topentry']) { 70 | continue; 71 | } 72 | $lastname = $name; 73 | } 74 | 75 | foreach ($data as $name => $node) { 76 | if ($depth == 0 && !$node['topentry']) { 77 | continue; 78 | } 79 | $is_last = ($name == $lastname); 80 | 81 | // Padding 82 | $padding = ''; 83 | for ($i = 0; $i < $depth; $i++) { 84 | if ($last_status[$i]) { 85 | $padding .= ' '; 86 | } else { 87 | $padding .= '| '; 88 | } 89 | } 90 | $padding .= ($is_last ? '`' : '|').'-- '; 91 | 92 | // Output 93 | echo $padding.$name."\n"; 94 | 95 | if ($node['children']) { 96 | $last_status[$depth] = $is_last; 97 | $this->outputTree($node['children'], $depth + 1, $last_status); 98 | } 99 | } 100 | } 101 | } 102 | -------------------------------------------------------------------------------- /src/Changes/RemoveTableItemTrait.php: -------------------------------------------------------------------------------- 1 | $dummy) { 12 | $this->$name->del($item); 13 | } 14 | } 15 | } 16 | -------------------------------------------------------------------------------- /src/Changes/v5dot3/Deprecated.php: -------------------------------------------------------------------------------- 1 | 'use call_user_func() instead', 18 | 'call_user_method_array' => 'use call_user_func_array() instead', 19 | 'define_syslog_variables' => '', 20 | // 'dl' => 'available only under CLI, CGI, and embed SAPIs', // dl() is moved to Removed 21 | 'ereg' => 'use preg_match() instead', 22 | 'ereg_replace' => 'use preg_replace() instead', 23 | 'eregi' => 'use preg_match() with the "i" modifier instead', 24 | 'eregi_replace' => 'use preg_replace() with the "i" modifier instead', 25 | 'set_magic_quotes_runtime' => '', 26 | 'magic_quotes_runtime' => '', 27 | 'session_register' => 'use the $_SESSION superglobal instead', 28 | 'session_unregister' => 'use the $_SESSION superglobal instead', 29 | 'session_is_registered' => 'use the $_SESSION superglobal instead', 30 | 'set_socket_blocking' => 'use stream_set_blocking() instead', 31 | 'split' => 'use preg_split() instead', 32 | 'spliti' => 'use preg_split() with the "i" modifier instead', 33 | 'sql_regcase' => '', 34 | 'mysql_db_query' => 'use mysql_select_db() and mysql_query() instead', 35 | 'mysql_escape_string' => 'use mysql_real_escape_string() instead', 36 | // Passing locale category names as strings is now deprecated. Use the LC_* family of constants instead. 37 | // The is_dst parameter to mktime(). Use the new timezone handling functions instead. 38 | ]; 39 | 40 | protected $checkCallTimePassByRef = true; 41 | 42 | public function __construct() 43 | { 44 | $this->funcTable = new SymbolTable($this->funcTable, SymbolTable::IC); 45 | } 46 | 47 | /** 48 | * For another Changes to set whether skip the check for Call-time 49 | * Pass-by-ref. 50 | */ 51 | public function skipCallTimePassByRef($off) 52 | { 53 | $this->checkCallTimePassByRef = !$off; 54 | } 55 | 56 | public function leaveNode($node) 57 | { 58 | // Function call 59 | if ($this->isDeprecatedFunc($node)) { 60 | $advice = $this->funcTable->get($node->name); 61 | if ($advice) { 62 | $errmsg = sprintf('Function %s() is deprecated, %s', $node->name, $advice); 63 | } else { 64 | $errmsg = sprintf('Function %s() is deprecated', $node->name); 65 | } 66 | /* 67 | * {Errmsg} 68 | * Deprecated: Function {function} is deprecated 69 | * 70 | * {Reference} 71 | * http://php.net/manual/en/migration53.deprecated.php 72 | */ 73 | $this->addSpot('DEPRECATED', true, $errmsg); 74 | 75 | // Assign new instance 76 | } elseif ($this->isAssignNewByRef($node)) { 77 | /* 78 | * {Description} 79 | * Assigning the return value of new by reference is now deprecated. 80 | * 81 | * {Errmsg} 82 | * Deprecated: Assigning the return value of new by reference is deprecated 83 | * 84 | * {Reference} 85 | * http://php.net/manual/en/migration53.deprecated.php 86 | */ 87 | $this->addSpot('DEPRECATED', true, 'Assigning the return value of new by reference is deprecated'); 88 | 89 | // Call-time pass-by-reference 90 | } elseif ($this->checkCallTimePassByRef && $this->isCallTimePassByRef($node)) { 91 | /* 92 | * {Description} 93 | * Call-time pass-by-reference is now deprecated 94 | * 95 | * {Reference} 96 | * http://php.net/manual/en/language.references.pass.php 97 | * http://php.net/manual/en/migration53.deprecated.php 98 | */ 99 | $this->addSpot('DEPRECATED', true, 'Call-time pass-by-reference is deprecated'); 100 | } 101 | } 102 | 103 | protected function isDeprecatedFunc($node) 104 | { 105 | return $node instanceof Expr\FuncCall && $this->funcTable->has($node->name); 106 | } 107 | 108 | protected function isAssignNewByRef($node) 109 | { 110 | return $node instanceof Expr\AssignRef && $node->expr instanceof Expr\New_; 111 | } 112 | 113 | protected function isCallTimePassByRef($node) 114 | { 115 | if (!($node instanceof Expr\FuncCall || $node instanceof Expr\StaticCall || 116 | $node instanceof Expr\MethodCall)) { 117 | return false; 118 | } 119 | 120 | foreach ($node->args as $arg) { 121 | if ($arg->byRef) { 122 | return true; 123 | } 124 | } 125 | 126 | return false; 127 | } 128 | } 129 | -------------------------------------------------------------------------------- /src/Changes/v5dot3/IncompCallFromGlobal.php: -------------------------------------------------------------------------------- 1 | funcTable = new SymbolTable($this->funcTable, SymbolTable::IC); 20 | } 21 | 22 | protected function emitSpot($node) 23 | { 24 | /** 25 | * {Description} 26 | * func_get_arg(), func_get_args() and func_num_args() can no longer be 27 | * called from the outermost scope of a file that has been included by 28 | * calling include or require from within a function in the calling 29 | * file. 30 | * 31 | * {Errmsg} 32 | * Warning: {method} Called from the global scope - no function context 33 | * 34 | * {Reference} 35 | * http://php.net/manual/en/migration53.incompatible.php 36 | */ 37 | $message = sprintf( 38 | '%s() Called from the global scope - no function context', 39 | $node->name 40 | ); 41 | $this->addSpot('WARNING', true, $message); 42 | } 43 | 44 | public function enterNode($node) 45 | { 46 | // Populate 47 | if ($node instanceof Expr\FuncCall && 48 | $this->funcTable->has($node->name) && 49 | !$this->visitor->inFunction()) { 50 | $this->emitSpot($node); 51 | } 52 | } 53 | } 54 | -------------------------------------------------------------------------------- /src/Changes/v5dot3/IncompMagic.php: -------------------------------------------------------------------------------- 1 | funcTable = new SymbolTable($this->funcTable, SymbolTable::IC); 21 | } 22 | 23 | protected function emitNonPub($node) 24 | { 25 | /** 26 | * {Description} 27 | * The magic methods __get(), __set(), __isset(), __unset(), and 28 | * __call() must always be public and can no longer be static. Method 29 | * signatures are now enforced. 30 | * 31 | * {Errmsg} 32 | * Warning: The magic method {method} must have public visibility and cannot be static 33 | * 34 | * {Reference} 35 | * http://php.net/manual/en/migration53.incompatible.php 36 | */ 37 | $message = sprintf( 38 | 'The magic method %s::%s() must have public visibility and cannot be static', 39 | $this->visitor->getClassName(), 40 | $node->name 41 | ); 42 | $this->addSpot('WARNING', true, $message, $node->getLine()); 43 | } 44 | 45 | protected function emitToString($node) 46 | { 47 | /** 48 | * {Description} 49 | * The __toString() magic method can no longer accept arguments. 50 | * 51 | * {Errmsg} 52 | * Fatal error: Method {class}::__tostring() cannot take arguments 53 | * 54 | * {Reference} 55 | * http://php.net/manual/en/migration53.incompatible.php 56 | */ 57 | $message = sprintf( 58 | 'Method %s::__tostring() cannot take arguments', 59 | $this->visitor->getClassName() 60 | ); 61 | $this->addSpot('FATAL', true, $message, $node->getLine()); 62 | } 63 | 64 | public function leaveNode($node) 65 | { 66 | if (!($node instanceof Stmt\Class_)) { 67 | return; 68 | } 69 | 70 | foreach ($node->getMethods() as $mnode) { 71 | if ((!$mnode->isPublic() || $mnode->isStatic()) && $this->funcTable->has($mnode->name)) { 72 | $this->emitNonPub($mnode); 73 | } elseif (ParserHelper::isSameFunc($mnode->name, '__toString') && count($mnode->params) > 0) { 74 | $this->emitToString($mnode); 75 | } 76 | } 77 | } 78 | } 79 | -------------------------------------------------------------------------------- /src/Changes/v5dot3/IncompMagicInvoked.php: -------------------------------------------------------------------------------- 1 | visitor->getClassName() 26 | ); 27 | $this->addSpot('NOTICE', false, $message, $node->getLine()); 28 | } 29 | 30 | public function leaveNode($node) 31 | { 32 | $non_public = []; 33 | $has_magic_call = false; 34 | 35 | if ($node instanceof Stmt\Class_) { 36 | foreach ($node->getMethods() as $mnode) { 37 | if (ParserHelper::isSameFunc($mnode->name, '__call')) { 38 | $has_magic_call = true; 39 | $magic_node = $mnode; 40 | } elseif (!$mnode->isPublic()) { 41 | $non_public[] = $mnode->name; 42 | } 43 | } 44 | } 45 | 46 | if ($has_magic_call && $non_public) { 47 | $this->emitSpot($magic_node, $non_public); 48 | } 49 | } 50 | } 51 | -------------------------------------------------------------------------------- /src/Changes/v5dot3/IncompMisc.php: -------------------------------------------------------------------------------- 1 | arrFuncTable = new SymbolTable($this->arrFuncTable, SymbolTable::IC); 21 | } 22 | 23 | public function leaveNode($node) 24 | { 25 | if ($node instanceof Expr\FuncCall) { 26 | if (ParserHelper::isSameFunc($node->name, 'clearstatcache')) { 27 | /* 28 | * {Description} 29 | * clearstatcache() no longer clears the realpath cache by default. 30 | * 31 | * {Reference} 32 | * http://php.net/manual/en/migration53.incompatible.php 33 | */ 34 | $this->addSpot('NOTICE', false, 'clearstatcache() no longer clears the realpath cache by default'); 35 | } elseif (ParserHelper::isSameFunc($node->name, 'realpath')) { 36 | /* 37 | * {Description} 38 | * realpath() is now fully platform-independent. Consequence of 39 | * this is that invalid relative paths such as __FILE__ . "/../x" 40 | * do not work anymore. 41 | * Prior to this release, if only the last path component did not 42 | * exist, realpath() would not fail on *BSD systems. realpath() now 43 | * fails in this case. 44 | * 45 | * {Reference} 46 | * http://php.net/manual/en/function.realpath.php 47 | * http://php.net/manual/en/migration53.incompatible.php 48 | */ 49 | $this->addSpot('NOTICE', false, 'realpath() is now fully platform-independent, especially on *BSD.'); 50 | } elseif ($this->arrFuncTable->has($node->name)) { 51 | /* 52 | * {Description} 53 | * The array functions natsort(), natcasesort(), usort(), uasort(), 54 | * uksort(), array_flip(), and array_unique() no longer accept 55 | * objects passed as arguments. To apply these functions to an 56 | * object, cast the object to an array first. 57 | * 58 | * {Reference} 59 | * http://php.net/manual/en/migration53.incompatible.php 60 | */ 61 | $this->addSpot( 62 | 'NOTICE', 63 | false, 64 | sprintf('%s() no longer accept objects passed as arguments', $node->name) 65 | ); 66 | } elseif (ParserHelper::isSameFunc($node->name, 'call_user_func_array')) { 67 | /* 68 | * {Description} 69 | * call_user_func_array() no longer accepts null as a second 70 | * parameter and calls the function. It now emits a warning and 71 | * does not call the function. 72 | * 73 | * {Reference} 74 | * User Contributed Notes by Chris Bolt 75 | * http://php.net/manual/en/migration53.incompatible.php 76 | */ 77 | if (isset($node->args[1]) && !($node->args[1]->value instanceof Expr\Array_)) { 78 | $this->addSpot( 79 | 'NOTICE', 80 | false, 81 | sprintf('%s() no longer accept non-array passed as arguments', $node->name) 82 | ); 83 | } 84 | } elseif (ParserHelper::isSameFunc($node->name, 'gd_info')) { 85 | /* 86 | * {Description} 87 | * Image Processing and GD The "JPG Support" index returned from 88 | * gd_info() has been renamed to "JPEG Support". 89 | * 90 | * {Reference} 91 | * http://php.net/manual/en/migration53.extensions-other.php 92 | */ 93 | $this->addSpot('NOTICE', false, 'gd_info() JPG Support attribute renamed to JPEG Support'); 94 | } 95 | } 96 | } 97 | } 98 | -------------------------------------------------------------------------------- /src/Changes/v5dot3/IncompReserved.php: -------------------------------------------------------------------------------- 1 | 'added clear_realpath_cache and filename', 144 | 'copy' => 'added a stream context parameter, context', 145 | 'fgetcsv' => 'added escape', 146 | 'ini_get_all' => 'added details', 147 | 'nl2br' => 'added is_xhtml', 148 | 'parse_ini_file' => 'added scanner_mode', 149 | 'round' => 'added mode', 150 | 'stream_context_create' => 'added params', 151 | 'strstr' => 'added before_needle', 152 | 'stristr' => 'added before_needle', 153 | 154 | // json 155 | 'json_encode' => 'added options', 156 | 'json_decode' => 'added depth', 157 | 158 | // Streams 159 | 'stream_select' => 'now work with user-space stream wrappers', 160 | 'stream_set_blocking' => 'now work with user-space stream wrappers', 161 | 'stream_set_timeout' => 'now work with user-space stream wrappers', 162 | 'stream_set_write_buffer' => 'now work with user-space stream wrappers', 163 | 164 | // sybase_ct 165 | 'sybase_connect' => 'added new', 166 | ]; 167 | } 168 | -------------------------------------------------------------------------------- /src/Changes/v5dot3/Removed.php: -------------------------------------------------------------------------------- 1 | funcTable = new SymbolTable($this->funcTable, SymbolTable::IC); 24 | } 25 | 26 | public function leaveNode($node) 27 | { 28 | // Function call 29 | if ($this->isDeprecatedFunc($node)) { 30 | /* 31 | * {Errmsg} 32 | * Deprecated: Function {function} is deprecated 33 | * 34 | * {Reference} 35 | * http://php.net/manual/en/migration54.deprecated.php 36 | */ 37 | $this->addSpot('WARNING', true, sprintf('Function %s() is deprecated', $node->name)); 38 | } 39 | } 40 | 41 | protected function isDeprecatedFunc($node) 42 | { 43 | return $node instanceof Expr\FuncCall && $this->funcTable->has($node->name); 44 | } 45 | } 46 | -------------------------------------------------------------------------------- /src/Changes/v5dot4/IncompBreakContinue.php: -------------------------------------------------------------------------------- 1 | num) && !($node->num instanceof Scalar\LNumber)) { 40 | $this->addSpot('FATAL', true, $operator.' operator with non-constant operand is no longer supported'); 41 | } elseif ($node->num instanceof Scalar\LNumber && $node->num->value < 1) { 42 | $this->addSpot('FATAL', true, $operator.' operator accepts only positive numbers'); 43 | } 44 | } 45 | } 46 | -------------------------------------------------------------------------------- /src/Changes/v5dot4/IncompByReference.php: -------------------------------------------------------------------------------- 1 | visitor->callChange('v5dot3\Deprecated', 'skipCallTimePassByRef', true); 15 | } 16 | 17 | public function leaveNode($node) 18 | { 19 | if ($this->isCallTimePassByRef($node)) { 20 | /* 21 | * {Description} 22 | * Call-time pass by reference has been removed. 23 | * 24 | * {Errmsg} 25 | * Fatal error: Call-time pass-by-reference has been removed 26 | * 27 | * {Reference} 28 | * http://php.net/manual/en/language.references.pass.php 29 | * http://php.net/manual/en/migration54.incompatible.php 30 | */ 31 | $this->addSpot('FATAL', true, 'Call-time pass-by-reference has been removed'); 32 | } 33 | } 34 | 35 | /** 36 | * Duplicated with same method in Changes/v5dot3/Deprecated.php. 37 | */ 38 | protected function isCallTimePassByRef($node) 39 | { 40 | if (!($node instanceof Expr\FuncCall || $node instanceof Expr\StaticCall || 41 | $node instanceof Expr\MethodCall)) { 42 | return false; 43 | } 44 | 45 | foreach ($node->args as $arg) { 46 | if ($arg->byRef) { 47 | return true; 48 | } 49 | } 50 | 51 | return false; 52 | } 53 | } 54 | -------------------------------------------------------------------------------- /src/Changes/v5dot4/IncompHashAlgo.php: -------------------------------------------------------------------------------- 1 | funcTable = new SymbolTable($this->funcTable, SymbolTable::IC); 25 | } 26 | 27 | public function leaveNode($node) 28 | { 29 | /* 30 | * {Description} 31 | * The Salsa10 and Salsa20 hash algorithms have been removed. 32 | * 33 | * {Reference} 34 | * http://php.net/manual/en/migration54.incompatible.php 35 | */ 36 | if ($node instanceof Expr\FuncCall && $this->funcTable->has($node->name)) { 37 | $affected = true; 38 | $certain = false; 39 | 40 | if (!isset($node->args[0])) { 41 | return; 42 | } 43 | $param = $node->args[0]->value; 44 | 45 | if ($param instanceof Scalar\String_) { 46 | $certain = $affected = (strcasecmp($param->value, 'salsa10') === 0 || 47 | strcasecmp($param->value, 'salsa20') === 0); 48 | } 49 | 50 | if ($affected) { 51 | $this->addSpot('WARNING', $certain, 'Salsa10 and Salsa20 hash algorithms have been removed'); 52 | } 53 | } 54 | } 55 | } 56 | -------------------------------------------------------------------------------- /src/Changes/v5dot4/IncompMisc.php: -------------------------------------------------------------------------------- 1 | name, 'array_combine')) { 17 | /* 18 | * {Description} 19 | * array_combine() now returns array() instead of FALSE when two empty 20 | * arrays are provided as parameters. 21 | * 22 | * {Reference} 23 | * http://php.net/manual/en/migration54.incompatible.php 24 | */ 25 | $this->addSpot( 26 | 'NOTICE', 27 | false, 28 | 'array_combine() now returns array() instead of FALSE when two empty arrays given' 29 | ); 30 | 31 | // ob_start() 32 | } elseif ($node instanceof Expr\FuncCall && ParserHelper::isSameFunc($node->name, 'ob_start') && 33 | isset($node->args[2])) { 34 | /* 35 | * {Description} 36 | * The third parameter of ob_start() has changed from boolean erase 37 | * to integer flags. Note that code that explicitly set erase to 38 | * FALSE will no longer behave as expected in PHP 5.4: please 39 | * follow this example to write code that is compatible with PHP 40 | * 5.3 and 5.4. 41 | * 42 | * {Reference} 43 | * http://php.net/manual/en/function.ob-start.php 44 | * http://php.net/manual/en/migration54.incompatible.php 45 | */ 46 | $this->addSpot('WARNING', true, 'The third parameter of ob_start() has changed'); 47 | } elseif ($node instanceof Expr\FuncCall && 48 | (ParserHelper::isSameFunc($node->name, 'htmlentities') || 49 | ParserHelper::isSameFunc($node->name, 'htmlspecialchars'))) { 50 | /** 51 | * {Description} 52 | * If you use htmlentities() with asian character sets, it works 53 | * like htmlspecialchars() - this has always been the case in 54 | * previous versions of PHP, but now an E_STRICT level error is 55 | * emitted. 56 | * 57 | * The default character set for htmlspecialchars() and 58 | * htmlentities() is now UTF-8, instead of ISO-8859-1. Note that 59 | * changing your output charset via the default_charset 60 | * configuration setting does not affect 61 | * htmlspecialchars/htmlentities unless you are passing "" (an 62 | * empty string) as the encoding parameter to your 63 | * htmlspecialchars()/htmlentities() calls. Generally we do not 64 | * recommend doing this because you should be able to change your 65 | * output charset without affecting the runtime charset used by 66 | * these functions. The safest approach is to explicitly set the 67 | * charset on each call to htmlspecialchars() and htmlentities(). 68 | * 69 | * {Reference} 70 | * http://php.net/manual/en/function.htmlentities.php 71 | * http://php.net/manual/en/function.htmlspecialchars.php 72 | * http://php.net/manual/en/migration54.other.php 73 | * http://php.net/manual/en/migration54.incompatible.php 74 | */ 75 | $level = false; 76 | $msgbox = []; 77 | 78 | if (ParserHelper::isSameFunc($node->name, 'htmlentities')) { 79 | $level = 'WARNING'; 80 | $msgbox[] = 'won\'t encode asian character sets'; 81 | } 82 | 83 | if (!isset($node->args[2])) { // Set encoding 84 | $level = 'NOTICE'; 85 | $msgbox[] = 'default encoding was changed'; 86 | } 87 | 88 | if ($level) { 89 | $this->addSpot($level, false, $node->name.'() '.implode(', ', $msgbox)); 90 | } 91 | } 92 | } 93 | } 94 | -------------------------------------------------------------------------------- /src/Changes/v5dot4/IncompParamName.php: -------------------------------------------------------------------------------- 1 | autoGlobals = new SymbolTable($this->autoGlobals, SymbolTable::CS); 20 | } 21 | 22 | public function leaveNode($node) 23 | { 24 | /** 25 | * {Description} 26 | * Parameter names that shadow super globals now cause a fatal error. 27 | * This prohibits code like function foo($_GET, $_POST) {}. 28 | * 29 | * {Errmsg} 30 | * Fatal error: Cannot re-assign auto-global variable 31 | * Fatal error: Cannot re-assign $this 32 | * 33 | * {Reference} 34 | * http://php.net/manual/en/migration54.incompatible.php 35 | */ 36 | if (($node instanceof Stmt\Function_ || $node instanceof Stmt\ClassMethod) 37 | && $this->hasParamShadowGlobal($node)) { 38 | $this->addSpot('FATAL', true, 'Cannot re-assign auto-global variable'); 39 | } 40 | } 41 | 42 | protected function hasParamShadowGlobal($node) 43 | { 44 | foreach ($node->params as $param) { 45 | // auto-global 46 | if ($this->autoGlobals->has($param->name)) { 47 | return true; 48 | 49 | // $this 50 | } elseif ($param->name == 'this' && $node instanceof Stmt\ClassMethod && !$node->isStatic()) { 51 | return true; 52 | } 53 | } 54 | 55 | return false; 56 | } 57 | } 58 | -------------------------------------------------------------------------------- /src/Changes/v5dot4/IncompRegister.php: -------------------------------------------------------------------------------- 1 | longArray = new SymbolTable($this->longArray, SymbolTable::CS); 26 | } 27 | 28 | public function leaveNode($node) 29 | { 30 | /* 31 | * {Description} 32 | * The register_globals and register_long_arrays php.ini directives 33 | * have been removed. 34 | * 35 | * {Reference} 36 | * http://php.net/manual/en/migration54.incompatible.php 37 | */ 38 | if ($node instanceof Expr\Variable && is_string($node->name) && $this->longArray->has($node->name)) { 39 | $this->addSpot( 40 | 'WARNING', 41 | true, 42 | 'The register_long_arrays is removed, $'.$node->name.' no longer available' 43 | ); 44 | } 45 | } 46 | } 47 | -------------------------------------------------------------------------------- /src/Changes/v5dot4/IncompReserved.php: -------------------------------------------------------------------------------- 1 | funcTable = new SymbolTable($this->funcTable, SymbolTable::IC); 47 | $this->mysqlTable = new SymbolTable($this->mysqlTable, SymbolTable::IC); 48 | } 49 | 50 | public function prepare() 51 | { 52 | $this->visitor->callChange('v5dot3\Deprecated', 'removeTableItems', ['funcTable', $this->mysqlTable]); 53 | } 54 | 55 | public function leaveNode($node) 56 | { 57 | if ($node instanceof Expr\FuncCall && $this->mysqlTable->has($node->name)) { 58 | /* 59 | * {Description} 60 | * The original MySQL extension is now deprecated, and will generate 61 | * E_DEPRECATED errors when connecting to a database. Instead, use the 62 | * MySQLi or PDO_MySQL extensions. 63 | * 64 | * {Reference} 65 | * http://php.net/manual/en/migration55.deprecated.php#migration55.deprecated.mysql 66 | */ 67 | $this->addSpot( 68 | 'DEPRECATED', 69 | true, 70 | 'The original MySQL extension is deprecated, use MySQLi or PDO_MySQL extensions instead' 71 | ); 72 | } elseif ($node instanceof Expr\FuncCall && ParserHelper::isSameFunc($node->name, 'preg_replace')) { 73 | /** 74 | * {Description} 75 | * The preg_replace() /e modifier is now deprecated. Instead, use the 76 | * preg_replace_callback() function. 77 | * 78 | * {Reference} 79 | * http://php.net/manual/en/migration55.deprecated.php#migration55.deprecated.preg-replace-e 80 | */ 81 | $affected = true; 82 | $certain = false; 83 | 84 | if (!isset($node->args[0])) { 85 | return; 86 | } 87 | $pattern = $node->args[0]->value; 88 | 89 | // TODO: shoud be full tested 90 | // Read right-most if concat, encapsed 91 | if ($pattern instanceof Expr\BinaryOp\Concat) { 92 | $pattern = $pattern->right; 93 | } 94 | if ($pattern instanceof Scalar\Encapsed) { 95 | $pattern = end($pattern->parts); 96 | } 97 | // Extract to string 98 | if ($pattern instanceof Scalar\String_ || $pattern instanceof Scalar\EncapsedStringPart) { 99 | $pattern = $pattern->value; 100 | } 101 | // Extract array to strings 102 | if ($pattern instanceof Expr\Array_) { 103 | foreach ($pattern->items as $key => $value) { 104 | $this->verifyPregReplace($value->value->value); 105 | } 106 | } else { 107 | $this->verifyPregReplace($pattern); 108 | } 109 | } elseif ($node instanceof Expr\FuncCall && $this->funcTable->has($node->name)) { 110 | /* 111 | * TODO: how to check IntlDateFormatter::setTimeZoneId 112 | * 113 | * {Reference} 114 | * http://php.net/manual/en/migration55.deprecated.php#migration55.deprecated.intl 115 | * http://php.net/manual/en/migration55.deprecated.php#migration55.deprecated.mcrypt 116 | */ 117 | $this->addSpot('DEPRECATED', true, 'Function '.$node->name.'() is deprecated'); 118 | } 119 | } 120 | 121 | private function verifyPregReplace($pattern) 122 | { 123 | $affected = true; 124 | $certain = false; 125 | 126 | if (is_string($pattern)) { 127 | $modifier = strrchr($pattern, substr($pattern, 0, 1)); 128 | if ((strpos($modifier, 'e') !== false)) { 129 | $affected = true; 130 | $certain = true; 131 | } else { 132 | $affected = false; 133 | } 134 | } 135 | if ($affected) { 136 | $this->addSpot( 137 | 'DEPRECATED', 138 | $certain, 139 | 'preg_replace() /e modifier is deprecated, use preg_replace_callback() instead.' 140 | ); 141 | } 142 | } 143 | } 144 | -------------------------------------------------------------------------------- /src/Changes/v5dot5/IncompCaseInsensitive.php: -------------------------------------------------------------------------------- 1 | 'self', 16 | 'parent' => 'parent', 17 | 'static' => 'static', 18 | ]; 19 | 20 | public function __construct() 21 | { 22 | $this->keywords = new SymbolTable($this->keywords, SymbolTable::IC); 23 | } 24 | 25 | public function leaveNode($node) 26 | { 27 | /* 28 | * {Description} 29 | * self, parent and static are now always case insensitive 30 | * Prior to PHP 5.5, cases existed where the self, parent, and static 31 | * keywords were treated in a case sensitive fashion. These have now 32 | * been resolved, and these keywords are always handled case 33 | * insensitively: SELF::CONSTANT is now treated identically to 34 | * self::CONSTANT. 35 | * 36 | * {Reference} 37 | * http://php.net/manual/en/migration55.incompatible.php#migration55.incompatible.self-parent-static 38 | */ 39 | if (($node instanceof Expr\StaticCall || $node instanceof Expr\StaticPropertyFetch) 40 | && $node->class instanceof Name) { 41 | $name = $node->class->toString(); 42 | if ($this->keywords->has($name) && $this->keywords->get($name) != $name) { 43 | $this->addSpot( 44 | 'NOTICE', 45 | true, 46 | $name.' will be case insensitive, treated identically to '.strtolower($name) 47 | ); 48 | } 49 | } 50 | } 51 | } 52 | -------------------------------------------------------------------------------- /src/Changes/v5dot5/IncompPack.php: -------------------------------------------------------------------------------- 1 | name, 'unpack')) { 37 | $affected = true; 38 | $certain = false; 39 | 40 | if (!isset($node->args[0])) { 41 | return; 42 | } 43 | $format = $node->args[0]->value; 44 | 45 | // Try to check arg $format 46 | if ($format instanceof Scalar\String_) { 47 | // using stripos for both "a" and "A" 48 | $certain = $affected = (stripos($format->value, 'a') !== false); 49 | } 50 | 51 | if ($affected) { 52 | $this->addSpot('WARNING', $certain, 'Behavior of pack() with "a", "A" in format is changed'); 53 | } 54 | } 55 | } 56 | } 57 | -------------------------------------------------------------------------------- /src/Changes/v5dot5/Introduced.php: -------------------------------------------------------------------------------- 1 | checkHRPD = !$off; 20 | } 21 | 22 | public function leaveNode($node) 23 | { 24 | /* 25 | * {Description} 26 | * always_populate_raw_post_data will now generate an E_DEPRECATED 27 | * error when used. New code should use php://input instead of 28 | * $HTTP_RAW_POST_DATA, which will be removed in a future release. You 29 | * can opt in for the new behaviour (in which $HTTP_RAW_POST_DATA is 30 | * never defined) by setting always_populate_raw_post_data to -1. 31 | * 32 | * {Reference} 33 | * http://php.net/manual/en/migration56.deprecated.php#migration56.deprecated.raw-post-data 34 | */ 35 | if ($this->checkHRPD && $node instanceof Expr\Variable && !($node->name instanceof Expr\Variable) && 36 | $node->name == 'HTTP_RAW_POST_DATA') { 37 | $this->addSpot('DEPRECATED', true, '$HTTP_RAW_POST_DATA is deprecated, use php://input instead'); 38 | } 39 | } 40 | } 41 | -------------------------------------------------------------------------------- /src/Changes/v5dot6/IncompMisc.php: -------------------------------------------------------------------------------- 1 | gmpTable = new SymbolTable($this->gmpTable, SymbolTable::IC); 38 | $this->mcryptTable = new SymbolTable($this->mcryptTable, SymbolTable::IC); 39 | } 40 | 41 | public function leaveNode($node) 42 | { 43 | // json_decode() 44 | if ($node instanceof Expr\FuncCall && ParserHelper::isSameFunc($node->name, 'json_decode')) { 45 | /* 46 | * {Description} 47 | * json_decode() now rejects non-lowercase variants of the JSON 48 | * literals true, false and null at all times, as per the JSON 49 | * specification, and sets json_last_error() accordingly. 50 | * Previously, inputs to json_decode() that consisted solely of one 51 | * of these values in upper or mixed case were accepted. 52 | * 53 | * This change will only affect cases where invalid JSON was being 54 | * passed to json_decode(): valid JSON input is unaffected and will 55 | * continue to be parsed normally. 56 | * 57 | * {Reference} 58 | * http://php.net/manual/en/migration56.incompatible.php#migration56.incompatible.json-decode 59 | */ 60 | $this->addSpot('NOTICE', false, 'json_decode() rejects non-lowercase variants of true, false, null'); 61 | 62 | // GMP 63 | } elseif ($node instanceof Expr\FuncCall && $this->gmpTable->has($node->name)) { 64 | /* 65 | * {Description} 66 | * GMP resources are now objects. The functional API implemented in 67 | * the GMP extension has not changed, and code should run 68 | * unmodified unless it checks explicitly for a resource using 69 | * is_resource() or similar. 70 | * 71 | * {Reference} 72 | * http://php.net/manual/en/migration56.incompatible.php#migration56.incompatible.gmp 73 | */ 74 | $this->addSpot('NOTICE', false, 'GMP resource is now object, do not use is_resource() to test'); 75 | 76 | // Mcrypt 77 | } elseif ($node instanceof Expr\FuncCall && $this->mcryptTable->has($node->name)) { 78 | /* 79 | * {Description} 80 | * mcrypt_encrypt(), mcrypt_decrypt(), mcrypt_cbc(), mcrypt_cfb(), 81 | * mcrypt_ecb(), mcrypt_generic() and mcrypt_ofb() will no longer 82 | * accept keys or IVs with incorrect sizes, and block cipher modes 83 | * that require IVs will now fail if an IV isn't provided. 84 | * 85 | * {Reference} 86 | * http://php.net/manual/en/migration56.incompatible.php#migration56.incompatible.mcrypt 87 | */ 88 | $this->addSpot('NOTICE', false, $node->name.'() no longer accept keys or IVs with incorrect size'); 89 | } 90 | } 91 | } 92 | -------------------------------------------------------------------------------- /src/Changes/v5dot6/IncompPropertyArray.php: -------------------------------------------------------------------------------- 1 | stmts as $stmt) { 35 | if ($stmt instanceof Stmt\Property) { 36 | foreach ($stmt->props as $prop) { 37 | if ($prop instanceof Stmt\PropertyProperty && 38 | $prop->default instanceof Expr\Array_) { 39 | $array_list[] = $prop->default; 40 | } 41 | } 42 | } elseif ($stmt instanceof Stmt\ClassConst) { 43 | foreach ($stmt->consts as $const) { 44 | if ($const->value instanceof Scalar) { 45 | $const_table['self::'.$const->name] = $const->value->value; 46 | } 47 | } 48 | } 49 | } 50 | 51 | // Check keys in array 52 | foreach ($array_list as $arr) { 53 | // Emulate array key initialization 54 | $keylist = []; 55 | $has = [ 56 | 'scalar' => false, 57 | 'const' => false, 58 | 'null' => false, 59 | 'unfetched' => false, 60 | ]; 61 | $counter = 0; 62 | foreach ($arr->items as $item) { 63 | if ($item->key instanceof Expr\ClassConstFetch) { 64 | $has['const'] = true; 65 | // Try to fetch const value 66 | $const_name = $item->key->class.'::'.$item->key->name; 67 | if (isset($const_table[$const_name])) { 68 | $keylist[] = 'V:'.$const_table[$const_name]; 69 | } else { 70 | $has['unfetched'] = true; 71 | $keylist[] = 'C:'.$const_name; 72 | } 73 | } elseif ($item->key instanceof Expr\ConstFetch) { 74 | $has['const'] = true; 75 | $has['unfetched'] = true; 76 | $keylist[] = 'C:'.$item->key->name->toString(); 77 | } elseif (is_null($item->key)) { 78 | $has['null'] = true; 79 | $keylist[] = 'N:'.$counter++; 80 | } else { 81 | $has['scalar'] = true; 82 | $keylist[] = 'V:'.$item->key->value; 83 | } 84 | } 85 | $has['duplicated'] = count($keylist) != count(array_unique($keylist)); 86 | 87 | // Check condition 88 | if ($has['const'] && ($has['null'] || $has['unfetched'] || $has['duplicated'])) { 89 | $this->addSpot( 90 | 'WARNING', 91 | false, 92 | 'Array key may be overwritten when defining as a property and using constants', 93 | $arr->getLine() 94 | ); 95 | } 96 | } 97 | } 98 | } 99 | } 100 | -------------------------------------------------------------------------------- /src/Changes/v5dot6/Introduced.php: -------------------------------------------------------------------------------- 1 | migName == $this->visitor->getClass()->name) { 23 | $this->addSpot('DEPRECATED', true, 'PHP 4 style constructor is deprecated'); 24 | 25 | /* 26 | * password_hash() salt option 27 | * 28 | * @see http://php.net/manual/en/migration70.deprecated.php#migration70.deprecated.pwshash-salt-option 29 | */ 30 | } elseif ($node instanceof Expr\FuncCall && 31 | ParserHelper::isSameFunc($node->migName, 'password_hash') && 32 | isset($node->args[2])) { 33 | $this->addSpot('DEPRECATED', false, 'salt option for password_hash() is deprecated'); 34 | 35 | /* 36 | * LDAP deprecations 37 | * 38 | * @see http://php.net/manual/en/migration70.deprecated.php#migration70.deprecated.ldap 39 | */ 40 | } elseif ($node instanceof Expr\FuncCall && 41 | ParserHelper::isSameFunc($node->migName, 'ldap_sort')) { 42 | $this->addSpot('DEPRECATED', true, 'ldap_sort() is deprecated'); 43 | } 44 | } 45 | } 46 | -------------------------------------------------------------------------------- /src/Changes/v7dot0/ExceptionHandle.php: -------------------------------------------------------------------------------- 1 | name, 'set_exception_handler') || 23 | !isset($node->args[0])) { 24 | return; 25 | } 26 | 27 | $affected = true; 28 | $certain = false; 29 | $callback = $node->args[0]->value; 30 | 31 | if ($callback instanceof Expr\Closure) { 32 | if (!isset($callback->params[0]) || !isset($callback->params[0]->type)) { 33 | $affected = false; 34 | $certain = true; 35 | } elseif (ParserHelper::isSameClass($callback->params[0]->type, 'Exception')) { 36 | $affected = true; 37 | $certain = true; 38 | } 39 | } 40 | 41 | if ($affected) { 42 | $this->addSpot('WARNING', $certain, 'set_exception_handler() is no longer guaranteed to receive Exception objects'); 43 | } 44 | } 45 | } 46 | -------------------------------------------------------------------------------- /src/Changes/v7dot0/ForeachLoop.php: -------------------------------------------------------------------------------- 1 | funcTable = new SymbolTable($this->funcTable, SymbolTable::IC); 32 | } 33 | 34 | public function beforeTraverse(array $nodes) 35 | { 36 | $this->depth = 0; 37 | } 38 | 39 | public function enterNode($node) 40 | { 41 | if ($node instanceof Stmt\Foreach_ && $node->byRef) { 42 | $this->depth++; 43 | } 44 | } 45 | 46 | public function leaveNode($node) 47 | { 48 | if ($this->depth > 0 && $node instanceof Expr\FuncCall && $this->funcTable->has($node->name)) { 49 | $this->addSpot('NOTICE', true, 'foreach no longer changes the internal array pointer'); 50 | } 51 | 52 | if ($node instanceof Stmt\Foreach_ && $node->byRef) { 53 | $this->depth--; 54 | } 55 | } 56 | } 57 | -------------------------------------------------------------------------------- /src/Changes/v7dot0/FuncList.php: -------------------------------------------------------------------------------- 1 | checkVarOrder($node); 22 | $this->checkEmpty($node); 23 | } 24 | } 25 | 26 | protected function checkVarOrder(Expr\List_ $node) 27 | { 28 | // Any var is dim[null] 29 | foreach ($node->vars as $var) { 30 | if ($var instanceof Expr\ArrayDimFetch && is_null($var->dim)) { 31 | $this->addSpot('NOTICE', true, 'list() no longer assigns variables in reverse order'); 32 | 33 | return; 34 | } 35 | } 36 | } 37 | 38 | protected function checkEmpty(Expr\List_ $node) 39 | { 40 | // All var is null 41 | foreach ($node->vars as $var) { 42 | if (!is_null($var)) { 43 | return; 44 | } 45 | } 46 | 47 | $this->addSpot('NOTICE', true, 'empty list() assignments have been removed'); 48 | } 49 | } 50 | -------------------------------------------------------------------------------- /src/Changes/v7dot0/FuncParameters.php: -------------------------------------------------------------------------------- 1 | params)) { 19 | return; 20 | } 21 | 22 | $set = []; 23 | foreach ($node->params as $param) { 24 | if (isset($set[$param->name])) { 25 | $this->addSpot('WARNING', true, 'Can not define two or more parameters with the same name'); 26 | break; 27 | } 28 | $set[$param->name] = true; 29 | } 30 | } 31 | } 32 | -------------------------------------------------------------------------------- /src/Changes/v7dot0/IntegerOperation.php: -------------------------------------------------------------------------------- 1 | right instanceof Scalar\LNumber) { 26 | $affect = false; 27 | } elseif ($node->right instanceof Expr\UnaryMinus && 28 | $node->right->expr instanceof Scalar\LNumber) { 29 | $certain = true; 30 | } 31 | 32 | if ($affect) { 33 | $this->addSpot('NOTICE', $certain, 'Bitwise shifts should not by negative numbers'); 34 | } 35 | 36 | /* 37 | * Changes to Modulus By Zero 38 | * 39 | * The modulus operator E_WARNING has been removed and will throw a DivisionByZeroError exception. 40 | * 41 | * @see http://php.net/manual/en/migration70.incompatible.php#migration70.incompatible.integers.div-by-zero 42 | */ 43 | } elseif ($node instanceof Expr\BinaryOp\Mod) { 44 | $affect = true; 45 | $certain = false; 46 | 47 | if ($node->right instanceof Scalar\LNumber) { 48 | $certain = true; 49 | $affect = ($node->right->value == 0); 50 | } 51 | 52 | if ($affect) { 53 | $this->addSpot('NOTICE', $certain, 'Modulus operator will throw a exception if divisor is 0'); 54 | } 55 | } 56 | } 57 | } 58 | -------------------------------------------------------------------------------- /src/Changes/v7dot0/Introduced.php: -------------------------------------------------------------------------------- 1 | forbiddenTable = new SymbolTable($this->forbiddenTable, SymbolTable::IC); 38 | $this->reservedTable = new SymbolTable($this->reservedTable, SymbolTable::IC); 39 | } 40 | 41 | public function leaveNode($node) 42 | { 43 | if (!$node instanceof Stmt\ClassLike || is_null($node->name)) { 44 | return; 45 | } 46 | 47 | $name = $node->name; 48 | 49 | if ($this->forbiddenTable->has($name)) { 50 | $this->addSpot('FATAL', true, 'Keyword "'.$name.'" cannot be used to name class-like'); 51 | } elseif ($this->reservedTable->has($name)) { 52 | $this->addSpot('NOTICE', true, 'Keyword "'.$name.'" is reserved'); 53 | } 54 | } 55 | } 56 | -------------------------------------------------------------------------------- /src/Changes/v7dot0/ParseDifference.php: -------------------------------------------------------------------------------- 1 | ast2plain($node, $records); 37 | } 38 | } elseif ($entry instanceof Node) { 39 | $records[] = get_class($entry); 40 | foreach ($entry->getSubNodeNames() as $name) { 41 | $this->ast2plain($entry->$name, $records); 42 | } 43 | } 44 | } 45 | 46 | /** 47 | * Normalize node name and convert NameResolver's FullyQualified node. 48 | */ 49 | protected function normalizeNodeList(array &$nodes) 50 | { 51 | foreach ($nodes as &$node) { 52 | if ($node == 'PhpParser\Node\Name\FullyQualified') { 53 | $node = 'Node\Name'; 54 | } else { 55 | $node = substr($node, 10); // Strip namespace prefix 56 | } 57 | } 58 | } 59 | 60 | public function __construct() 61 | { 62 | $this->parser5 = (new ParserFactory())->create(ParserFactory::ONLY_PHP5); 63 | } 64 | 65 | public function beforeTraverse(array $nodes) 66 | { 67 | $this->lines = $this->plain5 = $this->plain7 = []; 68 | } 69 | 70 | public function enterNode($node) 71 | { 72 | $this->lines[] = $node->getLine(); 73 | $this->plain7[] = get_class($node); 74 | } 75 | 76 | public function afterTraverse(array $nodes) 77 | { 78 | // Parse code as PHP 5 79 | try { 80 | $stmts = $this->parser5->parse($this->visitor->getCode()); 81 | } catch (PhpParserError $e) { 82 | $this->addSpot('WARNING', true, 'Parse failed as PHP 5 "'.$e->getMessage().'"', $e->getStartLine()); 83 | 84 | return; 85 | } 86 | 87 | // Compare 88 | $this->ast2plain($stmts, $this->plain5); 89 | $this->normalizeNodeList($this->plain5); 90 | $this->normalizeNodeList($this->plain7); 91 | $diff = array_diff_assoc($this->plain5, $this->plain7); 92 | 93 | $lset = []; 94 | foreach ($diff as $i => $name) { 95 | // TODO we do like double-? in PHP 7 such as `$line = $this->lines[$i] ?? 0;` 96 | $line = isset($this->lines[$i]) ? $this->lines[$i] : 0; 97 | if (isset($lset[$line])) { 98 | continue; 99 | } 100 | $lset[$line] = true; 101 | 102 | $this->addSpot('WARNING', true, 'Different behavior between PHP 5/7', $line); 103 | } 104 | } 105 | } 106 | -------------------------------------------------------------------------------- /src/Changes/v7dot0/Removed.php: -------------------------------------------------------------------------------- 1 | visitor->callChange('v5dot3\Deprecated', 'removeTableItems', ['funcTable', $this->funcTable]); 93 | $this->visitor->callChange('v5dot4\Deprecated', 'removeTableItems', ['funcTable', $this->funcTable]); 94 | $this->visitor->callChange('v5dot5\Deprecated', 'removeTableItems', ['funcTable', $this->funcTable]); 95 | $this->visitor->callChange('v5dot5\Deprecated', 'removeTableItems', ['mysqlTable', $this->funcTable]); 96 | $this->visitor->callChange('v5dot6\IncompMisc', 'removeTableItems', ['mcryptTable', $this->funcTable]); 97 | $this->visitor->callChange('v5dot6\Deprecated', 'skipHRPD', true); 98 | } 99 | } 100 | -------------------------------------------------------------------------------- /src/Changes/v7dot0/StringOperation.php: -------------------------------------------------------------------------------- 1 | value) && 21 | filter_var($node->value, FILTER_VALIDATE_INT, FILTER_FLAG_ALLOW_HEX)) { 22 | $this->addSpot('NOTICE', false, 'hexadecimal strings are no longer considered numeric'); 23 | } 24 | } 25 | } 26 | -------------------------------------------------------------------------------- /src/Changes/v7dot0/SwitchMultipleDefaults.php: -------------------------------------------------------------------------------- 1 | currentSwitch = $node; 29 | $this->hasDefault = false; 30 | } 31 | } 32 | 33 | public function leaveNode($node) 34 | { 35 | if ($node instanceof Stmt\Case_ && !is_null($this->currentSwitch) && is_null($node->cond)) { 36 | if ($this->hasDefault) { 37 | $this->addSpot('FATAL', true, 'Switch statements cannot have multiple default blocks'); 38 | } else { 39 | $this->hasDefault = true; 40 | } 41 | } elseif ($node instanceof Stmt\Switch_) { 42 | $this->currentSwitch = null; 43 | } 44 | } 45 | } 46 | -------------------------------------------------------------------------------- /src/CheckVisitor.php: -------------------------------------------------------------------------------- 1 | spots = []; 63 | $this->changes = $changes; 64 | $this->filename = $this->class = $this->method = $this->function = null; 65 | } 66 | 67 | /** 68 | * The interface that allow a Change call another Change's method. 69 | */ 70 | public function callChange($name, $method, $args) 71 | { 72 | if (!is_array($args)) { 73 | $args = [$args]; 74 | } 75 | 76 | foreach ($this->changes as $change) { 77 | if ('PhpMigration\Changes\\'.$name == get_class($change)) { 78 | return call_user_func_array([$change, $method], $args); 79 | } 80 | } 81 | } 82 | 83 | public function setCode($code) 84 | { 85 | $this->code = $code; 86 | } 87 | 88 | public function getCode() 89 | { 90 | return $this->code; 91 | } 92 | 93 | public function setFile(\SplFileInfo $file) 94 | { 95 | $this->file = $file; 96 | } 97 | 98 | public function getFile() 99 | { 100 | return $this->file; 101 | } 102 | 103 | public function getClass() 104 | { 105 | return $this->class; 106 | } 107 | 108 | public function getClassName() 109 | { 110 | return is_null($this->class) ? null : $this->class->migName; 111 | } 112 | 113 | public function inClass() 114 | { 115 | return !is_null($this->class); 116 | } 117 | 118 | public function getFunction() 119 | { 120 | return $this->function; 121 | } 122 | 123 | public function inFunction() 124 | { 125 | return !is_null($this->function); 126 | } 127 | 128 | public function prepare() 129 | { 130 | foreach ($this->changes as $change) { 131 | $change->setVisitor($this); 132 | $change->prepare(); 133 | } 134 | } 135 | 136 | public function beforeTraverse(array $nodes) 137 | { 138 | $this->classStack = $this->funcStack = []; 139 | 140 | foreach ($this->changes as $change) { 141 | $change->beforeTraverse($nodes); 142 | } 143 | } 144 | 145 | public function enterNode(Node $node) 146 | { 147 | $this->node = $node; 148 | 149 | // Record current 150 | if ($node instanceof Stmt\ClassLike) { 151 | /* 152 | * Class, Interface, Trait are stored in one same HashTable 153 | * (zend_executor_globals.class_table). Their name will be conflict 154 | * if duplicated (eg, class Demo {} and Interface Demo {}). So, we 155 | * treat all these class-like's name as Class name. 156 | */ 157 | $this->class = $node; 158 | $this->classStack[] = $node; 159 | } elseif ($node instanceof FunctionLike) { 160 | $this->function = $node; 161 | $this->funcStack[] = $node; 162 | } 163 | 164 | foreach ($this->changes as $change) { 165 | $change->enterNode($node); 166 | } 167 | 168 | $this->node = null; 169 | } 170 | 171 | public function leaveNode(Node $node) 172 | { 173 | $this->node = $node; 174 | 175 | foreach ($this->changes as $change) { 176 | $change->leaveNode($node); 177 | } 178 | 179 | // Pop current stack 180 | if ($node instanceof Stmt\ClassLike) { 181 | $this->class = array_pop($this->classStack); 182 | } elseif ($node instanceof FunctionLike) { 183 | $this->function = array_pop($this->funcStack); 184 | } 185 | 186 | $this->node = null; 187 | } 188 | 189 | public function afterTraverse(array $nodes) 190 | { 191 | foreach ($this->changes as $change) { 192 | $change->afterTraverse($nodes); 193 | } 194 | } 195 | 196 | public function finish() 197 | { 198 | foreach ($this->changes as $change) { 199 | $change->finish(); 200 | } 201 | } 202 | 203 | /** 204 | * Add a new spot. 205 | */ 206 | public function addSpot($cate, $identified, $message, $version = '', $line = null, $file = null) 207 | { 208 | if (is_null($line) && $this->node instanceof Node) { 209 | $line = $this->node->getLine(); 210 | } 211 | 212 | if (is_null($file)) { 213 | $file = $this->getFile(); 214 | } 215 | if ($file instanceof \SplFileInfo) { 216 | $filename = $file->getRealpath(); 217 | } else { 218 | $filename = ''; 219 | } 220 | 221 | // Add by file 222 | $this->spots[$filename][] = [ 223 | 'cate' => $cate, 224 | 'identified' => $identified, 225 | 'message' => $message, 226 | 'version' => $version, 227 | 'line' => $line, 228 | 'file' => $file, 229 | ]; 230 | } 231 | 232 | /** 233 | * Get all spots. 234 | */ 235 | public function getSpots() 236 | { 237 | return $this->spots; 238 | } 239 | } 240 | -------------------------------------------------------------------------------- /src/Logger.php: -------------------------------------------------------------------------------- 1 | interpolate($message, $context); 33 | 34 | // Open stream 35 | if (!isset($this->strerr)) { 36 | $this->stderr = fopen('php://stderr', 'w'); 37 | } 38 | 39 | fprintf( 40 | $this->stderr, 41 | "[%s] %s\n", 42 | strtoupper($level), 43 | $message 44 | ); 45 | } 46 | 47 | /** 48 | * Example implementation in http://www.php-fig.org/psr/psr-3/. 49 | */ 50 | protected function interpolate($message, array $context = []) 51 | { 52 | $replace = []; 53 | foreach ($context as $key => $val) { 54 | if ($key == 'exception') { 55 | $val = $val->getMessage(); 56 | } 57 | $replace['{'.$key.'}'] = $val; 58 | } 59 | 60 | return strtr($message, $replace); 61 | } 62 | } 63 | -------------------------------------------------------------------------------- /src/ReduceVisitor.php: -------------------------------------------------------------------------------- 1 | migName = $this->getName($node->namespacedName); 20 | } elseif (property_exists($node, 'name')) { 21 | $node->migName = $this->getName($node->name); 22 | } 23 | 24 | // Extends name 25 | if (property_exists($node, 'extends')) { 26 | $node->migExtends = $this->getName($node->extends); 27 | } 28 | } 29 | 30 | public function leaveNode(Node $node) 31 | { 32 | } 33 | 34 | public function afterTraverse(array $nodes) 35 | { 36 | } 37 | 38 | /** 39 | * Get data from name node or pure string. 40 | */ 41 | protected function getName($name) 42 | { 43 | return $name instanceof Name ? $name->toString() : $name; 44 | } 45 | } 46 | -------------------------------------------------------------------------------- /src/Sets/classtree.json: -------------------------------------------------------------------------------- 1 | { 2 | "desc": "List contents of classes in a tree-like format", 3 | "changes": [ 4 | "ClassTree" 5 | ] 6 | } 7 | -------------------------------------------------------------------------------- /src/Sets/to53.json: -------------------------------------------------------------------------------- 1 | { 2 | "desc": "Migrating from ANY version to PHP 5.3.x", 3 | "link": "http://php.net/manual/en/migration53.php", 4 | "depend": [ 5 | "v53" 6 | ] 7 | } 8 | -------------------------------------------------------------------------------- /src/Sets/to54.json: -------------------------------------------------------------------------------- 1 | { 2 | "desc": "Migrating from ANY version to PHP 5.4.x", 3 | "link": "http://php.net/manual/en/migration54.php", 4 | "depend": [ 5 | "to53", "v54" 6 | ] 7 | } 8 | -------------------------------------------------------------------------------- /src/Sets/to55.json: -------------------------------------------------------------------------------- 1 | { 2 | "desc": "Migrating from ANY version to PHP 5.5.x", 3 | "link": "http://php.net/manual/en/migration55.php", 4 | "depend": [ 5 | "to54", "v55" 6 | ] 7 | } 8 | -------------------------------------------------------------------------------- /src/Sets/to56.json: -------------------------------------------------------------------------------- 1 | { 2 | "desc": "Migrating from ANY version to PHP 5.6.x", 3 | "link": "http://php.net/manual/en/migration56.php", 4 | "depend": [ 5 | "to55", "v56" 6 | ] 7 | } 8 | -------------------------------------------------------------------------------- /src/Sets/to70.json: -------------------------------------------------------------------------------- 1 | { 2 | "desc": "Migrating from ANY version to PHP 7.0.x", 3 | "link": "http://php.net/migration70", 4 | "depend": [ 5 | "to56", "v70" 6 | ] 7 | } 8 | -------------------------------------------------------------------------------- /src/Sets/v53.json: -------------------------------------------------------------------------------- 1 | { 2 | "desc": "Migrating from PHP 5.2.x to PHP 5.3.x", 3 | "link": "http://php.net/manual/en/migration53.php", 4 | "changes": [ 5 | "v5dot3\\IncompByReference", 6 | "v5dot3\\IncompCallFromGlobal", 7 | "v5dot3\\IncompMagic", 8 | "v5dot3\\IncompMagicInvoked", 9 | "v5dot3\\IncompMisc", 10 | "v5dot3\\IncompReserved", 11 | "v5dot3\\Removed", 12 | "v5dot3\\Deprecated", 13 | "v5dot3\\Introduced" 14 | ] 15 | } 16 | -------------------------------------------------------------------------------- /src/Sets/v54.json: -------------------------------------------------------------------------------- 1 | { 2 | "desc": "Migrating from PHP 5.3.x to PHP 5.4.x", 3 | "link": "http://php.net/manual/en/migration54.php", 4 | "changes": [ 5 | "v5dot4\\IncompBreakContinue", 6 | "v5dot4\\IncompByReference", 7 | "v5dot4\\IncompHashAlgo", 8 | "v5dot4\\IncompMisc", 9 | "v5dot4\\IncompParamName", 10 | "v5dot4\\IncompRegister", 11 | "v5dot4\\IncompReserved", 12 | "v5dot4\\Removed", 13 | "v5dot4\\Deprecated", 14 | "v5dot4\\Introduced" 15 | ] 16 | } 17 | -------------------------------------------------------------------------------- /src/Sets/v55.json: -------------------------------------------------------------------------------- 1 | { 2 | "desc": "Migrating from PHP 5.4.x to PHP 5.5.x", 3 | "link": "http://php.net/manual/en/migration55.php", 4 | "changes": [ 5 | "v5dot5\\IncompCaseInsensitive", 6 | "v5dot5\\IncompPack", 7 | "v5dot5\\Removed", 8 | "v5dot5\\Deprecated", 9 | "v5dot5\\Introduced" 10 | ] 11 | } 12 | -------------------------------------------------------------------------------- /src/Sets/v56.json: -------------------------------------------------------------------------------- 1 | { 2 | "desc": "Migrating from PHP 5.5.x to PHP 5.6.x", 3 | "link": "http://php.net/manual/en/migration56.php", 4 | "changes": [ 5 | "v5dot6\\IncompPropertyArray", 6 | "v5dot6\\IncompMisc", 7 | "v5dot6\\Removed", 8 | "v5dot6\\Deprecated", 9 | "v5dot6\\Introduced" 10 | ] 11 | } 12 | -------------------------------------------------------------------------------- /src/Sets/v70.json: -------------------------------------------------------------------------------- 1 | { 2 | "desc": "Migrating from PHP 5.6.x to PHP 7.0.x", 3 | "link": "http://php.net/migration70", 4 | "changes": [ 5 | "v7dot0\\ExceptionHandle", 6 | "v7dot0\\ForeachLoop", 7 | "v7dot0\\FuncList", 8 | "v7dot0\\FuncParameters", 9 | "v7dot0\\IntegerOperation", 10 | "v7dot0\\ParseDifference", 11 | "v7dot0\\StringOperation", 12 | "v7dot0\\SwitchMultipleDefaults", 13 | 14 | "v7dot0\\Deprecated", 15 | "v7dot0\\Introduced", 16 | "v7dot0\\KeywordReserved", 17 | "v7dot0\\Removed" 18 | ], 19 | "options": { 20 | "parse_as_version": 7 21 | } 22 | } 23 | -------------------------------------------------------------------------------- /src/SymbolTable.php: -------------------------------------------------------------------------------- 1 | data = []; 22 | foreach ($data as $rawkey => $value) { 23 | $key = $case_sensitive ? $rawkey : strtolower($rawkey); 24 | $this->data[$key] = $value; 25 | } 26 | $this->caseSensitive = $case_sensitive; 27 | } 28 | 29 | protected function prepareKey(&$key) 30 | { 31 | // Compatible with almost all type key 32 | if (is_object($key) && method_exists($key, '__toString')) { 33 | $key = (string) $key; 34 | } 35 | if (!is_string($key)) { 36 | return false; 37 | } 38 | 39 | $key = $this->caseSensitive ? $key : strtolower($key); 40 | 41 | return true; 42 | } 43 | 44 | /** 45 | * Basic operation. 46 | */ 47 | public function has($key) 48 | { 49 | if (!$this->prepareKey($key)) { 50 | return false; 51 | } 52 | 53 | return isset($this->data[$key]); 54 | } 55 | 56 | public function get($key) 57 | { 58 | if (!$this->prepareKey($key)) { 59 | return; 60 | } 61 | 62 | return isset($this->data[$key]) ? $this->data[$key] : null; 63 | } 64 | 65 | public function set($key, $value) 66 | { 67 | if (!$this->prepareKey($key)) { 68 | return; 69 | } 70 | 71 | return $this->data[$key] = $value; 72 | } 73 | 74 | public function del($key) 75 | { 76 | if (!$this->prepareKey($key)) { 77 | return false; 78 | } 79 | 80 | if (!isset($this->data[$key])) { 81 | return false; 82 | } 83 | 84 | unset($this->data[$key]); 85 | 86 | return true; 87 | } 88 | 89 | /** 90 | * Implement Iterator. 91 | */ 92 | public function current() 93 | { 94 | return current($this->data); 95 | } 96 | 97 | public function key() 98 | { 99 | return key($this->data); 100 | } 101 | 102 | public function next() 103 | { 104 | next($this->data); 105 | } 106 | 107 | public function rewind() 108 | { 109 | reset($this->data); 110 | } 111 | 112 | public function valid() 113 | { 114 | return $this->current() !== false; 115 | } 116 | 117 | /** 118 | * Implement ArrayAccess. 119 | */ 120 | public function offsetExists($offset) 121 | { 122 | return $this->has($offset); 123 | } 124 | 125 | public function offsetGet($offset) 126 | { 127 | return $this->get($offset); 128 | } 129 | 130 | public function offsetSet($offset, $value) 131 | { 132 | return $this->set($offset, $value); 133 | } 134 | 135 | public function offsetUnset($offset) 136 | { 137 | return $this->del($offset); 138 | } 139 | } 140 | -------------------------------------------------------------------------------- /src/Utils/FunctionListExporter.php: -------------------------------------------------------------------------------- 1 | [], 9 | 'type' => null, 10 | 'name' => null, 11 | 'params' => [], 12 | ]; 13 | 14 | protected static $defaultParam = [ 15 | 'optional' => false, 16 | 'type' => null, 17 | 'name' => null, 18 | 'initializer' => null, 19 | 'reference' => false, 20 | ]; 21 | 22 | public function parse($dhtml) 23 | { 24 | $dhtml = $this->prepare($dhtml); 25 | 26 | libxml_use_internal_errors(true); 27 | libxml_clear_errors(); 28 | $droot = new \SimpleXMLElement($dhtml, LIBXML_NONET); 29 | if ($droot->attributes()->class != 'methodsynopsis dc-description') { 30 | throw new \Exception('Invalid method description html'); 31 | } 32 | $errors = libxml_get_errors(); 33 | 34 | $method = self::$defaultMethod; 35 | foreach ($droot as $element) { 36 | $class = $element->attributes()->class; 37 | $text = strip_tags($element->asXML()); 38 | 39 | if ($class == 'modifier') { 40 | $method['modifier'][$text] = $text; 41 | } elseif ($class == 'type') { 42 | $method['type'] = $text; 43 | } elseif ($class == 'methodname') { 44 | $method['name'] = $text; 45 | } elseif ($class == 'methodparam') { 46 | if ($element != 'void') { 47 | $param = $this->parseParam($element); 48 | $method['params'][] = $param; 49 | } 50 | } else { 51 | throw new \Exception('Unknown method defination class <'.$class.'>'); 52 | } 53 | } 54 | 55 | return $method; 56 | } 57 | 58 | protected function prepare($dhtml) 59 | { 60 | // Add CDATA to reference param 61 | $dhtml = preg_replace('/]+)>(.*?)<\/code>/s', '', $dhtml); 62 | 63 | return $dhtml; 64 | } 65 | 66 | protected function parseParam($droot) 67 | { 68 | $param = self::$defaultParam; 69 | foreach ($droot as $element) { 70 | $class = $element->attributes()->class; 71 | 72 | $xml = $element->asXML(); 73 | if ($element->getName() == 'code') { 74 | // Strip CDATA 75 | $xml = preg_replace('//', '$1', $xml); 76 | } 77 | $text = strip_tags($xml); 78 | 79 | if ($class == 'type') { 80 | $param['type'] = $text; 81 | } elseif ($class == 'parameter') { 82 | $param['name'] = $text; 83 | } elseif ($class == 'parameter reference') { 84 | $param['name'] = $text; 85 | $param['reference'] = true; 86 | } elseif ($class == 'initializer') { 87 | $param['optional'] = true; // FIXME: has default-value isn't meanning be optional 88 | $param['initializer'] = $text; 89 | } else { 90 | throw new \Exception('Unknown param defination class <'.$class.'>'); 91 | } 92 | } 93 | 94 | return $param; 95 | } 96 | 97 | public function parseAll($wholehtml) 98 | { 99 | $list = []; 100 | 101 | preg_match_all('/
.+?<\/div>/s', $wholehtml, $matches); 102 | if (!$matches[0]) { 103 | throw new \Exception('Invalid single document html'); 104 | } 105 | 106 | foreach ($matches[0] as $desc) { 107 | try { 108 | $method = $this->parse($desc); 109 | $list[] = $method; 110 | } catch (\Exception $e) { 111 | continue; 112 | } 113 | } 114 | 115 | return $list; 116 | } 117 | } 118 | -------------------------------------------------------------------------------- /src/Utils/Logging.php: -------------------------------------------------------------------------------- 1 | $method(); 25 | 26 | case 1: 27 | return $logger->$method($args[0]); 28 | 29 | case 2: 30 | return $logger->$method($args[0], $args[1]); 31 | 32 | case 3: 33 | return $logger->$method($args[0], $args[1], $args[2]); 34 | 35 | case 4: 36 | return $logger->$method($args[0], $args[1], $args[2], $args[3]); 37 | 38 | default: 39 | return call_user_func_array([$logger, $method], $args); 40 | } 41 | } 42 | } 43 | -------------------------------------------------------------------------------- /src/Utils/Packager.php: -------------------------------------------------------------------------------- 1 | run(); 108 | 109 | __HALT_COMPILER(); 110 | EOC; 111 | $phar->setStub($code); 112 | 113 | // File 114 | foreach ($this->filelist as $file) { 115 | $phar->addFile($file); 116 | } 117 | 118 | // Vendor 119 | chdir(__DIR__.'/../../'); 120 | $iterator = new \RecursiveIteratorIterator( 121 | new \RecursiveDirectoryIterator('vendor'), 122 | 0, 123 | \RecursiveIteratorIterator::CATCH_GET_CHILD 124 | ); 125 | foreach ($iterator as $file) { 126 | if (!preg_match('/\/(\.|test\/)/i', $file)) { 127 | $phar->addFile($file); 128 | } 129 | } 130 | 131 | // Add execute permission 132 | chmod(self::NAME, 0755); 133 | } 134 | } 135 | -------------------------------------------------------------------------------- /src/Utils/ParserHelper.php: -------------------------------------------------------------------------------- 1 | name); 20 | } elseif ($node instanceof Expr\StaticCall) { 21 | return !is_string($node->name) || !self::isConstName($node->class); 22 | } elseif ($node instanceof Expr\FuncCall) { 23 | return !($node->name instanceof Name); 24 | } else { 25 | throw new \Exception('Invalid function, method call node ('.get_class($node).')'); 26 | } 27 | } 28 | 29 | /** 30 | * Test if given variable is a const name. 31 | */ 32 | public static function isConstName($prop) 33 | { 34 | return is_string($prop) || method_exists($prop, '__toString'); 35 | } 36 | 37 | public static function isSameFunc($name, $const) 38 | { 39 | if (!self::isConstName($name)) { 40 | return false; 41 | } 42 | 43 | return strcasecmp($name, $const) === 0; 44 | } 45 | 46 | public static function isSameClass($name, $const) 47 | { 48 | if (!self::isConstName($name)) { 49 | return false; 50 | } 51 | 52 | return strcasecmp($name, $const) === 0; 53 | } 54 | } 55 | -------------------------------------------------------------------------------- /tests/Changes/AbstractChangeTest.php: -------------------------------------------------------------------------------- 1 | change = new $chgname(); 18 | } 19 | 20 | public function assertHasSpot($code) 21 | { 22 | $spots = TestHelper::runChange($this->change, $code); 23 | $this->assertNotEmpty($spots); 24 | } 25 | 26 | public function assertNotSpot($code) 27 | { 28 | $spots = TestHelper::runChange($this->change, $code); 29 | $this->assertEmpty($spots); 30 | } 31 | } 32 | -------------------------------------------------------------------------------- /tests/Changes/AbstractIntroducedTest.php: -------------------------------------------------------------------------------- 1 | assertNotSpot($code); 14 | 15 | $table = TestHelper::fetchProperty($this->change, 'funcTable'); 16 | if (is_null($table)) { 17 | return; 18 | } 19 | foreach ($table as $name => $dummy) { 20 | // Normal name 21 | $code = sprintf('function %s() {}', $name); 22 | $this->assertHasSpot($code); 23 | 24 | // Case Insensitive name 25 | $code = sprintf('function %s() {}', strtoupper($name)); 26 | $this->assertHasSpot($code); 27 | 28 | // Namespaced 29 | $code = sprintf('namespace Dummy; function %s() {}', $name); 30 | $this->assertNotSpot($code); 31 | 32 | // Conditional name 33 | $code = sprintf("if (!function_exists('%s')) { function %s() {} }", $name, $name); 34 | $this->assertNotSpot($code); 35 | } 36 | 37 | // Error-conditional name 38 | $code = sprintf("if (!function_exists('nothing')) { function %s() {} }", $name); 39 | $this->assertHasSpot($code); 40 | } 41 | 42 | protected function genMethod($class, $method) 43 | { 44 | return sprintf('class Sample extends %s { public function %s() {} }', $class, $method); 45 | } 46 | 47 | public function testNewMethod() 48 | { 49 | $table = TestHelper::fetchProperty($this->change, 'methodTable'); 50 | if (is_null($table)) { 51 | return; 52 | } 53 | foreach ($table as $name => $dummy) { 54 | list($class, $method) = explode('::', $name); 55 | 56 | // Normal name 57 | $code = $this->genMethod($class, $method); 58 | $this->assertHasSpot($code); 59 | 60 | // Case Insensitive name 61 | $code = $this->genMethod(strtoupper($class), strtoupper($method)); 62 | $this->assertHasSpot($code); 63 | 64 | // Namespaced 65 | $code = 'namespace Dummy; '.$this->genMethod($class, $method); 66 | $this->assertNotSpot($code); 67 | } 68 | } 69 | 70 | public function testNewClass() 71 | { 72 | // Not-new 73 | $code = 'class not_new {}'; 74 | $this->assertNotSpot($code); 75 | 76 | $table = TestHelper::fetchProperty($this->change, 'classTable'); 77 | if (is_null($table)) { 78 | return; 79 | } 80 | foreach ($table as $name => $dummy) { 81 | // Normal name 82 | $code = sprintf('class %s {}', $name); 83 | $this->assertHasSpot($code); 84 | 85 | // Case Insensitive name 86 | $code = sprintf('class %s {}', strtoupper($name)); 87 | $this->assertHasSpot($code); 88 | 89 | // Namespaced 90 | $code = sprintf('namespace Dummy; class %s {}', $name); 91 | $this->assertNotSpot($code); 92 | 93 | // Conditional name 94 | // Removed, because of autoload it's too rare to see 95 | // $code = sprintf("if (!class_exists('%s')) { class %s {} }", $name, $name); 96 | // $this->assertNotSpot($code); 97 | } 98 | } 99 | 100 | protected function genDefine($name) 101 | { 102 | return 'define("'.$name.'", 0);'; 103 | } 104 | 105 | public function testNewConst() 106 | { 107 | // Not-new 108 | $code = $this->genDefine('NOTNEW'); 109 | $this->assertNotSpot($code); 110 | 111 | $table = TestHelper::fetchProperty($this->change, 'constTable'); 112 | foreach ($table as $name => $dummy) { 113 | // Normal name 114 | $code = $this->genDefine($name); 115 | $this->assertHasSpot($code); 116 | 117 | // Case Insensitive name 118 | $code = $this->genDefine(strtolower($name)); 119 | $this->assertNotSpot($code); 120 | } 121 | 122 | // #Issue 7: First argument not a string 123 | $this->assertNotSpot('define(DUMMY, 0);'); 124 | $this->assertNotSpot('define(123, 0);'); 125 | $this->assertNotSpot('define($dummy, 0);'); 126 | } 127 | } 128 | -------------------------------------------------------------------------------- /tests/Changes/AbstractRemovedTest.php: -------------------------------------------------------------------------------- 1 | assertNotSpot($code); 14 | 15 | $table = TestHelper::fetchProperty($this->change, 'funcTable'); 16 | if (is_null($table)) { 17 | return; 18 | } 19 | foreach ($table as $name => $dummy) { 20 | // Normal name 21 | $code = sprintf('%s();', $name); 22 | $this->assertHasSpot($code); 23 | 24 | // Case Insensitive name 25 | $code = sprintf('%s();', strtoupper($name)); 26 | $this->assertHasSpot($code); 27 | 28 | // Namespaced 29 | $code = sprintf('use Dummy as %s; %s();', $name, $name); 30 | $this->assertHasSpot($code); 31 | 32 | $code = sprintf("dummy\%s();", $name, $name); 33 | $this->assertNotSpot($code); 34 | } 35 | } 36 | 37 | public function testConst() 38 | { 39 | // Not-new 40 | $code = 'NOT_REMOVED;'; 41 | $this->assertNotSpot($code); 42 | 43 | $table = TestHelper::fetchProperty($this->change, 'constTable'); 44 | if (is_null($table)) { 45 | return; 46 | } 47 | foreach ($table as $name => $dummy) { 48 | // Normal name 49 | $code = $name.';'; 50 | $this->assertHasSpot($code); 51 | 52 | // Case Insensitive name 53 | $code = strtolower($name).';'; 54 | $this->assertNotSpot($code); 55 | } 56 | } 57 | 58 | public function testVar() 59 | { 60 | // Not-new 61 | $code = '$not_removed;'; 62 | $this->assertNotSpot($code); 63 | 64 | $table = TestHelper::fetchProperty($this->change, 'varTable'); 65 | if (is_null($table)) { 66 | return; 67 | } 68 | foreach ($table as $name => $dummy) { 69 | $code = '$'.$name.';'; 70 | $this->assertHasSpot($code); 71 | } 72 | } 73 | } 74 | -------------------------------------------------------------------------------- /tests/Changes/v5dot3/DeprecatedTest.php: -------------------------------------------------------------------------------- 1 | assertNotSpot('not_new();'); 14 | 15 | $table = TestHelper::fetchProperty($this->change, 'funcTable'); 16 | foreach ($table as $name => $dummy) { 17 | // Normal name 18 | $this->assertHasSpot(sprintf('%s();', $name)); 19 | 20 | // Case Insensitive name 21 | $this->assertHasSpot(sprintf('%s();', strtoupper($name))); 22 | } 23 | } 24 | 25 | public function testAssignNewByRef() 26 | { 27 | // Direct assign 28 | $this->assertNotSpot('$o = new Class_();'); 29 | 30 | // By-reference 31 | $this->assertHasSpot('$o = &new Class_();'); 32 | } 33 | 34 | public function testCallTimePassByRef() 35 | { 36 | $this->assertNotSpot('func($a, $b, "asdf");'); 37 | 38 | // Call-time pass-by-reference 39 | $this->assertHasSpot('func($a, &$b, "asdf");'); 40 | 41 | // Set skip 42 | $this->change->skipCallTimePassByRef(true); 43 | $this->assertNotSpot('func($a, &$b, "asdf");'); 44 | 45 | // Cancle skip 46 | $this->change->skipCallTimePassByRef(false); 47 | $this->assertHasSpot('func($a, &$b, "asdf");'); 48 | } 49 | } 50 | -------------------------------------------------------------------------------- /tests/Changes/v5dot3/IncompByReferenceTest.php: -------------------------------------------------------------------------------- 1 | assertNotSpot('function sample() {} sample();'); 12 | 13 | $this->assertNotSpot('function sample() {} sample(1);'); 14 | 15 | $this->assertNotSpot('function sample($a, $b) {} sample(1, 2);'); 16 | 17 | $this->assertNotSpot('function sample($a, &$b) {} sample(1, $a);'); 18 | 19 | $this->assertHasSpot('function sample($a, &$b) {} sample(1, 2);'); 20 | 21 | $this->assertHasSpot('function sample($a, &$b) {} sample(1, "h");'); 22 | 23 | $this->assertHasSpot('function sample($a, &$b) {} sample(1, array(1));'); 24 | 25 | $this->assertHasSpot('function sample($a, &$b) {} sample(1, true);'); 26 | 27 | // Case Insensitive 28 | $this->assertHasSpot('function SAMple($a, &$b) {} samPLE(1, "h");'); 29 | 30 | // Built-in 31 | $this->assertNotSpot('array_shift($a);'); 32 | $this->assertNotSpot('ksort($a);'); 33 | 34 | $this->assertHasSpot('array_shift(1);'); 35 | $this->assertHasSpot('ksort(1);'); 36 | } 37 | 38 | public function testMehtod() 39 | { 40 | $code = 'class Sample {static function sample($a) {}} Sample::sample();'; 41 | $this->assertNotSpot($code); 42 | 43 | $code = 'class Sample {static function sample($a, $b) {}} Sample::sample(1, 2);'; 44 | $this->assertNotSpot($code); 45 | 46 | $code = 'class Sample {static function sample($a, &$b) {}} Sample::sample(1, 2);'; 47 | $this->assertHasSpot($code); 48 | 49 | $code = 'class Sample {static function sample($a, &$b) {}} sAMPLE::saMPLe(1, 2);'; 50 | $this->assertHasSpot($code); 51 | 52 | // #Issue 13: Call to undefined method PhpParser\Node\Expr\ArrayDimFetch::toString() 53 | $code = '$cb[0]::sample(1);'; 54 | $this->assertNotSpot($code); 55 | } 56 | } 57 | -------------------------------------------------------------------------------- /tests/Changes/v5dot3/IncompCallFromGlobalTest.php: -------------------------------------------------------------------------------- 1 | assertHasSpot('func_get_arg();'); 12 | 13 | $this->assertNotSpot('function func() { func_get_arg(); }'); 14 | 15 | $code = 'class Same { function func() { func_get_arg(); } }'; 16 | $this->assertNotSpot($code); 17 | } 18 | } 19 | -------------------------------------------------------------------------------- /tests/Changes/v5dot3/IncompMagicInvokedTest.php: -------------------------------------------------------------------------------- 1 | assertHasSpot($code); 18 | 19 | $code = <<<'EOC' 20 | class Sample { 21 | public function oh() {} 22 | public function __call() {} 23 | } 24 | EOC; 25 | $this->assertNotSpot($code); 26 | 27 | $code = <<<'EOC' 28 | class Sample { 29 | protected function oh() {} 30 | } 31 | EOC; 32 | $this->assertNotSpot($code); 33 | } 34 | } 35 | -------------------------------------------------------------------------------- /tests/Changes/v5dot3/IncompMagicTest.php: -------------------------------------------------------------------------------- 1 | assertNotSpot($code); 13 | 14 | $code = 'class Sample{ function __toString($a){} }'; 15 | $this->assertHasSpot($code); 16 | } 17 | 18 | public function testNonPub() 19 | { 20 | $code = 'class Sample{ function __get(){} }'; 21 | $this->assertNotSpot($code); 22 | 23 | $code = 'class Sample{ public function __get(){} }'; 24 | $this->assertNotSpot($code); 25 | 26 | $code = 'class Sample{ protected function __get($a){} }'; 27 | $this->assertHasSpot($code); 28 | 29 | $code = 'class Sample{ private function __get($a){} }'; 30 | $this->assertHasSpot($code); 31 | 32 | $code = 'class Sample{ public static function __get($a){} }'; 33 | $this->assertHasSpot($code); 34 | } 35 | } 36 | -------------------------------------------------------------------------------- /tests/Changes/v5dot3/IncompMiscTest.php: -------------------------------------------------------------------------------- 1 | assertNotSpot('never_emit_spot();'); 13 | 14 | $this->assertHasSpot('clearstatcache();'); 15 | 16 | $this->assertHasSpot('realpath();'); 17 | 18 | $this->assertHasSpot('gd_info();'); 19 | 20 | $table = TestHelper::fetchProperty($this->change, 'arrFuncTable'); 21 | foreach ($table as $name => $dummy) { 22 | $this->assertHasSpot($name.'();'); 23 | } 24 | } 25 | 26 | public function testCallFunc() 27 | { 28 | $this->assertNotSpot('call_user_func_array();'); 29 | 30 | $this->assertNotSpot('call_user_func_array($a, array());'); 31 | 32 | $this->assertHasSpot('call_user_func_array($a, $b);'); 33 | 34 | $this->assertHasSpot('call_user_func_array($a, null);'); 35 | 36 | $this->assertHasSpot('call_user_func_array($a, "str");'); 37 | 38 | $this->assertHasSpot('call_user_func_array($a, 123);'); 39 | } 40 | } 41 | -------------------------------------------------------------------------------- /tests/Changes/v5dot3/IntroducedTest.php: -------------------------------------------------------------------------------- 1 | assertHasSpot('mcrypt_generic_end();'); 12 | $this->assertHasSpot('mysql_list_dbs();'); 13 | } 14 | } 15 | -------------------------------------------------------------------------------- /tests/Changes/v5dot4/IncompBreakContinueTest.php: -------------------------------------------------------------------------------- 1 | assertNotSpot('break;'); 13 | $this->assertNotSpot('break 1;'); 14 | $this->assertNotSpot('break 100;'); 15 | 16 | $this->assertHasSpot('break $a;'); 17 | $this->assertHasSpot('break 0;'); 18 | $this->assertHasSpot('break 1 + 1;'); 19 | 20 | // Continue 21 | $this->assertNotSpot('continue;'); 22 | $this->assertNotSpot('continue 1;'); 23 | $this->assertNotSpot('continue 100;'); 24 | 25 | $this->assertHasSpot('continue $a;'); 26 | $this->assertHasSpot('continue 0;'); 27 | $this->assertHasSpot('continue 1 + 1;'); 28 | } 29 | } 30 | -------------------------------------------------------------------------------- /tests/Changes/v5dot4/IncompByReferenceTest.php: -------------------------------------------------------------------------------- 1 | assertNotSpot('func();'); 12 | $this->assertNotSpot('func(1);'); 13 | $this->assertNotSpot('func($a);'); 14 | 15 | $this->assertHasSpot('func(&$a);'); 16 | $this->assertHasSpot('$obj->func(&$a);'); 17 | $this->assertHasSpot('Sample::func(&$a);'); 18 | } 19 | } 20 | -------------------------------------------------------------------------------- /tests/Changes/v5dot4/IncompHashAlgoTest.php: -------------------------------------------------------------------------------- 1 | change, 'funcTable'); 13 | foreach ($table as $name => $dummy) { 14 | $this->assertHasSpot($name.'("salsa10");'); 15 | $this->assertHasSpot($name.'("salsa20");'); 16 | $this->assertNotSpot($name.'("md5");'); 17 | 18 | // Uncertain 19 | $this->assertHasSpot($name.'($a);'); 20 | 21 | // Emtpy 22 | $this->assertNotSpot($name.'();'); 23 | } 24 | } 25 | } 26 | -------------------------------------------------------------------------------- /tests/Changes/v5dot4/IncompMiscTest.php: -------------------------------------------------------------------------------- 1 | assertNotSpot('never_emit_spot();'); 12 | 13 | // array_combine 14 | $this->assertHasSpot('array_combine();'); 15 | 16 | // ob_start 17 | $this->assertNotSpot('ob_start();'); 18 | $this->assertNotSpot('ob_start(1, 2);'); 19 | $this->assertHasSpot('ob_start(1, 2, 3);'); 20 | 21 | // htmlentities, htmlspecialchars 22 | $this->assertHasSpot('htmlentities();'); 23 | $this->assertHasSpot('htmlspecialchars();'); 24 | } 25 | } 26 | -------------------------------------------------------------------------------- /tests/Changes/v5dot4/IncompParamNameTest.php: -------------------------------------------------------------------------------- 1 | change, 'autoGlobals'); 14 | foreach ($table as $name => $dummy) { 15 | $this->assertHasSpot(sprintf('function f($%s) {}', $name)); 16 | $this->assertHasSpot(sprintf('class Cl { function f($%s) {} }', $name)); 17 | 18 | $this->assertHasSpot(sprintf('function f($a, $b, $c, $%s) {}', $name)); 19 | $this->assertHasSpot(sprintf('class Cl { function f($a, $b, $c, $%s) {} }', $name)); 20 | } 21 | 22 | // Method 23 | $this->assertHasSpot('class Cl { public function me($this) {} }'); 24 | $this->assertNotSpot('class Cl { public static function me($this) {} }'); 25 | } 26 | } 27 | -------------------------------------------------------------------------------- /tests/Changes/v5dot4/IncompRegisterTest.php: -------------------------------------------------------------------------------- 1 | assertHasSpot('$HTTP_POST_VARS;'); 12 | $this->assertHasSpot('$HTTP_GET_VARS;'); 13 | $this->assertHasSpot('$HTTP_ENV_VARS;'); 14 | $this->assertHasSpot('$HTTP_SERVER_VARS;'); 15 | $this->assertHasSpot('$HTTP_COOKIE_VARS;'); 16 | $this->assertHasSpot('$HTTP_SESSION_VARS;'); 17 | $this->assertHasSpot('$HTTP_POST_FILES;'); 18 | 19 | // Case error 20 | $this->assertNotSpot('$HttP_POST_VARS;'); 21 | $this->assertNotSpot('$HttP_GET_VARS;'); 22 | $this->assertNotSpot('$HttP_ENV_VARS;'); 23 | $this->assertNotSpot('$HttP_SERVER_VARS;'); 24 | $this->assertNotSpot('$HttP_COOKIE_VARS;'); 25 | $this->assertNotSpot('$HttP_SESSION_VARS;'); 26 | $this->assertNotSpot('$HttP_POST_FILES;'); 27 | 28 | $this->assertNotSpot('$HTTP_SHIT_VARS;'); 29 | 30 | $this->assertNotSpot('$$name;'); 31 | } 32 | } 33 | -------------------------------------------------------------------------------- /tests/Changes/v5dot4/IntroducedTest.php: -------------------------------------------------------------------------------- 1 | change, 'funcTable'); 13 | foreach ($table as $name => $dummy) { 14 | $this->assertHasSpot($name.'();'); 15 | } 16 | } 17 | 18 | public function testMysqlFunc() 19 | { 20 | $table = TestHelper::fetchProperty($this->change, 'mysqlTable'); 21 | foreach ($table as $name => $dummy) { 22 | $this->assertHasSpot($name.'();'); 23 | } 24 | } 25 | 26 | public function testPregReplace() 27 | { 28 | $this->assertNotSpot('preg_replace();'); 29 | 30 | $this->assertNotSpot('preg_replace("//i");'); 31 | $this->assertNotSpot('preg_replace(""."//i");'); 32 | $this->assertNotSpot('preg_replace("/{$part}$pattern/i");'); 33 | 34 | $this->assertHasSpot('preg_replace("//e");'); 35 | $this->assertHasSpot('preg_replace(""."//e");'); 36 | $this->assertHasSpot('preg_replace("/{$part}$pattern/e");'); 37 | } 38 | } 39 | -------------------------------------------------------------------------------- /tests/Changes/v5dot5/IncompCaseInsensitiveTest.php: -------------------------------------------------------------------------------- 1 | assertNotSpot('self::call();'); 13 | $this->assertNotSpot('parent::call();'); 14 | $this->assertNotSpot('static::call();'); 15 | 16 | $this->assertHasSpot('sElf::call();'); 17 | $this->assertHasSpot('pArent::call();'); 18 | $this->assertHasSpot('sTatic::call();'); 19 | 20 | // Property 21 | $this->assertNotSpot('self::$fetch;'); 22 | $this->assertNotSpot('parent::$fetch;'); 23 | $this->assertNotSpot('static::$fetch;'); 24 | 25 | $this->assertHasSpot('sElf::$fetch;'); 26 | $this->assertHasSpot('pArent::$fetch;'); 27 | $this->assertHasSpot('sTatic::$fetch;'); 28 | } 29 | } 30 | -------------------------------------------------------------------------------- /tests/Changes/v5dot5/IncompPackTest.php: -------------------------------------------------------------------------------- 1 | assertNotSpot('unpack();'); 12 | $this->assertNotSpot('unpack("x");'); 13 | 14 | $this->assertHasSpot('unpack("a");'); 15 | $this->assertHasSpot('unpack("A");'); 16 | $this->assertHasSpot('unpack($a);'); 17 | $this->assertHasSpot('unpack($a."");'); 18 | $this->assertHasSpot('unpack("$b");'); 19 | } 20 | } 21 | -------------------------------------------------------------------------------- /tests/Changes/v5dot5/IntroducedTest.php: -------------------------------------------------------------------------------- 1 | assertHasSpot('$HTTP_RAW_POST_DATA;'); 12 | } 13 | } 14 | -------------------------------------------------------------------------------- /tests/Changes/v5dot6/IncompMiscTest.php: -------------------------------------------------------------------------------- 1 | assertHasSpot('json_decode();'); 13 | } 14 | 15 | public function testGmp() 16 | { 17 | $table = TestHelper::fetchProperty($this->change, 'gmpTable'); 18 | foreach ($table as $name => $dummy) { 19 | $this->assertHasSpot($name.'();'); 20 | } 21 | } 22 | 23 | public function testMcrypt() 24 | { 25 | $table = TestHelper::fetchProperty($this->change, 'mcryptTable'); 26 | foreach ($table as $name => $dummy) { 27 | $this->assertHasSpot($name.'();'); 28 | } 29 | } 30 | } 31 | -------------------------------------------------------------------------------- /tests/Changes/v5dot6/IncompPropertyArrayTest.php: -------------------------------------------------------------------------------- 1 | assertNotSpot('class Sample { }'); 13 | 14 | // Without default value, non-array type 15 | $this->assertNotSpot('class Sample { public $pro; protected $int = 5; private $str = "he";}'); 16 | 17 | // Empty array 18 | $this->assertNotSpot('class Sample { public $arr = array(); }'); 19 | 20 | // Class-const-key with null 21 | $code = <<<'EOC' 22 | class Sample 23 | { 24 | const CK = 'one'; 25 | public $data = array( 26 | self::CK => 1, 27 | 2, 28 | ); 29 | } 30 | EOC; 31 | $this->assertHasSpot($code); 32 | 33 | // Class-const-key unfetched 34 | $code = <<<'EOC' 35 | class Sample 36 | { 37 | public $data = array( 38 | self::CK => 1, 39 | 'two' => 2, 40 | ); 41 | } 42 | EOC; 43 | $this->assertHasSpot($code); 44 | 45 | // Class-const-key fetched, but duplicated 46 | $code = <<<'EOC' 47 | class Sample 48 | { 49 | const CK = 'one'; 50 | public $data = array( 51 | self::CK => 1, 52 | 'one' => 2, 53 | ); 54 | } 55 | EOC; 56 | $this->assertHasSpot($code); 57 | 58 | // Global-const-key is always unfetched 59 | $code = <<<'EOC' 60 | class Sample 61 | { 62 | public $data = array( 63 | CK => 1, 64 | ); 65 | } 66 | EOC; 67 | $this->assertHasSpot($code); 68 | 69 | // Class-const-key duplicated 70 | $code = <<<'EOC' 71 | class Sample 72 | { 73 | const CK = 'one'; 74 | public $data = array( 75 | self::CK => 1, 76 | self::CK => 1, 77 | ); 78 | } 79 | EOC; 80 | $this->assertHasSpot($code); 81 | 82 | // Exception, all const feteched, and no duplicated 83 | $code = <<<'EOC' 84 | class Sample 85 | { 86 | const CK1 = 'one'; 87 | const CK2 = 'two'; 88 | public $data = array( 89 | self::CK1 => 1, 90 | self::CK2 => 1, 91 | 'third' => 0, 92 | ); 93 | } 94 | EOC; 95 | $this->assertNotSpot($code); 96 | } 97 | } 98 | -------------------------------------------------------------------------------- /tests/Changes/v5dot6/IntroducedTest.php: -------------------------------------------------------------------------------- 1 | assertHasSpot('class OldClass { function OldClass() {} }'); 12 | 13 | $this->assertHasSpot('namespace Dummy; class OldClass { function OldClass() {} }'); 14 | 15 | $this->assertNotSpot('class OldClass { function setName() {} }'); 16 | 17 | $this->assertNotSpot('class OldClass { function _construct() {} }'); 18 | } 19 | 20 | public function testPasswordHash() 21 | { 22 | $this->assertHasSpot('password_hash($a, $b, $c);'); 23 | 24 | $this->assertHasSpot('password_hash($a, $b, []);'); 25 | 26 | $this->assertNotSpot('password_hash($a, $b);'); 27 | 28 | $this->assertNotSpot('Dummy\password_hash($a, $b, []);'); 29 | } 30 | 31 | public function testLdapSort() 32 | { 33 | $this->assertHasSpot('ldap_sort();'); 34 | 35 | $this->assertNotSpot('Dummy\ldap_sort();'); 36 | } 37 | } 38 | -------------------------------------------------------------------------------- /tests/Changes/v7dot0/ExceptionHandleTest.php: -------------------------------------------------------------------------------- 1 | assertNotSpot('set_exception_handler();'); 12 | 13 | $this->assertHasSpot('set_exception_handler($handler);'); 14 | 15 | $this->assertHasSpot('set_exception_handler([$this, "handler"]);'); 16 | 17 | $this->assertNotSpot('set_exception_handler(function () {});'); 18 | 19 | $this->assertNotSpot('set_exception_handler(function ($e) {});'); 20 | 21 | $this->assertHasSpot('set_exception_handler(function (Exception $e) {});'); 22 | 23 | $this->assertHasSpot('set_exception_handler(function (\Exception $e) {});'); 24 | 25 | $this->assertHasSpot('use Dummy\Exception; set_exception_handler(function (Exception $e) {});'); 26 | } 27 | } 28 | -------------------------------------------------------------------------------- /tests/Changes/v7dot0/ForeachLoopTest.php: -------------------------------------------------------------------------------- 1 | assertHasSpot('foreach ($arr as &$i) { current(); }'); 12 | 13 | $this->assertHasSpot('foreach ($arr as &$i) { key(); }'); 14 | 15 | $this->assertNotSpot('foreach ($arr as &$i) { end(); }'); 16 | 17 | $this->assertHasSpot('foreach ($arr as $key => &$i) { key(); }'); 18 | 19 | $this->assertNotSpot('foreach ($arr as $key => $i) { key(); }'); 20 | 21 | $this->assertNotSpot('foreach ($arr as $key => $i) {}'); 22 | 23 | $this->assertHasSpot('foreach ($a as $b) {foreach ($c as &$i) { key(); }}'); 24 | 25 | $this->assertHasSpot('foreach ($a as &$b) {foreach ($c as $i) { key(); }}'); 26 | } 27 | } 28 | -------------------------------------------------------------------------------- /tests/Changes/v7dot0/FuncListTest.php: -------------------------------------------------------------------------------- 1 | assertHasSpot('list($a[], $a[], $a[]) = [1, 2, 3];'); 12 | 13 | $this->assertHasSpot('list($a[], $b, $a[]) = [1, 2, 3];'); 14 | 15 | $this->assertNotSpot('list($a, $b) = [1, 2];'); 16 | } 17 | 18 | public function testEmpty() 19 | { 20 | $this->assertHasSpot('list() = $a;'); 21 | 22 | $this->assertHasSpot('list(,,) = $a;'); 23 | 24 | $this->assertHasSpot('list($x, list(), $y) = $a;'); 25 | 26 | $this->assertNotSpot('list(, , , , $b) = [1, 2, 2, 4,];'); 27 | 28 | $this->assertNotSpot('list($x, list($l), $y) = $a;'); 29 | } 30 | } 31 | -------------------------------------------------------------------------------- /tests/Changes/v7dot0/FuncParametersTest.php: -------------------------------------------------------------------------------- 1 | assertHasSpot('function f($a, $a) {}'); 12 | 13 | $this->assertHasSpot('class C { function f($a, $a) {} }'); 14 | 15 | $this->assertHasSpot('$a = function ($a, $a) {};'); 16 | 17 | $this->assertHasSpot('function f($a, $b, $a) {}'); 18 | 19 | $this->assertHasSpot('class C { function f($a, $b, $a) {} }'); 20 | 21 | $this->assertHasSpot('$a = function ($a, $b, $a) {};'); 22 | 23 | $this->assertNotSpot('function f() {}'); 24 | 25 | $this->assertNotSpot('function f($a, $b) {}'); 26 | } 27 | } 28 | -------------------------------------------------------------------------------- /tests/Changes/v7dot0/IntegerOperationTest.php: -------------------------------------------------------------------------------- 1 | assertHasSpot('1 >> -1;'); 12 | 13 | $this->assertNotSpot('1 >> 1;'); 14 | 15 | $this->assertHasSpot('$a >> -1;'); 16 | 17 | $this->assertNotSpot('$a >> 1;'); 18 | 19 | $this->assertHasSpot('1 << -1;'); 20 | 21 | $this->assertNotSpot('1 << 1;'); 22 | 23 | $this->assertHasSpot('1 << $a;'); 24 | 25 | $this->assertNotSpot('1 << 0;'); 26 | } 27 | 28 | public function testModulu() 29 | { 30 | $this->assertHasSpot('$a % 0;'); 31 | 32 | $this->assertNotSpot('$a % 1;'); 33 | 34 | $this->assertHasSpot('1 % 0;'); 35 | 36 | $this->assertNotSpot('1 % 1;'); 37 | 38 | $this->assertHasSpot('$a % $b;'); 39 | } 40 | } 41 | -------------------------------------------------------------------------------- /tests/Changes/v7dot0/IntroducedTest.php: -------------------------------------------------------------------------------- 1 | change, 'forbiddenTable'); 15 | foreach ($table as $name => $dummy) { 16 | $words[] = $name; 17 | } 18 | 19 | $table = TestHelper::fetchProperty($this->change, 'reservedTable'); 20 | foreach ($table as $name => $dummy) { 21 | $words[] = $name; 22 | } 23 | 24 | $this->assertNotSpot('class not_keyword {}'); 25 | 26 | $this->assertNotSpot('trait not_keyword {}'); 27 | 28 | $this->assertNotSpot('interface not_keyword {}'); 29 | 30 | $this->assertNotSpot('$o = new class {};'); 31 | 32 | foreach ($words as $word) { 33 | $this->assertHasSpot('class '.$word.' {}'); 34 | 35 | $this->assertHasSpot('trait '.$word.' {}'); 36 | 37 | $this->assertHasSpot('interface '.$word.' {}'); 38 | 39 | $this->assertHasSpot('namespace Dummy; class '.$word.' {}'); 40 | 41 | $this->assertHasSpot('namespace Dummy; trait '.$word.' {}'); 42 | 43 | $this->assertHasSpot('namespace Dummy; interface '.$word.' {}'); 44 | } 45 | } 46 | } 47 | -------------------------------------------------------------------------------- /tests/Changes/v7dot0/ParseDifferenceTest.php: -------------------------------------------------------------------------------- 1 | assertHasSpot('$$foo[\'bar\'][\'baz\'];'); 17 | $this->assertHasSpot('$foo->$bar[\'baz\'];'); 18 | $this->assertHasSpot('$foo->$bar[\'baz\']();'); 19 | $this->assertHasSpot('Foo::$bar[\'baz\']();'); 20 | 21 | $this->assertNotSpot('$foo[\'bar\'];'); 22 | } 23 | 24 | /** 25 | * yield is now a right associative operator. 26 | * 27 | * @see http://php.net/manual/en/migration70.incompatible.php#migration70.incompatible.other.yield 28 | */ 29 | public function testYield() 30 | { 31 | $this->assertHasSpot('echo yield -1;'); 32 | 33 | $this->assertHasSpot('yield $foo or die;'); 34 | } 35 | } 36 | -------------------------------------------------------------------------------- /tests/Changes/v7dot0/ParserTest.php: -------------------------------------------------------------------------------- 1 | parser = (new ParserFactory())->create(ParserFactory::ONLY_PHP7); 15 | } 16 | 17 | /** 18 | * global only accepts simple variables. 19 | * 20 | * @see http://php.net/manual/en/migration70.incompatible.php#migration70.incompatible.variable-handling.global 21 | * @expectedException PhpParser\Error 22 | */ 23 | public function testInvalidGlobal() 24 | { 25 | // BC for PHPUnit 4.8 26 | if (method_exists($this, 'expectException')) { 27 | $this->expectException(Error::class); 28 | } 29 | 30 | $this->parser->parse('bar; }'); 31 | } 32 | 33 | public function testValidGlobal() 34 | { 35 | $this->parser->parse('bar}; }'); 36 | } 37 | 38 | /** 39 | * Invalid octal literals. 40 | * 41 | * @see http://php.net/manual/en/migration70.incompatible.php#migration70.incompatible.integers.invalid-octals 42 | * @expectedException PhpParser\Error 43 | */ 44 | public function testInvalidOctal() 45 | { 46 | // BC for PHPUnit 4.8 47 | if (method_exists($this, 'expectException')) { 48 | $this->expectException(Error::class); 49 | } 50 | 51 | $this->parser->parse('parser->parse('assertHasSpot('1 + "0xab";'); 12 | 13 | $this->assertHasSpot('1 + "0x01F";'); 14 | 15 | $this->assertHasSpot('1 + "0XFFF";'); 16 | 17 | $this->assertNotSpot('1 + "0xEOF";'); 18 | 19 | $this->assertNotSpot('1 + "24";'); 20 | } 21 | } 22 | -------------------------------------------------------------------------------- /tests/Changes/v7dot0/SwitchMultipleDefaultsTest.php: -------------------------------------------------------------------------------- 1 | assertHasSpot('switch ($a) { default: default: }'); 12 | 13 | $this->assertNotSpot('switch ($a) { case 1: default: }'); 14 | 15 | $this->assertHasSpot('switch ($a) { default: case 2: default: }'); 16 | 17 | $this->assertHasSpot('switch ($a) { default: break; default: break; }'); 18 | } 19 | } 20 | -------------------------------------------------------------------------------- /tests/SymbolTableTest.php: -------------------------------------------------------------------------------- 1 | 'CamelCase', 9 | ]; 10 | 11 | protected $csEmptyTable; 12 | 13 | protected $icEmptyTable; 14 | 15 | protected $csFilledTable; 16 | 17 | protected $icFilledTable; 18 | 19 | protected function setUp() 20 | { 21 | $this->csEmptyTable = new SymbolTable([], SymbolTable::CS); 22 | $this->icEmptyTable = new SymbolTable([], SymbolTable::IC); 23 | $this->csFilledTable = new SymbolTable($this->fillData, SymbolTable::CS); 24 | $this->icFilledTable = new SymbolTable($this->fillData, SymbolTable::IC); 25 | } 26 | 27 | public function testHas() 28 | { 29 | // Varied param type 30 | $this->assertFalse($this->csFilledTable->has(false)); 31 | $this->assertFalse($this->csFilledTable->has(null)); 32 | $this->assertFalse($this->csFilledTable->has(1234)); 33 | $this->assertFalse($this->csFilledTable->has(12.34)); 34 | $this->assertFalse($this->csFilledTable->has(new \DateTime())); 35 | 36 | // Case Sensitive 37 | $this->assertTrue($this->csFilledTable->has('CamelCase')); 38 | $this->assertFalse($this->csFilledTable->has('camelcase')); 39 | 40 | // Case Insensitive 41 | $this->assertTrue($this->icFilledTable->has('CamelCase')); 42 | $this->assertTrue($this->icFilledTable->has('camelcase')); 43 | } 44 | 45 | public function testGet() 46 | { 47 | // Case Sensitive 48 | $this->assertEquals('CamelCase', $this->csFilledTable->get('CamelCase')); 49 | $this->assertEquals(null, $this->csFilledTable->get('cAMELcASE')); 50 | 51 | // Case Insensitive 52 | $this->assertEquals('CamelCase', $this->icFilledTable->get('CamelCase')); 53 | $this->assertEquals('CamelCase', $this->icFilledTable->get('cAMELcASE')); 54 | } 55 | 56 | public function testSet() 57 | { 58 | // Case Sensitive 59 | $this->csEmptyTable->set('KEY', 'Upper key'); 60 | $this->assertTrue($this->csEmptyTable->has('KEY')); 61 | $this->assertFalse($this->csEmptyTable->has('key')); 62 | $this->assertEquals('Upper key', $this->csEmptyTable->get('KEY')); 63 | $this->assertEquals(null, $this->csEmptyTable->get('key')); 64 | 65 | // Case Insensitive 66 | $this->icEmptyTable->set('KEY', 'Upper key'); 67 | $this->assertTrue($this->icEmptyTable->has('KEY')); 68 | $this->assertTrue($this->icEmptyTable->has('key')); 69 | $this->assertEquals('Upper key', $this->icEmptyTable->get('KEY')); 70 | $this->assertEquals('Upper key', $this->icEmptyTable->get('key')); 71 | } 72 | } 73 | -------------------------------------------------------------------------------- /tests/Utils/TestHelper.php: -------------------------------------------------------------------------------- 1 | getProperty($name); 17 | $property->setAccessible(true); 18 | 19 | return $property->getValue($object); 20 | } 21 | 22 | public static function runChange($change, $code) 23 | { 24 | static $traverser_pre, $parser; 25 | 26 | $code = 'addVisitor($visitor); 32 | 33 | $visitor->prepare(); 34 | $visitor->setCode($code); 35 | 36 | if (!isset($parser)) { 37 | $parser = (new ParserFactory())->create(ParserFactory::PREFER_PHP7); 38 | } 39 | $stmts = $parser->parse($code); 40 | 41 | if (!isset($traverser_pre)) { 42 | $traverser_pre = new NodeTraverser(); 43 | $traverser_pre->addVisitor(new NameResolver()); 44 | $traverser_pre->addVisitor(new ReduceVisitor()); 45 | } 46 | $stmts = $traverser_pre->traverse($stmts); 47 | 48 | $traverser->traverse($stmts); 49 | 50 | $visitor->finish(); 51 | 52 | return $visitor->getSpots(); 53 | } 54 | } 55 | -------------------------------------------------------------------------------- /tests/bootstrap.php: -------------------------------------------------------------------------------- 1 | addPsr4('PhpMigration\\', __DIR__); 5 | 6 | $oldClass = '\PHPUnit\Framework\TestCase'; 7 | $newClass = '\PHPUnit_Framework_TestCase'; 8 | if (!class_exists($newClass) && class_exists($oldClass)) { 9 | class_alias($oldClass, $newClass); 10 | } 11 | --------------------------------------------------------------------------------