Создание объектно-ориентированного аналога массива в PHP. Часть 1

Stepler, 24.12.2008

В этой статье речь пойдет о том, как создать объектно-ориентированный аналог массива в PHP.

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

class Client  {
  public $name;
//описание класса 
}
class Manager  {
  public $clients = array();
//описание класса
//где-то в нем происходит  заполнение массива 
//объектами класса Client
}
//просмотр список обслуженных клиентов
$objManager  = new Manager(‘id’);
foreach ($objManager->clients as $client) {
  echo $client->name;
}

В принципе мы получили что хотели — список клиентов. Однако данный подход не является хорошим решением по нескольким причинам:

  • открытый массив $objManager->clients нарушает инкапсуляцию. Массив можно сделать закрытым с доступом к нему через метод $objManager->getClients(). Но такой подход не поможет, в случае если возникнет необходимость его модифицировации по ходу его просмотра. Если же оставить массив открытым для возможности модификации, то мы не сможем контролировать и/или фильтровать информацию заносящуюся в него.
  • информация по клиентам будет загружена при создании объекта, даже если нужно только имя менеджера и не больше. Конечно, можно создать метод $objManager->loadClientsInfo(), только после вызова которого будет подгружена информация о клиентах, однако удобнее было бы если информация будет подгружена автоматически в момент когда к ней обратятся.

Для решения этих проблем создадим класс ObjectsArr — оболочку вокруг массива объектов. Предъявим к нему следующие требования:

  1. предоставление механизма добавления, удаления и извлечения элементов;
  2. заполнение информации по клиентам при первом обращении к ней;
  3. возможность перебора всей коллекции элементов. (for, foreach)

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

Итак, давайте создадим класс ObjectsArr и прикрутим к нему механизм управления содержимым.

Класс ObjectsArr

class ObjectsArr {
  private $_list = array();
}

Метод добавления элемента

public  function add($value, $key = null) {
  if ($key === null || empty($key)){
    $this->_list[] = $data;
    return;
  }
  
  if (!isset($this->_list[$key])){
    $this->_list[$key] = $data;
  } else {
    throw new Exception('Ключ "' . $key . '" уже  используется.');
  }
}

Метод add принимает 2 аргумента, добавляемый элемент и его ключ. Ключ является необязательным, если его опустить, то он будет генерироваться автоматически. При попытке добавить элемент с уже существующим ключом будет сгенерировано исключение.

Метод удаления элемента

public  function del($key)  {
  if (isset($this->_list[$key])) {
    unset($this->_list[$key]);
  } else {
    throw new Exception('Элемент с ключем  "' . $key . '" отсутсвует.');
  }
}

Удаляет элемент по его ключу. Если ключ отсутствует, то генерируется исключение.

Метод извлечения элемента

public  function get($key) {
  if (isset($this->_list[$key])) {
    return $this->_list[$key];
  } else {
    throw new Exception('Элемент с ключем  "' . $key .  '" отсутсвует.');
  }
}

Извлекает элемент по его ключу. Если ключ отсутствует, то генерируется исключение.

Метод проверки наличия элемента

public function exist($key) {
  return isset($this->_list[$key]);
}

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

Метод получения всех элементов

public function keys() {
  return array_keys($this->_list);
}

Эта функция позволяет получить все имеющиеся ключи добавленных элементов.

Метод подсчета добавленных элементов

public function length() {
  return count($this->_list);
}

Эта функция возвращает количество установленных элементов.

Весь класс, полностью

Это все. Полностью класс ObjectsArr будет выглядеть так:

class ObjectsArr {
  private $_list = array();
  
  public function add($value, $key = null) {
    if ($key === null || empty($key)){
      $this->_list[] = $data;
      return;
    }
    
    if (!isset($this->_list[$key])){
      $this->_list[$key] = $data;
    } else {
      throw new Exception('Ключ "' . $key . '" уже  используется.');
    }
  }
  
  public function del($key) {
    if (isset($this->_list[$key])) {
      unset($this->_list[$key]);
    } else {
      throw new Exception('Элемент с  ключем "' . $key .  '" отсутсвует.');
    }
  }
  
  public function get($key) {
    if (isset($this->_list[$key])) {
      return $this->_list[$key];
    } else {
      throw new Exception('Элемент с  ключем "' . $key .  '" отсутсвует.');
    }
  }
  
  public function exist($key) {
    return isset($this->_list[$key]);
  }
  
  public function keys() {
    return array_keys($this->_list);
  }
  
  public function length() {
    return count($this->_list);
  }
}

Тестирование класса ObjectsArr

$clientsList = new ObjectsArr();
try {
$clientsList->add(new Client('Ваня'), 'ivan'); //Добавляем нового клиента
$clientsList->add(new Client('Саша'), 'alexander'); //Добавляем нового клиента
$clientsList->add(new Client('Петя'), 'petr'); //Добавляем нового клиента

$objIvan = $clientsList->get('ivan'); //Извлекаем клиента
echo $objIvan; // выведет ->  Клиент - "Ваня"
$clientsList->del('ivan'); //удаляем клиента
$clientsList->add(new Client('Петр Иванович'), 'petr'); /*перехват исключения, т.к. ключ "petr" уже используется
                                                          выведет -> Ключ "petr" уже используется. */
} catch (Exception $e) {
  echo $e->getMessage();
}

Добавляемую информацию, так же можно фильтровать. Я намеренно не стал этого делать, т.к. от приложения к приложению требования (а значит и фильтры) могут меняться. Все их можно добавить, расширив базовый класс ObjectsArr. Для примера, реализуем возможность добавления только объектов в коллекцию:

