Хитрый Exception

AnrDaemon

Продвинутый новичок
По ходу написания одной штучки понадобилось обернуть ошибку в исключение. Пока придумалось вот такое:
PHP:
final class XMLParserException extends Exception
{
  protected $srcFile;
  protected $srcLine;
  protected $srcColumn;
  protected $srcIndex;

  public function __construct($parser, SplFileObject $file = NULL)
  {
    $this->code = xml_get_error_code($parser);
    if(false === $this->code)
      throw new BadMethodCallException('This is not a valid xml_parser resource.');
    parent::__construct(xml_error_string($this->code), $this->code);
    $this->srcFile = $file ? $file->getPathname() : '(data stream)';
    $this->srcLine = xml_get_current_line_number($parser);
    $this->srcColumn = xml_get_current_column_number($parser);
    $this->srcIndex = xml_get_current_byte_index($parser);
  }

  public function __toString()
  {
    return "XML Parser error '{$this->message}' in {$this->srcFile}:{$this->srcIndex}({$this->srcLine},{$this->srcColumn})\n" .
      "Stack trace:\n" . $this->getTraceAsString();
  }
}
Вывод получается в виде
Код:
PHP Fatal error:  Uncaught XML Parser error 'Mismatched tag' in php://stdin:2540(22,21)
Stack trace:
#0 …/xml.php(95): xml->parse('        </secto...', false)
#1 …/xml.php(135): xml->parseFile('php://stdin')
#2 {main}
  thrown in …/xml.php on line 105
Это очень коряво?

P.S.
Пишу враппер для XML Parser. (XMLReader вообще непонятно как работает, и код с ним превращается в дикий ужас. XMLDOM не годится - входной поток может быть совсем любых размеров.)
 

Kotofey

FloodMaster.
Под твой обработчик ошибок похоже надо делать отдельный обработчик ошибок похоже :D
 

Redjik

Джедай-мастер
А почему нельзя перекинуть message в родительский конструктор?
Код:
$message =  "XML Parser error '{$this->message}' in {$this->srcFile}:{$this->srcIndex}({$this->srcLine},{$this->srcColumn});
 

Вурдалак

Продвинутый новичок
Я бы убрал зависимость от $parser и SplFileObject, передавая туда всю эту муть типа имени файла, номер строки, etc. через конструктор, формируя там message.
И я бы убрал __toString(), он не нужен.

да, у тебя грубое нарушение LSP
Где?
 

Redjik

Джедай-мастер
в общем случае да, в этом конкретном - ломается Exception Chaining
 

AnrDaemon

Продвинутый новичок
У этого исключения не может быть предыдущих исключений, оно первично с точки зрения PHP (сформировано в закрытом расширении при работе с внешними данными). Так что чейнинг отпадает. Могу конечно добавить, но это будет просто галочка, которая никогда не будет использоваться. Смысла eq. 0.
Думал сделать фабрику, но как выше сказали, это конструктор, используемый в единственном методе единственного класса.
То, что вы приняли за $message в __toString - это не месседж, это расшифровка (откуда и почему). Сам месседж - это, например, "Mismatched tag" с соответствующим кодом.
Спасибо за отзывы, пожалуй, так и оставлю. Только доступ до спецсвойств донесу.
Под твой обработчик ошибок похоже надо делать отдельный обработчик ошибок похоже
(Надеюсь, это была опечатка и ты имел в виду "ошибку" а не "обработчик".) На поверхности исключение вполне совместимо и может чейнится в другие исключения при необходимости.

Чего я прямо сейчас не понимаю, так это почему парсер постоянно показывает srcColumn на 2 больше, чем выходит по srcIndex…
PHP:
<?php

final class XmlParserError extends Exception
{
  protected $srcFile;
  protected $srcLine;
  protected $srcColumn;
  protected $srcIndex;

  public function __construct($parser, SplFileObject $file = NULL)
  {
    $this->code = xml_get_error_code($parser);
    if(false === $this->code)
      throw new BadMethodCallException('This is not a valid xml_parser resource.');

    parent::__construct(xml_error_string($this->code), $this->code);

    $this->srcFile = $file ? $file->getPathname() : '(data stream)';
    $this->srcLine = xml_get_current_line_number($parser);
    $this->srcColumn = xml_get_current_column_number($parser);
    $this->srcIndex = xml_get_current_byte_index($parser);
  }

  public function __get($name)
  {
    if(in_array($name, array('srcFile', 'srcLine', 'srcColumn', 'srcIndex')))
      return $this->$name;
  }

  public function __toString()
  {
    return "XML Parser error '{$this->message}' in {$this->srcFile}:{$this->srcIndex}({$this->srcLine},{$this->srcColumn})\n" .
      "Stack trace:\n" . $this->getTraceAsString();
  }
}

class XmlParser
{
  private $parser = NULL;
  private $file = NULL;
  private $indent = '';

