PHPDoc类型提示数组对象?

因此,在PHPDoc中,可以在成员变量声明上面指定@var来提示其类型。然后一个IDE,例如PHPEd,将知道它正在处理的对象类型,并能够为该变量提供代码洞察力。

<?php
class Test
{
/** @var SomeObj */
private $someObjInstance;
}
?>

这很好,直到我需要对对象数组做同样的事情,以便在以后迭代这些对象时能够得到适当的提示。

那么,是否有一种方法来声明PHPDoc标记来指定成员变量是__abc0的数组?例如,@var数组是不够的,而且@var array(SomeObj)似乎是无效的。

238343 次浏览

使用:

/* @var $objs Test[] */
foreach ($objs as $obj) {
// Typehinting will occur after typing $obj->
}

在输入内联变量时,和

class A {
/** @var Test[] */
private $items;
}

类属性。

2009年PHPDoc(以及像Zend Studio和Netbeans这样的ide)没有这个选项时的先前答案:

你最多只能说,

foreach ($Objs as $Obj)
{
/* @var $Obj Test */
// You should be able to get hinting after the preceding line if you type $Obj->
}

我在Zend Studio经常这样做。我不知道其他编辑器的情况,但它应该能起作用。

问题是@var只能表示一个类型-不包含复杂的公式。如果你有一个语法的“数组的Foo”,为什么停在那里,不添加一个语法的数组的数组,其中包含2个Foo和3个Bar”?我知道元素列表可能比这更通用,但这是一个滑坡。

就我个人而言,我曾多次使用@var Foo[]来表示“Foo的数组”,但IDE不支持。

在JetBrains的PhpStorm IDE中,你可以使用/** @var SomeObj[] */,例如:

/**
* @return SomeObj[]
*/
function getSomeObjects() {...}

phpdoc文档推荐这个方法:

指定包含单个类型时,type定义通知读者每个数组元素的类型。对于给定的数组,只期望有一个Type作为元素。

例如:@return int[]

我发现了一些有用的东西,它可以拯救生命!

private $userList = array();
$userList = User::fetchAll(); // now $userList is an array of User objects
foreach ($userList as $user) {
$user instanceof User;
echo $user->getName();
}
<?php foreach($this->models as /** @var Model_Object_WheelModel */ $model): ?>
<?php
// Type hinting now works:
$model->getImage();
?>
<?php endforeach; ?>
我更喜欢阅读和编写干净的代码-正如Robert C. Martin在“干净的代码”中概述的那样。 在遵循他的信条时,你不应该要求开发人员(作为API的用户)知道数组的(内部)结构。< / p >

API用户可能会问:这是一个只有一维的数组吗?对象是否分布在多维数组的所有层次上?有多少嵌套循环(foreach等)我需要访问所有对象?什么类型的对象被“存储”在该数组中?

正如您所概述的,您希望将该数组(其中包含对象)用作一维数组。

正如Nishi所述,你可以使用:

/**
* @return SomeObj[]
*/

的。

但是,请再次注意—这不是标准的文档块表示法。这个符号是由一些IDE生产者引入的。

好吧,好吧,作为开发人员,您知道“[]”与PHP中的数组绑定。但是“something[]”在正常的PHP上下文中是什么意思呢?“[]”表示:在“某物”中创建新元素。新元素可以是一切。但你想表达的是:具有相同类型和确切类型的对象数组。如您所见,IDE生成器引入了一个新的上下文。一个你必须学习的新环境。其他PHP开发人员必须学习的新上下文(以理解您的文档块)。糟糕的风格!

因为你的数组只有一个维度,你可能想把这个“对象数组”称为“列表”。请注意,“list”在其他编程语言中具有非常特殊的含义。例如,将其称为“集合”会更好。

记住:你使用的编程语言使你可以选择所有的面向对象。 使用类而不是数组,并使您的类像数组一样可遍历。例如:< / p >
class orderCollection implements ArrayIterator

或者如果你想在多维数组/对象结构的不同层次上存储内部对象:

class orderCollection implements RecursiveArrayIterator

