Jul 20

На этот пост меня сподвигла неправильная (на мой взгляд работа fillin-фильтра в Symfony). Итак, поехали.

DOM - это мощный компонент PHP для работы с Document Object Model. Почитать о его возможностях можно здесь (php manual). Я же хочу заострить внимание на том, что это расширение, в отличии от SimpleXML, например, может работать как с HTML, так и с XML.

DomDocument - один из классов компонента DOM, который отвечает за полный XML или HTML-документ.

И вот хотелось бы поговорить и показать, как этот DomDocument работает с кодировками и символами, отличными от латиницы.

Для начала - небольшое отступление: DomDocument я создаю вот так: new DomDocument('1.0', 'UTF-8'), указывая в качестве кодировки (”The encoding of the document as part of the XML declaration.”) UTF-8, так как, судя по моему опыту - указание кодировки здесь не дает вообще ничего.

Рассказывать тут особо нечего, поэтому я приведу код и результаты, а потом их проанализирую.

Весь текст в скриптах в кодировке UTF-8.

Код класса “тестов”

class Test_DomDocument_HTML_Charset {
  protected $dom;

  protected $html =
        '<!DOCTYPE html PUBLIC "-//W3C//DTD HTML 4.01 Frameset//EN" "http://www.w3.org/TR/html4/frameset.dtd">
        <html>
        <head>
          <meta http-equiv="Content-type" content="text/html; charset=UTF-8">
          <title>Тестовая страничка</title>
        </head>
        <body>
          <p>Привет</p>
        </body>
        </html>';

  protected $html_without_charset =
        '<!DOCTYPE html PUBLIC "-//W3C//DTD HTML 4.01 Frameset//EN" "http://www.w3.org/TR/html4/frameset.dtd">
        <html>
        <head>
          <title>Тестовая страничка</title>
        </head>
        <body>
          <p>Привет</p>
        </body>
        </html>';

  protected function checkTestFunction($function)
  {
    return (strpos($function, 'test') === 0);
  }

  public function execute($callback) {
    $functions = get_class_methods(get_class($this));
    $functions = array_filter($functions, array($this, 'checkTestFunction'));
    foreach($functions as $function) {
      $this->setUp();
      $result = $this->$function();
      $callback($result, $function);
      $this->tearDown();
    }
  }

  protected function setUp() {
    $this->dom = new DomDocument('1.0', 'UTF-8');
  }

  protected function tearDown() {
  }

  /**
   * Возвращает кодировку документа.
   * Используется документ, в котором не указана кодировка.
   *
   * @return string
   */
  protected function testWithoutCharset() {
    $this->dom->loadHTML($this->html_without_charset);
    return $this->dom->encoding;
  }

  /**
   * Возвращает документ, после обработки его DomDocument'ом.
   * Используется документ, в котором не указана кодировка.
   *
   * @return string
   */
  protected function testWithoutCharsetHtml() {
    $this->dom->loadHTML($this->html_without_charset);
    return $this->dom->saveHTML();
  }

  /**
   * Возвращает кодировку документа.
   * Используется документ, в котором указана кодировка.
   *
   * @return string
   */
  protected function testWithCharset() {
    $this->dom->loadHTML($this->html);
    return $this->dom->encoding;
  }

  /**
   * Возвращает документ, после обработки его DomDocument'ом.
   * Используется документ, в котором указана кодировка.
   *
   * @return string
   */
  protected function testWithCharsetHtml() {
    $this->dom->loadHTML($this->html);
    return $this->dom->saveHTML();
  }

}

Код, показывающий результаты

function echoHTMLResult($result, $function)
{
  echo "<h3>$function</h3>\n";
  echo '<pre><code>';
  if(is_string($result))
    $result = str_replace('<', '&lt;', str_replace('>', '&gt;', $result));
  var_dump($result);
  echo '</code></pre>';
}
<?php $test = new Test_DomDocument_HTML_Charset(); ?>
<!DOCTYPE html PUBLIC "-//W3C//DTD HTML 4.01 Frameset//EN" "http://www.w3.org/TR/html4/frameset.dtd">
<html>
<head>
  <meta http-equiv="Content-type" content="text/html; charset=UTF-8">
  <title>Тестовая страничка</title>
</head>
<body>
<?php $test->execute('echoHTMLResult') ?>
</body>
</html>

Результаты

Выводы

Как можно понять из результатов - кодировка документа при использовании HTML определяется исключительно через тег meta, а точнее - через charset, который там указан:

