Skip to content

Commit

Permalink
Initial commit in
Browse files Browse the repository at this point in the history
  • Loading branch information
BorislavSabev committed Jul 3, 2017
1 parent d071629 commit eb5e1c1
Show file tree
Hide file tree
Showing 6 changed files with 317 additions and 0 deletions.
5 changes: 5 additions & 0 deletions .gitignore
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
# General entries

# IntelliJ project files
.idea
*.iml
109 changes: 109 additions & 0 deletions README.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,109 @@
# SimpleXMlLoader
A simple wrapper around SimpleXML's interface for loading XML resources or string data.

Main goals:
- provide a transparent OOP interface to SimpleXML's `simplexml_load_file()` and `simplexml_load_string()` functions
- handle SimpleXML's internal errors
- follow LibXMl's functional API
- throw catchable Exceptions (XmlLoaderException)

This wrapper will:
- try to load your XML resource into a \SimpleXMLElement object
- handle any LibXML errors that occur during loading

This wrapper will not:
- validate your XML
- do any operation over the \SimpleXMLElement

# Basic usage
There are two main functions in the SimpleXMlLoader:
```
public function loadFile($filename, $class_name = "SimpleXMLElement", $options = 0, $ns = "", $is_prefix = false);
public function loadString($data, $class_name = "SimpleXMLElement", $options = 0, $ns = "", $is_prefix = false);
```
**NOTE:** Notice that these functions follow the signature of `simplexml_load_file()` and `simplexml_load_string()`. You should check the docs of [simplexml_load_file()](http://php.net/manual/en/function.simplexml-load-file.php) and [simplexml_load_string()](http://php.net/manual/en/function.simplexml-load-string.php) for details. And NO the author does not like the function parameters' names :).

The loader throws XmlLoaderException on any error that occurs you should always wrap it in a try/catch block and handle any errors yourself:
```
use BorislavSabev\SimpleXmlLoader\XmlLoader;
use BorislavSabev\SimpleXmlLoader\Exception\XmlLoaderException;
$xmlLoader = new XmlLoader;
try {
/** @var \SimpleXMLElement $simpleXmlElement */
$simpleXmlElement = $xmlLoader->loadFile($filename);
//Now do something with the loaded \SimpleXMLElement...
} catch (XmlLoaderException $e) {
/** @var array $xmlErrors */
$xmlErrors = $xmlLoader->getXmlPayload()
->getXmlErrors();
}
```

The XmlLoader instance is meant to be reused thus:
- Each call to `XmlLoader->loadFile()` or `XmlLoader->loadString()` will clear any LibXml errors stored
- Each call to `XmlLoader->loadFile()` or `XmlLoader->loadString()` will replace it's internal XmlPayload object

## Using it in a loop
As each consecutive call to a loader method resets the state of LibXML and the payload you must extract all the data that you need between consecutive calls.

```
use BorislavSabev\SimpleXmlLoader\XmlLoader;
use BorislavSabev\SimpleXmlLoader\Exception\XmlLoaderException;
$xmlLoader = new XmlLoader;
foreach ($aBunchOfXmlStrings as $xmlString {
try {
/** @var \SimpleXMLElement $simpleXmlElement */
$simpleXmlElement = $xmlLoader->loadString($xmlString);
} catch (XmlLoaderException $e) {
/** @var array $xmlErrors */
$xmlErrors = $xmlLoader->getXmlPayload()
->getXmlErrors();
}
//Any data within LibXML or our XmlPayload will be lost after this iteration of the loop
}
```

# Reusing XmlPayload
You can also reuse the XmlPayload in your application if you which. Say for example you use this wrapper to parse different XML file but then want to pass the result to different Services in your code that will handle any business logic. You could just get the XmlPayload and pass it along to a specific service (or whatever really):

```
use BorislavSabev\SimpleXmlLoader\XmlLoader;
use BorislavSabev\SimpleXmlLoader\Exception\XmlLoaderException;
$xmlLoader = new XmlLoader;
try {
$xmlLoader->loadFile($filename);
/** @var XmlPayload $xmlPayload */
$xmlPayload = $xmlLoader->getXmlPayload();
//Generic example:
$serviceBroker->pass(
MyCoolService::class,
$xmlPayload
);
//Now do something with the loaded \SimpleXMLElement...
} catch (XmlLoaderException $e) {
/** @var array $xmlErrors */
$xmlErrors = $xmlLoader->getXmlPayload()
->getXmlErrors();
}
```
**NOTE:** Generally you should have you own payload objects to pass data around in your Domain. The idea of XmlPayload is to be internal for XmlLoader thus it cannot contain any logic outside of that task.

# The XmlLoaderException and LibXML's libXMLError
XmlLoaderException's codes are specific to this wrapper.
LibXML's libXMLError objects are just returned in an array and as received from SimpleXML/LibXML.

# Personal Opinion on the SimpleXML PHP Extension
Generally SimpleXML is not a solid PHP extension and, in my mind, it should be used rarely when you need to do something simple fast. Any serious work should be done via DomDocument.
The previous two sentences are the author's personal opinion which as with any opinion should be taken with a grain of salt.

# Contributing
Please do! PR's are very welcome. The author is far from thinking that this wrapper library is perfect.
18 changes: 18 additions & 0 deletions composer.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,18 @@
{
"name": "borislavsabev/simple-xml-loader",
"description": "A simple wrapper library around SimpleXml's simplexml_load_file() and simplexml_load_string() functions. Provides LibXMLError handling.",
"type": "library",
"require": {
"ext-SimpleXML": "^0.1.0"
},
"require-dev": {
"phpunit/phpunit": "^6.2"
},
"authors": [
{
"name": "Borislav Sabev",
"email": "[email protected]"
}
],
"minimum-stability": "dev"
}
18 changes: 18 additions & 0 deletions src/Exception/XmlLoaderException.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,18 @@
<?php


namespace BorislavSabev\SimpleXmlLoader\Exception;

/**
* Class XmlLoaderException
*
* @project SimpleXmlLoader
* @package BorislavSabev\SimpleXmlLoader
* @author Borislav Sabev <[email protected]>
*/
class XmlLoaderException extends \RuntimeException
{
const ERROR_CODE_FILE = 1;
const ERROR_CODE_STRING = 2;
const ERROR_CODE_XML_ERRORS = 3;
}
99 changes: 99 additions & 0 deletions src/XmlLoader.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,99 @@
<?php

namespace BorislavSabev\SimpleXmlLoader;

use BorislavSabev\SimpleXmlLoader\XmlPayload;
use BorislavSabev\SimpleXmlLoader\Exception\XmlLoaderException;


/**
* Class XmlLoader
*
* @project SimpleXmlLoader
* @package BorislavSabev\SimpleXmlLoader
* @author Borislav Sabev <[email protected]>
*/
class XmlLoader
{
/** @var \BorislavSabev\SimpleXmlLoader\XmlPayload|null The XmlPayload object */
private $xmlPayload = null;

/**
* XmlLoader constructor.
*/
public function __construct()
{
libxml_use_internal_errors(true);
}

/**
* Interprets an XML file into an object
* Follows simplexml_load_file()'s method signature
*
* @param $filename
* @param string $class_name
* @param int $options
* @param string $ns
* @param bool $is_prefix
* @return \SimpleXMLElement
*/
public function loadFile($filename, $class_name = "SimpleXMLElement", $options = 0, $ns = "", $is_prefix = false)
{
if (!file_exists($filename)) {
throw new XmlLoaderException("(File does not exist: {$filename}", XmlLoaderException::ERROR_CODE_FILE);
}

libxml_clear_errors();
$this->xmlPayload = new XmlPayload();
$this->xmlPayload->xmlFilename = basename($filename);
$this->xmlPayload->xmlElement = simplexml_load_file($filename, $class_name, $options, $ns, $is_prefix);

if ($this->xmlPayload->xmlElement === false) {
$this->handleLibXmlErrors();
}

return $this->xmlPayload->getXmlElement();
}

/**
* Interprets a string of XML into an object
* Follows simplexml_load_string()'s method signature
* @param $data
* @param string $class_name
* @param int $options
* @param string $ns
* @param bool $is_prefix
* @throws XmlLoaderException
* @return \SimpleXMLElement
*/
public function loadString($data, $class_name = "SimpleXMLElement", $options = 0, $ns = "", $is_prefix = false)
{
libxml_clear_errors();
$this->xmlPayload = new XmlPayload();
$this->xmlPayload->xmlElement = simplexml_load_string($data, $class_name, $options, $ns, $is_prefix);
if ($this->xmlPayload->xmlElement === false) {
$this->handleLibXmlErrors();
}

return $this->xmlPayload->getXmlElement();
}

/**
* Get the XmlPayload object or null if you did not call any of the loaders
* @return \BorislavSabev\SimpleXmlLoader\XmlPayload|null
*/
public function getXmlPayload()
{
return $this->xmlPayload;
}

/**
* Handle LibXml errors internally
* @throws XmlLoaderException
*/
private function handleLibXmlErrors()
{
$this->xmlPayload->loadErrors(libxml_get_errors());
throw new XmlLoaderException('Encountered LibXML errors', XmlLoaderException::ERROR_CODE_XML_ERRORS);
}
}
68 changes: 68 additions & 0 deletions src/XmlPayload.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,68 @@
<?php
namespace BorislavSabev\SimpleXmlLoader;

/**
* Class XmlPayload
*
* A payload for LibXML's stuff
*
* @project SimpleXmlLoader
* @package BorislavSabev\SimpleXmlLoader
* @author Borislav Sabev <[email protected]>
*/
class XmlPayload
{
/** @var null|string XML file's filename if any */
public $xmlFilename = null;
/** @var null|\SimpleXMLElement The resulting XML element as returned by SimpleXml */
public $xmlElement = null;
/** @var bool Bit showing if there are errors on load */
public $xmlHasErrors = false;
/** @var array An array of LibXMLError objects if there are any */
public $xmlErrors = [];

/**
* Get the loaded \SimpleXMLElement
* @return null|\SimpleXMLElement
*/
public function getXmlElement()
{
return $this->xmlElement;
}

/**
* Get an array with LibXMLError objects if there are any; False otherwise
* @return array|bool
*/
public function getXmlErrors()
{
if (empty($this->xmlErrors)) {
return false;
}

return $this->xmlErrors;
}

/**
* Does the payload currently contain errors?
* @return bool
*/
public function hasErrors()
{
return $this->xmlHasErrors;
}

/**
* Load errors into the payload
* @param $xmlErrors
*/
public function loadErrors($xmlErrors)
{
if (!empty($xmlErrors)) {
$this->xmlHasErrors = true;
foreach ($xmlErrors as $xmlError) {
$this->xmlErrors[] = $xmlError;
}
}
}
}

0 comments on commit eb5e1c1

Please sign in to comment.