该解决方案将数组替换为“orderCollection”类型的对象,但到目前为止还没有在IDE中启用代码完成功能。好的。下一步:

实现由docblock接口引入的方法——特别是:

/**
* [...]
* @return Order
*/
orderCollection::current()


/**
* [...]
* @return integer E.g. database identifier of the order
*/
orderCollection::key()


/**
* [...]
* @return Order
*/
orderCollection::offsetGet()

不要忘记在以下情况下使用类型提示:

orderCollection::append(Order $order)
orderCollection::offsetSet(Order $order)

这个解决方案不再引入很多:

/** @var $key ... */
/** @var $value ... */

在你的代码文件中(例如在循环中),正如Zahymaka与她/他的回答所确认的那样。你的API用户不需要强制引入文档块来完成代码。只在一个地方使用@return可以尽可能地减少冗余(@var)。在“docBlocks”中添加@var会让你的代码可读性最差。

最后你完成了。看起来很难实现?看起来像拿大锤砸坚果?不完全是,因为您熟悉这些接口和干净的代码。记住:你的源代码只写一次/读很多次。

如果您的IDE的代码完成不适合这种方法,请切换到更好的方法(例如IntelliJ IDEA, PhpStorm, Netbeans)或在IDE生成器的问题跟踪器上提交特性请求。

感谢克里斯蒂安·韦斯(来自德国)作为我的教练,教我这么棒的东西。PS:在XING上见我和他。

指定一个变量是一个对象数组:

$needles = getAllNeedles();
/* @var $needles Needle[] */
$needles[1]->...                        //codehinting works

这在Netbeans 7.2中工作(我正在使用它)

还与:

$needles = getAllNeedles();
/* @var $needles Needle[] */
foreach ($needles as $needle) {
$needle->...                        //codehinting works
}

因此,在foreach中使用声明是不必要的。

在NetBeans 7.0(可能更低)中,你可以声明返回类型“数组与文本对象”,就像@return Text和代码提示将工作:

编辑:用@Bob Fanger的建议更新了示例

/**
* get all Tests
*
* @return Test|Array $tests
*/
public function getAllTexts(){
return array(new Test(), new Test());
}

使用它:

$tests =  $controller->getAllTests();
//$tests->         //codehinting works!
//$tests[0]->      //codehinting works!


foreach($tests as $text){
//$test->      //codehinting works!
}

这并不完美,但总比“混合”要好,因为“混合”没有任何价值。

缺点是你可以将数组作为文本对象,这会抛出错误。

在Zend Studio中使用array[type]

在Zend Studio中,array[MyClass]array[int]甚至array[array[MyClass]]工作得很好。

正如DanielaWaranie在她的回答中提到的——当你迭代$collectionObject中的$items时,有一种方法可以指定$item的类型:将@return MyEntitiesClassName添加到current()Iterator和__abc3 -方法的其余部分,这些方法返回值。

繁荣!/** @var SomeObj[] $collectionObj */上不需要在foreach上,并且与集合对象一起工作,不需要使用描述为@return SomeObj[]的特定方法返回集合。

我怀疑不是所有的IDE都支持它,但它在PhpStorm中工作得很好,这让我更高兴。

例子:

class MyCollection implements Countable, Iterator, ArrayAccess {


/**
* @return User
*/
public function current() {
return $this->items[$this->cursor];
}


//... implement rest of the required `interface` methods and your custom
}

有什么有用的我要添加张贴这个答案

在我的例子中,current()和其余的__abc1 -方法是在Abstract-collection类中实现的,我不知道什么样的实体最终将存储在collection中。

这里有一个技巧:不要在抽象类中指定返回类型,而是在特定的集合类的描述中使用PhpDoc指令@method

例子:

class User {


function printLogin() {
echo $this->login;
}


}


abstract class MyCollection implements Countable, Iterator, ArrayAccess {


protected $items = [];


public function current() {
return $this->items[$this->cursor];
}


//... implement rest of the required `interface` methods and your custom
//... abstract methods which will be shared among child-classes
}


