nish/phpstan-safestring-rule

PHPStan 扩展:警告不安全的字符串

安装: 15

依赖项: 0

建议者: 0

安全: 0

星标: 2

关注者: 2

分支: 0

开放问题: 0

类型:phpstan-extension

v0.1.1 2024-08-16 02:26 UTC

This package is auto-updated.

Last update: 2024-09-16 05:57:34 UTC


README

此包是用于检查不安全字符串的 PHPStan 扩展,例如:检查在未调用 htmlspecialchars 的情况下调用 echo,检查在未使用预处理语句的情况下调用数据库查询。

注意

此包不符合“向后兼容承诺”。因为它扩展了核心的基本处理,不能保证与版本差异兼容。

https://phpstan.org/developing-extensions/backward-compatibility-promise

安装

composer require --dev nish/phpstan-safestring-rule

如何使用

添加到 phpstan.neon

includes:
  - vendor/nish/phpstan-safestring-rule/extension.neon

services:
  -
    class: Nish\PHPStan\Rules\EchoHtmlRule
    tags: [phpstan.rules.rule]
  -
    factory: Nish\PHPStan\Type\SafeHtmlStringReturnTypeExtension([htmlspecialchars, h, raw])
    tags: [phpstan.broker.dynamicFunctionReturnTypeExtension]

composer.json

    "autoload": {
        "psr-4": { "App\\": "src" },
        "files": [
            "src/functions.php"
        ]
    },

值对象类 src/ProductDto.php

<?php

namespace App;

class ProductDto
{
    /** @var int */
    public $product_id;
    /** @var string */
    public $name;
    /** @var ?string */
    public $description;
}

HTML 模板 src/ProductHtml.php

<?php
namespace App;
class ProductHtml {
    public function view(ProductDto $product): void {
?>

<div>
  <div>
    <?= $product->product_id ?>
  </div>
  <div>
    <?= $product->name ?>
  </div>
  <div>
    <?= $product->description ?>
  </div>
</div>

<?php
    }
}

在这种情况下,phpstan 的执行结果如下

 3/3 [▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓] 100%

 ------ ---------------------------------------------------- 
  Line   ProductHtml.php                                     
 ------ ---------------------------------------------------- 
  12     Parameter #1 (string) is not safehtml-string.       
  15     Parameter #1 (string|null) is not safehtml-string.  
 ------ ---------------------------------------------------- 

                                                             
 [ERROR] Found 2 errors                                      

然后,不能直接调用 echo 字符串类型。

safehtml-string 是一个虚拟类型,可以通过添加一个辅助函数来解决。

src/functions.php:

<?php

/**
 * @param int|string|null $input
 */
function h($input): string
{
    return htmlspecialchars((string)$input);
}

/**
 * @param int|string|null $input
 */
function raw($input): string
{
    return (string)$input;
}

phpstan.neon

services:
# ...
  -
    factory: Nish\PHPStan\Type\SafeHtmlStringReturnTypeExtension([htmlspecialchars, h, raw])
    tags: [phpstan.broker.dynamicFunctionReturnTypeExtension]

src/ProductHtml.php:

<?php
namespace App;
class ProductHtml {
    public function view(ProductDto $product): void {
?>

<div>
  <div>
    <?= $product->product_id ?>
  </div>
  <div>
    <?= h($product->name) ?>
  </div>
  <div>
    <?= h($product->description) ?>
  </div>
</div>

<?php
    }
}

运行 phpstan

an/phpstan.neon.
 3/3 [▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓] 100%

                                                             
 [OK] No errors

OK,没有错误,它很安全!

提示

常量字符串类型不需要转换为 safehtml-string。

<?php
namespace App;
class TypeHtml {
    const CURRENT_TYPE_ID = 2;
    const TYPES = [
        1 => 'TYPE 1',
        2 => 'TYPE 2',
        3 => 'TYPE 3',
    ];
    public function view(): void {
?>

<div>
  <div>
    <?= self::CURRENT_TYPE_ID ?>
  </div>
  <div>
    <?= self::TYPES[self::CURRENT_TYPE_ID] ?>
  </div>
</div>

<?php
    }
}

这是没有错误的。

当用于方法而不是函数时

  -
    factory: Nish\PHPStan\Type\SafeHtmlStringReturnTypeExtension(DateTimeInterface::format)
    tags: [phpstan.broker.dynamicMethodReturnTypeExtension]
  -
    factory: Nish\PHPStan\Type\SafeHtmlStringReturnTypeExtension(App\FormUtil::makeForm)
    tags: [phpstan.broker.dynamicMethodReturnTypeExtension]

一次不能指定多个。

使用 safe-string 自定义类型

如果您有以下数据库访问程序

<?php

namespace App;

use PDO;

class ProductDb
{
    /** @var PDO */
    private $pdo;

    public function __construct(PDO $pdo)
    {
        $this->pdo = $pdo;
    }

    /**
     * @return array<int,ProductDto>
     */
    public function getProductList(string $where): array
    {
        $stmt = $this->pdo->query('select * from products ' . $where);
        if (!$stmt)
            return [];
        $ret = $stmt->fetchAll(PDO::FETCH_CLASS, ProductDto::class);
        if (!$ret)
            return [];

        /** @var array<int,ProductDto> $ret */
        return $ret;
    }
}

pdo->query() 是不安全的。

如果类是以下程序

<?php

namespace App;

use PDO;

class ProductPage
{
    /** @return mixed */
    public static function index(PDO $pdo, string $where)
    {
        $productModel = new ProductDb($pdo);
        $products = $productModel->getProductList($where);

        return [
            'templateData' => ['products' => $products],
        ];
    }
}

我想显示一个错误。

通过在 phpstan.neon 中写入以下设置来实现。

services:
# ...
  -
    factory: Nish\PHPStan\Rules\SafeStringCallRule([
        'PDO::query': 0,
    ])
    tags: [phpstan.rules.rule]

0 是参数的索引。

运行 phpstan。

 6/6 [▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓] 100%

 ------ ------------------------------------------- 
  Line   ProductDb.php                              
 ------ ------------------------------------------- 
  22     Parameter #1 (string) is not safe-string.  
 ------ ------------------------------------------- 

                                                             
 [ERROR] Found 1 error

更多控制,可以使用 safe-string 类型。

    /**
     * @param safe-string $where
     * @return array<int,ProductDto>
     */
    public function getProductList(string $where): array

如果我写下提示会发生什么?

 6/6 [▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓] 100%

 ------ ------------------------------------------------------ 
  Line   ProductPage.php                                       
 ------ ------------------------------------------------------ 
  13     Parameter #1 $where of method                         
         App\ProductDb::getProductList() expects safe-string,  
         string given.                                         
 ------ ------------------------------------------------------ 
                                                              
 [ERROR] Found 1 error

改为调用者错误。

如果字符串明显是“常量字符串(及其派生类型)”,则不会抛出错误。

class ProductPage
{
    /** @return mixed */
    public static function index(PDO $pdo, int $id)
    {
        $productModel = new ProductDb($pdo);
        $where = sprintf('where product_id > %d', $id);
        $products = $productModel->getProductList($where);
 6/6 [▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓] 100%

                                                               
 [OK] No errors

提示

添加返回类型规则

factory: Nish\PHPStan\Rules\SafeStringReturnTypeRule([
    App\Db\Utils::getSafeConditionString,
])
tags: [phpstan.rules.rule]