在 PHPDoc 中记录 Array 选项的最佳方式?

我正在努力编写可读且易于理解的文档,描述传递给函数的 Array 选项的多树结构。

下面是一个数组结构示例。

$arr = [
'fields' => [
'title' => [
'name'     => 'Document.title',
'format'   => 'string',
'readonly' => true
]
]
];

上面的数组有许多可能的选项,但是这个选项用作理解该结构的函数的参数。

function doSomething(array $arr) { ... }

我想说明在 PHPDoc 中应该如何构造数组,但我不确定正确的方法是什么。

这是我现在拥有的。

/**
* Holds configuration settings for each field in a model.
* Defining the field options
*
* array['fields'] array Defines the feilds to be shown by scaffolding.
* array['fields'][fieldName] array Defines the options for a field, or just enables the field if array is not applied.
* array['fields'][fieldName]['name'] string Overrides the field name (default is the array key)
* array['fields'][fieldName]['model'] string (optional) Overrides the model if the field is a belongsTo assoicated value.
* array['fields'][fieldName]['width'] string Defines the width of the field for paginate views. Examples are "100px" or "auto"
* array['fields'][fieldName]['align'] string Alignment types for paginate views (left, right, center)
* array['fields'][fieldName]['format'] string Formatting options for paginate fields. Options include ('currency','nice','niceShort','timeAgoInWords' or a valid Date() format)
* array['fields'][fieldName]['title'] string Changes the field name shown in views.
* array['fields'][fieldName]['desc'] string The description shown in edit/create views.
* array['fields'][fieldName]['readonly'] boolean True prevents users from changing the value in edit/create forms.
* array['fields'][fieldName]['type'] string Defines the input type used by the Form helper (example 'password')
* array['fields'][fieldName]['options'] array Defines a list of string options for drop down lists.
* array['fields'][fieldName]['editor'] boolean If set to True will show a WYSIWYG editor for this field.
* array['fields'][fieldName]['default'] string The default value for create forms.
*
* @param array $arr (See above)
* @return Object A new editor object.
**/

我的问题是,当生成 HTML 文档时,它的格式不是很好。另外,我不确定上面清楚地解释了数组结构。

还有别的办法吗?

102661 次浏览

AS this is purely display rather than a directive, and should retain space formatting within the docs, I'd be inclined to go for readability with indentation rather than a wall of characters:

 * array['fields'] array Defines the feilds to be shown by scaffolding.
*           [fieldName] array Defines the options for a field, or just enables
*                             the field if array is not applied.
*                 ['name'] string Overrides the field name (default is the
*                                  array key)
*                 ['model'] string (optional) Overrides the model if the field is
*                                  a belongsTo assoicated value.
*                 ['width'] string Defines the width of the field for paginate
*                                  views.
*                                  Examples are "100px" or "auto"
*                 ['align'] string Alignment types for paginate views (left,
*                                 right, center)
*                 ['format'] string Formatting options for paginate fields.
*                                   Options include 'currency', 'nice',
*                                  'niceShort', 'timeAgoInWords' or a valid
*                                  Date() format)
*                 ['title'] string Changes the field name shown in views.
*                 ['desc'] string The description shown in edit/create views.
*                 ['readonly'] boolean True prevents users from changing the
*                                 value in edit/create forms.
*                 ['type'] string Defines the input type used by the Form helper
*                                  (example 'password')
*                 ['options'] array Defines a list of string options for drop-
*                                  down lists.
*                 ['editor'] boolean If set to True will show a WYSIWYG editor
*                                  for this field.
*                 ['default'] string The default value for create forms.

Though using an actual PHP array definition with indentation is even cleaner

Can you use objects instead of arrays? That would make documentation easy.

class Field {


/**
* The name of the thing.
* @var string
*/
protected $name;


protected $model;
protected $width;
//...


public function getName() {...}
public function setName() {...}
//...
}


class FieldList implements SomeKindOfIterator {


/**
* Some fields.
* @var Field[]
*/
protected $fields = array();


/**
* ...
*/
public function push(Field $field) {
$this->fields[] = $field;
}


//...
}

Then you can use a type hint where the class is required.

/**
* Do something.
* @param FieldList $field_list The field.
*/
function doSomething(FieldList $field_list) {...}

Just adding some tabulation will make it look good and easy to understand

/**
* Holds configuration settings for each field in a model.
* Defining the field options
*
* array['fields']              array Defines the fields to be shown by scaffolding.
*          [fieldName]         array Defines the options for a field, or just enables the field if array is not applied.
*              ['name']        string Overrides the field name (default is the array key)
*              ['model']       string (optional) Overrides the model if the field is a belongsTo associated value.
*              ['width']       string Defines the width of the field for paginate views. Examples are "100px" or "auto"
*              ['align']       string Alignment types for paginate views (left, right, center)
*              ['format']      string Formatting options for paginate fields. Options include ('currency','nice','niceShort','timeAgoInWords' or a valid Date() format)
*              ['title']       string Changes the field name shown in views.
*              ['desc']        string The description shown in edit/create views.
*              ['readonly']    boolean True prevents users from changing the value in edit/create forms.
*              ['type']        string Defines the input type used by the Form helper (example 'password')
*              ['options']     array Defines a list of string options for drop down lists.
*              ['editor']      boolean If set to True will show a WYSIWYG editor for this field.
*              ['default']     string The default value for create forms.
*
* @param array $arr (See above)
* @return Object A new editor object.
**/

A nested list approach:

<ul>
<li>
array['fields'] array Defines the fields to be shown by scaffolding.
<ul>
<li>
[fieldName]             array Defines the options for a field, or just enables the field if array is not applied.
<ul>
<li> ['name']       <i><u>string</u></i> Overrides the field name (default is the array key) </li>
<li> ['model']      <i><u>string</u></i> (optional) Overrides the model if the field is a belongsTo associated value.</li>
<li> ['width']      <i><u>string</u></i> Defines the width of the field for paginate views. Examples are "100px" or "auto"</li>
<li> ['align']      <i><u>string</u></i> Alignment types for paginate views (left, right, center)</li>
<li> ['format']     <i><u>string</u></i> Formatting options for paginate fields. Options include ('currency','nice','niceShort','timeAgoInWords' or a valid Date() format)</li>
<li> ['title']      <i><u>string</u></i> Changes the field name shown in views.</li>
<li> ['desc']       <i><u>string</u></i> The description shown in edit/create views.</li>
<li> ['readonly']   <i><u>boolean</u></i> True prevents users from changing the value in edit/create forms.</li>
<li> ['type']       <i><u>string</u></i> Defines the input type used by the Form helper (example 'password')</li>
<li> ['options']    <i><u>array</u></i> Defines a list of string options for drop down lists.</li>
<li> ['editor']     <i><u>boolean</u></i> If set to True will show a WYSIWYG editor for this field.</li>
<li> ['default']    <i><u>string</u></i> The default value for create forms.</li>
</ul>
</li>
</ul>
</li>
</ul>

Result:

  • array['fields'] array Defines the fields to be shown by scaffolding.
    • [fieldName] array Defines the options for a field, or just enables the field if array is not applied.
      • ['name'] string Overrides the field name (default is the array key)
      • ['model'] string (optional) Overrides the model if the field is a belongsTo associated value.
      • ['width'] string Defines the width of the field for paginate views. Examples are "100px" or "auto"
      • ['align'] string Alignment types for paginate views (left, right, center)
      • ['format'] string Formatting options for paginate fields. Options include ('currency','nice','niceShort','timeAgoInWords' or a valid Date() format)
      • ['title'] string Changes the field name shown in views.
      • ['desc'] string The description shown in edit/create views.
      • ['readonly'] boolean True prevents users from changing the value in edit/create forms.
      • ['type'] string Defines the input type used by the Form helper (example 'password')
      • ['options'] array Defines a list of string options for drop down lists.
      • ['editor'] boolean If set to True will show a WYSIWYG editor for this field.
      • ['default'] string The default value for create forms.

If you want it to look fancy, with a bit of Css it will make wonders! xd

I kinda like this better:

 * @param array $doc
*          'type'=>Doc::MY_DOC_TYPE,
*          'created'=>$now,
*          'modified'=>$now

I just paste in the code from where it gets initialized, quick and easy.

This is how I do it instead:

/**
* Class constructor.
*
* @param array $params Array containing the necessary params.
*    $params = [
*      'hostname'     => (string) DB hostname. Required.
*      'databaseName' => (string) DB name. Required.
*      'username'     => (string) DB username. Required.
*      'password'     => (string) DB password. Required.
*      'port'         => (int) DB port. Default: 1433.
*      'sublevel'     => [
*          'key' => (\stdClass) Value description.
*      ]
*    ]
*/
public function __construct(array $params){}

Think it's quite clean and easy to understand what $params should be.

Markdown Syntax for Object Notation (MSON) may be a better choice.

example

/**
* @param array $config
*   + app (string, required) - app directory name
*   + view (string, required) - view directory name
*   + type (enum[string]) - site type
*     + pc - PC version
*     + wap - mobile version
*     + other - other, default value
*   + table_prefix (string) - database table prefix
*/

I wrote a plugin for phpstorm that allows specifying keys like this:

