Skip to content

Instantly share code, notes, and snippets.

@mvriel
Created October 2, 2012 19:49
Show Gist options
  • Star 20 You must be signed in to star a gist
  • Fork 4 You must be signed in to fork a gist
  • Save mvriel/3822861 to your computer and use it in GitHub Desktop.
Save mvriel/3822861 to your computer and use it in GitHub Desktop.
Option arrays in PHPDoc
<?php
/*
This code sample demonstrates several style for representing an option array with
the following fields: name, label, type_id, visible, default.
When designing this we should keep in mind that the option key may be represented
by either a literal or a (class)constant. As such we mix and match those for
illustrative purposes.
*/
const NAME = "name";
/**
* ...
*
* @param array $options [description]
* @option string NAME [description]
* @option string "label" [description]
* @option integer "type_id" [description]
* @option boolean "visible" [description]
* @option string "default" [description]
*
* @return void
*/
function ryan_parman($options = array())
{
}
/**
* ...
*
* @param mixed[] $options [description] {
* @type string NAME [description]
* @type string "label" [description]
* @type integer "type_id" [description]
* @type boolean "visible" [description]
* @type string "default" [description]
* }
*
* @return void
*/
function mike_van_riel($options = array())
{
}
/**
* ...
*
* @param `Options` $options [description]
*
* @struct Options {
* @type string NAME [description]
* @type string "label" [description]
* @type integer "type_id" [description]
* @type boolean "visible" [description]
* @type string "default" [description]
* }
*
* @return void
*/
function mike_van_riel2($options = array())
{
}
@schmittjoh
Copy link

I'd like to throw in one more:

/**
 * @param string $options[label] blablub
 * @param integer $options[type_id]
 * @param boolean $options[visible]
 * @param string $options[default]
 */

@skyzyx
Copy link

skyzyx commented Oct 2, 2012

Since descriptions can wrap, perhaps going whitespace-only isn't the best idea. I also feel that it's a little awkward to have an opening brace after a description.

As such, I propose a slight variation:

/**
 * ...
 *
 * @param array $normal [description]
 *
 * @param array $options {
 *     [description]
 *
 *     @option string  NAME      [description]
 *     @option string  "label"   [description]
 *     @option integer "type_id" [description]
 *     @option boolean "visible" [description]
 *     @option string  "default" [description]
 * }
 *
 * @return void
 */
function ryan_parman2($options = array())
{

}

Perhaps in these cases, @option and @type might be aliases?

@skyzyx
Copy link

skyzyx commented Oct 2, 2012

Taking it a step further, this is how the format might be re-used with @method (and to some degree, @Property) for dynamically-generated methods.

/**
 * ...
 *
 * @method [return type] [name]([type] [parameter], [...]) [description]
 *
 * @method [return type] [name]([type] [parameter], [...]) {
 *     [description]
 * 
 *     @param array $options {
 *         [description]
 * 
 *         @option string  NAME      [description]
 *         @option string  "label"   [description]
 *         @option integer "type_id" [description]
 *         @option boolean "visible" [description]
 *         @option string  "default" [description]
 *     }
 *
 *     @return [type] [description]
 * }
 *
 * @return void
 */
function __call($method, $params = array())
{

}

@skyzyx
Copy link

skyzyx commented Oct 2, 2012

@schmittjoh: If you end up having 3, 4, or more levels of nesting (for better or for worse), how well do you anticipate that approach scaling?

@mvriel
Copy link
Author

mvriel commented Oct 2, 2012

@skyzyx Those are interesting variations! So, if I summarize this in words:

A tag may replace its multiline description with a set of curly braces; any content inside those curly braces are interpreted as an inline DocBlock for that specific element. 

Which will make the {} notation allowable for any element (and thus be a normal language construct) but the way the contents are parsed differ on the type of tag (just as the way a DocBlock is parsed differs on the type of Syntactical Element it accompanies).

p.s. @method tags go on the class and not on the __call; this is because methods and properties are syntactical elements / attributes of a class and allows the reader to have one specific location where it can read about this meta data; otherwise the reader would have to look at the __set, __get and __call to get an idea of all magic elements.

@skyzyx
Copy link

skyzyx commented Oct 2, 2012

@skyzyx Those are interesting variations! So, if I summarize this in words:

A tag may replace its multiline description with a set of curly braces; any content inside those curly braces are interpreted as an inline DocBlock for that specific element.

Precisely. I don't know how well it would apply to tags such as @copyright, @api, @deprecated, etc., but for tags such as @param and @method, this could be a very powerful construct.

p.s. @method tags go on the class and not on the __call; this is because methods and properties are syntactical elements / attributes of a class and allows the reader to have one specific location where it can read about this meta data; otherwise the reader would have to look at the __set, __get and __call to get an idea of all magic elements.

Yes, you're right. :)

@ashnazg
Copy link

ashnazg commented Oct 4, 2012

I like the idea of @skyzyx regarding curly brace encapsulation, particularly in how @mvriel abstracted it into a more general use case.

Further, once we get "inside the array", we are effectively dealing with "types"... the term "options" isn't necessarily universal here. As such, I like the idea of using the @type tag here, as its original scope definition still fits its use.

My key "smell test" will be to see how little the ABNF has to be changed to accommodate this, as I think at first glance that this combination of solutions can indeed be particularly elegant.

@mvriel
Copy link
Author

mvriel commented Oct 4, 2012

Well, the ABNF needs to be adapted in 2 key areas.

