PHP 7 中的类型提示 - 对象数组
声明:本页面是StackOverFlow热门问题的中英对照翻译,遵循CC BY-SA 4.0协议,如果您需要使用它,必须同样遵循CC BY-SA许可,注明原文地址和作者信息,同时你必须将它归于原作者(不是我):StackOverFlow
原文地址: http://stackoverflow.com/questions/34273367/
Warning: these are provided under cc-by-sa 4.0 license. You are free to use/share it, But you must attribute it to the original authors (not me):
StackOverFlow
Type hinting in PHP 7 - array of objects
提问by Marcin Nabia?ek
Maybe I missed something but is there any option to define that function should have argument or return for example array of User objects?
也许我错过了一些东西,但是有没有任何选项可以定义该函数应该有参数或返回例如 User 对象数组?
Consider the following code:
考虑以下代码:
<?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";
}
Is there any option in PHP7 to tell function findUserByAge
should return array of users? I would expect that when type hinting was added it should be possible but I haven't found any info for type hinting for array of objects so probably it's not included in PHP 7. If it's not included, do you have Any clue why it was not included when type hinting was added?
PHP7 中是否有任何选项可以告诉函数findUserByAge
应该返回用户数组?我希望当添加类型提示时它应该是可能的,但我还没有找到任何关于对象数组类型提示的信息,所以它可能不包含在 PHP 7 中。如果它不包含,你有任何线索为什么它是添加类型提示时不包括在内?
回答by Fabian Schmengler
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.
它实际上已经被提议用于 PHP 5.6 但被拒绝:RFC "arrayof"- 有趣的是不是因为性能问题被证明是可以忽略不计的,而是因为在它应该如何实现方面没有达成一致。也有人反对,如果没有标量类型提示,它是不完整的。如果您对整个讨论感兴趣,请在邮件列表存档中阅读。
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.
所以也许是时候制定一个新的 RFC 并重新开始这个讨论了。
Partial Workaround:
部分解决方法:
you can type hint variadic arguments and thus write the signature as
您可以键入提示可变参数,从而将签名写为
function findUserByAge(int $age, User ...$users) : array
Usage:
用法:
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.
在这个调用中,参数$userInput
将被“解包”到单个变量中,并在方法本身中“打包”回一个数组$users
。每个项目都被验证为类型User
。$userInput
也可以是一个迭代器,它会被转换成一个数组。
Unfortunately there is no similar workaround for return types, and you can only use it for the last argument.
不幸的是,返回类型没有类似的解决方法,您只能将它用于最后一个参数。
回答by Steini
As arrays can contain mixed values this is not possible.
由于数组可以包含混合值,这是不可能的。
You have to use an objects / class for that purpose.
为此,您必须使用对象/类。
You could create a class that will manage its own list array (private/protected attribute) and deny adding other values as a workarround for this issue if this is really needed.
您可以创建一个类来管理其自己的列表数组(私有/受保护属性),并在确实需要时拒绝添加其他值作为解决此问题的方法。
However no responsible programmer will ever break the intended pattern, especially not if you comment it correctly. It will be recognized in occuring errors in the program anyway.
然而,没有一个负责任的程序员会打破预期的模式,尤其是如果你正确地评论它的话。无论如何,它会在程序中发生错误时被识别。
The exaplanation:
说明:
For example you can create any array:
例如,您可以创建任何数组:
$myArray = array();
and add a number:
并添加一个数字:
$myArray[] = 1;
a string:
一个字符串:
$myArray[] = "abc123";
and an object
和一个对象
$myArray[] = new MyClass("some parameter", "and one more");
Also do not forget that you can have a simple array, a multi-dimensional stacked array and also associative arrays which can have mixed patterns aswell.
也不要忘记你可以有一个简单的数组,一个多维堆叠数组,也可以有混合模式的关联数组。
Its pretty hard till impossible to found a parser/nottation to make all that versions work with an expression that forces the format for an array I think.
很难找到一个解析器/符号来使所有版本与我认为强制数组格式的表达式一起工作。
It would be cool on the one side but on the other side of the medal you would loose some ability to mix data within an array which could be crucial to alot of existing code and the flexibility PHP has to offer.
一方面它会很酷,但另一方面,你会失去一些在数组中混合数据的能力,这对于大量现有代码和 PHP 必须提供的灵活性至关重要。
Because of the mixed content which feature we do not want to miss in PHP 7 it is not possible to type-hint the exact contents of an array as you can put inside anything.
由于我们不想错过 PHP 7 中的混合内容功能,因此无法键入提示数组的确切内容,因为您可以将其放入任何内容中。
回答by Stefmachine
I'm giving a generic answer about the type hinting of arrays in general.
我给出了一个关于数组类型提示的通用答案。
I made a variation of the selected answer. The main difference is that the parameter is an array instead of many instances of the checked class.
我对所选答案进行了变体。主要区别在于参数是一个数组,而不是被检查类的许多实例。
/**
* @param $_foos Foo[]
*/
function doFoo(array $_foos)
{return (function(Foo ...$_foos){
// Do whatever you want with the $_foos array
})(...$_foos);}
It looks a bit fuzzy but it's pretty easy to understand. Instead of always unpacking the array at each call manually, the closure inside the function gets called with your array unpacked as the parameter.
它看起来有点模糊,但很容易理解。不是每次调用时都手动解包数组,而是调用函数内部的闭包,并将解包的数组作为参数。
function doFoo(array $_foos)
{
return (function(Foo ...$_foos){ // Closure
// Do whatever you want with the $_foos array
})(...$_foos); //Main function's parameter $_foos unpacked
}
I find this pretty cool since you can use the function like any other language function having a ArrayOfType parameter. Plus, the error is handled the same way as the rest of PHP type hint errors. Furthermore, you are not confusing other programmers who will use your function and would have to unpack their array which always feels a bit hacky.
我觉得这很酷,因为您可以像使用具有 ArrayOfType 参数的任何其他语言函数一样使用该函数。此外,该错误的处理方式与其他 PHP 类型提示错误的处理方式相同。此外,您不会混淆将使用您的函数的其他程序员,并且必须解压缩他们的数组,这总是感觉有点hacky。
You do need a bit of experience in programming to understand how this works. If you need more than one parameter you can always add them in the 'use' section of the closure.
您确实需要一些编程经验才能了解其工作原理。如果您需要多个参数,您可以随时将它们添加到闭包的“使用”部分。
You can also use doc comments to expose the type hint.
您还可以使用文档注释来公开类型提示。
/**
* @param $_foos Foo[] <- An array of type Foo
*/
Here is an OO example:
这是一个面向对象的示例:
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...
Note: This also works with non-object types (int, string, etc.)
注意:这也适用于非对象类型(int、string 等)
回答by visualex
Adding on to what Steini has answered.
补充斯坦尼的回答。
You could create a class ObjectNIterator that manages your ObjectN and implements an Iterator: http://php.net/manual/en/class.iterator.php
您可以创建一个 ObjectNIterator 类来管理您的 ObjectN 并实现一个迭代器:http://php.net/manual/en/class.iterator.php
From methodN, call the classMethodM that gives back a populated ObjectNIterator then pass this data to a methodO that expects ObjectNIterator:
从methodN,调用返回填充的ObjectNIterator的classMethodM,然后将此数据传递给需要ObjectNIterator的methodO:
public function methodO(ObjectNIterator $objectNCollection)
public function methodO(ObjectNIterator $objectNCollection)
回答by Richard A Quadling
In our codebase, we have the concept of collections. These are based upon a class called TypedArray which is based upon ArrayObject.
在我们的代码库中,我们有集合的概念。这些基于一个名为 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);
}
}
and
和
/**
* 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);
}
}
An example use.
一个例子使用。
class PaymentChannelCollection extends AbstractTypedArray
{
const ARRAY_TYPE = PaymentChannel::class;
}
You can now typehint with PaymentChannelCollection
and be sure you've got a collection of PaymentChannels (for example).
您现在可以输入提示PaymentChannelCollection
并确保您拥有一组 PaymentChannels(例如)。
Some of the code may call exceptions in our namespace. I think there's a type validator too from danielgsims/php-collections (we initially used those collections but had issues around the flexibility of them - they're good, just not for us - so maybe take a look at them anyway!).
一些代码可能会在我们的命名空间中调用异常。我认为 danielgsims/php-collections 也有一个类型验证器(我们最初使用这些集合,但在它们的灵活性方面存在问题——它们很好,只是不适合我们——所以也许无论如何都可以看看它们!)。
回答by Jimmy Thomsen
A fairly simple approach is to create your own array type which works with PHP's built-in functions such as foreach, count, unset, indexing, etc. Here is an example:
一个相当简单的方法是创建您自己的数组类型,它可以与 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);
}
}
Usage example:
用法示例:
$data = new DataRowCollection(); // = array();
$data[] = new DataRow("person");
$data[] = new DataRow("animal");
It works just like a traditional array, but it is typed like you wanted it to be. Very simple and effective.
它就像一个传统的数组一样工作,但它的类型是你想要的。非常简单有效。