(the format is similar to @siannone's, but with example values instead of types)

/**
* @param array $arr = [
*     'fields' => [ // Defines the feilds to be shown by scaffolding
*         $anyKey => [
*             'name' => 'sale', // Overrides the field name (default is the array key)
*             'model' => 'customer', // (optional) Overrides the model if the field is a belongsTo associated value.
*             'width' => '100px', // Defines the width of the field for paginate views. Examples are "100px" or "auto"
*             'align' => 'center', // Alignment types for paginate views (left, right, center)
*             'format' => 'nice', // Formatting options for paginate fields. Options include ('currency','nice','niceShort','timeAgoInWords' or a valid Date() format)
*             'title' => 'Sale', // Changes the field name shown in views.
*             'desc' => 'A deal another person that results in money', // The description shown in edit/create views.
*             'readonly' => false, // True prevents users from changing the value in edit/create forms.
*             'type' => 'password', // Defines the input type used by the Form helper
*             'options' => ['option1', 'option2'], // Defines a list of string options for drop down lists.
*             'editor' => false, // If set to True will show a WYSIWYG editor for this field.
*             'default' => '', // The default value for create forms.
*         ],
*     ],
* ]
*/
public static function processForm($arr)
{
$fieldName = 'sale';
$arr['fields'][$fieldName][''];
}

enter image description here

It allows to specify @return keys as well:

/**
* @return array [
*     'success' => true,
*     'formObject' => new Form,
*     'errors' => [],
* ]
*/
public static function processForm($arr);

enter image description here

Arrays in PHP are really more like anonymous structs.

For arbitrary data structures there are a number of schema validators but unfortunately it's not widely supported in type hinting. Perhaps some common schemes have plugins? The problem is things working in only one or a few places. You might get the right thing working for your IDE but run static analysis and it can all got to hell.

Care needs to be taken to separate things out so that if possible other tools not supporting a scheme, such as via a plugin, will simply ignore it.

PHPDoc tends to be supported everywhere but is also very limited.

There are often proposals but there's no real good standard. Most solutions are non-standard, not widely supported, partial hacks with limitations or purely superficial (documentation).

There are specific implementations in specific places but nothing widespread and dedicated to PHP itself. Though you can make your own schemas in PHP IDE's tend to lack decent code bridges, even those with PHP in the name.

You should define your field struct separately. Your outer data structure is as pseudo code @key fields field[] rather than representing as multi-dimensional arrays. Conceptually and confusingly you can go so far as:

@start struct custom
@key \stdClass *
@end struct


@start struct fields
@key string hostname
@key string databaseName
@key string password
@key int port=1433
@key custom|undefined sublevel
@end struct


@start struct connection
@key fields fields
@end struct

Or steal from C then invent a laguage...

struct connection {
struct fields {
string hostname;
string databaseName;
string password;
int port = 1433;
custom optional sublevel {
stdClass *;
};
};
};

You can invent a schema based on structs to allow both flat and nested. Nested should be the default, things should only be defined to be as accessible as they need to be for reuse.

An unusual approach is to instead use objects. This doesn't have to entail using interfaces such as array access. In PHP an object wraps an array for properties. It's possible to cast an array to an object (without implementation, only properties) and back.

If you use objects instead as an associative array ($array[$key] versus $object->{$key}) then you can make dummy objects to at least fool the IDE...

final class PersonStruct {
/** @var int */
public $x;


/** @var int $y */


public int $z;
}

Of those three options which may or may not work depends on the tool used.

You can then lie...

/** @var PersonStruct $ps */
$ps = (object)['x' => 0, 'y' => 1, 'z' => 2];


/**
* @param PersonStruct $ps
* @return PersonStruct
*/
function f(object $ps):object {
return $ps;
}


/**
* @param PersonStruct $ps
* @return PersonStruct
*/
function f(stdClass $ps):stdClass {
return $ps;
}

The problem with this is that it means converting arrays to objects. That have both performance implications and changes pass by value to pass by reference. Which is faster is debatable. Arrays should in theory be faster, though objects have benefits being references by default and work better with JSON which separates object from array unlike PHP.

Objects also don't support a property that might not be set very well in regards to type hinting, even though properties in an object are just a PHP array (use ->{key} instead of [key]). There's the potential for other weird things.

If performance is a real concern, you can turn PHP into a compiled language. In the same way you can extend an interface to make an object compilable you can do the same for where you might do everything with OOP and auto-complete but can then do the equivalent of inlining a class by specifying the property it wraps, then using reflection to pretty much replace uses with the contents of the matching methods, with a few extra bits needed (marking what to inline or convert to procedural, single property to wrap or multiple, etc).

The concept is similar to boxing and unboxing. If you're really insane about SA support and broad support for IDEs (auto-complete, checking, etc), analysers, tools, etc then it might be the only way.

Among widely accepted key type documenting formats, I'd like to mention few popular ones here:

Psalm/PHPStan/phan format

/** @param array{foo: string, bar: int} $args */

as a bonus, can also be used for static code analysis with their tools

Wordpress format

/**
* @param array $args {
*     Optional. An array of arguments.
*
*     @type type $key Description. Default 'value'. Accepts 'value', 'value'.
*                     (aligned with Description, if wraps to a new line)
*     @type type $key Description.
* }
*/

and both are supported by deep-assoc-completion plugin