PHP의 반복자(실용 가이드)

26628 단어 codingshowdevphpwebdev
이거 볼때마다

$users = [new User(), new User()];

Iterator를 사용할 기회를 놓쳤습니다.

왜 이터레이터인가?



컬렉션은 이전에 명명되지 않은 배열을 구성하는 멋진 방법입니다. 반복자를 사용해야 하는 몇 가지 이유가 있습니다. 동작에 대한 이유 중 하나는 다음, 현재, 유효 등과 같은 표준 호출에서 정확한 동작을 지정할 수 있습니다. 다른 이유는 컬렉션에 특정 유형의 객체만 포함되도록 하기를 원할 수 있습니다.

알 수 없는 값 유형의 배열을 사용하여 발생하는 문제를 이해합니다.
PHP 세계에서 매우 일반적인 배열은 모든 종류의 데이터를 여러 중첩 형식의 여러 차원으로 저장하는 데 사용됩니다. 배열은 개발자에게 무한한 유연성을 제공했지만 그 때문에 매우 사악해졌습니다.

예시:


  • 함수(getUsers)는 User 개체의 배열을 반환합니다.
  • getUsers 출력 배열을 사용하는 다른 함수(setUsersToActiveState)는 모든 사용자의 활성 상태를 true로 설정합니다.
  • setUsersToActiveState는 배열을 반복하고 배열 항목에서 특정 메서드를 호출할 것으로 예상합니다. 예를 들어 메서드 이름은 getActiveStatus입니다.
  • 주어진 배열이 호출 가능한 메서드 getActiveStatus가 있는 원하는 개체의 배열이면 모두 괜찮습니다. 그러나 그렇지 않으면 예외가 발생합니다.
  • 주어진 배열이 항상 특정 유형의 객체 배열인지 어떻게 확인할 수 있습니까?

  • public function getUsers(): array
    {
        /** 
        here happen something which gets users from database
        ....
        **/
        return $userArray;
    }
    
    public function setUsersToActiveState()
    {
        $users = $this->getUsers();
         /** @var User $param */
        foreach ($users as $user) {
            if(!$user->getActiveStatus()) {
                $user->setActiveStatus(true);
            }
        }
    }
    

    즉시 두 가지 문제가 발생했습니다.
  • 하나는 유형의 문제입니다. 우리 IDE는 $users 배열 안에 무엇이 있는지 모르기 때문에 IDE는 $user 요소를 사용하는 방법을 제안할 수 없습니다. (이 댓글 블록을 넣었습니다/**
    사용자 $param */위의 foreach, phpStorm에서 작동하고 다른 IDE에서 추측)
  • 당신의 동료들! 힌트가 없으면 배열 내부에 무엇이 있는지 어떻게 알 수 있습니까?
  • 보너스 문제는 getUsers가 문자 그대로 모든 배열을 반환할 수 있으며 시스템에 경고가 표시되지 않는다는 것입니다.

  • 해결책



    // Create a collection which accepts only Users 
    
    class UsersCollection implements \IteratorAggregate
    {
        /** @var array */
        private $users = [];
    
        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 count() : int
        {
            return count($this->users);
        }
    
        public function addUser(User $users)
        {
            $this->users[] = $users;
        }
    }
    
    // Create an Iterator for User
    class UserIterator implements \Iterator
    {
        /** @var int */
        private $position = 0;
    
        /** @var UsersCollection */
        private $userCollection;
    
        public function __construct(UsersCollection $userCollection)
        {
            $this->userCollection = $userCollection;
        }
    
        public function current() : User
        {
            return $this->userCollection->getUser($this->position);
        }
    
        public function next()
        {
            $this->position++;
        }
    
        public function key() : int
        {
            return $this->position;
        }
    
        public function valid() : bool
        {
            return !is_null($this->userCollection->getUser($this->position));
        }
    
        public function rewind()
        {
            $this->position = 0;
        }
    }
    

    테스트



    물론 컬렉션과 반복자가 매력처럼 작동하는지 확인하는 테스트가 있습니다. 이 예제에서는 PHPUnit 프레임워크용 구문을 사용합니다.

    class UsersCollectionTest extends TestCase
    {
        /**
         * @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());
        }
    
        private function getUserMock()
        {
            // returns the mock of User class
        }
    }
    
    
    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;
        }
    
        private function getUserMock()
        {
            // returns the mock of User class
        }
    }
    
    

    용법



    public function getUsers(): UsersCollection
    {
        $userCollection = new UsersCollection();
        /** 
        here happen something which gets users from database
        ....
        **/
        foreach ($whatIGetFromDatabase as $user) {
            $userCollection->addUser($user);
        }
        return $userCollection;
    }
    
    public fucntion setUsersToActiveState()
    {
        $users = $this->getUsers();
    
        foreach ($users as $user) {
            if(!$user->getActiveStatus()) {
                $user->setActiveStatus(true);
            }
        }
    }
    

    보시다시피 setUsersToActiveState는 동일하게 유지되므로 IDE 또는 동료에게 $users 변수 유형을 지정할 필요가 없습니다.

    기능 확장



    이 두 개체를 재사용하고 대부분의 필요에 맞게 변수 이름을 변경할 수 있습니다. 그러나 더 복잡한 기능을 원하면 반복자 또는 컬렉션에 자유롭게 추가하십시오.

    예 1



    예를 들어 userCollection이 18세 이상의 사용자만 허용한다고 가정해 보겠습니다. 구현은 addUser 메서드의 UsersCollection 클래스에서 발생합니다.

        public function addUser(User $users)
        {
            if ($user->getAge() > 18) {
                $this->users[] = $users;
            }    
        }
    
    

    예 2



    대량 사용자를 추가해야 합니다. 그런 다음 추가 메서드 addUsers를 사용하여 userCollection을 확장하면 다음과 같이 표시될 수 있습니다.

    
        public function addUsers(array $users)
        {
            foreach($users as $user) {
                $this->addUser(User $users);
            }
        }
    

    메모



    나는 대답하는이 훌륭한 기사를 찾았습니다. 왜 일반적으로 배열을 반환하는 것이 나쁜 생각인지, 나는 더 이상 동의 할 수 없습니다
    이 주제에


    좋은 웹페이지 즐겨찾기