class MyObjectsArr extends ObjectsArr {
  public function add(object $value, $key = null) {
    parent::add($value, $key);
  }
}

Или

class MyObjectsArr extends ObjectsArr {
  public function add($value, $key = null) {
    if (!is_object($value))
      throw new Exception('Добавляемый элемент не является объектом.');
    
    parent::add($value, $key);
  }
}

Итак, класс ObjectsArr функционирует и отвечает нашему первому требованию:

предоставление механизма добавления, удаления и извлечения элементов

Использовать класс в таком виде, в каком он представлен сейчас — неудобно. Связано это с тем что мы не реализовали все требования предъявленные к нему.

В следующий статьях мы продолжим наращивать его функциональность.

Подписаться на обновления блога

Вам понравился наш блог, хотите следить за обновлениями? Подпишитесь на RSS рассылку или рассылку по электронной почте. Так же вы можете следить за нами в Twitter.

Категории: PHP | Комментировать

Комментарии (16)

  1. Горбунов Олег / 25.12.2008 в 08:15

    По моему, это не «объектно-ориентированный аналог массива» а модель данных.

  2. Ryzhov / 25.12.2008 в 09:56

    Малополезный класс.
    Если реализовать интерфейсы ArrayAccess, Iterator и Counter класс превращается в полноценный массив.
    Доступ не $object->key а $object['key]. Конструкция foreach и функция count($object)

  3. Stepler / 25.12.2008 в 13:05

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

  4. Vadim Voituk / 26.12.2008 в 19:10

    IMHO куда логичнее было бы начинать с расширения SPL-класса ArrayObject
    http://ua.php.net/manual/en/class.arrayobject.php

  5. Stepler / 27.12.2008 в 14:46

    IMHO куда логичнее было бы начинать с расширения SPL-класса ArrayObject
    http://ua.php.net/manual/en/class.arrayobject.php

    Класс ArrayObject меня не устроил своей реализацией. Например :

    $arrayObject = new ArrayObject();
    $arrayObject = new ArrayObject();
    $arrayObject[] = 'first';
    $arrayObject[] = 'second';
    $arrayObject[] = 'third';
    $arrayObject[0] = 'anotherFirst';
    
    //Первый цикл
    foreach ($arrayObject as $key => $value) {
        echo $key . ' -> ' . $value;
        if ($key == 1)
            unset($arrayObject[$key]);
    }
    
    //Второй цикл
    foreach ($arrayObject as $key => $value) {
        echo $key . ' -> ' . $value;
        $arrayObject[] = '---';
    }

    Не всегда нужно, чтобы имелась возможность замены элементов в массиве. (элемент anotherFirst заменил элемент first). В моем случае возвращается Exception.

    Первый цикл начнет перебор элементов сначала, после того, как удалит 2-й элемент.

    Второй цикл — бесконечный.

    Все недочеты можно исправить, расширив класс ArrayObject и дополнив нужным функцционалом. Но тогда нужно будет описать внутренне его строение, это и вернуло бы нас к написанию точно-такой же статьи.

    Я решил в качестве примера написать свой собственный класс с описанием его работы (хотя он во многом и копирует ArrayObject).

  6. nickwais / 09.01.2009 в 16:56

    В методах add и del нужно использовать созданный метод exist, а не проверять вручную. Реализация exist может измениться, придется переписывать все методы.

    В конструкции if ($key === null || empty($key))
    $key === null —> не нужно. Это делает сам empty.

  7. HACPAKA / 16.01.2009 в 00:25

    Хм….
    if ($key === null || empty($key)){
    …}
    А если $key=0 ?
    Ваш метод добавит в конец массива новый элемент, а я хочу добавить элемент на позицию 0.
    Не есть гуд…

  8. блабла / 11.04.2010 в 21:04

    дадада, надо поумолчанию сделать $key=-1, и проверять на больше равно нулю

  9. Linney / 30.08.2011 в 16:31

    В Pear уже есть куча готовых решений таких массивов

  10. Econ / 02.12.2011 в 13:53

    Кидать exception по моему не корректно, лучше просто возвращать код ошибки

  11. ФФФ / 04.10.2014 в 10:00

    В функции add серьезная ошибка. Необходимо заменить строку
    public function add($value, $key = null) {
    на
    public function add($data, $key = null)

  12. ФФФ / 04.10.2014 в 10:01

    Уточнение:
    public function add($data, $key = null) {

  13. Derrt / 02.10.2016 в 14:12

    У меня одного появляется ошибка при добавлении NULL в массив?
    $array = new ArrayCollection();
    $array->add(NULL, ‘k’);
    print_r($array->get(‘k’));
    Бросает исключение (я заменил Exception на OutOfRangeException)
    OutOfRangeException с сообщением ‘k is absent in array’

  14. Derrt / 02.10.2016 в 14:13

    При этом, если просто вывести весь массив print_r(), то элемент там есть, а доступа к нему нет.

  15. Derrt / 02.10.2016 в 15:19

    Проблема решена. Метод get() неправильно написан. Нужно проверять не функцией isset() , а array_key_exists(). Она ищет ключ. А isset еще и проверяет, не null ли там.

  16. Gina / 03.02.2017 в 16:04

    In realtà ne ho viste alcune di belle realizzate anche qui in Italia. Peccato nn ricordare i brand per linarvene qualcuna. Una però è di una catena o fanchising di negozi di rinaarziopi sartoriali, specie di jeans e la sacca è appunto stamapata con un jenas vero, un effetto molto bello.

Оставить комментарий

480×60
480×60