Статьи

Веселье с массивами интерфейсов

Как программист, который работает с разными языками каждый день, я нахожу большую радость в том, чтобы узнать, как обстоят дела с другими языками по-разному, и посмотреть, смогу ли я сделать то же самое в PHP. В Python мне особенно понравилось то, как можно эмулировать функции собственных типов данных в пользовательских классах.

Взять, к примеру, этот класс списка членов:

class Members: def __init__(self, members): self.members = members // other methods 

__iter__ метод __iter__ вы можете перебирать данные в экземпляре этого класса так же, как список (массив в PHP):

 class Members: def __iter__(self): return self.members ls = Members(["You", "Me"]) for member in members: print members 

Тестирование на членство просто повлечет за собой реализацию метода __contains__ :

 class Members: def __contains__(self, member): return member in self.members 

Оттуда вы можете сделать:

 "Me" in members: print "I am a member!" 

Я подумал, что было бы неплохо, если бы вы могли сделать то же самое в PHP на экземпляре ваших пользовательских классов, а не только на массивах:

 isset($myObject["test"]); 

PHP позволяет нам делать это с интерфейсами массивов.

Интерфейсы в двух словах

Думайте об интерфейсах как о контрактах, определяющих методы, которые должен содержать класс.

 interface Multiplier { public function multiply($num1, $num2); } 

Любой класс, использующий этот интерфейс, должен иметь такой метод умножения. Существует ключевое слово для обозначения того, что класс выполняет этот контракт: Implements.

 class SmartMultiplier implements Multiplier { public function multiply($num1, $num2) { return $num1*$num2; } } 

Неважно, как выполняется контракт, пока он есть. Альтернативный способ реализации метода умножения может выглядеть следующим образом:

 class NotSoSmartMultiplier implements Multiplier { public function multiply($num1, $num2) { $product = $num1; foreach(range(1,$num2-1)) { $product = $num + $num2; } return $product; } } 

SPL и PHP интерфейсы

PHP предоставляет библиотеку предопределенных интерфейсов, которые могут сделать наши объекты массивными, просто внедрив их в классы.

Некоторые из этих интерфейсов включены в список предопределенных интерфейсов и классов, а некоторые — в стандартную библиотеку PHP (SPL).

Если эти термины звучат пугающе, не позволяйте им. Вы все использовали $_GET раньше. $_GET — это языковая конструкция, которая называется предопределенной .

С другой стороны, из документации SPL просто

коллекция интерфейсов и классов, предназначенных для решения общих проблем.

Все, что нужно сделать сейчас, — это увидеть некоторые из этих интерфейсов в действии. Итак, давайте погрузимся!

Мы собираемся создать класс временной шкалы Twitter,

 $tweets = new Timeline("jeunito"); 

умеет считать свои твиты,

 count($tweets); 

цикл через них,

 foreach($tweets as $tweet) { echo $tweet; } 

и получить твит через идентификатор твита,

 // get if (isset($tweets["some tweet id"])) { echo $tweets["some tweet id"]; } 

как в обычном массиве!

Мы должны убрать некоторые вещи с дороги. Сначала создайте учетную запись Twitter, если у вас ее еще нет. Теперь зарегистрируйте учетную запись разработчика и создайте токен доступа и секрет.

Затем загрузите или клонируйте код с Github и запустите composer install внутри папки с исходным кодом. Если вы не знакомы с Composer, см . Предыдущую статью SitePoint об этом . Откройте файл index.php и добавьте необходимые данные Oauth.

Счетный интерфейс

Счетный интерфейс, пожалуй, самый понятный. Это позволяет нам передавать объекты в метод count() просто путем реализации метода count.

Мы можем получить количество твитов для пользователя, выполнив запрос GET в «/ users / show».

  public function count() { $result = $this->api->get("users/show", array( 'screen_name' => $this->username )); return $result['statuses_count']; } 

Интерфейс ArrayAccess

Теперь мы собираемся немного повысить ставку, узнав о более интересном интерфейсе.

Когда реализовано, ArrayAccess позволит получить доступ к нашим объектам как к карте, которая действительно является тем, чем они являются. Методы реализации:

 ArrayAccess { abstract public boolean offsetExists ( mixed $offset ) abstract public mixed offsetGet ( mixed $offset ) abstract public void offsetSet ( mixed $offset , mixed $value ) abstract public void offsetUnset ( mixed $offset ) } 