  private function init()
  {
/*
xml_parser_get_option — Get options from an XML parser
xml_parser_set_option — Set options in an XML parser
*/
    $this->parser = xml_parser_create_ns();

    xml_set_object($this->parser, $this);

/*
xml_set_default_handler — Set up default handler

xml_set_processing_instruction_handler — Set up processing instruction (PI) handler
xml_set_start_namespace_decl_handler — Set up start namespace declaration handler
xml_set_end_namespace_decl_handler — Set up end namespace declaration handler
xml_set_external_entity_ref_handler — Set up external entity reference handler
xml_set_notation_decl_handler — Set up notation declaration handler
xml_set_unparsed_entity_decl_handler — Set up unparsed entity declaration handler
*/
    xml_set_element_handler($this->parser, "element_start", "element_end");
    xml_set_character_data_handler($this->parser, "cdata");
  }

  private function destroy()
  {
    if(xml_parser_free($this->parser))
    {
      $this->parser = NULL;
    }
    else
    {
      throw new RuntimeException('I know not what you did, but the parser I created may not be released.');
    }
    unset($this->file);
  }

  function parseString($data)
  {
    $this->init();
    $this->parse($data, true);
    $this->destroy();
    return true;
  }

  function parseFile($name)
  {
    $this->file = new SplFileObject($name, 'rb');
    $this->file->setMaxLineLen(4096);
    $this->file->setFlags(0);
    $this->init();
    foreach($this->file as $data)
      $this->parse($data, false);
    $this->parse(NULL, true);
    $this->destroy();
    return true;
  }

  private function parse($data, $final = false)
  {
    if(xml_parse($this->parser, $data, $final))
      return true;
    throw new XmlParserError($this->parser, $this->file);
  }

  private function element_start($self, $name, $attributes)
  {
    print $this->indent . "{$name} " . json_encode($attributes) . "\n";
    $this->indent .= "\t";
  }

  private function element_end($self, $name)
  {
    $this->indent = substr($this->indent, 0, -1);
    //print $this->indent . __METHOD__ . " {$name}\n";
  }

  private function cdata($self, $data)
  {
    if(trim($data))
      print $this->indent . __METHOD__ . " {$data}\n";
  }

  function __call($name, $arguments)
  {
    print __CLASS__ . ":$name = " . json_encode($arguments) . "\n";
    throw new BadMethodCallException('Unimplemented. Care to lend a hand?');
  }
}

$xml_parser = new XmlParser();
$xml_parser->parseString(<<<XML
<A ID='hallo'>\n  PHP\n</B>
XML
);
$xml_parser->parseFile("php://stdin");
 

AnrDaemon

Продвинутый новичок
А вообще жалко, что нельзя бросаться произвольными объектами…
PHP:
<?php

class Gauntlet
{
  protected $inscription;
  protected $size;

  public function __construct($inscription, $size)
  {
  }

  public function __toString()
  {
    return "gauntlet, inscribed '{$this->inscription}', of size {$this->size},";
  }
}

try
{
  throw new Gauntlet('FTW!', 'M');
}
catch(Gauntlet $g)
{
  print "I've caught a $g";
}
 

AnrDaemon

Продвинутый новичок
А это вообще нормально, что для чейнутых эксепшенов не вызвается __toString() ?
 

AnrDaemon

Продвинутый новичок
Ещё один глупый, но относящийся к теме вопрос.
У XML Parser есть такая фича, как "default handler". Если в XML встречается функциональность, не укладывающаяся в рамки базовой структуры, для неё либо вызывается назначенный обработчик, либо обработчик по умолчанию, либо… так, минуту. Да, если нет обработчика по умолчанию, генерится ошибка, но уже в процессе парсинга, если встречена отсылка на объявленную, но неподдерживаемую, функциональность.

Вопрос, что с этим делать? Генерить исключение, когда встречается объявление этого функционала, или пусть оно тупо игнорируется и кидается исключениями, если встречено этого функционала упоминание?
 

AnrDaemon

Продвинутый новичок
Всё больше склоняюсь к тому, что default_handler - зло.
PHP:
<?xml version="1.0" encoding="UTF-8"?>
<!DOCTYPE greeting [
  <!ENTITY err 'grrr' >
]>
<A ID='hallo'>
  PHP &err;
</A>
Все хэндлеры включены (вообще все, включая дефолт):
Код:
Declared ENTITY &err; = 'grrr'
A {"ID":"hallo"}
    #text: PHP
XML Parser error 'Unhandled declaration or undeclared entity encountered.' in (data stream):113(6,12)
Stack trace:
#0 [internal function]: org\rootdir\Wrapper\XmlParser->default_handler(Resource id #9, '&err;')
#1 …/XmlParser.php(111): xml_parse(Resource id #9, '<?xml version="...', 1)
#2 …/XmlParser.php(91): org\rootdir\Wrapper\XmlParser->parse('<?xml version="...', true)
#3 …/xml-test.php(130): org\rootdir\Wrapper\XmlParser->parseString('<?xml version="...')
#4 {main}
Выключаю дефолт (все остальные хэндлеры включены!):
Код:
Declared ENTITY &err; = 'grrr'
A {"ID":"hallo"}
    #text: PHP
    #text: grrr
/A
Распарсилось и подставилось без вопросов. Ни одного хэндлера, кроме element и CDATA не было вызвано.
 
Последнее редактирование:

AnrDaemon

Продвинутый новичок
Отгрузил вагон багов на трекер. Делаем ставки, как быстро их закроют как "воркинг аз интендед"?…
 
Сверху