也许我错过了一些东西但是有没有选项来定义该函数应该有参数或返回用户对象的示例数组?
请考虑以下代码:
<?php
class User
{
protected $name;
protected $age;
/**
* User constructor.
*
* @param $name
*/
public function __construct(string $name, int $age)
{
$this->name = $name;
$this->age = $age;
}
/**
* @return mixed
*/
public function getName() : string
{
return $this->name;
}
public function getAge() : int
{
return $this->age;
}
}
function findUserByAge(int $age, array $users) : array
{
$result = [];
foreach ($users as $user) {
if ($user->getAge() == $age) {
if ($user->getName() == 'John') {
// complicated code here
$result[] = $user->getName(); // bug
} else {
$result[] = $user;
}
}
}
return $result;
}
$users = [
new User('John', 15),
new User('Daniel', 25),
new User('Michael', 15),
];
$matches = findUserByAge(15, $users);
foreach ($matches as $user) {
echo $user->getName() . ' '.$user->getAge() . "\n";
}
PHP7中是否有任何选项告诉函数findUserByAge
应该返回用户数组?我希望在添加类型提示时应该是可能的,但是我还没有找到任何关于对象数组的类型提示的信息,所以可能它不包含在PHP 7中。如果它不是包含,你有任何线索,为什么在添加类型提示时不包括它?
答案 0 :(得分:76)
It's not included.
If it's not included, do you have Any clue why it was not included when type hinting was added?
With the current array implementation, it would require checking all array elements at runtime, because the array itself contains no type information.
It has actually already been proposed for PHP 5.6 but rejected: RFC "arrayof" - interestingly not because of performance issues which turned out to be neglible, but because there was no agreement in how exactly it should be implemented. There was also the objection that it is incomplete without scalar type hints. If you are interested in the whole discussion, read it in the mailing list archive.
IMHO array type hints would provide most benefit together with typed arrays, and I'd love to see them implemented.
So maybe it's about time for a new RFC and to reopen this discussion.
you can type hint variadic arguments and thus write the signature as
function findUserByAge(int $age, User ...$users) : array
findUserByAge(15, ...$userInput);
In this call, the argument $userInput
will be "unpacked" into single variables, and in the method itself "packed" back into an array $users
. Each item is validated to be of type User
. $userInput
can also be an iterator, it will be converted to an array.
Unfortunately there is no similar workaround for return types, and you can only use it for the last argument.
答案 1 :(得分:7)
由于数组可以包含混合值,因此这是不可能的。
您必须为此目的使用对象/类。
如果确实需要,您可以创建一个将管理自己的列表数组(私有/受保护属性)并拒绝添加其他值作为此问题的工作范围的类。
然而,没有负责任的程序员会破坏预期的模式,尤其是如果你正确地评论它。无论如何,它将在程序中发生错误时被识别出来。
摘要:
例如,您可以创建任何数组:
$myArray = array();
并添加一个数字:
$myArray[] = 1;
一个字符串:
$myArray[] = "abc123";
和一个对象
$myArray[] = new MyClass("some parameter", "and one more");
另外请不要忘记,您可以拥有一个简单的数组,一个多维堆叠数组以及可以具有混合模式的关联数组。
很难找到一个解析器/ nottation来使所有版本都能使用强制我认为的数组格式的表达式。
一方面很酷,但是在奖牌的另一方面你会失去一些混合数据的能力,这对于很多现有代码和PHP必须提供的灵活性至关重要。
由于混合内容是我们不希望在PHP 7中遗漏的功能,因此无法键入提示数组的确切内容,因为您可以放入任何内容。
答案 2 :(得分:3)
再加上Steini的回答。
您可以创建一个ObjectNIterator类来管理您的ObjectN并实现Iterator:http://php.net/manual/en/class.iterator.php
从methodN,调用classMethodM,它返回一个填充的ObjectNIterator,然后将此数据传递给期望ObjectNIterator的methodO:
public function methodO(ObjectNIterator $objectNCollection)
答案 3 :(得分:2)
在我们的代码库中,我们有集合的概念。这些基于名为TypedArray的类,该类基于ArrayObject。
class ArrayObject extends \ArrayObject
{
/**
* Clone a collection by cloning all items.
*/
public function __clone()
{
foreach ($this as $key => $value) {
$this[$key] = is_object($value) ? clone $value : $value;
}
}
/**
* Inserting the provided element at the index. If index is negative, it will be calculated from the end of the Array Object
*
* @param int $index
* @param mixed $element
*/
public function insert(int $index, $element)
{
$data = $this->getArrayCopy();
if ($index < 0) {
$index = $this->count() + $index;
}
$data = array_merge(array_slice($data, 0, $index, true), [$element], array_slice($data, $index, null, true));
$this->exchangeArray($data);
}
/**
* Remove a portion of the array and optionally replace it with something else.
*
* @see array_splice()
*
* @param int $offset
* @param int|null $length
* @param null $replacement
*
* @return static
*/
public function splice(int $offset, int $length = null, $replacement = null)
{
$data = $this->getArrayCopy();
// A null $length AND a null $replacement is not the same as supplying null to the call.
if (is_null($length) && is_null($replacement)) {
$result = array_splice($data, $offset);
} else {
$result = array_splice($data, $offset, $length, $replacement);
}
$this->exchangeArray($data);
return new static($result);
}
/**
* Adding a new value at the beginning of the collection
*
* @param mixed $value
*
* @return int Returns the new number of elements in the Array
*/
public function unshift($value): int
{
$data = $this->getArrayCopy();
$result = array_unshift($data, $value);
$this->exchangeArray($data);
return $result;
}
/**
* Extract a slice of the array.
*
* @see array_slice()
*
* @param int $offset
* @param int|null $length
* @param bool $preserveKeys
*
* @return static
*/
public function slice(int $offset, int $length = null, bool $preserveKeys = false)
{
return new static(array_slice($this->getArrayCopy(), $offset, $length, $preserveKeys));
}
/**
* Sort an array.
*
* @see sort()
*
* @param int $sortFlags
*
* @return bool
*/
public function sort($sortFlags = SORT_REGULAR)
{
$data = $this->getArrayCopy();
$result = sort($data, $sortFlags);
$this->exchangeArray($data);
return $result;
}
/**
* Apply a user supplied function to every member of an array
*
* @see array_walk
*
* @param callable $callback
* @param mixed|null $userData
*
* @return bool Returns true on success, otherwise false
*
* @see array_walk()
*/
public function walk($callback, $userData = null)
{
$data = $this->getArrayCopy();
$result = array_walk($data, $callback, $userData);
$this->exchangeArray($data);
return $result;
}
/**
* Chunks the object into ArrayObject containing
*
* @param int $size
* @param bool $preserveKeys
*
* @return ArrayObject
*/
public function chunk(int $size, bool $preserveKeys = false): ArrayObject
{
$data = $this->getArrayCopy();
$result = array_chunk($data, $size, $preserveKeys);
return new ArrayObject($result);
}
/**
* @see array_column
*
* @param mixed $columnKey
*
* @return array
*/
public function column($columnKey): array
{
$data = $this->getArrayCopy();
$result = array_column($data, $columnKey);
return $result;
}
/**
* @param callable $mapper Will be called as $mapper(mixed $item)
*
* @return ArrayObject A collection of the results of $mapper(mixed $item)
*/
public function map(callable $mapper): ArrayObject
{
$data = $this->getArrayCopy();
$result = array_map($mapper, $data);
return new self($result);
}
/**
* Applies the callback function $callable to each item in the collection.
*
* @param callable $callable
*/
public function each(callable $callable)
{
foreach ($this as &$item) {
$callable($item);
}
unset($item);
}
/**
* Returns the item in the collection at $index.
*
* @param int $index
*
* @return mixed
*
* @throws InvalidArgumentException
* @throws OutOfRangeException
*/
public function at(int $index)
{
$this->validateIndex($index);
return $this[$index];
}
/**
* Validates a number to be used as an index
*
* @param int $index The number to be validated as an index
*
* @throws OutOfRangeException
* @throws InvalidArgumentException
*/
private function validateIndex(int $index)
{
$exists = $this->indexExists($index);
if (!$exists) {
throw new OutOfRangeException('Index out of bounds of collection');
}
}
/**
* Returns true if $index is within the collection's range and returns false
* if it is not.
*
* @param int $index
*
* @return bool
*
* @throws InvalidArgumentException
*/
public function indexExists(int $index)
{
if ($index < 0) {
throw new InvalidArgumentException('Index must be a non-negative integer');
}
return $index < $this->count();
}
/**
* Finding the first element in the Array, for which $callback returns true
*
* @param callable $callback
*
* @return mixed Element Found in the Array or null
*/
public function find(callable $callback)
{
foreach ($this as $element) {
if ($callback($element)) {
return $element;
}
}
return null;
}
/**
* Filtering the array by retrieving only these elements for which callback returns true
*
* @param callable $callback
* @param int $flag Use ARRAY_FILTER_USE_KEY to pass key as the only argument to $callback instead of value.
* Use ARRAY_FILTER_USE_BOTH pass both value and key as arguments to $callback instead of value.
*
* @return static
*
* @see array_filter
*/
public function filter(callable $callback, int $flag = 0)
{
$data = $this->getArrayCopy();
$result = array_filter($data, $callback, $flag);
return new static($result);
}
/**
* Reset the array pointer to the first element and return the element.
*
* @return mixed
*
* @throws \OutOfBoundsException
*/
public function first()
{
if ($this->count() === 0) {
throw new \OutOfBoundsException('Cannot get first element of empty Collection');
}
return reset($this);
}
/**
* Reset the array pointer to the last element and return the element.
*
* @return mixed
*
* @throws \OutOfBoundsException
*/
public function last()
{
if ($this->count() === 0) {
throw new \OutOfBoundsException('Cannot get last element of empty Collection');
}
return end($this);
}
/**
* Apply a user supplied function to every member of an array
*
* @see array_reverse
*
* @param bool $preserveKeys
*
* @return static
*/
public function reverse(bool $preserveKeys = false)
{
return new static(array_reverse($this->getArrayCopy(), $preserveKeys));
}
public function keys(): array
{
return array_keys($this->getArrayCopy());
}
/**
* Use a user supplied callback to reduce the array to a single member and return it.
*
* @param callable $callback
* @param mixed|null $initial
*
* @return mixed
*/
public function reduce(callable $callback, $initial = null)
{
return array_reduce($this->getArrayCopy(), $callback, $initial);
}
}
和
/**
* Class TypedArray
*
* This is a typed array
*
* By enforcing the type, you can guarantee that the content is safe to simply iterate and call methods on.
*/
abstract class AbstractTypedArray extends ArrayObject
{
use TypeValidator;
/**
* Define the class that will be used for all items in the array.
* To be defined in each sub-class.
*/
const ARRAY_TYPE = null;
/**
* Array Type
*
* Once set, this ArrayObject will only accept instances of that type.
*
* @var string $arrayType
*/
private $arrayType = null;
/**
* Constructor
*
* Store the required array type prior to parental construction.
*
* @param mixed[] $input Any data to preset the array to.
* @param int $flags The flags to control the behaviour of the ArrayObject.
* @param string $iteratorClass Specify the class that will be used for iteration of the ArrayObject object. ArrayIterator is the default class used.
*
* @throws InvalidArgumentException
*/
public function __construct($input = [], $flags = 0, $iteratorClass = ArrayIterator::class)
{
// ARRAY_TYPE must be defined.
if (empty(static::ARRAY_TYPE)) {
throw new \RuntimeException(
sprintf(
'%s::ARRAY_TYPE must be set to an allowable type.',
get_called_class()
)
);
}
// Validate that the ARRAY_TYPE is appropriate.
try {
$this->arrayType = $this->determineType(static::ARRAY_TYPE);
} catch (\Collections\Exceptions\InvalidArgumentException $e) {
throw new InvalidArgumentException($e->getMessage(), $e->getCode(), $e);
}
// Validate that the input is an array or an object with an Traversable interface.
if (!(is_array($input) || (is_object($input) && in_array(Traversable::class, class_implements($input))))) {
throw new InvalidArgumentException('$input must be an array or an object that implements \Traversable.');
}
// Create an empty array.
parent::__construct([], $flags, $iteratorClass);
// Append each item so to validate it's type.
foreach ($input as $key => $value) {
$this[$key] = $value;
}
}
/**
* Adding a new value at the beginning of the collection
*
* @param mixed $value
*
* @return int Returns the new number of elements in the Array
*
* @throws InvalidArgumentException
*/
public function unshift($value): int
{
try {
$this->validateItem($value, $this->arrayType);
} catch (\Collections\Exceptions\InvalidArgumentException $e) {
throw new InvalidArgumentException($e->getMessage(), $e->getCode(), $e);
}
return parent::unshift($value);
}
/**
* Check the type and then store the value.
*
* @param mixed $offset The offset to store the value at or null to append the value.
* @param mixed $value The value to store.
*
* @throws InvalidArgumentException
*/
public function offsetSet($offset, $value)
{
try {
$this->validateItem($value, $this->arrayType);
} catch (\Collections\Exceptions\InvalidArgumentException $e) {
throw new InvalidArgumentException($e->getMessage(), $e->getCode(), $e);
}
parent::offsetSet($offset, $value);
}
/**
* Sort an array, taking into account objects being able to represent their sortable value.
*
* {@inheritdoc}
*/
public function sort($sortFlags = SORT_REGULAR)
{
if (!in_array(SortableInterface::class, class_implements($this->arrayType))) {
throw new \RuntimeException(
sprintf(
"Cannot sort an array of '%s' as that class does not implement '%s'.",
$this->arrayType,
SortableInterface::class
)
);
}
// Get the data from
$originalData = $this->getArrayCopy();
$sortableData = array_map(
function (SortableInterface $item) {
return $item->getSortValue();
},
$originalData
);
$result = asort($sortableData, $sortFlags);
$order = array_keys($sortableData);
uksort(
$originalData,
function ($key1, $key2) use ($order) {
return array_search($key1, $order) <=> array_search($key2, $order);
}
);
$this->exchangeArray($originalData);
return $result;
}
/**
* {@inheritdoc}
*/
public function filter(callable $callback, int $flag = 0)
{
if ($flag == ARRAY_FILTER_USE_KEY) {
throw new InvalidArgumentException('Cannot filter solely by key. Use ARRAY_FILTER_USE_BOTH and amend your callback to receive $value and $key.');
}
return parent::filter($callback, $flag);
}
}
示例用法。
class PaymentChannelCollection extends AbstractTypedArray
{
const ARRAY_TYPE = PaymentChannel::class;
}
您现在可以使用PaymentChannelCollection
键入提示,并确保您有一个PaymentChannels集合(例如)。
某些代码可能会在我们的命名空间中调用异常。我认为danielgsims / php-collections中也有一个类型验证器(我们最初使用的是这些集合,但是围绕它们的灵活性存在问题-它们很好,只是对我们不起作用-无论如何也可以看看它们!)。 / p>
答案 4 :(得分:1)
我通常会给出有关数组类型提示的通用答案。
我对所选答案进行了修改。主要区别在于参数是一个数组,而不是被检查类的许多实例。
/**
* @param $_foos Foo[]
*/
function doFoo(array $_foos)
{return (function(Foo ...$_foos){
// Do whatever you want with the $_foos array
})(...$_foos);}
它看起来有点模糊,但很容易理解。不必总是在每次调用时都手动解压缩数组,而是调用函数内部的闭包,并将数组解压缩为参数。
function doFoo(array $_foos)
{
return (function(Foo ...$_foos){ // Closure
// Do whatever you want with the $_foos array
})(...$_foos); //Main function's parameter $_foos unpacked
}
我觉得这很酷,因为您可以像使用具有ArrayOfType参数的任何其他语言函数一样使用该函数。另外,该错误的处理方式与其余PHP类型提示错误的处理方式相同。此外,您不会混淆其他将使用您的函数并不得不解压缩数组的程序员,而这些数组总是让人感到有些疑惑。
您确实需要一点编程经验来了解其工作原理。如果您需要多个参数,可以随时将它们添加到闭包的“使用”部分。
您还可以使用文档注释来公开类型提示。
/**
* @param $_foos Foo[] <- An array of type Foo
*/
这是一个面向对象的示例:
class Foo{}
class NotFoo{}
class Bar{
/**
* @param $_foos Foo[]
*/
public function doFoo(array $_foos, $_param2)
{return (function(Foo ...$_foos) use($_param2){
return $_param2;
})(...$_foos);}
}
$myBar = new Bar();
$arrayOfFoo = array(new Foo(), new Foo(), new Foo());
$notArrayOfFoo = array(new Foo(), new NotFoo(), new Foo());
echo $myBar->doFoo($arrayOfFoo, 'Success');
// Success
echo $myBar->doFoo($notArrayOfFoo, 'Success');
// Uncaught TypeError: Argument 2 passed to Bar::{closure}() must be an instance of Foo, instance of NotFoo given...
注意:这也适用于非对象类型(整数,字符串等)
答案 5 :(得分:0)
一种相当简单的方法是创建自己的数组类型,该数组类型可与PHP的内置函数(如foreach,count,unset,indexing等)一起使用。这是一个示例:
class DataRowCollection implements \ArrayAccess, \Iterator, \Countable
{
private $rows = array();
private $idx = 0;
public function __construct()
{
}
// ArrayAccess interface
// Used when adding or updating an array value
public function offsetSet($offset, $value)
{
if ($offset === null)
{
$this->rows[] = $value;
}
else
{
$this->rows[$offset] = $value;
}
}
// Used when isset() is called
public function offsetExists($offset)
{
return isset($this->rows[$offset]);
}
// Used when unset() is called
public function offsetUnset($offset)
{
unset($this->rows[$offset]);
}
// Used to retrieve a value using indexing
public function offsetGet($offset)
{
return $this->rows[$offset];
}
// Iterator interface
public function rewind()
{
$this->idx = 0;
}
public function valid()
{
return $this->idx < count($this->rows);
}
public function current()
{
return $this->rows[$this->idx];
}
public function key()
{
return $this->idx;
}
public function next()
{
$this->idx++;
}
// Countable interface
public function count()
{
return count($this->rows);
}
}
用法示例:
$data = new DataRowCollection(); // = array();
$data[] = new DataRow("person");
$data[] = new DataRow("animal");
它的工作方式与传统数组类似,但是键入的类型与您想要的一样。非常简单有效。
答案 6 :(得分:0)
目前,无法在对象数组的函数签名上对其进行定义。但是您可以在功能文档中对其进行定义。如果您传递混合值,它不会产生PHP错误/警告,但是大多数IDE会给出提示。这是示例:
asset.value