뭔가 빠뜨린 것 같습니다만, 예를 들어 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"; }
PHP7에 함수를 알려주는 옵션이 있습니까?findUserByAge
사용자 배열을 반환해야 합니까?타입 힌트가 추가되었을 때 가능한 일이라고 생각합니다만, 오브젝트 배열에 대한 타입 힌트에 대한 정보를 찾을 수 없기 때문에 PHP 7에는 포함되어 있지 않은 것 같습니다만, 타입 힌트가 추가되었을 때 포함되지 않은 이유를 알 수 있습니까?
질문에 대한 답변
포함되어 있지 않습니다.
포함되어 있지 않다면, 타입 힌트가 추가되었을 때 왜 포함되지 않았는지 알 수 있습니까?
현재 어레이 구현에서는 어레이 자체에 유형 정보가 없기 때문에 런타임에 모든 어레이 요소를 확인해야 합니다.
이것은 실제로 PHP 5.6에 대해 이미 제안되었지만 거부되었습니다: RFC “arrayof” – 흥미롭게도 성능 문제 때문이 아니라 어떻게 구현해야 하는지에 대한 합의가 없었기 때문입니다.스칼라형 힌트가 없으면 불완전하다는 반론도 있었다.전체 토론에 관심이 있는 경우 메일 목록 보관에서 읽어보십시오.
IMHO 어레이 타입의 힌트는 타입 어레이와 함께 가장 큰 메리트를 얻을 수 있습니다.그것들이 실장되어 있는 것을 보고 싶습니다.
새로운 RFC를 도입하여 이 논의를 재개할 때가 된 것 같습니다.
부분적인 회피책:
hint variadic 인수를 입력하여 시그니처를 다음과 같이 쓸 수 있습니다.
function findUserByAge(int $age, User ...$users) : array
사용방법:
findUserByAge(15, ...$userInput);
이 콜에서 인수는$userInput
단일 변수로 분할되어 메서드 자체에서 어레이로 ‘패킹”됩니다.$users
. 각 항목이 유형으로 검증되었습니다.User
.$userInput
반복기일 수도 있습니다.배열로 변환됩니다.
유감스럽게도 반환 유형에 대한 유사한 회피책은 없으며 마지막 인수에만 사용할 수 있습니다.
코드 베이스에는 컬렉션이라는 개념이 있습니다.이들은 ArrayObject를 기반으로 하는 TypedArray라는 클래스를 기반으로 합니다.
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 (CollectionsExceptionsInvalidArgumentException $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 (CollectionsExceptionsInvalidArgumentException $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 (CollectionsExceptionsInvalidArgumentException $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
Payment Channels 컬렉션이 있는지 확인합니다(예를 들어).
일부 코드는 네임스페이스에서 예외를 호출할 수 있습니다.danielgsims/php-collections에도 타입 검증기가 있다고 생각합니다(처음에는 이러한 컬렉션을 사용했지만 유연성에 문제가 있었습니다.우리에겐 좋지 않기 때문에, 어쨌든 한 번 봐 주세요).
어레이의 일반적인 유형 힌트에 대한 일반적인 답변을 드립니다.
나는 선택된 답을 변형했다.주요 차이점은 파라미터가 체크된 클래스의 많은 인스턴스가 아닌 배열이라는 것입니다.
/**
* @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 }
Array Of Type 파라미터가 있는 다른 언어 기능과 마찬가지로 사용할 수 있기 때문에 매우 멋집니다.게다가 이 에러는, 다른 PHP 타입의 힌트 에러와 같은 방법으로 처리됩니다.또, 다른 프로그래머가 당신의 기능을 사용하고 있기 때문에, 항상 약간 허술한 느낌이 드는 어레이를 언팩할 필요가 있습니다.
프로그래밍에 대한 약간의 경험이 있어야 이 기능이 어떻게 작동하는지 이해할 수 있습니다.둘 이상의 매개변수가 필요한 경우 언제든지 폐쇄의 ‘사용’ 섹션에 매개변수를 추가할 수 있습니다.
또한 문서 주석을 사용하여 유형 힌트를 표시할 수도 있습니다.
/**
* @param $_foos Foo[] <- An array of type Foo
*/
다음은 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...
주의: 이것은 오브젝트 이외의 타입(int, string 등)에서도 동작합니다.
어레이에 혼재된 값을 포함할 수 있으므로 이는 불가능합니다.
그러기 위해서는 오브젝트/클래스를 사용해야 합니다.
이 문제가 꼭 필요한 경우 자체 목록 배열(개인/보호된 속성)을 관리하고 다른 값을 이 문제의 회피책으로 추가하는 것을 거부하는 클래스를 만들 수 있습니다.
그러나 책임 있는 프로그래머는 의도한 패턴을 깨는 일은 없습니다.특히, 코멘트를 올바르게 하고 있는 경우는 그렇습니다.어쨌든, 프로그램의 에러 발생시에 인식됩니다.
확장:
예를 들어 임의의 배열을 생성할 수 있습니다.
$myArray = array();
번호를 추가합니다.
$myArray[] = 1;
문자열:
$myArray[] = "abc123";
및 오브젝트
$myArray[] = new MyClass("some parameter", "and one more");
또, 심플한 어레이, 다차원 스택형 어레이, 및 패턴이 혼재하는 어소시에이션 어레이도 사용할 수 있습니다.
어레이의 포맷을 강요하는 표현으로 모든 버전을 동작시키는 파서/노테이션을 찾는 것은 매우 어렵습니다.
한편으론 멋지지만 다른 한편으론 기존 코드와 PHP가 제공하는 유연성에 매우 중요한 어레이 내에서 데이터를 혼합할 수 있는 능력을 잃게 됩니다.
PHP 7에서 놓치고 싶지 않은 기능이 혼재되어 있기 때문에 배열의 정확한 내용을 입력할 수 없습니다.
Steini가 대답한 것에 더해서.
클래스 오브젝트를 만들 수 있습니다.Niterator는 ObjectN을 관리하고 Iterator를 구현합니다.http://php.net/manual/en/class.iterator.php
methodN에서 classMethodM을 호출하여 채워진 개체를 반환합니다.그런 다음 Niterator는 이 데이터를 오브젝트를 예상하는 메서드O에 전달합니다.니테레이터:
public function methodO(ObjectNIterator $objectNCollection)