У меня есть этот вопрос
Как бы вы реализовали решение, в котором индексы коллекции не начинались с 0? В проекте, над которым я работаю, есть несколько мест, где обход большого массива занимает слишком много времени, поэтому я указываю индекс с известным номером. Тогда я могу сделать набор($arr[$obj->id]) чтобы найти, извлечь, удалить или перезаписать объект в этой позиции. Я попытался реализовать это, адаптируя ваш код, но это создает проблему с переменной $position в итераторе. В PHP 7.3 они добавили метод array_key_first() , чтобы легче было получить первый ключ, но я работаю в 7.2. Я попробовал взломать foreach, но это создает внутренний цикл в итераторе.
Мне это показалось очень интересным, поэтому я решил посвятить этому целый пост, чтобы ответить на него, даже если кодовая база на 80% похожа на ту, что была в исходном посте. Я решил подойти к этой проблеме с точки зрения коллекции, поскольку итератор не имеет ничего общего с функцией поиска идентификатора, которую я хочу реализовать. Тем не менее, итерационные способности остаются прежними:) так что все должны быть счастливы. Пожалуйста, обратите пристальное внимание на тесты, которые я написал, они остаются такими же, как и в оригинальной статье, только с дополнительными функциями
Оригинал поста находится здесь:
Итератор в PHP (практическое руководство)
Дамнян Йованович ・ 2 декабря 18 ・ 4 минуты чтения
записка
Если вы прочитали этот вопрос и это сообщение в блоге и считаете, что у вас есть лучшее, умное, более оптимальное решение для очистки ar, пожалуйста, не стесняйтесь вносить свой вклад, я буду рад увидеть другие подходы.
Класс пользователя
Класс пользователя – это всего лишь пример класса, который возвращает идентификатор, мы будем постоянно издеваться над ним в нашем тесте, чтобы вы могли реализовать свой собственный
// Example class with getId method
class User
{
public function getId()
{
return rand(1, 1000000);
}
}
Пользовательлитератор
Чем у нас неизменный итератор
class UserIterator implements \Iterator
{
/** @var int */
private $position = 0;
/** @var UsersCollection */
private $usersCollection;
public function __construct(UsersCollection $userCollection)
{
$this->usersCollection = $userCollection;
}
public function current() : User
{
return $this->usersCollection->getUser($this->position);
}
public function next()
{
$this->position++;
}
public function key() : int
{
return $this->position;
}
public function valid() : bool
{
return !is_null($this->usersCollection->getUser($this->position));
}
public function rewind()
{
$this->position = 0;
}
}
Коллекция пользователей
И, наконец, коллекция здесь с новыми общедоступными методами Getuserbyid, removebyid и updateUserById. Также обратите внимание, что при добавлении нового пользователя в коллекцию мы также добавляем новый идентификатор в массив идентификаторов пользователей. Этот массив содержит идентификатор пользователя в качестве ключа и позицию итератора в качестве значения. Более подробная информация о том, как все работает, приведена в разделе “Тестирование”
class UsersCollection implements \IteratorAggregate
{
/** @var array */
private $users = [];
private $userIds = [];
public function getIterator() : UserIterator
{
return new UserIterator($this);
}
public function getUser($position)
{
if (isset($this->users[$position])) {
return $this->users[$position];
}
return null;
}
public function getUserById($userId)
{
if (isset($this->userIds[$userId])) {
return $this->getUser($this->userIds[$userId]);
}
return null;
}
public function count() : int
{
return count($this->users);
}
public function removeById($userId)
{
if (isset($this->userIds[$userId])) {
unset($this->userIds[$userId]);
}
}
public function updateUserById($userId, User $user)
{
if ($user->getId() !== $userId) {
throw new Exception('User Id mismatch');
}
if (isset($this->userIds[$userId])) {
$position = $this->userIds[$userId];
$this->users[$position] = $user;
}
}
public function addUser(User $user)
{
$this->users[] = $user;
$this->setUserId($user);
}
private function setUserId(User $user)
{
$userId = $user->getId();
$currentPosition = count($this->users) - 1;
$this->userIds[$userId] = $currentPosition;
}
}
Тест пользовательского итератора
Тесты для итератора почти такие же, как в оригинальной статье, я просто изменил его, чтобы реализовать реальный пользовательский макет.
class UserIteratorTest extends MockClass
{
/**
* @covers UserIterator
*/
public function testCurrent()
{
$iterator = $this->getIterator();
$current = $iterator->current();
$this->assertEquals($this->getUserMock(), $current);
}
/**
* @covers UserIterator
*/
public function testNext()
{
$iterator = $this->getIterator();
$iterator->next();
$this->assertEquals(1, $iterator->key());
}
/**
* @covers UserIterator
*/
public function testKey()
{
$iterator = $this->getIterator();
$iterator->next();
$iterator->next();
$this->assertEquals(2, $iterator->key());
}
/**
* @covers UserIterator
*/
public function testValidIfItemInvalid()
{
$iterator = $this->getIterator();
$iterator->next();
$iterator->next();
$iterator->next();
$this->assertEquals(false, $iterator->valid());
}
/**
* @covers UserIterator
*/
public function testValidIfItemIsValid()
{
$iterator = $this->getIterator();
$iterator->next();
$this->assertEquals(true, $iterator->valid());
}
/**
* @covers UserIterator
*/
public function testRewind()
{
$iterator = $this->getIterator();
$iterator->rewind();
$this->assertEquals(0, $iterator->key());
}
private function getIterator() : UserIterator
{
return new UserIterator($this->getCollection());
}
private function getCollection() : UsersCollection
{
$userItems[] = $this->getUserMock();
$userItems[] = $this->getUserMock();
$usersCollection = new UsersCollection();
foreach ($userItems as $user) {
$usersCollection->addUser($user);
}
return $usersCollection;
}
/**
* @return \PHPUnit\Framework\MockObject\MockObject | User
*/
private function getUserMock()
{
$userMock = $this->getMockBuilder(User::class)->getMock();
return $userMock;
}
}
Проверка сбора пользователей
И тесты для сбора теперь тестируются для двух новых случаев “получить”, получить пользователя по существующему идентификатору и, если идентификатор пользователя не существует, вернуть null. Кроме того, существует тест на удаление пользователя и два случая обновления существующего.
class UsersCollectionTest extends MockClass
{
/**
* @covers UsersCollection
*/
public function testUsersCollectionShouldReturnNullForNotExistingUserPosition()
{
$usersCollection = new UsersCollection();
$this->assertEquals(null, $usersCollection->getUser(1));
}
/**
* @covers UsersCollection
*/
public function testEmptyUsersCollection()
{
$usersCollection = new UsersCollection();
$this->assertEquals(new UserIterator($usersCollection), $usersCollection->getIterator());
$this->assertEquals(0, $usersCollection->count());
}
/**
* @covers UsersCollection
*/
public function testUsersCollectionWithUserElements()
{
$usersCollection = new UsersCollection();
$usersCollection->addUser($this->getUserMock());
$usersCollection->addUser($this->getUserMock());
$this->assertEquals(new UserIterator($usersCollection), $usersCollection->getIterator());
$this->assertEquals($this->getUserMock(), $usersCollection->getUser(1));
$this->assertEquals(2, $usersCollection->count());
}
/**
* @covers UsersCollection
*/
public function testSearchForUserByIdShouldReturnUserWithGivenId()
{
$user1 = $this->getUserMock();
$user2 = $this->getUserMock();
$user3 = $this->getUserMock();
$user1->expects($this->once())
->method('getId')
->willReturn(123);
$user2->expects($this->once())
->method('getId')
->willReturn(111);
$user3->expects($this->once())
->method('getId')
->willReturn(345);
$usersCollection = new UsersCollection();
$usersCollection->addUser($user1);
$usersCollection->addUser($user2);
$usersCollection->addUser($user3);
$this->assertEquals($user3, $usersCollection->getUserById(345));
$this->assertEquals($user2, $usersCollection->getUserById(111));
$this->assertEquals($user1, $usersCollection->getUserById(123));
}
/**
* @covers UsersCollection
*/
public function testSearchForUserByIdWhichNotExistShouldReturnNull()
{
$user1 = $this->getUserMock();
$user2 = $this->getUserMock();
$user1->expects($this->once())
->method('getId')
->willReturn(1);
$user2->expects($this->once())
->method('getId')
->willReturn(2);
$usersCollection = new UsersCollection();
$usersCollection->addUser($user1);
$usersCollection->addUser($user2);
$this->assertEquals(null, $usersCollection->getUserById(4));
$this->assertEquals(null, $usersCollection->getUserById(100));
}
/**
* @covers UsersCollection
*/
public funCtion testIfOneUserIsRemovedFromCollectionSearchOnUserIdShouldReturnNull()
{
$user1 = $this->getUserMock();
$user2 = $this->getUserMock();
$user3 = $this->getUserMock();
$user1->expects($this->once())
->method('getId')
->willReturn(123);
$user2->expects($this->once())
->method('getId')
->willReturn(111);
$user3->expects($this->once())
->method('getId')
->willReturn(345);
$usersCollection = new UsersCollection();
$usersCollection->addUser($user1);
$usersCollection->addUser($user2);
$usersCollection->addUser($user3);
$usersCollection->removeById(111);
$this->assertEquals($user3, $usersCollection->getUserById(345));
$this->assertEquals(null, $usersCollection->getUserById(111));
$this->assertEquals($user1, $usersCollection->getUserById(123));
}
/**
* @covers UsersCollection
*/
public funCtion testUpdateUserByIdShouldReplaceUserObjectOnThisPosition()
{
$user1 = $this->getUserMock();
$user2 = $this->getUserMock();
$user3 = $this->getUserMock();
$user4 = $this->getUserMock();
// this property is set to ensure that object is different than $user1
$user4->property = 4;
$user1->expects($this->once())
->method('getId')
->willReturn(123);
$user2->expects($this->once())
->method('getId')
->willReturn(111);
$user3->expects($this->once())
->method('getId')
->willReturn(345);
$user4->expects($this->once())
->method('getId')
->willReturn(123);
$usersCollection = new UsersCollection();
$usersCollection->addUser($user1);
$usersCollection->addUser($user2);
$usersCollection->addUser($user3);
$usersCollection->updateUserById(123, $user4);
$this->assertEquals($user4, $usersCollection->getUserById(123));
}
/**
* @expectedExceptionMessage User Id mismatch
* @expectedException \Exception
* @covers UsersCollection
*/
public funCtion testUpdateUserWhenUserIdAndGivenIdMismatchShouldThrowException()
{
$user1 = $this->getUserMock();
$user2 = $this->getUserMock();
$user1->expects($this->once())
->method('getId')
->willReturn(123);
$user2->expects($this->once())
->method('getId')
->willReturn(444);
$usersCollection = new UsersCollection();
$usersCollection->addUser($user1);
$usersCollection->updateUserById(123, $user2);
}
/**
* @return \PHPUnit\Framework\MockObject\MockObject | User
*/
private function getUserMock()
{
$userMock = $this->getMockBuilder(User::class)->getMock();
return $userMock;
}
}
Оригинал: “https://dev.to/damnjan/answer-to-the-iterator-question-588a”