Pragmatism in the real world

Throw an exception when simplexml_load_string fails

I keep having to look up how to stop the warning that are emitted when simplexml_load_string & simplexml_load_file fail, so this time I’ve written the world’s simplest little class to take care of it for me from now on:

<?php

namespace Rka;

use UnexpectedValueException;

class Xml
{
    /**
     * Load an XML String and convert any warnings to an exception
     *
     * @param string  $string
     * @param string $class_name
     * @param int $options
     * @param string $ns
     * @param bool $is_prefix
     *
     * @throws UnexpectedValueException
     *
     * @return SimpleXMLElement
     */
    public static function loadXMLString(
        $string,
        $class_name = "SimpleXMLElement",
        $options = 0,
        $ns = "",
        $is_prefix = false
    ) {
        $previous = libxml_use_internal_errors(true);

        $xml = simplexml_load_string($string, $class_name, $options, $ns,
                 $is_prefix);

        if (!$xml) {
            $errors = self::getXMLErrorString();
            libxml_use_internal_errors($previous);
            throw new UnexpectedValueException($errors);
        }

        libxml_use_internal_errors($previous);
        return $xml;
    }

    /**
     * Load an XML File and convert any warnings to an exception
     *
     * @param string  $string
     * @param string $class_name
     * @param int $options
     * @param string $ns
     * @param bool $is_prefix
     *
     * @throws UnexpectedValueException
     *
     * @return SimpleXMLElement
     */
    public static function loadXMLFile(
        $string,
        $class_name = "SimpleXMLElement",
        $options = 0,
        $ns = "",
        $is_prefix = false
    ) {
        $previous = libxml_use_internal_errors(true);

        $xml = simplexml_load_file($string, $class_name, $options, $ns,
                 $is_prefix);

        if (!$xml) {
            $errors = self::getXMLErrorString();
            libxml_use_internal_errors($previous);
            throw new UnexpectedValueException($errors);
        }

        libxml_use_internal_errors($previous);
        return $xml;
    }

    /**
     * Helper method to format the XML errors into a string.
     *
     * @return string
     */
    protected static function getXMLErrorString()
    {
        $message = '';
        foreach (libxml_get_errors() as $error) {
            $message .= trim($error->message)
              . " on line: $error->line, column: $error->column.\n";
        }
        libxml_clear_errors();
        return trim($message);
    }
}

Update: The code has been updated based on the comments below. Thanks!

4 thoughts on “Throw an exception when simplexml_load_string fails

  1. Thanks for the code! I think it makes a lot of sense to use exceptions.

    There are two things I would like to suggest:

    * Use a more detailed exception: either "UnexpectedValueException", or maybe your custom XMLException (based on RunTimeException). This allows for an even more fine-grained exception catching (even though nothing else will throw an exception in the code, we shouldn't assume)

    * save the result from initial libxml_use_internal_errors() and restore this instead of returning it to false. There is no guarantee that this value was false to begin with, and in that case, this function will change external behaviour.

  2. Also, it's probably a good idea to restore the libxml_use_internal_errors even if the call fails, otherwise the function will again change external behavior on errors.

  3. Nice, I didn't know libxml_use_internal_errors suppresses the PHP warning. If you are using PHP 5.5+, the code can be further improved by adding a try/finally block so the libxml_use_internal_errors() value is also restored in case an exception is thrown.

    e.g.

    $previousInternalErrors = libxml_use_internal_errors(true);
    try {
      $xml = ...
      if(!$xml) {
        throw ...
      }
      return $xml;
    } finally {
      libxml_use_internal_errors($previousInternalErrors);
    }
    

Comments are closed.