<meta http-equiv="Content-type" content="text/html; charset=UTF-8">

При отсутствии указанного тега/charset’а в нём - $dom->encoding будет равен NULL (что можно с успехом использовать).

В общем-то всё, в ближайшие дни еще будет статья про кодировку в XML-документах, где всё немного интереснее.

written by FX Poster \\ tags: , , ,

Jul 20

На работе пришлось столкнуться с очень не нравившейся мне ORM’кой собственного производства. Стал делать свою (ну не дурак ли, а? :)), наваял за 3 дня простенькую ORM, отображающую структуру таблиц на обьекты, не контроллируя типов. Итог получился примерно такой:

  • класс базы данных (относледовался от mysqli, pdo использовать было нельзя)
  • класс таблицы, хранящий в себе бд, и отвечающий за CRUD записей
  • класс записи, перенаправляющий методы CUD классу таблицы

Примерное использование:

$table = new ArticleTable();
$record = $table->fetchOneWhere("slug = 'hello'"); // получаем существующую запись
$record->name = 'Fucking Article!';
$record->save(); // вызывает insert/update в зависимости от того, новая ли это запись
$record = $table->create(); // создаем новую запись
$record->name = 'Fucking Article2!';
$record->slug = 'fucking_article';
// ...
$record->save();

И что-то мне это очень сильно напомнило, а именно - Zend_Db: Zend_Db_Table / Zend_Db_Table_Row. Недолго думая - выкинул нафиг свою систему и залил в проект кусок Zend Framework’а (если нужно - потом скажу, какие именно файлы нужны для полноценной работы всего компонента Zend_Db), а также решил почитать, что сейчас вообще есть в этой Zend_Db, а есть, как оказалось - довольно много:

  • Хорошая абстракция работы с бд
  • Классы записи/таблицы
  • Поддержка fetch’инга связанных обьектов
  • Поддержка many-to-many связей (этого даже в Propel нет)

На самом деле - еще есть вещи, коотрые бы можно было добавить, чтобы они работали автоматически:

  • Валидаторы в зависимости от типов полей таблицы
  • Возможность сразу fetch’ить данные из нескольких таблиц (точнее - получить такие данные довольно легко, но вот разбрасывать их по разным обьектам и связывать эти обьекты сейчас нужно ручками, если я не ошибаюсь, но опять же - это проблем не составляет)

Вроде всё. Общее впечатление - просто замечательная система. Использовать легко и приятно. :)

Пример:
Сначала пойдут мои супертипы слоя (кто читал Patters of EAA - поймет):

class Db_Table extends Zend_Db_Table_Abstract {
  /**
   * @return Zend_Db_Table_Rowset_Abstract
   */
  public function fetchAllBy($key, $value) {
    $where = $this->getAdapter()->quoteInto("$key = ?", $value);
    return $this->fetchAll($where);
  }

  /**
   * @return Zend_Db_Table_Row_Abstract
   */
  public function fetchRowBy($key, $value) {
    $where = $this->getAdapter()->quoteInto("$key = ?", $value);
    return $this->fetchRow($where);
  }

  public function __call($name, $arguments) {
    if(strpos($name, 'fetchRowBy') === 0) {
      array_unshift($arguments, substr($name, 10));
      return call_user_func_array(array($this, 'fetchRowBy'), $arguments);
    }

    if(strpos($name, 'fetchAllBy') === 0) {
      array_unshift($arguments, substr($name, 10));
      return call_user_func_array(array($this, 'fetchAllBy'), $arguments);
    }

    throw new Exception("Undefined method $name");
  }
}

class Db_Record extends Zend_Db_Table_Row_Abstract {
}

А теперь - пример использования:

class Item extends Db_Table {
  protected $_name = 'items';
  protected $_rowClass = 'ItemRecord';
  protected $_referenceMap = array(
      'Group' => array(
        'columns'       => 'groupid',
        'refTableClass' => 'Group',
        'refColumns'    => 'groupid',
      )
    );
}

class ItemRecord extends Db_Record {
}

class Group extends Db_Table {
  protected $_name = 'groups';
  protected $_rowClass = 'GroupRecord';
  protected $_dependentTables = array('Item');
}

class GroupRecord extends Db_Record {
}

$itemTable = new Item();
$item = $itemTable->fetchRowBySlug('hello');
$group = $item->findParentGroup();

Согласитель - всё просто и удобно, не так ли?

