PHP:发送选项列表作为参数(替代命名参数/参数包)

PHP: Sending a list of options as an argument (alternative to named parameters/ argument bag)

我希望提供一个选项列表作为函数的参数。

理想场景:命名参数

如果 PHP 有命名参数,它会像这样完成:

function setOptions($title, $url, $public = true, $placeholder = "type here...") {
   ...
}

setOptions($title = "Hello World", $url = "example.com", $placeholder = "hi"); 

不幸的是 PHP 没有 有命名参数 (请告诉我 PHP7 是否计划有一些作为评论).

其他人都在使用的解决方案:关联数组

我见过的大多数 PHP 脚本都使用替代数组方法,如下所示:

function setOptions($options) {
   ...
}

setOptions(array(
   'title' => "Hello World",
   'url' => "example.com",
   'placeholder' => "hi"
));

关联数组方法的缺点

虽然这很好用,但存在以下缺点:

有没有更好的方法?

是否有更好的方法可以解决这些问题(在当前 PHP 或 PHP7 或什至 hacklang(?))。

解决方案:使用流畅的 setter

我发现这个问题的一个潜在解决方案是使用 classes 和 fluent setter,如下所示:

class PostOptions {

    protected
      $title,
      $url,
      $public = TRUE,
      $placeholder = "type here..."; //Default Values can be set here

    static function getInstance(): PostOptions {
        return new self();
    }

    public function setTitle($title) {
        $this->title = $title;
        return $this;
    }

    public function setUrl($url) {
        $this->url = $url;
        return $this;
    }

    public function setPublic($public) {
        $this->public = $public;
        return $this;
    }

    public function setPlaceholder($placeholder) {
        $this->placeholder = $placeholder;
        return $this;
    }

}

然后您可以像这样发送选项:

function setOptions(PostOptions $postOptions) {
    //...
}

setOptions(
  PostOptions::getInstance()
             ->setTitle("Hello World")
             ->setUrl("example.com")
             ->setPlaceholder("hi")
);

赶快行动吧! (这个看起来很长)

虽然这可能看起来 很长,但实际上可以使用IDE 工具非常 快速实施。

例如在 InteliJ 或 PHPStorm 中,只需键入 ALT+INS > Select setters > Select 您要设置的字段并选中 fluent setters 的复选框 > 单击 OK

为什么要流利的二传手?为什么不把所有字段都设为 public?

使用 public 字段要慢很多。这是因为 fluent setter 可以使用链式方法,而 public 字段方式必须这样写:

$options = new PostOptions(); 
$options->title = "hello"; 
$options->placeholder = "..."; 
$options->url "..."
setOptions($options); 

与提议的解决方案相比,需要输入更多的内容

为什么这样更好?

  • 在 IDE 中使用自动完成比数组方法更快
  • 不太可能出现拼写错误(感谢自动完成)
  • 轻松查看可用选项(再次感谢自动完成)
  • 可以使用 PHPDoc 为各个字段提供单独的文档
  • 可以更轻松地使用嵌套选项,例如如果您有一个选项列表,并且该选项还有更多选项列表
  • 其他 OOP 优势,例如继承与抽象 类

这种方法要快多少?

我为 Wordpress 标签数组实现了一个快速 class:https://codex.wordpress.org/Function_Reference/register_post_type

我发现为每个值设置一个 属性(在第二台显示器上有您旁边的文档),流畅的 setter 方法大约快 25%比数组方法多亏了自动完成!但是,如果文档不在您身边,我预计这种方法 将远远超过 25%,因为 发现选项 会快得多!

欢迎使用其他方法

来自数组的声明

这就是我通常声明 class 结构的方式。唯一的缺点就是写的时间比较长,但是它允许可选参数,默认值等等

public static $defaults = array(
    'user_id' => null,
    'username' => null,
    'avatar' => null,
    'email' => null,
    'description' => null,
);

public function __construct(array $args = array()) {
    $this->dbc = Database::connection();
    $defaults = self::$defaults;
    $args = array_merge($defaults, $args);

    //Assign the object properites
    $this->user_id = (is_numeric($args['user_id'])) ? $args['user_id'] : null;
    $this->username = $args['username'];
    $this->avatar = AVATAR_DIR . $args['avatar'];
    $this->email = $args['email'];
    $this->description = $args['description'];
}

通过这种方式,您可以像 $x = new User() 一样声明一个对象,它会工作得很好。假设您只从 SQL 语句中选择了几列。您可以将 public static $defaults 中的 keys 设置为与您选择的 columns 相同的名称,这样来实例化您的对象,您可以轻松地做到:

$row = mysqli_fetch_array($result, MYSQLI_ASSOC);
$object = new User($row);

array_merge 负责处理在它们提供的参数中不需要的任何无关键。如果您需要更改选项,您可以使用默认数组和 array_merge 以相同的方式声明它们以捕获参数并模拟命名参数和默认值(如 Python)

在 Hack 中,您可以使用 Shapes。形状为关联数组定义了一个结构,这样事情就可以自动完成(取决于 IDE 支持)并且类型检查器会发现拼写错误。

例如,您的示例可以改写为:

function setOptions(shape(
  'title' => string,
  'url' => string,
  'public' => ?bool,
  'placeholder' => ?string,
) $options) {
  $title = $options['title'];
  $url = $options['url'];
  $public = Shapes::idx($options, 'public', true);
  $placeholder = Shapes::idx($options, 'placeholder', 'type here...');
  ...
}

setOptions(shape(
  'title' => 'Hello World',
  'url' => 'example.com',
  'placeholder' => 'hi',
));

这标记 titleurl 都是必需的选项,而 publicplaceholder 是可选的(形状中的所有可为 null 的类型都被认为是可选的)。 Shapes::idx 然后用于获取提供的值,如果未传入值,则使用默认值(第三个参数)。

Syntactic: https://github.com/topclaudy/php-syntactic

你可以这样做:

function foo($a = 1, $b = 2, $c = 3, $d = 4){
    return $a * $b * $c * $d;
}

然后用你想要的参数调用它:

//Call with argument b only
echo s('foo')->in('b', 5)->out(); //Outputs 60

//Call with argument a and argument at index/position 1 (b),
echo s('foo')->in('a', 7)->in(1, 5)->out(); //Outputs 420

//Call with argument c only through dynamic method
echo s('foo')->c(9)->out(); //Outputs 72

如果你有那么多参数,我会考虑创建一个你将传递给 class 的对象,而不是 n 个参数,每个参数都是一个字段。在构造函数中输入所需的参数,这就是干净的解决方案。