├── .github └── workflows │ └── ci.yml ├── .phan └── config.php ├── LICENSE.txt ├── README.md ├── composer.json ├── phpmd.xml ├── phpstan.neon └── src └── DamerauLevenshtein.php /.github/workflows/ci.yml: -------------------------------------------------------------------------------- 1 | --- 2 | name: CI 3 | 'on': 4 | pull_request: 5 | push: 6 | branches: 7 | - master 8 | schedule: 9 | - cron: '30 1 * * 3' 10 | 11 | jobs: 12 | 13 | syntax: 14 | name: Syntax 15 | runs-on: ubuntu-latest 16 | strategy: 17 | matrix: 18 | include: 19 | - php: 7.1 20 | - php: 7.2 21 | - php: 7.3 22 | - php: 7.4 23 | steps: 24 | - name: Check out the codebase 25 | uses: actions/checkout@v2 26 | 27 | - name: Set up PHP 28 | uses: shivammathur/setup-php@v2 29 | with: 30 | php-version: "${{ matrix.php }}" 31 | coverage: none 32 | 33 | - name: Lint code 34 | run: | 35 | [ "$(find . \( -path './vendor' \) -prune -o -type f \( -name '*.ctp' -o -name '*.php' \) -print0 | xargs -0 --no-run-if-empty -L1 -i'{}' php -l '{}' | grep -vc 'No syntax errors')" -eq 0 ] 36 | 37 | test: 38 | name: Test 39 | runs-on: ubuntu-latest 40 | needs: 41 | - syntax 42 | strategy: 43 | fail-fast: false 44 | matrix: 45 | include: 46 | - php: 7.1 47 | - php: 7.2 48 | - php: 7.3 49 | - php: 7.4 50 | steps: 51 | - name: Check out the codebase 52 | uses: actions/checkout@v2 53 | 54 | - name: Set up PHP 55 | uses: shivammathur/setup-php@v2 56 | with: 57 | php-version: "${{ matrix.php }}" 58 | coverage: none 59 | 60 | - name: Install dependencies 61 | run: | 62 | composer install --dev --no-ansi --no-progress --no-interaction --quiet; 63 | 64 | - name: Test code 65 | run: | 66 | vendor/bin/phpunit --stderr --configuration phpunit.xml; 67 | 68 | cs: 69 | name: Cs 70 | runs-on: ubuntu-latest 71 | needs: 72 | - test 73 | strategy: 74 | fail-fast: false 75 | matrix: 76 | include: 77 | - php: 7.4 78 | phpcs: true 79 | - php: 7.4 80 | phpmd: true 81 | - php: 7.4 82 | phpcpd: true 83 | - php: 7.4 84 | phpstan: true 85 | - php: 7.4 86 | phan: true 87 | steps: 88 | - name: Check out the codebase 89 | uses: actions/checkout@v2 90 | 91 | - name: Set up PHP 92 | uses: shivammathur/setup-php@v2 93 | with: 94 | php-version: "${{ matrix.php }}" 95 | coverage: none 96 | extensions: ast 97 | tools: cs2pr 98 | 99 | - name: Install dependencies 100 | run: | 101 | composer install --dev --no-ansi --no-progress --no-interaction --quiet; 102 | 103 | - name: Run phpcs 104 | run: | 105 | excludePaths=( \ 106 | '**/vendor' \ 107 | '**/.phan' \ 108 | ); 109 | excludePathsJoined=$(printf ",%s" "${excludePaths[@]}"); 110 | excludePathsJoined=${excludePathsJoined:1}; 111 | 112 | extensions=( \ 113 | 'php' \ 114 | 'ctp' \ 115 | ); 116 | extensionsJoined=$(printf ",%s" "${extensions[@]}"); 117 | extensionsJoined=${extensionsJoined:1}; 118 | 119 | vendor/bin/phpcs . \ 120 | --standard=PSR2 \ 121 | --extensions="${extensionsJoined}" \ 122 | --ignore="${excludePathsJoined}" \ 123 | --report=checkstyle | cs2pr; 124 | if: matrix.phpcs 125 | 126 | - name: Run phpcsmd 127 | run: | 128 | excludePaths=( \ 129 | '**/vendor' \ 130 | ); 131 | excludePathsJoined=$(printf ",%s" "${excludePaths[@]}"); 132 | excludePathsJoined=${excludePathsJoined:1}; 133 | 134 | vendor/bin/phpmd . xml phpmd.xml --suffixes php --exclude "${excludePathsJoined}" | cs2pr; 135 | if: matrix.phpmd 136 | 137 | - name: Run phpcpd 138 | run: | 139 | vendor/bin/phpcpd . \ 140 | --names '*.php,*.ctp' \ 141 | --exclude vendor \ 142 | --no-interaction \ 143 | --quiet \ 144 | --fuzzy \ 145 | --log-pmd 'php://stdout' | cs2pr; 146 | if: matrix.phpcpd 147 | 148 | - name: Run phpstan 149 | run: | 150 | vendor/bin/phpstan analyse -c phpstan.neon \ 151 | --no-ansi \ 152 | --no-progress \ 153 | --no-interaction \ 154 | --error-format=checkstyle | cs2pr; 155 | if: matrix.phpstan 156 | 157 | - name: Install phan 158 | run: | 159 | composer require --dev --no-ansi --no-progress --no-interaction --quiet 'phan/phan=^2.7.3'; 160 | if: matrix.phan 161 | 162 | - name: Run phan 163 | run: | 164 | vendor/bin/phan -d . --no-color --no-progress-bar --output-mode checkstyle | cs2pr; 165 | if: matrix.phan 166 | 167 | coverage: 168 | name: Coverage 169 | runs-on: ubuntu-latest 170 | needs: 171 | - cs 172 | strategy: 173 | fail-fast: false 174 | matrix: 175 | include: 176 | - php: 7.4 177 | steps: 178 | - name: Check out the codebase 179 | uses: actions/checkout@v2 180 | 181 | - name: Set up PHP 182 | uses: shivammathur/setup-php@v2 183 | with: 184 | php-version: "${{ matrix.php }}" 185 | 186 | - name: Install dependencies 187 | run: | 188 | composer install --dev --no-ansi --no-progress --no-interaction --quiet; 189 | 190 | - name: Generate coverage report 191 | run: | 192 | phpdbg -qrr vendor/bin/phpunit --stderr --configuration phpunit.xml --coverage-clover build/logs/clover.xml; 193 | 194 | - name: Upload coverage report 195 | run: | 196 | bash <(curl -sSL https://codecov.io/bash); 197 | -------------------------------------------------------------------------------- /.phan/config.php: -------------------------------------------------------------------------------- 1 | 7.1, 39 | 40 | // Default: true. If this is set to true, 41 | // and target_php_version is newer than the version used to run Phan, 42 | // Phan will act as though functions added in newer PHP versions exist. 43 | // 44 | // NOTE: Currently, this only affects Closure::fromCallable 45 | 'pretend_newer_core_functions_exist' => true, 46 | 47 | // If true, missing properties will be created when 48 | // they are first seen. If false, we'll report an 49 | // error message. 50 | 'allow_missing_properties' => false, 51 | 52 | // Allow null to be cast as any type and for any 53 | // type to be cast to null. 54 | 'null_casts_as_any_type' => false, 55 | 56 | // Allow null to be cast as any array-like type 57 | // This is an incremental step in migrating away from null_casts_as_any_type. 58 | // If null_casts_as_any_type is true, this has no effect. 59 | 'null_casts_as_array' => false, 60 | 61 | // Allow any array-like type to be cast to null. 62 | // This is an incremental step in migrating away from null_casts_as_any_type. 63 | // If null_casts_as_any_type is true, this has no effect. 64 | 'array_casts_as_null' => false, 65 | 66 | // If enabled, Phan will warn if **any** type in a method's object expression 67 | // is definitely not an object, 68 | // or if **any** type in an invoked expression is not a callable. 69 | // Setting this to true will introduce numerous false positives 70 | // (and reveal some bugs). 71 | 'strict_method_checking' => true, 72 | 73 | // If enabled, Phan will warn if **any** type in the argument's type 74 | // cannot be cast to a type in the parameter's expected type. 75 | // Setting this to true will introduce a large number of false positives (and some bugs). 76 | // (For self-analysis, Phan has a large number of suppressions and file-level suppressions, due to \ast\Node being difficult to type check) 77 | 'strict_param_checking' => true, 78 | 79 | // If enabled, Phan will warn if **any** type in a property assignment's type 80 | // cannot be cast to a type in the property's expected type. 81 | // Setting this to true will introduce a large number of false positives (and some bugs). 82 | // (For self-analysis, Phan has a large number of suppressions and file-level suppressions, due to \ast\Node being difficult to type check) 83 | 'strict_property_checking' => true, 84 | 85 | // If enabled, Phan will warn if **any** type in the return statement's union type 86 | // cannot be cast to a type in the method's declared return type. 87 | // Setting this to true will introduce a large number of false positives (and some bugs). 88 | // (For self-analysis, Phan has a large number of suppressions and file-level suppressions, due to \ast\Node being difficult to type check) 89 | 'strict_return_checking' => true, 90 | 91 | // If enabled, Phan will warn if **any** type of the object expression for a property access 92 | // does not contain that property. 93 | 'strict_object_checking' => true, 94 | 95 | // If enabled, scalars (int, float, bool, string, null) 96 | // are treated as if they can cast to each other. 97 | // This does not affect checks of array keys. See scalar_array_key_cast. 98 | 'scalar_implicit_cast' => false, 99 | 100 | // If enabled, any scalar array keys (int, string) 101 | // are treated as if they can cast to each other. 102 | // E.g. array can cast to array and vice versa. 103 | // Normally, a scalar type such as int could only cast to/from int and mixed. 104 | 'scalar_array_key_cast' => false, 105 | 106 | // If this has entries, scalars (int, float, bool, string, null) 107 | // are allowed to perform the casts listed. 108 | // E.g. ['int' => ['float', 'string'], 'float' => ['int'], 'string' => ['int'], 'null' => ['string']] 109 | // allows casting null to a string, but not vice versa. 110 | // (subset of scalar_implicit_cast) 111 | 'scalar_implicit_partial' => [], 112 | 113 | // If true, Phan will convert the type of a possibly undefined array offset to the nullable, defined equivalent. 114 | // If false, Phan will convert the type of a possibly undefined array offset to the defined equivalent (without converting to nullable). 115 | 'convert_possibly_undefined_offset_to_nullable' => false, 116 | 117 | // If true, seemingly undeclared variables in the global 118 | // scope will be ignored. This is useful for projects 119 | // with complicated cross-file globals that you have no 120 | // hope of fixing. 121 | 'ignore_undeclared_variables_in_global_scope' => false, 122 | 123 | // Backwards Compatibility Checking (This is very slow) 124 | 'backward_compatibility_checks' => true, 125 | 126 | // If true, check to make sure the return type declared 127 | // in the doc-block (if any) matches the return type 128 | // declared in the method signature. This process is 129 | // slow. 130 | 'check_docblock_signature_return_type_match' => true, 131 | 132 | // If true, check to make sure the param types declared 133 | // in the doc-block (if any) matches the param types 134 | // declared in the method signature. 135 | 'check_docblock_signature_param_type_match' => true, 136 | 137 | // (*Requires check_docblock_signature_param_type_match to be true*) 138 | // If true, make narrowed types from phpdoc params override 139 | // the real types from the signature, when real types exist. 140 | // (E.g. allows specifying desired lists of subclasses, 141 | // or to indicate a preference for non-nullable types over nullable types) 142 | // Affects analysis of the body of the method and the param types passed in by callers. 143 | 'prefer_narrowed_phpdoc_param_type' => true, 144 | 145 | // (*Requires check_docblock_signature_return_type_match to be true*) 146 | // If true, make narrowed types from phpdoc returns override 147 | // the real types from the signature, when real types exist. 148 | // (E.g. allows specifying desired lists of subclasses, 149 | // or to indicate a preference for non-nullable types over nullable types) 150 | // Affects analysis of return statements in the body of the method and the return types passed in by callers. 151 | 'prefer_narrowed_phpdoc_return_type' => true, 152 | 153 | // If enabled, check all methods that override a 154 | // parent method to make sure its signature is 155 | // compatible with the parent's. This check 156 | // can add quite a bit of time to the analysis. 157 | // This will also check if final methods are overridden, etc. 158 | 'analyze_signature_compatibility' => true, 159 | 160 | // Set this to true to allow contravariance in real parameter types of method overrides (Introduced in php 7.2) 161 | // See https://secure.php.net/manual/en/migration72.new-features.php#migration72.new-features.param-type-widening 162 | // (Users may enable this if analyzing projects that support only php 7.2+) 163 | // This is false by default. (Will warn if real parameter types are omitted in an override) 164 | 'allow_method_param_type_widening' => false, 165 | 166 | // Set this to true to make Phan guess that undocumented parameter types 167 | // (for optional parameters) have the same type as default values 168 | // (Instead of combining that type with `mixed`). 169 | // E.g. `function($x = 'val')` would make Phan infer that $x had a type of `string`, not `string|mixed`. 170 | // Phan will not assume it knows specific types if the default value is false or null. 171 | 'guess_unknown_parameter_type_using_default' => false, 172 | 173 | // Allow adding types to vague return types such as @return object, @return ?mixed in function/method/closure union types. 174 | // Normally, Phan only adds inferred returned types when there is no `@return` type or real return type signature.. 175 | // This setting can be disabled on individual methods by adding `@phan-hardcode-return-type` to the doc comment. 176 | // 177 | // Disabled by default. This is more useful with `--analyze-twice`. 178 | 'allow_overriding_vague_return_types' => true, 179 | 180 | // When enabled, infer that the types of the properties of `$this` are equal to their default values at the start of `__construct()`. 181 | // This will have some false positives due to Phan not checking for setters and initializing helpers. 182 | // This does not affect inherited properties. 183 | 'infer_default_properties_in_construct' => true, 184 | 185 | // Set this to true to enable the plugins that Phan uses to infer more accurate return types of `implode`, `json_decode`, and many other functions. 186 | // 187 | // Phan is slightly faster when these are disabled. 188 | 'enable_extended_internal_return_type_plugins' => true, 189 | 190 | // This setting maps case insensitive strings to union types. 191 | // This is useful if a project uses phpdoc that differs from the phpdoc2 standard. 192 | // If the corresponding value is the empty string, Phan will ignore that union type (E.g. can ignore 'the' in `@return the value`) 193 | // If the corresponding value is not empty, Phan will act as though it saw the corresponding union type when the keys show up in a UnionType of @param, @return, @var, @property, etc. 194 | // 195 | // This matches the **entire string**, not parts of the string. 196 | // (E.g. `@return the|null` will still look for a class with the name `the`, but `@return the` will be ignored with the below setting) 197 | // 198 | // (These are not aliases, this setting is ignored outside of doc comments). 199 | // (Phan does not check if classes with these names exist) 200 | // 201 | // Example setting: ['unknown' => '', 'number' => 'int|float', 'char' => 'string', 'long' => 'int', 'the' => ''] 202 | 'phpdoc_type_mapping' => [ ], 203 | 204 | // Set to true in order to attempt to detect dead 205 | // (unreferenced) code. Keep in mind that the 206 | // results will only be a guess given that classes, 207 | // properties, constants and methods can be referenced 208 | // as variables (like `$class->$property` or 209 | // `$class->$method()`) in ways that we're unable 210 | // to make sense of. 211 | 'dead_code_detection' => false, 212 | 213 | // Set to true in order to attempt to detect unused variables. 214 | // dead_code_detection will also enable unused variable detection. 215 | 'unused_variable_detection' => true, 216 | 217 | // Set to true in order to force tracking references to elements 218 | // (functions/methods/consts/protected). 219 | // dead_code_detection is another option which also causes references 220 | // to be tracked. 221 | 'force_tracking_references' => false, 222 | 223 | // Set to true in order to attempt to detect redundant and impossible conditions. 224 | // 225 | // This has some false positives involving loops, 226 | // variables set in branches of loops, and global variables. 227 | 'redundant_condition_detection' => true, 228 | 229 | // Set to true in order to attempt to detect error-prone truthiness/falsiness checks. 230 | // 231 | // This is not suitable for all codebases. 232 | 'error_prone_truthy_condition_detection' => true, 233 | 234 | // Enable this to warn about harmless redundant use for classes and namespaces such as `use Foo\bar` in namespace Foo. 235 | // 236 | // Note: This does not affect warnings about redundant uses in the global namespace. 237 | 'warn_about_redundant_use_namespaced_class' => true, 238 | 239 | // If true, then run a quick version of checks that takes less time. 240 | // False by default. 241 | 'quick_mode' => false, 242 | 243 | // If true, then before analysis, try to simplify AST into a form 244 | // which improves Phan's type inference in edge cases. 245 | // 246 | // This may conflict with 'dead_code_detection'. 247 | // When this is true, this slows down analysis slightly. 248 | // 249 | // E.g. rewrites `if ($a = value() && $a > 0) {...}` 250 | // into $a = value(); if ($a) { if ($a > 0) {...}}` 251 | 'simplify_ast' => true, 252 | 253 | // If true, Phan will read `class_alias` calls in the global scope, 254 | // then (1) create aliases from the *parsed* files if no class definition was found, 255 | // and (2) emit issues in the global scope if the source or target class is invalid. 256 | // (If there are multiple possible valid original classes for an aliased class name, 257 | // the one which will be created is unspecified.) 258 | // NOTE: THIS IS EXPERIMENTAL, and the implementation may change. 259 | 'enable_class_alias_support' => false, 260 | 261 | // Enable or disable support for generic templated 262 | // class types. 263 | 'generic_types_enabled' => true, 264 | 265 | // If enabled, warn about throw statement where the exception types 266 | // are not documented in the PHPDoc of functions, methods, and closures. 267 | 'warn_about_undocumented_throw_statements' => true, 268 | 269 | // If enabled (and warn_about_undocumented_throw_statements is enabled), 270 | // warn about function/closure/method calls that have (at)throws 271 | // without the invoking method documenting that exception. 272 | 'warn_about_undocumented_exceptions_thrown_by_invoked_functions' => true, 273 | 274 | // If this is a list, Phan will not warn about lack of documentation of (at)throws 275 | // for any of the listed classes or their subclasses. 276 | // This setting only matters when warn_about_undocumented_throw_statements is true. 277 | // The default is the empty array (Warn about every kind of Throwable) 278 | 'exception_classes_with_optional_throws_phpdoc' => [ 279 | 'LogicException', 280 | 'RuntimeException', 281 | 'InvalidArgumentException', 282 | 'AssertionError', 283 | 'TypeError', 284 | 'Phan\Exception\IssueException', // TODO: Make Phan aware that some arguments suppress certain issues 285 | 'Phan\AST\TolerantASTConverter\InvalidNodeException', // This is used internally in TolerantASTConverter 286 | 287 | // TODO: Undo the suppressions for the below categories of issues: 288 | 'Phan\Exception\CodeBaseException', 289 | // phpunit 290 | 'PHPUnit\Framework\ExpectationFailedException', 291 | 'SebastianBergmann\RecursionContext\InvalidArgumentException', 292 | ], 293 | 294 | // Increase this to properly analyze require_once statements 295 | 'max_literal_string_type_length' => 1000, 296 | 297 | // Setting this to true makes the process assignment for file analysis 298 | // as predictable as possible, using consistent hashing. 299 | // Even if files are added or removed, or process counts change, 300 | // relatively few files will move to a different group. 301 | // (use when the number of files is much larger than the process count) 302 | // NOTE: If you rely on Phan parsing files/directories in the order 303 | // that they were provided in this config, don't use this) 304 | // See https://github.com/phan/phan/wiki/Different-Issue-Sets-On-Different-Numbers-of-CPUs 305 | 'consistent_hashing_file_order' => false, 306 | 307 | // If enabled, Phan will act as though it's certain of real return types of a subset of internal functions, 308 | // even if those return types aren't available in reflection (real types were taken from php 7.3 or 8.0-dev, depending on target_php_version). 309 | // 310 | // Note that with php 7 and earlier, php would return null or false for many internal functions if the argument types or counts were incorrect. 311 | // As a result, enabling this setting with target_php_version 8.0 may result in false positives for `--redundant-condition-detection` when codebases also support php 7.x. 312 | 'assume_real_types_for_internal_functions' => true, 313 | 314 | // Override to hardcode existence and types of (non-builtin) globals. 315 | // Class names should be prefixed with '\\'. 316 | // (E.g. ['_FOO' => '\\FooClass', 'page' => '\\PageClass', 'userId' => 'int']) 317 | 'globals_type_map' => [], 318 | 319 | // The minimum severity level to report on. This can be 320 | // set to Issue::SEVERITY_LOW, Issue::SEVERITY_NORMAL or 321 | // Issue::SEVERITY_CRITICAL. 322 | 'minimum_severity' => Issue::SEVERITY_LOW, 323 | 324 | // Add any issue types (such as 'PhanUndeclaredMethod') 325 | // here to inhibit them from being reported 326 | 'suppress_issue_types' => [ 327 | 'PhanUnreferencedClosure', // False positives seen with closures in arrays, TODO: move closure checks closer to what is done by unused variable plugin 328 | 'PhanPluginNoCommentOnProtectedMethod', 329 | 'PhanPluginDescriptionlessCommentOnProtectedMethod', 330 | 'PhanPluginNoCommentOnPrivateMethod', 331 | 'PhanPluginDescriptionlessCommentOnPrivateMethod', 332 | 'PhanPluginDescriptionlessCommentOnPrivateProperty', 333 | // TODO: Fix edge cases in --automatic-fix for PhanPluginRedundantClosureComment 334 | 'PhanPluginRedundantClosureComment', 335 | 'PhanPluginPossiblyStaticPublicMethod', 336 | 'PhanPluginPossiblyStaticProtectedMethod', 337 | // The types of ast\Node->children are all possibly unset. 338 | 'PhanTypePossiblyInvalidDimOffset', 339 | 'PhanPluginRedundantReturnComment', 340 | ], 341 | 342 | // If empty, no filter against issues types will be applied. 343 | // If non-empty, only issues within the list will be emitted 344 | // by Phan. 345 | // 346 | // See https://github.com/phan/phan/wiki/Issue-Types-Caught-by-Phan 347 | // for the full list of issues that Phan detects. 348 | // 349 | // Phan is capable of detecting hundreds of types of issues. 350 | // Projects should almost always use `suppress_issue_types` instead. 351 | 'whitelist_issue_types' => [ 352 | // 'PhanUndeclaredClass', 353 | ], 354 | 355 | // A list of files to include in analysis 356 | 'file_list' => [], 357 | 358 | // A regular expression to match files to be excluded 359 | // from parsing and analysis and will not be read at all. 360 | // 361 | // This is useful for excluding groups of test or example 362 | // directories/files, unanalyzable files, or files that 363 | // can't be removed for whatever reason. 364 | // (e.g. '@Test\.php$@', or '@vendor/.*/(tests|Tests)/@') 365 | 'exclude_file_regex' => '@^vendor/.*/(tests?|Tests?)/@', 366 | 367 | // Enable this to enable checks of require/include statements referring to valid paths. 368 | 'enable_include_path_checks' => true, 369 | 370 | // A list of include paths to check when checking if `require_once`, `include`, etc. are valid. 371 | // 372 | // To refer to the directory of the file being analyzed, use `'.'` 373 | // To refer to the project root directory, you must use \Phan\Config::getProjectRootDirectory() 374 | // 375 | // (E.g. `['.', \Phan\Config::getProjectRootDirectory() . '/src/folder-added-to-include_path']`) 376 | 'include_paths' => ['.'], 377 | 378 | // Enable this to warn about the use of relative paths in `require_once`, `include`, etc. 379 | // Relative paths are harder to reason about, and opcache may have issues with relative paths in edge cases. 380 | 'warn_about_relative_include_statement' => true, 381 | 382 | // A list of files that will be excluded from parsing and analysis 383 | // and will not be read at all. 384 | // 385 | // This is useful for excluding hopelessly unanalyzable 386 | // files that can't be removed for whatever reason. 387 | 'exclude_file_list' => [ 388 | 'internal/Sniffs/ValidUnderscoreVariableNameSniff.php', 389 | ], 390 | 391 | // The number of processes to fork off during the analysis 392 | // phase. 393 | 'processes' => 1, 394 | 395 | // A list of directories that should be parsed for class and 396 | // method information. After excluding the directories 397 | // defined in exclude_analysis_directory_list, the remaining 398 | // files will be statically analyzed for errors. 399 | // 400 | // Thus, both first-party and third-party code being used by 401 | // your application should be included in this list. 402 | 'directory_list' => [ 403 | '.', 404 | ], 405 | 406 | // List of case-insensitive file extensions supported by Phan. 407 | // (e.g. php, html, htm) 408 | 'analyzed_file_extensions' => ['php'], 409 | 410 | // A directory list that defines files that will be excluded 411 | // from static analysis, but whose class and method 412 | // information should be included. 413 | // 414 | // Generally, you'll want to include the directories for 415 | // third-party code (such as 'vendor/') in this list. 416 | // 417 | // n.b.: If you'd like to parse but not analyze 3rd 418 | // party code, directories containing that code 419 | // should be added to the `directory_list` as 420 | // to `exclude_analysis_directory_list`. 421 | 'exclude_analysis_directory_list' => [ 422 | 'tests/', 423 | 'tmp/', 424 | 'vendor/', 425 | ], 426 | 427 | // By default, Phan will log error messages to stdout if PHP is using options that slow the analysis. 428 | // (e.g. PHP is compiled with --enable-debug or when using Xdebug) 429 | 'skip_slow_php_options_warning' => false, 430 | 431 | // You can put paths to internal stubs in this config option. 432 | // Phan will continue using its detailed type annotations, but load the constants, classes, functions, and classes (and their Reflection types) from these stub files (doubling as valid php files). 433 | // Use a different extension from php to avoid accidentally loading these. 434 | // The 'mkstubs' script can be used to generate your own stubs (compatible with php 7.0+ right now) 435 | 'autoload_internal_extension_signatures' => [ 436 | 'ast' => '.phan/internal_stubs/ast.phan_php', 437 | 'ctype' => '.phan/internal_stubs/ctype.phan_php', 438 | 'igbinary' => '.phan/internal_stubs/igbinary.phan_php', 439 | 'mbstring' => '.phan/internal_stubs/mbstring.phan_php', 440 | 'pcntl' => '.phan/internal_stubs/pcntl.phan_php', 441 | 'posix' => '.phan/internal_stubs/posix.phan_php', 442 | 'readline' => '.phan/internal_stubs/readline.phan_php', 443 | 'sysvmsg' => '.phan/internal_stubs/sysvmsg.phan_php', 444 | 'sysvsem' => '.phan/internal_stubs/sysvsem.phan_php', 445 | 'sysvshm' => '.phan/internal_stubs/sysvshm.phan_php', 446 | ], 447 | 448 | // Set this to false to emit `PhanUndeclaredFunction` issues for internal functions that Phan has signatures for, 449 | // but aren't available in the codebase, or from Reflection. 450 | // (may lead to false positives if an extension isn't loaded) 451 | // 452 | // If this is true(default), then Phan will not warn. 453 | // 454 | // Even when this is false, Phan will still infer return values and check parameters of internal functions 455 | // if Phan has the signatures. 456 | 'ignore_undeclared_functions_with_known_signatures' => false, 457 | 458 | 'plugin_config' => [ 459 | // A list of 1 or more PHP binaries (Absolute path or program name found in $PATH) 460 | // to use to analyze your files with PHP's native `--syntax-check`. 461 | // 462 | // This can be used to simultaneously run PHP's syntax checks with multiple PHP versions. 463 | // e.g. `'plugin_config' => ['php_native_syntax_check_binaries' => ['php72', 'php70', 'php56']]` 464 | // if all of those programs can be found in $PATH 465 | 466 | // 'php_native_syntax_check_binaries' => [PHP_BINARY], 467 | 468 | // The maximum number of `php --syntax-check` processes to run at any point in time (Minimum: 1). 469 | // This may be temporarily higher if php_native_syntax_check_binaries has more elements than this process count. 470 | 'php_native_syntax_check_max_processes' => 4, 471 | 472 | // blacklist of methods to warn about for HasPHPDocPlugin 473 | 'has_phpdoc_method_ignore_regex' => '@^Phan\\\\Tests\\\\.*::(test.*|.*Provider)$@', 474 | // Warn about duplicate descriptions for methods and property groups within classes. 475 | // (This skips over deprecated methods) 476 | // This may not apply to all code bases, 477 | // but is useful in avoiding copied and pasted descriptions that may be inapplicable or too vague. 478 | 'has_phpdoc_check_duplicates' => true, 479 | 480 | // If true, then never allow empty statement lists, even if there is a TODO/FIXME/"deliberately empty" comment. 481 | 'empty_statement_list_ignore_todos' => true, 482 | 483 | // Automatically infer which methods are pure (i.e. should have no side effects) in UseReturnValuePlugin. 484 | 'infer_pure_methods' => true, 485 | ], 486 | 487 | // A list of plugin files to execute 488 | // NOTE: values can be the base name without the extension for plugins bundled with Phan (E.g. 'AlwaysReturnPlugin') 489 | // or relative/absolute paths to the plugin (Relative to the project root). 490 | 'plugins' => [ 491 | 'AlwaysReturnPlugin', 492 | 'DollarDollarPlugin', 493 | 'UnreachableCodePlugin', 494 | 'DuplicateArrayKeyPlugin', 495 | 'PregRegexCheckerPlugin', 496 | 'PrintfCheckerPlugin', 497 | 'PHPUnitAssertionPlugin', // analyze assertSame/assertInstanceof/assertTrue/assertFalse 498 | 'UseReturnValuePlugin', 499 | 500 | // UnknownElementTypePlugin warns about unknown types in element signatures. 501 | 'UnknownElementTypePlugin', 502 | 'DuplicateExpressionPlugin', 503 | // warns about carriage returns("\r"), trailing whitespace, and tabs in PHP files. 504 | 'WhitespacePlugin', 505 | // Warn about inline HTML anywhere in the files. 506 | 'InlineHTMLPlugin', 507 | //////////////////////////////////////////////////////////////////////// 508 | // Plugins for Phan's self-analysis 509 | //////////////////////////////////////////////////////////////////////// 510 | 511 | // Warns about the usage of assert() for Phan's self-analysis. See https://github.com/phan/phan/issues/288 512 | 'NoAssertPlugin', 513 | 'PossiblyStaticMethodPlugin', 514 | 515 | 'HasPHPDocPlugin', 516 | 'PHPDocToRealTypesPlugin', // suggests replacing (at)return void with `: void` in the declaration, etc. 517 | 'PHPDocRedundantPlugin', 518 | 'PreferNamespaceUsePlugin', 519 | 'EmptyStatementListPlugin', 520 | 521 | // Report empty (not overridden or overriding) methods and functions 522 | // 'EmptyMethodAndFunctionPlugin', 523 | 524 | // This should only be enabled if the code being analyzed contains Phan plugins. 525 | 'PhanSelfCheckPlugin', 526 | // Warn about using the same loop variable name as a loop variable of an outer loop. 527 | 'LoopVariableReusePlugin', 528 | // Warn about assigning the value the variable already had to that variable. 529 | 'RedundantAssignmentPlugin', 530 | // These are specific to Phan's coding style 531 | 'StrictComparisonPlugin', 532 | // Warn about `$var == SOME_INT_OR_STRING_CONST` due to unintuitive behavior such as `0 == 'a'` 533 | // '.phan/plugins/StrictLiteralComparisonPlugin.php', 534 | // 'UnknownClassElementAccessPlugin' is more useful with batch analysis than in an editor. 535 | // It's used in tests/run_test __FakeSelfFallbackTest 536 | 537 | //////////////////////////////////////////////////////////////////////// 538 | // End plugins for Phan's self-analysis 539 | //////////////////////////////////////////////////////////////////////// 540 | 541 | // 'SleepCheckerPlugin' is useful for projects which heavily use the __sleep() method. Phan doesn't use __sleep(). 542 | // InvokePHPNativeSyntaxCheckPlugin invokes 'php --no-php-ini --syntax-check ${abs_path_to_analyzed_file}.php' and reports any error messages. 543 | // Using this can cause phan's overall analysis time to more than double. 544 | // 'InvokePHPNativeSyntaxCheckPlugin', 545 | 546 | // 'PHPUnitNotDeadCodePlugin', // Marks PHPUnit test case subclasses and test cases as referenced code. This is only useful for runs when dead code detection is enabled. 547 | 548 | // NOTE: This plugin only produces correct results when 549 | // Phan is run on a single core (-j1). 550 | // 'UnusedSuppressionPlugin', 551 | ], 552 | ]; 553 | -------------------------------------------------------------------------------- /LICENSE.txt: -------------------------------------------------------------------------------- 1 | Copyright (c) Oefenweb.nl 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 furnished 8 | 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 19 | THE SOFTWARE. 20 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # Damerau Levenshtein 2 | 3 | [![CI](https://github.com/Oefenweb/damerau-levenshtein/workflows/CI/badge.svg)](https://github.com/Oefenweb/damerau-levenshtein/actions?query=workflow%3ACI) 4 | [![PHP 7 ready](http://php7ready.timesplinter.ch/Oefenweb/damerau-levenshtein/badge.svg)](https://travis-ci.org/Oefenweb/damerau-levenshtein) 5 | [![codecov](https://codecov.io/gh/Oefenweb/damerau-levenshtein/branch/master/graph/badge.svg)](https://codecov.io/gh/Oefenweb/damerau-levenshtein) 6 | [![Packagist downloads](http://img.shields.io/packagist/dt/Oefenweb/damerau-levenshtein.svg)](https://packagist.org/packages/oefenweb/damerau-levenshtein) 7 | [![Code Climate](https://codeclimate.com/github/Oefenweb/damerau-levenshtein/badges/gpa.svg)](https://codeclimate.com/github/Oefenweb/damerau-levenshtein) 8 | [![Scrutinizer Code Quality](https://scrutinizer-ci.com/g/Oefenweb/damerau-levenshtein/badges/quality-score.png?b=master)](https://scrutinizer-ci.com/g/Oefenweb/damerau-levenshtein/?branch=master) 9 | 10 | Get text similarity level with Damerau-Levenshtein distance. 11 | 12 | ## Requirements 13 | 14 | * PHP 7.1.0 or greater. 15 | 16 | ## Installation 17 | 18 | `composer require oefenweb/damerau-levenshtein` 19 | 20 | ## Usage 21 | 22 | ```php 23 | $pattern = 'foo bar'; 24 | $string = 'fuu baz'; 25 | 26 | $damerauLevenshtein = new DamerauLevenshtein($pattern, $string); 27 | 28 | $damerauLevenshtein->getSimilarity(); // absolute edit distance; == 3 29 | 30 | $damerauLevenshtein->getRelativeDistance(); // relative edit distance; == 0.57142857142857 31 | 32 | $damerauLevenshtein->getMatrix(); // get complete distance matrix 33 | /* == 34 | * [ 35 | * [0,1,2,3,4,5,6,7], 36 | * [1,0,1,2,3,4,5,6], 37 | * [2,1,1,2,3,4,5,6], 38 | * [3,2,2,2,3,4,5,6], 39 | * [4,3,3,3,2,3,4,5], 40 | * [5,4,4,4,3,2,3,4], 41 | * [6,5,5,5,4,3,2,3], 42 | * [7,6,6,6,5,4,3,3], 43 | * ] 44 | */ 45 | 46 | $damerauLevenshtein->displayMatrix(); // get readable and formatted distance matrix 47 | /* 48 | * ' foo bar' . PHP_EOL 49 | * . ' 01234567' . PHP_EOL 50 | * . 'f10123456' . PHP_EOL 51 | * . 'u21123456' . PHP_EOL 52 | * . 'u32223456' . PHP_EOL 53 | * . ' 43332345' . PHP_EOL 54 | * . 'b54443234' . PHP_EOL 55 | * . 'a65554323' . PHP_EOL 56 | * . 'z76665433' 57 | */ 58 | ``` 59 | 60 | Different costs are supported by the constructor and getters / setters. 61 | 62 | Character comparison (equal check) can easily be overridden by parent class (see `DamerauLevenshtein::compare`). 63 | 64 | For more examples look at `/tests/DamerauLevenshteinTest.php` or **RTFC**. 65 | 66 | ## License 67 | 68 | MIT 69 | 70 | #### Author Information 71 | 72 | Mischa ter Smitten (based on work of [Ph4r05](http://www.phpclasses.org/package/7021-PHP-Get-text-similarity-level-with-Damerau-Levenshtein.html)) 73 | -------------------------------------------------------------------------------- /composer.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "oefenweb/damerau-levenshtein", 3 | "description": "Get text similarity level with Damerau-Levenshtein distance", 4 | "type": "library", 5 | "keywords": [ 6 | "damerau", "levenshtein", "distance", "similarity" 7 | ], 8 | "homepage": "http://github.com/Oefenweb/damerau-levenshtein", 9 | "license": "MIT", 10 | "authors": [{ 11 | "name": "Ph4r05", 12 | "homepage": "http://www.phpclasses.org/package/7021-PHP-Get-text-similarity-level-with-Damerau-Levenshtein.html" 13 | }, { 14 | "name": "Oefenweb.nl BV team", 15 | "homepage": "https://github.com/Oefenweb/damerau-levenshtein/contributors" 16 | } 17 | ], 18 | "require": { 19 | "php": ">=7.1.0", 20 | "ext-mbstring": "*" 21 | }, 22 | "require-dev": { 23 | "oefenweb/cakephp-codesniffer": "^2.0", 24 | "phpmd/phpmd": "^2.0", 25 | "sebastian/phpcpd": "^4.0", 26 | "phpunit/phpunit": "^7.5", 27 | "phpstan/phpstan": "^0.12.66" 28 | }, 29 | "support": { 30 | "issues": "https://github.com/Oefenweb/damerau-levenshtein/issues", 31 | "source": "https://github.com/Oefenweb/damerau-levenshtein" 32 | }, 33 | "autoload": { 34 | "psr-4": { 35 | "Oefenweb\\DamerauLevenshtein\\": "src" 36 | } 37 | }, 38 | "autoload-dev": { 39 | "psr-4": { 40 | "Oefenweb\\DamerauLevenshtein\\Test\\": "tests" 41 | } 42 | } 43 | } 44 | -------------------------------------------------------------------------------- /phpmd.xml: -------------------------------------------------------------------------------- 1 | 2 | 7 | Oefenweb ruleset 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | 16 | 17 | 18 | 19 | 20 | 21 | 22 | 23 | 24 | 25 | 26 | 27 | 28 | 29 | 30 | 31 | 32 | 33 | 34 | 35 | 36 | 37 | 38 | 39 | 40 | -------------------------------------------------------------------------------- /phpstan.neon: -------------------------------------------------------------------------------- 1 | parameters: 2 | level: 5 3 | paths: 4 | - src 5 | - tests 6 | -------------------------------------------------------------------------------- /src/DamerauLevenshtein.php: -------------------------------------------------------------------------------- 1 | compOne = $firstString; 103 | $this->compOneLength = (int)mb_strlen($this->compOne, 'UTF-8'); 104 | $this->compTwo = $secondString; 105 | $this->compTwoLength = (int)mb_strlen($this->compTwo, 'UTF-8'); 106 | } 107 | 108 | $this->insCost = $insCost; 109 | $this->delCost = $delCost; 110 | $this->subCost = $subCost; 111 | $this->transCost = $transCost; 112 | } 113 | 114 | /** 115 | * Returns computed matrix for given input strings. 116 | * 117 | * @return int[][] matrix 118 | */ 119 | public function getMatrix(): array 120 | { 121 | if (!$this->calculated) { 122 | $this->setupMatrix(); 123 | } 124 | 125 | return $this->matrix; 126 | } 127 | 128 | /** 129 | * Returns similarity of strings, absolute number = Damerau Levenshtein distance. 130 | * 131 | * @return int 132 | */ 133 | public function getSimilarity(): int 134 | { 135 | if (!$this->calculated) { 136 | $this->setupMatrix(); 137 | } 138 | 139 | return $this->matrix[$this->compOneLength][$this->compTwoLength]; 140 | } 141 | 142 | /** 143 | * Procedure to compute matrix for given input strings. 144 | * 145 | * @return void 146 | * @SuppressWarnings(PHPMD.CyclomaticComplexity) 147 | */ 148 | private function setupMatrix(): void 149 | { 150 | $this->matrix = [[]]; 151 | 152 | for ($i = 0; $i <= $this->compOneLength; $i += 1) { 153 | // @phan-suppress-next-line PhanTypeInvalidDimOffset 154 | $this->matrix[$i][0] = $i > 0 ? $this->matrix[$i - 1][0] + $this->delCost : 0; 155 | } 156 | 157 | for ($i = 0; $i <= $this->compTwoLength; $i += 1) { 158 | // Insertion actualy 159 | $this->matrix[0][$i] = $i > 0 ? $this->matrix[0][$i - 1] + $this->insCost : 0; 160 | } 161 | 162 | for ($i = 1; $i <= $this->compOneLength; $i += 1) { 163 | // Curchar for the first string 164 | $cOne = (string)mb_substr($this->compOne, $i - 1, 1, 'UTF-8'); 165 | for ($j = 1; $j <= $this->compTwoLength; $j += 1) { 166 | // Curchar for the second string 167 | $cTwo = (string)mb_substr($this->compTwo, $j - 1, 1, 'UTF-8'); 168 | 169 | // Compute substitution cost 170 | if ($this->compare($cOne, $cTwo) === 0) { 171 | $cost = 0; 172 | $trans = 0; 173 | } else { 174 | $cost = $this->subCost; 175 | $trans = $this->transCost; 176 | } 177 | 178 | // Deletion cost 179 | // @phan-suppress-next-line PhanTypeInvalidDimOffset, PhanTypeInvalidLeftOperandOfAdd 180 | $del = $this->matrix[$i - 1][$j] + $this->delCost; 181 | 182 | // Insertion cost 183 | 184 | // @codingStandardsIgnoreLine Generic.Files.LineLength 185 | // @phan-suppress-next-line PhanTypeArraySuspiciousNull, PhanTypeInvalidDimOffset, PhanTypeInvalidLeftOperandOfAdd 186 | $ins = $this->matrix[$i][$j - 1] + $this->insCost; 187 | 188 | // Substitution cost, 0 if same 189 | $sub = $this->matrix[$i - 1][$j - 1] + $cost; 190 | 191 | // Compute optimal 192 | $this->matrix[$i][$j] = min($del, $ins, $sub); 193 | 194 | // Transposition cost 195 | if ($i > 1 && $j > 1) { 196 | // Last two 197 | // @phan-suppress-next-line PhanPartialTypeMismatchArgumentInternal 198 | $ccOne = (string)mb_substr($this->compOne, $i - 2, 1, 'UTF-8'); 199 | // @phan-suppress-next-line PhanPartialTypeMismatchArgumentInternal 200 | $ccTwo = (string)mb_substr($this->compTwo, $j - 2, 1, 'UTF-8'); 201 | 202 | if ($this->compare($cOne, $ccTwo) === 0 && $this->compare($ccOne, $cTwo) === 0) { 203 | // Transposition cost is computed as minimal of two 204 | // @phan-suppress-next-line PhanPartialTypeMismatchArgumentInternal 205 | $this->matrix[$i][$j] = min($this->matrix[$i][$j], $this->matrix[$i - 2][$j - 2] + $trans); 206 | } 207 | } 208 | } 209 | } 210 | 211 | $this->calculated = true; 212 | } 213 | 214 | /** 215 | * Returns maximal possible edit Damerau Levenshtein distance between texts. 216 | * 217 | * On common substring of same length perform substitution / insert + delete 218 | * (depends on what is cheaper), then on extra characters perform insertion / deletion 219 | * 220 | * @return int 221 | */ 222 | public function getMaximalDistance(): int 223 | { 224 | // Is substitution cheaper that delete + insert? 225 | $subCost = min($this->subCost, $this->delCost + $this->insCost); 226 | 227 | // Get common size 228 | $minSize = min($this->compOneLength, $this->compTwoLength); 229 | $maxSize = max($this->compOneLength, $this->compTwoLength); 230 | $extraSize = $maxSize - $minSize; 231 | 232 | // On common size perform substitution / delete + insert, what is cheaper 233 | $maxCost = $subCost * $minSize; 234 | 235 | // On resulting do insert/delete 236 | if ($this->compOneLength > $this->compTwoLength) { 237 | // Delete extra characters 238 | $maxCost += $extraSize * $this->delCost; 239 | } else { 240 | // Insert extra characters 241 | $maxCost += $extraSize * $this->insCost; 242 | } 243 | 244 | return (int)$maxCost; 245 | } 246 | 247 | /** 248 | * Returns relative distance of input strings (computed with maximal possible distance). 249 | * 250 | * @return float 251 | */ 252 | public function getRelativeDistance(): float 253 | { 254 | if (!$this->calculated) { 255 | $this->setupMatrix(); 256 | } 257 | 258 | return (float)(1 - ($this->getSimilarity() / $this->getMaximalDistance())); 259 | } 260 | 261 | /** 262 | * Compares two characters from string (this method may be overridden in child class). 263 | * 264 | * @param string $firstCharacter First character 265 | * @param string $secondCharacter Second character 266 | * @return int 267 | */ 268 | protected function compare(string $firstCharacter, string $secondCharacter): int 269 | { 270 | return strcmp($firstCharacter, $secondCharacter); 271 | } 272 | 273 | /** 274 | * Returns computed matrix for given input strings (For debugging purposes). 275 | * 276 | * @return string 277 | */ 278 | public function displayMatrix(): string 279 | { 280 | if (!$this->calculated) { 281 | $this->setupMatrix(); 282 | } 283 | 284 | $out = ' ' . $this->compOne . PHP_EOL; 285 | for ($y = 0; $y <= $this->compTwoLength; $y += 1) { 286 | if ($y - 1 < 0) { 287 | $out .= ' '; 288 | } else { 289 | $out .= (string)mb_substr($this->compTwo, $y - 1, 1, 'UTF-8'); 290 | } 291 | 292 | for ($x = 0; $x <= $this->compOneLength; $x += 1) { 293 | $out .= $this->matrix[$x][$y]; 294 | } 295 | 296 | $out .= PHP_EOL; 297 | } 298 | 299 | return $out; 300 | } 301 | 302 | /** 303 | * Returns current cost of insertion operation. 304 | * 305 | * @return int 306 | */ 307 | public function getInsCost(): int 308 | { 309 | return $this->insCost; 310 | } 311 | 312 | /** 313 | * Sets cost of insertion operation (insert characters to first string to match second string). 314 | * 315 | * @param int $insCost Cost of character insertion 316 | * @return void 317 | */ 318 | public function setInsCost(int $insCost): void 319 | { 320 | $this->calculated = $insCost === $this->insCost ? $this->calculated : false; 321 | $this->insCost = $insCost; 322 | } 323 | 324 | /** 325 | * Returns current cost of deletion operation. 326 | * 327 | * @return int 328 | */ 329 | public function getDelCost(): int 330 | { 331 | return $this->delCost; 332 | } 333 | 334 | /** 335 | * Sets cost of deletion operation (delete characters from first string to match second string). 336 | * 337 | * @param int $delCost Cost of character deletion 338 | * @return void 339 | */ 340 | public function setDelCost(int $delCost): void 341 | { 342 | $this->calculated = $delCost === $this->delCost ? $this->calculated : false; 343 | $this->delCost = $delCost; 344 | } 345 | 346 | /** 347 | * Returns current cost of substitution operation. 348 | * 349 | * @return int 350 | */ 351 | public function getSubCost(): int 352 | { 353 | return $this->subCost; 354 | } 355 | 356 | /** 357 | * Sets cost of substitution operation. 358 | * 359 | * @param int $subCost Cost of character substitution 360 | * @return void 361 | */ 362 | public function setSubCost(int $subCost): void 363 | { 364 | $this->calculated = $subCost === $this->subCost ? $this->calculated : false; 365 | $this->subCost = $subCost; 366 | } 367 | 368 | /** 369 | * Returns current cost of transposition operation. 370 | * 371 | * @return int 372 | */ 373 | public function getTransCost(): int 374 | { 375 | return $this->transCost; 376 | } 377 | 378 | /** 379 | * Sets cost of transposition operation. 380 | * 381 | * @param int $transCost Cost of character transposition 382 | * @return void 383 | */ 384 | public function setTransCost(int $transCost): void 385 | { 386 | $this->calculated = $transCost === $this->transCost ? $this->calculated : false; 387 | $this->transCost = $transCost; 388 | } 389 | } 390 | --------------------------------------------------------------------------------