Для заинтересовавшихся - очень советую проштудировать полностью главу о Zend_Db из документации Zend Framework’а. А также - мой пост про Zend_Db_Table, посвященный его улучшению (правда, я не знаю, насколько он сейчас актуален, проверять нет времени :( ).

written by FX Poster \\ tags: , ,

Jul 20

Собственно, о том, что вышла Symfony 1.1 писали многие, и вы об этом наверняка уже слышали. Но для тех, кто об этом еще не знает, повторю нововведения, а также выскажу о них своё [авторитетное :)] мнение:

  • Новая архитектура. Явное улучшение - компоненты теперь меньше зависят друг от друга.
  • Новая система конфигурирования. К счастью, конфигурация через YAML-файлы никуда не делась. Просто сама структура конфигов переделана.
  • “The new object-oriented form framework“. Звучит действительно круто. А смысл такой - генерация форм динамически, вместо статических хелперов (которые, кстати, остались), а также - вся работа с формами (указание полей, их валидаторов, настройка свойств показа формы и прочее)  теперь делается в одном месте, что очень удобно. Есть интеграция форм с Propel, например - автоматическая генерация форм по модели.
  • Новая система консольных комманд. Честно сказать - если вы не разрабатываете плагины, то разницу вы увидите только в том, что некоторые комманды поменяли своё название.
  • Новый парсер YAML. Разве что изменили выдачу инфы об ошибках. Ну и кое-что добавили. Как по мне - для конечного девелопера разница очень мала.
  • Теперь плагины - это обычные пакеты PEAR, со всеми вытекающими последствиями, из которых главным, на мой взгляд, является возможность указаниязависимостей от других плагинов.
  • Теперь можно выдавать различные вьюхи в зависимости от того, какоt значение принимает хедер Accept. Поподробнее - здесь.
  • Собствено, ORM теперь полностью вынесен в плагины. Symfony 1.0 содержала в себе Propel 1.2, и была возможность заменить его на Propel 1.3/Doctrine через плагины. Теперь же и Propel 1.2 вынесен в отдельный плагин. Скорее всего эта возможность пришла вследствии пункта №1.
  • Для того, чтобы приложения, написанные на Symfony 1.0 запускались на версии 1.1 был сделан sfCompat10Plugin, который, насколько я понял, в версии 1.2 уберут совсем. К сожалению, проекты всё таки прийдется дорабатывать, чтобы они работали на Symfony 1.1, подробнее об этом можно прочесть здесь.
  • Переписан класс routing’а. Теперь все пути кешируются + от этого класса можно отнаследоваться и переделать, от чего бы я не отказался.

А теперь посмотрите и скажите - что из этого может реально пригодится? ИМХО:

  • Form Framework
  • Зависимости в плагинах
  • Различные вьюхи в зависимости от того, какоt значение принимает хедер Accept

Причем последние два пункта лично я пока не использовал бы нигде. Просто нет необходимости. К чему это я клоню - этот релиз чуть ли не официально был признан “промежуточным” - минимум новых интересных фич и очень сильная переработка кода, причем зачастую внутреннего, для облегчения дальнейшей поддержки и добавления новых фич в будущем. В частности - в версии 1.2 нововведений меньше, но они гораздо больше коснуться конечных пользователей фреймворка (коснуться - в хорошем смысле). Кстати говоря, версия 1.2 выйдет уже в октябре, через 3 месяца… Но это уже несколько друга история, о которой мы поговорим, скорее всего завтра.

written by FX Poster \\ tags: ,

Jun 12

После прихода на работу сегодня меня “обрадовали” следующими словами - “сноси винду!”. Я уж часом подумал, что меня выгоняют за регулярные прогулы работы из-за универа в последнее время. :) Всё оказалось намного проще - следующая фраза звучала примерно так - “переходим на Linux”. После вопроса “а нафига, собственно?”, мне поведали довольно интересные новости.

Оказывается, в последнее время участились визиты людей, проверяющих ПО на лицензионность в IT’шные, и, наверное, не только, фирмы. Эти визиты, по слухам, оборачивались в солидные прибавки к зарплате нашей дорогой милиции и прилегающим структурам… Штрафы для крупных фирм, которых “поймали на горячем”, опять-таки - по слухам, сопоставимы с ценами однокомнатных квартир (для тех, кто не в теме - среднестатистическая однокомнатная квартира у нас сейчас стоит ~50-60 килобаксов).