/**
* @method User current()
* ...rest of methods (for ArrayAccess) if needed
*/
class UserCollection extends MyCollection {


function add(User $user) {
$this->items[] = $user;
}


// User collection specific methods...


}

现在,类的使用:

$collection = new UserCollection();
$collection->add(new User(1));
$collection->add(new User(2));
$collection->add(new User(3));


foreach ($collection as $user) {
// IDE should `recognize` method `printLogin()` here!
$user->printLogin();
}

再说一次:我怀疑不是所有的IDE都支持它,但是PhpStorm支持。试试你的,在结果中发表评论!

Netbeans提示:

你可以在$users[0]->$this->上为User类数组获得代码完成。

/**
* @var User[]
*/
var $users = array();

当你完成$this->...时,你也可以在类成员列表中看到数组的类型

PSR-5: PHPDoc提出了一种泛型风格的表示法。

语法

Type[]
Type<Type>
Type<Type[, Type]...>
Type<Type[|Type]...>

集合中的值甚至可以是另一个数组,甚至是另一个集合。

Type<Type<Type>>
Type<Type<Type[, Type]...>>
Type<Type<Type[|Type]...>>

例子

<?php


$x = [new Name()];
/* @var $x Name[] */


$y = new Collection([new Name()]);
/* @var $y Collection<Name> */


$a = new Collection();
$a[] = new Model_User();
$a->resetChanges();
$a[0]->name = "George";
$a->echoChanges();
/* @var $a Collection<Model_User> */

注意:如果你期望IDE来做代码辅助,那么IDE是否支持PHPDoc泛型风格的集合符号就是另一个问题了。

从我的回答这个问题

我知道我来晚了,但我最近一直在研究这个问题。我希望有人能看到这一点,因为公认的答案虽然是正确的,但是你可以做到这一点的最佳方式。至少在PHPStorm中没有,不过我还没有测试NetBeans。

最好的方法是扩展ArrayIterator类,而不是使用本机数组类型。这允许你在类级别而不是实例级别输入提示,这意味着你只需要PHPDoc一次,而不是贯穿你的代码(这不仅是混乱的,违反了DRY,但也可能是有问题的,当涉及到重构时PHPStorm有一个重构时丢失PHPDoc的习惯)

参见下面的代码:

class MyObj
{
private $val;
public function __construct($val) { $this->val = $val; }
public function getter() { return $this->val; }
}


/**
* @method MyObj current()
*/
class MyObjCollection extends ArrayIterator
{
public function __construct(Array $array = [])
{
foreach($array as $object)
{
if(!is_a($object, MyObj::class))
{
throw new Exception('Invalid object passed to ' . __METHOD__ . ', expected type ' . MyObj::class);
}
}
parent::__construct($array);
}


public function echoContents()
{
foreach($this as $key => $myObj)
{
echo $key . ': ' . $myObj->getter() . '<br>';
}
}
}


$myObjCollection = new MyObjCollection([
new MyObj(1),
new MyObj('foo'),
new MyObj('blah'),
new MyObj(23),
new MyObj(array())
]);


$myObjCollection->echoContents();

这里的关键是PHPDoc @method MyObj current()覆盖从ArrayIterator继承的返回类型(即mixed)。这个PHPDoc的包含意味着当我们使用foreach($this as $myObj)遍历类属性时,我们在引用变量$myObj->...时得到代码完成

对我来说,这是实现这一点的最利落的方法(至少在PHP引入类型化数组之前是这样,如果它们曾经这样做过的话),因为我们是在可迭代类中声明迭代器类型,而不是在分散在整个代码中的类实例上声明迭代器类型。

我没有在这里展示扩展ArrayIterator的完整解决方案,所以如果你使用这种技术,你可能还想:

  • 根据需要包括其他类级PHPDoc,例如offsetGet($index)next()
  • 将健全性检查is_a($object, MyObj::class)从构造函数移到私有方法中
  • offsetSet($index, $newval)append($value)等方法重写调用这个(现在是私有的)健全检查

如果你使用PHPStorm 2021.2+,你也可以使用这个语法(数组形状):

@property array{name: string, content: string}[] $files

@var array{name: string, content: string}[] $files