First: we need recursion.
As determined in this thread can the cycle of 'Description and Tags' be repeated inside the curly braces. The big question there is: are we going to repeat the entire ABNF (thus: short description, long description and tag) or just a subset (long description and tags).

Second: the 'tag-description' part of tag-details needs to be replaced with tag-description / "{" PHPDoc "}".

So a suggestion could be:

PHPDoc            = [short-description] [long-description] [tags]
short-description = *CHAR ("." 1*CRLF / 2*CRLF)
long-description  = 1*(CHAR / inline-tag) 1*CRLF ; any amount of characters
                                                 ; with inline tags inside
tags              = *(tag 1*CRLF)
inline-tag        = "{" tag "}"
tag               = "@" tag-name [tag-details]
tag-name          = (ALPHA / "\") *(ALPHA / DIGIT / "\" / "-" / "_")
tag-details       = *SP (SP tag-description / tag-signature / "{" PHPDoc "}" )
tag-description   = 1*CHAR
tag-signature     = "(" *tag-argument ")"
tag-argument      = *SP 1*CHAR [","] *SP

The only thing that has been added was "{" PHPDoc "}" to the tag-details section.

Important thing to note: this means that a tag may only end in either a description, a set of parenthesis with options (ergo: annotation style) OR a PHPDoc enclosed in braces. Not a combination of these three.

@ashnazg
Copy link

ashnazg commented Oct 5, 2012

I'd say go ahead with allowing the full ABNF to recurse. This keeps the spec cleaner, I presume the implementation can then be more straightforward, and we allow short+long descriptions in the braced PHPDoc from the very start (rather than waiting for someone to ask for it in an enhancement ;-) )

@mvriel
Copy link
Author

mvriel commented Oct 6, 2012

Right, unless anyone has an issue with this I think we should go ahead and add this to the PSR

@moufmouf
Copy link

moufmouf commented Oct 8, 2012

Hi Mike,

I like pretty much what you are proposing here.
Now, this is slightly off-topic, but I have a remark to do about the way arrays are commented.

So far, we had this annotation:
@type MyObject[]

This documentation is ok for documenting arrays.
Now, in this Gist, we have a new proposal for documenting option arrays.

Yet, we are still lacking a way to make the difference between indexed arrays and associative arrays (which are the same "array" object in PHP but have a completely different meaning)
My thought is that by reading documentation, we should know whether the developer expects us to provide a plain array, or a hashtable.

Could we try to find a way to make sure the docblock captures the difference between an indexed array and a hashtable?

On the PSR mailing list, I proposed this notation:
@type MyObject[int]
vs
@type MyObject[string]

Any thought about that? Any better notation idea?

Also, on a completely different topic, in an option array, in your current proposal, there is no way to tell the difference between a parameter that is required and one that is optional. Maybe we could add a third keyword, this way:

 *     @param array $options {
 *         [description]
 * 
 *         @option string  NAME      required [description]
 *         @option string  "label"   required [description]
 *         @option integer "type_id" required[description]
 *         @option boolean "visible" optional [description]
 *         @option string  "default" [description]
 *     }

What to you think?

@mvriel
Copy link
Author

mvriel commented Oct 8, 2012

Hi @moufmouf,

For a discussion about array types; please see this gist: https://gist.github.com/3823010

Regarding your suggestion with regards to mentioning whether an option is required or not; I am in doubt whether and if so, how, that should be captured.
Perhaps other people have ideas on this as well?

@skyzyx
Copy link

skyzyx commented Oct 14, 2012

So, one more thing to think about: Nested hashes.

Some of us need to support nested hashes of options for one reason or another. How well would this scale for that case? Would the "inner" sections be able to recurse (potentially) infinitely?

@totten
Copy link

totten commented Oct 18, 2012

The "@param { ... }" notation looks pleasant (like in "mike_van_riel()" and "ryan_parman2()"), but it's a special case. The @struct approach deals better with the general case -- and the general case is "developers using PHP arrays to define non-OOP data structures". While I'm a fan of OOP, it's a simple fact that there's a tremendous amount of non-OOP PHP code, and it's useful to provide documentation and tooling for it. I posted a more detailed discussion here:

https://groups.google.com/forum/?hl=en&fromgroups=#!searchin/php-fig/non-PHP$20typing/php-fig/o4ko1XsGtAw/-UcmR-ghhqYJ

Regarding nested hashes: if one is nesting hashes, I think the @struct approach provides greater expressiveness and maintainability than the "@param {...}" approach. For example, there's no equivalent of this in the @param notation:

/**
 * @param Widget widget_options
 * @struct Widget {
 *   @option int length
 *   @option int width
 *   @option Gizmo[string] gizmos
 * }
 * @struct Gizmo {
 *   @option int foo;
 *   @option string bar;
 *  }
 */
function create_widget($widget_options)

Of course, the "create_widget" function seems badly designed and needs refactoring. As one goes through the process of refactoring it, one will likely pull out code involving that nested structure (Gizmo) and create new functions or classes that work directly with Gizmo. The refactoring will be made simpler by having a clear, named type for "Gizmo".

@skyzyx
Copy link

skyzyx commented Nov 1, 2012

Is anybody working on a way to parse the contents of braces (e.g., {...}) yet?

Whether or not we use param, option, struct, or anything else, they all seem to depend on the ability to parse the contents of braces as sub-blocks.

I just wanted to know before I start this work myself.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment