Mar*_*łek 61 php arrays oop type-hinting php-7
也许我错过了一些东西,但有没有选项来定义该函数应该有参数或返回例如User对象的数组?
请考虑以下代码:
<?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";
}
Run Code Online (Sandbox Code Playgroud)
PHP7中是否有任何选项告诉函数findUserByAge应该返回用户数组?我希望当添加类型提示时,它应该是可能的,但我没有找到任何关于对象数组的类型提示的信息,所以可能它不包含在PHP 7中.如果它不包括在内,你有没有任何线索为什么它是添加类型提示时不包含?
Fab*_*ler 92
它不包括在内.
如果它不包括在内,你有没有任何线索为什么在添加类型提示时不包括它?
使用当前的数组实现,它需要在运行时检查所有数组元素,因为数组本身不包含类型信息.
它实际上已经被提议用于PHP 5.6但被拒绝:RFC"arrayof" - 有趣的是,并不是因为性能问题被证明是可以忽略的,而是因为没有就应该如何实现它达成一致.还有人反对在没有标量类型提示的情况下它是不完整的.如果您对整个讨论感兴趣,请在邮件列表存档中阅读.
IMHO数组类型提示将与类型化数组一起提供最大的好处,我很乐意看到它们实现.
所以也许是时候换新RFC了,重新开始讨论.
您可以键入提示可变参数,从而将签名写为
function findUserByAge(int $age, User ...$users) : array
Run Code Online (Sandbox Code Playgroud)
findUserByAge(15, ...$userInput);
Run Code Online (Sandbox Code Playgroud)
在这个调用中,参数$userInput将被"解包"为单个变量,并且在方法本身中"打包"回一个数组$users.每个项目都经过验证,属于类型User.$userInput也可以是迭代器,它将被转换为数组.
遗憾的是,返回类型没有类似的解决方法,您只能将它用于最后一个参数.
由于数组可以包含混合值,因此这是不可能的.
您必须为此目的使用对象/类.
您可以创建一个类来管理自己的列表数组(私有/受保护属性),并拒绝添加其他值作为此问题的工作范围(如果确实需要).
但是,没有负责任的程序员会破坏预期的模式,尤其是如果你正确地评论它.无论如何,它将在程序中发生错误时被识别.
出院:
例如,您可以创建任何数组:
$myArray = array();
Run Code Online (Sandbox Code Playgroud)
并添加一个数字:
$myArray[] = 1;
Run Code Online (Sandbox Code Playgroud)
一个字符串:
$myArray[] = "abc123";
Run Code Online (Sandbox Code Playgroud)
和一个对象
$myArray[] = new MyClass("some parameter", "and one more");
Run Code Online (Sandbox Code Playgroud)
另外不要忘记,您可以拥有一个简单的数组,一个多维堆叠数组以及可以具有混合模式的关联数组.
很难找到一个解析器/ nottation来使所有版本都能使用强制我认为的数组格式的表达式.
一方面很酷,但是在奖牌的另一方面你会失去一些混合数据的能力,这对于很多现有代码和PHP必须提供的灵活性至关重要.
由于混合内容是我们在PHP 7中不想错过的功能,因此无法键入提示数组的确切内容,因为您可以放入任何内容.
在我们的代码库中,我们有集合的概念。这些基于一个名为 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);
}
}
Run Code Online (Sandbox Code Playgroud)
和
/**
* 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);
}
}
Run Code Online (Sandbox Code Playgroud)
一个例子使用。
class PaymentChannelCollection extends AbstractTypedArray
{
const ARRAY_TYPE = PaymentChannel::class;
}
Run Code Online (Sandbox Code Playgroud)
您现在可以输入提示PaymentChannelCollection并确保您拥有一组 PaymentChannels(例如)。
一些代码可能会在我们的命名空间中调用异常。我认为 danielgsims/php-collections 也有一个类型验证器(我们最初使用这些集合,但在它们的灵活性方面存在问题——它们很好,只是不适合我们——所以也许无论如何都可以看看它们!)。
我给一般的答案有关数组的类型提示。
我对所选答案进行了修改。主要区别在于参数是一个数组,而不是被检查类的许多实例。
/**
* @param $_foos Foo[]
*/
function doFoo(array $_foos)
{return (function(Foo ...$_foos){
// Do whatever you want with the $_foos array
})(...$_foos);}
Run Code Online (Sandbox Code Playgroud)
看起来有点模糊,但是很容易理解。并非总是在每次调用时都手动解压缩数组,而是调用函数内部的闭包,并将数组解压缩为参数。
function doFoo(array $_foos)
{
return (function(Foo ...$_foos){ // Closure
// Do whatever you want with the $_foos array
})(...$_foos); //Main function's parameter $_foos unpacked
}
Run Code Online (Sandbox Code Playgroud)
我觉得这很酷,因为您可以像使用具有ArrayOfType参数的任何其他语言函数一样使用该函数。另外,该错误的处理方式与其余PHP类型提示错误的处理方式相同。此外,您不会混淆其他程序员,他们会使用您的函数,并且不得不拆开它们的数组,这总是让人感到有些棘手。
您确实需要一点编程经验,以了解其工作原理。如果您需要多个参数,则可以始终在闭包的“使用”部分添加它们。
您也可以使用文档注释来公开类型提示。
/**
* @param $_foos Foo[] <- An array of type Foo
*/
Run Code Online (Sandbox Code Playgroud)
这是一个OO示例:
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...
Run Code Online (Sandbox Code Playgroud)
注意:这也适用于非对象类型(int,字符串等)