Это очень удобно в нашем объекте шкалы времени Twitter. Проверка того, существует ли твит на временной шкале, будет выполнена путем передачи нашего объекта в isset следующим образом:

 isset($tweets['some tweet id']); 

Чтобы сделать это, мы просто выполняем запрос GET для идентификатора твита.

 public function offsetExists($offset) { $tweet = $this->api->get("statuses/show", array("id" => $offset)); if ($tweet) { return $tweet["text"]; } return false; } 

Более того, мы также можем использовать вышеприведенное для offsetGet и позволить offsetExists вызывать смещение Get по очереди

 public function offsetGet($offset) { $tweet = $this->api->get("statuses/show", array("id" => $offset)); if ($tweet) { return $tweet["text"]; } return false; } public function offsetExists($offset) { return $this->offsetGet($offset) !== false; } 

С помощью offsetUnset мы также можем выполнить удаление по идентификатору твита, но я оставлю это на ваше offsetUnset .

К сожалению, реализация offsetSet не имеет особого смысла. Для таких вещей, простой способ — просто создать пользовательское исключение, например UnsupportedOperationException . Но с другой стороны, это также может зависеть от конкретных бизнес-правил вашего приложения.

Интерфейс Итератора

Я зарезервировал интерфейс, который мне нравится больше всего на последний! Интерфейс Iterator чрезвычайно полезен здесь, потому что я не думаю, что есть лучший способ инкапсулировать кровавые детали подкачки через удаленную коллекцию, чем зацикливание нашего объекта временной шкалы, как если бы это был обычный массив.

Во-первых, необходимо реализовать следующие методы:

 Iterator extends Traversable { /* Methods */ abstract public mixed current ( void ) abstract public scalar key ( void ) abstract public void next ( void ) abstract public void rewind ( void ) abstract public boolean valid ( void ) } 

Мы могли бы явно использовать методы выше для циклического прохождения нашей временной шкалы так:

 $it->rewind(); while ($it->valid()) { $key = $it->key(); $value = $it->current(); // do something $it->next(); } ?> 

Но зачем это делать, когда вы можете сделать это:

 foreach($tweets as $id => $tweet) { echo $tweet; } 

В нашем примере мы будем циклически просматривать твиты в нашей временной шкале, хронологически извлекая куски твитов и затем сохраняя их в буфере. Мы будем перебирать этот буфер до тех пор, пока он не закончится, а затем мы получим еще один пакет твитов, используя идентификатор последнего твита в качестве смещения.

Изначально у нас нет ни одного, и вот тут-то и появляется метод перемотки: чтобы получить последние 10 твитов, у нас есть смещение от того, где мы можем получить следующие 10.

 public function rewind() { $this->tweets = $this->api->get('statuses/user_timeline', array('count' => 20)); } 

Метод valid() только для того, чтобы указать, продолжать ли цикл. Это можно сделать, проверив, является ли наш буфер твитов emtpy:

 public function valid() { return !empty($this->tweets); } 

Методы key() и current() просто возвращают ключ и значение текущего твита в нашей итерации. Для наших целей мы просто получим идентификатор твита и текст последнего твита из нашего буфера.

 public function key() { $latest = reset($this->tweets); return $latest['id_str']; } public function current() { $latest = reset($this->tweets); return $latest['text']; } 

Наконец, есть следующий метод. Здесь мы снимаем заголовок нашего буфера, чтобы получить следующий элемент для повторения. Затем мы гарантируем, что получим следующий набор твитов, если мы находимся на последнем элементе нашего буфера.

 public function next() { $head = array_shift($this->tweets); if (!is_null($head) && empty($this->tweets)) { $this->tweets = $this->api->get('statuses/user_timeline', array('count' => 800, 'max_id' => $head['id_str'])); } } 

Мы сделали! Это была очень простая реализация циклического просмотра твитов пользователя. Можно сделать гораздо больше, например, локальное кэширование, чтобы сэкономить на вызовах API, но в этом и прелесть использования интерфейсов: они позволяют нам менять нашу стратегию под капотом, и пока наша реализация по-прежнему верна, мы можем ожидать, что она все еще работают.

Но сейчас вы можете наблюдать за работой нашего объекта временной шкалы, запустив php index.php в командной строке.

Вывод

Преимущество интерфейсов в два раза. Они позволяют нам инкапсулировать детали реализации и предоставляют нам синтаксический сахар, оба из которых могут быть настоящим обаянием в любом приложении, требующем взаимодействия. Если у вас есть какие-либо вопросы или комментарии, пожалуйста, оставьте их в разделе комментариев ниже!