Класс для вывода по формату контактной информации, хранящейся в XML-файле

14.09.2010
@LEXXX_NF

Трудно найти сайт в интернете, на котором не были бы указаны контактные телефоны, адреса и другие средства связи. Иногда эти данные меняются и их приходится обновлять. Хорошо, если телефон указан только в шапке, а адрес — только в подвале, не трудно изменить данные в двух местах. В моём случае телефон был указан в четырёх местах! «Какого черта?» — подумал я и написал небольшое решение, которое позволяет хранить все контакты в одном месте.

Где хранить данные

Не помню точно где, но, кажется, на сайте Лебедева, предлагалось хранить контактную информацию в XML в «сыром» виде, а форматирование переложить на шаблонизатор. Действительно, этот язык отлично подходит для хранения более-менее структурированной информации, и при этом неплохо расширяется. Еще одно огромное преимущество XML — его может понять простой контент-менеджер :)

Вот так выглядит мой XML:


<?xml version="1.0" encoding="UTF-8"?>
<contacts>
  <category name="general">
    <phone code="831" number="123-45-67">(831) 123-45-67</phone>
    <im type="icq" uin="123456789" />
    <im type="skype" uin="skype_uin" />
    <email>office@email.com</email>
    <address zip="603000" city="Нижний Новгород" street="Ленина" building="123" >г. Нижний Новгород, ул. Ленина, д. 132/35</address>
  </category>
  <category name="store">
    <phone state="+7" code="831" number="123-45-67">+7 (831) 123-45-67</phone>
    <email>store@email.com</email>
    <address city="Нижний Новгород" square="Минина" building="1">г. Нижний Новгород, пл. Минина, д. 1</address>
  </category>
</contacts>

Здесь у меня в корневом теге contacts расположено 2 тега category, это на тот случай, если будет несколько наборов контактов, например, для Интернет-магазина это могут быть офис и склады. Различаются они атрибутом name.

Внутри категории указан список разных контактов: телефон, интернет-мессенджер, e-mail, почтовый адрес. Каждый вид контакта представлен своим тегом, причем контактов одного вида может быть много. Рассмотрим более подробно адрес.


<address zip="603000" city="Нижний Новгород" street="Ленина" building="123" >г. Нижний Новгород, ул. Ленина, д. 132/35</address>

Каждый атрибут представляет собой сегмент адреса: индекс, город, улица, дом. Из этих сегментов я потом можно будет сформировать адреса в нужных форматах. Контентом тега является заранее отформатированный вариант адреса. Это на тот случай, если форматировать нечего, например как у e-mail’а, или, если хочется задать вариант вывода по умолчанию.

Как атрибуты тегов, так и сами теги могут быть любыми, лишь бы при выводе правильно формат написать. Не сложно, правда?

Вывод на страницу

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


$xml = XMLContacts::getInstance('contact.xml');

Сущность создаётся не через конструктор, потому что в классе я использую кэширование. Создавать глобальные переменные не комильфо, поэтому, везде, где нужно вывести телефон придётся создавать сущность, а значит парсить XML, этого делать не хочется. Поэтому сущность кэшируется. Более, того в классе создаётся массив сущностей — по одной для каждого XML-файла, это даёт возможность использовать несколько файлов, хотя, благодаря тегам category, в этом нет необходимости.

Получим все контакты для категории «general»:


$phone   = $xml->getContact('general', 'phone');
$im      = $xml->getContact('general', 'im');
$email   = $xml->getContact('general', 'email');
$address = $xml->getContact('general', 'address');

Для интернет-мессенджеров это даст массив вида:


Array
(
  [0] => Array
    (
      [type] => icq
      [uin] => 649665071
      [f] =>
    )

  [1] => Array
    (
      [type] => skype
      [uin] => refreshlab
      [f] =>
    )
)

Отлично! Получили все фрагменты контактов данного вида, плюс в параметре f - преформатированное значение. Осталось все эт овывести.


foreach($im as $msr){
  echo '
    <span class="',$msr['type'],'">
      <img alt="',$msr['type'],'" src="/images/icon-',$msr['type'],'.gif" class="icon">'
      ,$msr['uin'],
    '</span>';
}

Часто надо выводить не все контакты, а лишь один, первый. Для этого в методе getContact существует третий параметр $id: getContact($category, $type, $id = NULL). Если он задан, то функция вернёт не массив, а элемент с индексом $id.


$phone = $xml->getContact('general', 'phone', 0);
echo '(',$phone['code'],') ',$phone['number'];

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

Вывод по формату

Мне хотелось, чтобы номер телефона можно было вывести легко и удобно. Примерно так: state (code) number. Я нашел способ сделать даже лучше: можно указывать необязательные параметры! Вот так будет выглядеть шаблон для адреса с необязательными индексом, улицей и площадью: (<zip>, ) г. <city>, (ул. <street>, )(пл. <square>, )д. <building>. И вот что этот формат даёт:


<address zip="603000" city="Нижний Новгород" street="Ильинская" building="132"/>
603000, г. Нижний Новгород, ул. Ильинская, д. 132
<address city="Нижний Новгород" square="Минина" building="1"/>
г. Нижний Новгород, пл. Минина, д. 1

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

Из этого синтаксиса следуют 2 ограничения. Точнее неудобства. В текст нельзя использовать ни круглые, ни угловые скобки. Однако никто не мешает заменить их HTML-сущностями. И тогда, пример с номером телефона будет выглядеть так: ((<state>) &#40;<code>&#41;) <number>

Для вывода по формату в классе существует метод fGetContact($category, $type, $format = '', $id = NULL)

Вот тот же пример с интернет-мессанджерами, но с использованием форматов:


$im = $xml->fGetContact('general', 'im', '&lt;span class=&quot;<type>&quot;&gt;&lt;img alt=&quot;<type>&quot; src=&quot;/images/icon-<type>.gif&quot; class=&quot;icon&quot;&gt;<uin>&lt;/span&gt;');
echo html_entity_decode(implode($im));

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

Итого

Мне удалось решить проблему обновления контактов одновременно во всех местах на сайте с использованием приятного шаблонизатора.

Какие есть недостатки?

  • Нельзя использовать круглые и угловые скобки в чистом виде.
  • Нельзя применить форматирование к сегментам. Например, номер телефона должен быть жестко прописан, однако в некоторых местах было бы удобнее написать не через дефисы, а через пробелы или даже точки.
  • Нельзя вывести телефоны вот в таком виде: (123) 456-78-91, 234-56-78, потому что оба номера форматируются одинаково. Хотя без использования шаблонизатора это сделать можно.
  • Для обозначения улиц у нас используется сокращение «ул.», но ведь ещё существуют переулки, проезды, проспекты, шоссе, площади. Для всего этого приходится заводить отдельные атрибуты. Это немного неудобно.

Альтернативный шаблонизатор aka TODO

Есть идея формировать адрес не по заданному формату, а callback-функцией. Это позволит не только решить проблему с форматированием сегментов, но и позволит реализовать сколь угодно сложную логику формирования самих контактов. Но это будет не так просто и очевидно.

Скачать класс

В заключение, хочется отметить, что для парсинга форматов, используется функция из фреймворка Kohana. Там она применялась для формирования URL.

Комментов нет совсем... почему-то...

Писáть здесь

А еще у меня есть: