Рубрики
Uncategorized

Ответ на вопрос итератора

У меня возник вопрос в моем предыдущем посте в блоге об итераторах, вот ответ. С тегами php, ama, показать разработчика, кодирование.

У меня есть этот вопрос

Как бы вы реализовали решение, в котором индексы коллекции не начинались с 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”