diff --git a/be/.phan/config.php b/be/.phan/config.php new file mode 100644 index 00000000..29e10518 --- /dev/null +++ b/be/.phan/config.php @@ -0,0 +1,388 @@ + '8.1', + + // If enabled, missing properties will be created when + // they are first seen. If false, we'll report an + // error message if there is an attempt to write + // to a class property that wasn't explicitly + // defined. + 'allow_missing_properties' => false, + + // If enabled, null can be cast to any type and any + // type can be cast to null. Setting this to true + // will cut down on false positives. + 'null_casts_as_any_type' => false, + + // If enabled, allow null to be cast as any array-like type. + // + // This is an incremental step in migrating away from `null_casts_as_any_type`. + // If `null_casts_as_any_type` is true, this has no effect. + 'null_casts_as_array' => false, + + // If enabled, allow any array-like type to be cast to null. + // This is an incremental step in migrating away from `null_casts_as_any_type`. + // If `null_casts_as_any_type` is true, this has no effect. + 'array_casts_as_null' => false, + + // If enabled, scalars (int, float, bool, string, null) + // are treated as if they can cast to each other. + // This does not affect checks of array keys. See `scalar_array_key_cast`. + 'scalar_implicit_cast' => false, + + // If enabled, any scalar array keys (int, string) + // are treated as if they can cast to each other. + // E.g. `array` can cast to `array` and vice versa. + // Normally, a scalar type such as int could only cast to/from int and mixed. + 'scalar_array_key_cast' => false, + + // If this has entries, scalars (int, float, bool, string, null) + // are allowed to perform the casts listed. + // + // E.g. `['int' => ['float', 'string'], 'float' => ['int'], 'string' => ['int'], 'null' => ['string']]` + // allows casting null to a string, but not vice versa. + // (subset of `scalar_implicit_cast`) + 'scalar_implicit_partial' => [], + + // If enabled, Phan will warn if **any** type in a method invocation's object + // is definitely not an object, + // or if **any** type in an invoked expression is not a callable. + // Setting this to true will introduce numerous false positives + // (and reveal some bugs). + 'strict_method_checking' => true, + + // If enabled, Phan will warn if **any** type of the object expression for a property access + // does not contain that property. + 'strict_object_checking' => true, + + // If enabled, Phan will warn if **any** type in the argument's union type + // cannot be cast to a type in the parameter's expected union type. + // Setting this to true will introduce numerous false positives + // (and reveal some bugs). + 'strict_param_checking' => true, + + // If enabled, Phan will warn if **any** type in a property assignment's union type + // cannot be cast to a type in the property's declared union type. + // Setting this to true will introduce numerous false positives + // (and reveal some bugs). + 'strict_property_checking' => true, + + // If enabled, Phan will warn if **any** type in a returned value's union type + // cannot be cast to the declared return type. + // Setting this to true will introduce numerous false positives + // (and reveal some bugs). + 'strict_return_checking' => true, + + // If true, seemingly undeclared variables in the global + // scope will be ignored. + // + // This is useful for projects with complicated cross-file + // globals that you have no hope of fixing. + 'ignore_undeclared_variables_in_global_scope' => false, + + // Set this to false to emit `PhanUndeclaredFunction` issues for internal functions that Phan has signatures for, + // but aren't available in the codebase, or from Reflection. + // (may lead to false positives if an extension isn't loaded) + // + // If this is true(default), then Phan will not warn. + // + // Even when this is false, Phan will still infer return values and check parameters of internal functions + // if Phan has the signatures. + 'ignore_undeclared_functions_with_known_signatures' => false, + + // Backwards Compatibility Checking. This is slow + // and expensive, but you should consider running + // it before upgrading your version of PHP to a + // new version that has backward compatibility + // breaks. + // + // If you are migrating from PHP 5 to PHP 7, + // you should also look into using + // [php7cc (no longer maintained)](https://github.com/sstalle/php7cc) + // and [php7mar](https://github.com/Alexia/php7mar), + // which have different backwards compatibility checks. + // + // If you are still using versions of php older than 5.6, + // `PHP53CompatibilityPlugin` may be worth looking into if you are not running + // syntax checks for php 5.3 through another method such as + // `InvokePHPNativeSyntaxCheckPlugin` (see .phan/plugins/README.md). + 'backward_compatibility_checks' => false, + + // If true, check to make sure the return type declared + // in the doc-block (if any) matches the return type + // declared in the method signature. + 'check_docblock_signature_return_type_match' => true, + + // This setting maps case-insensitive strings to union types. + // + // This is useful if a project uses phpdoc that differs from the phpdoc2 standard. + // + // If the corresponding value is the empty string, + // then Phan will ignore that union type (E.g. can ignore 'the' in `@return the value`) + // + // If the corresponding value is not empty, + // then Phan will act as though it saw the corresponding UnionTypes(s) + // when the keys show up in a UnionType of `@param`, `@return`, `@var`, `@property`, etc. + // + // This matches the **entire string**, not parts of the string. + // (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) + // + // (These are not aliases, this setting is ignored outside of doc comments). + // (Phan does not check if classes with these names exist) + // + // Example setting: `['unknown' => '', 'number' => 'int|float', 'char' => 'string', 'long' => 'int', 'the' => '']` + 'phpdoc_type_mapping' => [], + + // Set to true in order to attempt to detect dead + // (unreferenced) code. Keep in mind that the + // results will only be a guess given that classes, + // properties, constants and methods can be referenced + // as variables (like `$class->$property` or + // `$class->$method()`) in ways that we're unable + // to make sense of. + // + // To more aggressively detect dead code, + // you may want to set `dead_code_detection_prefer_false_negative` to `false`. + 'dead_code_detection' => false, + + // Set to true in order to attempt to detect unused variables. + // `dead_code_detection` will also enable unused variable detection. + // + // This has a few known false positives, e.g. for loops or branches. + 'unused_variable_detection' => true, + + // Set to true in order to attempt to detect redundant and impossible conditions. + // + // This has some false positives involving loops, + // variables set in branches of loops, and global variables. + 'redundant_condition_detection' => true, + + // If enabled, Phan will act as though it's certain of real return types of a subset of internal functions, + // 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). + // + // 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. + // 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. + 'assume_real_types_for_internal_functions' => true, + + // If true, this runs a quick version of checks that takes less + // time at the cost of not running as thorough + // of an analysis. You should consider setting this + // to true only when you wish you had more **undiagnosed** issues + // to fix in your code base. + // + // In quick-mode the scanner doesn't rescan a function + // or a method's code block every time a call is seen. + // This means that the problem here won't be detected: + // + // ```php + // false, + + // Override to hardcode existence and types of (non-builtin) globals in the global scope. + // Class names should be prefixed with `\`. + // + // (E.g. `['_FOO' => '\FooClass', 'page' => '\PageClass', 'userId' => 'int']`) + 'globals_type_map' => [], + + // The minimum severity level to report on. This can be + // set to `Issue::SEVERITY_LOW`, `Issue::SEVERITY_NORMAL` or + // `Issue::SEVERITY_CRITICAL`. Setting it to only + // critical issues is a good place to start on a big + // sloppy mature code base. + 'minimum_severity' => Issue::SEVERITY_LOW, + + // Add any issue types (such as `'PhanUndeclaredMethod'`) + // to this list to inhibit them from being reported. + 'suppress_issue_types' => [], + + // A regular expression to match files to be excluded + // from parsing and analysis and will not be read at all. + // + // This is useful for excluding groups of test or example + // directories/files, unanalyzable files, or files that + // can't be removed for whatever reason. + // (e.g. `'@Test\.php$@'`, or `'@vendor/.*/(tests|Tests)/@'`) + 'exclude_file_regex' => '@^vendor/.*/(tests?|Tests?)/@', + + // A list of files that will be excluded from parsing and analysis + // and will not be read at all. + // + // This is useful for excluding hopelessly unanalyzable + // files that can't be removed for whatever reason. + 'exclude_file_list' => [], + + // A directory list that defines files that will be excluded + // from static analysis, but whose class and method + // information should be included. + // + // Generally, you'll want to include the directories for + // third-party code (such as "vendor/") in this list. + // + // n.b.: If you'd like to parse but not analyze 3rd + // party code, directories containing that code + // should be added to the `directory_list` as well as + // to `exclude_analysis_directory_list`. + 'exclude_analysis_directory_list' => [ + 'vendor/', + ], + + // Enable this to enable checks of require/include statements referring to valid paths. + // The settings `include_paths` and `warn_about_relative_include_statement` affect the checks. + 'enable_include_path_checks' => true, + + // The number of processes to fork off during the analysis + // phase. + 'processes' => 1, + + // List of case-insensitive file extensions supported by Phan. + // (e.g. `['php', 'html', 'htm']`) + 'analyzed_file_extensions' => [ + 'php', + ], + + // You can put paths to stubs of internal extensions in this config option. + // If the corresponding extension is **not** loaded, then Phan will use the stubs instead. + // 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). + // Use a different extension from php to avoid accidentally loading these. + // The `tools/make_stubs` script can be used to generate your own stubs (compatible with php 7.0+ right now) + // + // (e.g. `['xdebug' => '.phan/internal_stubs/xdebug.phan_php']`) + 'autoload_internal_extension_signatures' => [], + + // A list of plugin files to execute. + // + // Plugins which are bundled with Phan can be added here by providing their name (e.g. `'AlwaysReturnPlugin'`) + // + // Documentation about available bundled plugins can be found [here](https://github.com/phan/phan/tree/v5/.phan/plugins). + // + // Alternately, you can pass in the full path to a PHP file with the plugin's implementation (e.g. `'vendor/phan/phan/.phan/plugins/AlwaysReturnPlugin.php'`) + 'plugins' => [ + 'AlwaysReturnPlugin', + 'DollarDollarPlugin', + 'DuplicateArrayKeyPlugin', + 'DuplicateExpressionPlugin', + 'PregRegexCheckerPlugin', + 'PrintfCheckerPlugin', + 'SleepCheckerPlugin', + 'UnreachableCodePlugin', + 'UseReturnValuePlugin', + 'EmptyStatementListPlugin', + 'StrictComparisonPlugin', + 'LoopVariableReusePlugin', + ], + + // A list of directories that should be parsed for class and + // method information. After excluding the directories + // defined in `exclude_analysis_directory_list`, the remaining + // files will be statically analyzed for errors. + // + // Thus, both first-party and third-party code being used by + // your application should be included in this list. + 'directory_list' => [ + '../tbclient.protobuf/tbm_php/GPBMetadata', + '../tbclient.protobuf/tbm_php/TbClient', + 'app', + 'vendor/barryvdh/laravel-debugbar/src', + 'vendor/barryvdh/laravel-ide-helper/src', + 'vendor/friendsofphp/php-cs-fixer/src', + 'vendor/google/protobuf/src/GPBMetadata/Google/Protobuf', + 'vendor/google/protobuf/src/Google/Protobuf', + 'vendor/google/recaptcha/src/ReCaptcha', + 'vendor/infection/infection/src', + 'vendor/larastan/larastan/src', + 'vendor/laravel/framework/src/Illuminate', + 'vendor/nunomaduro/collision/src', + 'vendor/phan/phan/src/Phan', + 'vendor/phpmd/phpmd/src/main/php', + 'vendor/phpstan/extension-installer/src', + 'vendor/phpstan/phpstan-deprecation-rules/src', + 'vendor/phpstan/phpstan-strict-rules/src', + 'vendor/phpunit/phpunit/src', + 'vendor/psalm/plugin-laravel/src', + 'vendor/spatie/laravel-collection-macros/src', + 'vendor/spatie/laravel-ignition/src', + 'vendor/spatie/regex/src', + 'vendor/thecodingmachine/phpstan-safe-rule/src', + 'vendor/thecodingmachine/safe/deprecated/Exceptions', + 'vendor/thecodingmachine/safe/generated/Exceptions', + 'vendor/thecodingmachine/safe/lib/Exceptions', + 'vendor/vimeo/psalm/src/Psalm', + ], + + // A list of individual files to include in analysis + // with a path relative to the root directory of the + // project. + 'file_list' => [ + 'vendor/thecodingmachine/safe/lib/DateTime.php', + 'vendor/thecodingmachine/safe/lib/DateTimeImmutable.php', + ], +]; diff --git a/be/app/Exceptions/Handler.php b/be/app/Exceptions/Handler.php index aa328d36..33f7a938 100644 --- a/be/app/Exceptions/Handler.php +++ b/be/app/Exceptions/Handler.php @@ -2,24 +2,18 @@ namespace App\Exceptions; -use Illuminate\Foundation\Exceptions\Handler as ExceptionHandler; +use Illuminate\Validation\ValidationException; +use Symfony\Component\HttpFoundation\Response; -class Handler extends ExceptionHandler +class Handler extends \Illuminate\Foundation\Exceptions\Handler { /** * Override parent method to replace validate fail redirect with global error info json format - * - * @param \Illuminate\Validation\ValidationException $e * @param \Illuminate\Http\Request $request - * @return \Symfony\Component\HttpFoundation\Response */ - protected function convertValidationExceptionToResponse(\Illuminate\Validation\ValidationException $e, $request) + protected function convertValidationExceptionToResponse(ValidationException $e, $request): Response { - if ($e->response) { - return $e->response; - } - - return response()->json([ + return $e->response ?? response()->json([ 'errorCode' => 40000, 'errorInfo' => $e->validator->getMessageBag()->getMessages(), ], 400); diff --git a/be/phpstan.neon b/be/phpstan.neon index 30ae4333..8a230154 100644 --- a/be/phpstan.neon +++ b/be/phpstan.neon @@ -3,3 +3,5 @@ parameters: - app/ level: 9 ignoreErrors: +includes: + - vendor/thecodingmachine/phpstan-safe-rule/phpstan-safe-rule.neon diff --git a/be/tests/Feature/App/Http/PostsQuery/ParamsValidatorTest.php b/be/tests/Feature/App/Http/PostsQuery/ParamsValidatorTest.php index 42c6c09d..c85a9d1b 100644 --- a/be/tests/Feature/App/Http/PostsQuery/ParamsValidatorTest.php +++ b/be/tests/Feature/App/Http/PostsQuery/ParamsValidatorTest.php @@ -23,41 +23,38 @@ public static function newParamsValidator(array $rawParams): ParamsValidator #[DataProvider('provideValidate40001')] #[DataProvider('provideValidate40005')] - public function testValidate(array $params): void + #[DataProvider('provideValidate40003')] + #[DataProvider('provideValidate40004')] + public function testValidate(int $errorCode, array $params): void { - $this->assertThrows(fn() => self::newParamsValidator($params), HttpResponseException::class); + try { + $sut = self::newParamsValidator($params); + $sut->addDefaultParamsThenValidate(shouldSkip40003: false); + } catch (HttpResponseException $e) { + self::assertEquals($errorCode, \Safe\json_decode($e->getResponse()->getContent())['errorCode']); + } } public static function provideValidate40001(): array { $uniqueParams = [['postTypes' => ['thread']], ['orderBy' => 'postedAt']]; - return [[[$uniqueParams[0]]], [[$uniqueParams[1]]], [$uniqueParams]]; + return array_map(static fn(array $i) => [40001, $i], [[$uniqueParams[0]], [$uniqueParams[1]], $uniqueParams]); } public static function provideValidate40005(): array { $uniqueParams = [['fid' => 0], ['postTypes' => ['thread']], ['orderBy' => 'postedAt']]; - return array_map(static fn(array $p) => [[$p, $p]], $uniqueParams); - } - - #[DataProvider('validate40003Provider')] - #[DataProvider('validate40004Provider')] - public function testAddDefaultParamsThenValidate(array $params): void - { - $this->assertThrows(function () use ($params) { - $sut = self::newParamsValidator($params); - $sut->addDefaultParamsThenValidate(shouldSkip40003: false); - }, HttpResponseException::class); + return array_map(static fn(array $p) => [40005, [['tid' => 0], $p, $p]], $uniqueParams); } - public static function validate40003Provider(): array + public static function provideValidate40003(): array { - return [[[['postTypes' => ['thread']], ['spid' => '0']]]]; + return [[40003, [['postTypes' => ['thread']], ['spid' => '0']]]]; } - public static function validate40004Provider(): array + public static function provideValidate40004(): array { - return [[[['postTypes' => ['thread']], ['orderBy' => 'spid']]]]; + return [[40004, [['postTypes' => ['thread']], ['tid' => '0'], ['orderBy' => 'spid']]]]; } #[DataProvider('providerDateRangeParamValueOrder')]