Так что сегодня у меня вторая половина рабочего дня прошла под эгидой “linux - наше всё” - я ставил Ubuntu, обновлял её, ставил нужные пакеты… Эх… Не хочется, но, судя по всему, прийдется…

PS. Мне-то еще ничего. А у нас люди на .NET работают - теперь из заказчиков выбивают Windows, Visual Studio и Office. :)

written by FX Poster \\ tags: , ,

Jun 11

Вышел второй релиз-кандидат Symfony 1.1. Улучшений немного, по сравнению с предыдущими бетами и rc, что, несомненно, радует - версия 1.1 становиться всё стабильнее и стабильнее - уже и релиз не за горами.

Тем временем Fabien (главный разработчик Symfony) разродился кучкой постов:

written by FX Poster \\ tags: ,

Apr 08

Что бы вы предпочли - datepicker или 3 select’а для дня, месяца и года?

У нас на проекте мнения разделились, потому решил устроить опрос…

written by FX Poster

Mar 29

Собственно, продолжим начатое

Сегодня мы займемся установкой Symfony на ваш компьютер. Первым делом идем на офсайт и смотрим страницу, описывающую процесс установки! Несмотря на наличие этой страницы, мне всё же задают вопросы - “как ставить”, “какой способ установки выбрать” - судя по всему, такая статья лишней не будет.

Существует 3 основных способа установки Symfony на ваш компьютер:

  1. Скачать архив
  2. Установить из SVN
  3. Установить через PEAR

Установка путем скачивания архива

Скачиваем архив (symfony 1.1 beta 2) с официального сайта, и распаковываем в любую директорию (не обязательно в php’шный include_path). После чего фреймворком уже можно пользоваться. Но есть неудобства - для того, чтобы пользоваться консольными командами - прийдется каждый раз прописывать в консоли путь к файлу “symfony.bat” (или “symfony”, в зависимости от того - пользуетесь вы ОС на основе *nix или Windows), этот файл находится в ./data/bin. Для того, чтобы убрать этот недостаток:

  • под Windows - пропишите путь к “symfony.bat” в переменную PATH (помочь вам сделать это может Google).
  • под *nix - запустите “sudo ln -s /<путь к директории, куда вы распаковали архив>/data/bin/symfony /usr/bin/symfony”

После этого можете пользоваться в консоли командами типа “symfony init-project” и т.д.

Плюсы: простота - кроме скачивания и распаковывания архива ничего не нужно делать; если на компьютере, на который вы собираетесь устанавливать Symfony, нет интернета - достаточно просто принести архив на этот компьютер.

Минусы: отсутствие автоматизации - для обновления Symfony прийдется самостоятельно повторять процедуру.

Установка из SVN

Если вы не знакомы с SVN - лучше выберите первый вариант. Процедуру прописывания путей к ./data/bin/symfony прийдется повторять и здесь. Для установки - перейдите в директорию, куда хотите установить symfony и введите:

svn checkout http://svn.symfony-project.com/branches/1.1

в *nix-ОС, либо проделайте подобную процедуру (checkout) через TortoiseSVN под Windows - объяснять не буду - у TortoiseSVN интуитивно-понятный интерфейс.

Плюсы: можно удобно обновляться до самой последней версии с помощью возможностей SVN; если на компьютере, на который вы собираетесь устанавливать Symfony, нет интернета - достаточно просто принести директорию, куда вы “установили” Symfony на этот компьютер.

Минусы: в SVN может находится не совсем стабильная версия (стабильные версии хранятся в http://svn.symfony-project.com/tags/); способ сложнее для тех, кто не умеет работать с SVN.

Установка через PEAR

Если у вас не установлен PEAR - тогда вам пора обратится к Google.

Установка очень проста (перевод доки с офсайта):

Введите в коммандной строке:

$ pear channel-discover pear.symfony-project.com

Для установки стабильного релиза (на данный момент это 1.0.12), введите:

$ pear install symfony/symfony

Для установки бета-версии (на данный момент - 1.1 beta 2, т.е. то, что нам нужно), введите:

$ pear install symfony/symfony-beta

Для обновления Symfony воспользуйтесь следующими командами:

$ pear upgrade symfony/symfony

или

$ pear upgrade symfony/symfony-beta

Плюсы: очень простая установка, а также отсутствие производить махинации с переменной PATH/символическими ссылками; очень удобное и простое обновление.

Минусы: проблемы с установкой на компьютерах без интернета (я не зря это пишу, такие вопросы уже были).

Есть и еще один способ:

Установка sandbox’а

Sandbox - это “пустое” приложение Symfony вместе с самим фреймворком. Скачать его можно отсюда. Архив распаковываем (!!!) в директорию сервера, например: localhost у вас настроен на директорию c:\www\. Вы распаковали архив в директорию c:\www\test\ таким образом, что директория web у вас содержится в c:\www\test\web\. После этого запускаем сервер и заходим на адрес http://localhost/test/web/ и видим начальную страничку, сгенерированную Symfony.

Я сам в случае с Symfony 1.1 воспользуюсь именно sandbox’ом.

Ссылки:

  • У Макса появилась отличнейшая статья о том, что сейчас наблюдается в рунете с блогами с SMO. Знать бы, как с этим бороться…
  • Тем, кто использует Propel в Symfony -появился отличный плагин, упрощающий написание запросов через Criteria - sfPropelFinder.

written by FX Poster \\ tags: ,

Mar 27

Ну что ж, начнем повествование…

В Symfony 1.1, в отличии от предыдущей версии, полностью изменилась работа с формами (а также еще очень многими вещами, которых, я надеюсь, я коснусь позже) - вместо кучи кода, разбросанного по многим местам (которым, впрочем, вполне удобно пользоваться благодаря достаточно удобным view helper‘ам) появилось достаточно красиво спроектированная ОО-система, собирающая логику работы с формами в одном месте.

На That’s Quality не зря назвали серию постов “Forms, Widgets and Validators” - работа с формами предполагает знания именно об этих трех компонентах, которые объединены под общим названием Form Framework:

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

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

Как же это с этим всем великолепием работать, спросите вы? Ответы будут в следующих статьях, а пока же просто приведу пример использования формы.

Создаем саму форму в контроллере:

class userActions extends sfActions
{
  public function executeIndex()
  {
    $this->form = new RegisterForm();
  }
}

И выводим её во View:

<form method="post" action="<?php echo url_for('user/index') ?>">

<table cellspacing="0" cellpadding="0">
  <?php echo $form ?>
</table>

<div class="submit">
  <input type="submit" value="Submit" />
</div>

</form>

Что это еще за RegisterForm? Узнаете завтра, а пока что советую вспомнить, о чем я писал в статье про формы в Symfony 1.0. Вспомнили? Теперь стало понятно название RegisterForm? :)

written by FX Poster \\ tags: ,

Mar 26

Засел я сейчас за написание давно обещанной статьи про новый Form Framework, который будет включен в состав Symfony 1.1. Пока разобрался, что и как подключать - Symfony 1.1 Sandbox упорно не хотел работать с базой данных так, как нужно было бы, пока поглядел в кодах - что и как нужно делать (а то порядком уже подзабыл всё, что я смотрел при прошлом просмотре Symfony 1.1). В общем, решил я, что одной статьей дело не обойдется - слишком уж много всего рассказывать нужно, так что с завтрашнего дня я буду выкладывать одну статью в день, которые будут касаться форм в Symfony 1.1. Эти статьи можно будет считать “русским аналогом” вот этого цикла статей - это не будет перевод статей, скорее я буду руководствоваться тем, что написано там, чтобы знать, куда дальше двигаться в своем повествовании, при этом, возможно, останавливаясь на некоторых моментах, которые, на мой взгляд, достаточно важны для новичков в Symfony.

В общем, посмотрим… Завтра посмотрим. :)

PS. Сегодня, кстати, вышла вторая бета-версия Symfony 1.1, на которой я и буду тестировать всё, что я напишу. Список изменений по сравнению с Symfony 1.0 лежит здесь - достаточно занимательное чтиво.

written by FX Poster \\ tags: ,

Mar 21

Собственно, возникла проблема - заказчик хочет на сайте URL’ы определенного вида. Всё бы ничего - но то, что он хочет очень сильно расходится с тем, как работает сам сайт. Так вот, задание таково - нужно написать регулярку, которая бы выдавала true на всё, не включая (account|comment|user). В принципе регулярку такую я составил: /(?!(account|comment|user))/. Проблема в том, что рутинговая система накладывает свои ограничения на регулярки, а точнее приписывает к ним слева и справа свою фигню: '#^(?:\\/(<вот сюда можно вставлять всё, что угодно>))?$#'. Вопрос таков - можно ли что-то вставить в средину последней регулярки так, чтобы она вела себя как и моя, описанная выше. Если да - подскажите, что вставлять, а нет - так нет, буду через mod_rewrite решать проблему.

written by FX Poster