diff --git a/documents/Changelog.txt b/documents/Changelog.txt index c3557c5..57076f7 100644 --- a/documents/Changelog.txt +++ b/documents/Changelog.txt @@ -1,11 +1,17 @@ [+]: new [~]: changed [-]: removed [#]: fixed [@]: internal +1.12.2: (2016-01-18) +Internal refactoring and performance regression fix. + + * [@] internal refactoring. Performances are normal. + + 1.12.1: (2016-01-16) '->times()' and '->each()' are bound to the context. $this behaves as expected. - * [@] internal refactoring. Performances are worst but code is much - better. + * [@] internal refactoring. Performances are worst but code is much + better. 1.12: diff --git a/source/FluidXml.php b/source/FluidXml.php index 3891aa1..9bb1986 100644 --- a/source/FluidXml.php +++ b/source/FluidXml.php @@ -38,7 +38,15 @@ * @license https://opensource.org/licenses/BSD-2-Clause */ -namespace FluidXml; +namespace FluidXml +{ + +use \FluidXml\Core\FluidInterface; +use \FluidXml\Core\FluidDocument; +use \FluidXml\Core\FluidContext; +use \FluidXml\Core\NewableTrait; +use \FluidXml\Core\ReservedCallTrait; +use \FluidXml\Core\ReservedCallStaticTrait; /** * Constructs a new FluidXml instance. @@ -122,114 +130,6 @@ function simplexml_to_string_without_headers(\SimpleXMLElement $element) return $dom->ownerDocument->saveXML($dom); } -interface FluidInterface -{ - /** - * Executes an XPath query. - * - * ```php - * $xml = fluidxml(); - - * $xml->query("/doc/book[@id='123']"); - * - * // Relative queries are valid. - * $xml->query("/doc")->query("book[@id='123']"); - * ``` - * - * @param string $xpath The XPath to execute. - * - * @return FluidContext The context associated to the DOMNodeList. - */ - public function query(...$xpath); - public function times($times, callable $fn = null); - public function each(callable $fn); - - /** - * Append a new node as child of the current context. - * - * ```php - * $xml = fluidxml(); - - * $xml->appendChild('title', 'The Theory Of Everything'); - * $xml->appendChild([ 'author' => 'S. Hawking' ]); - * - * $xml->appendChild('chapters', true)->appendChild('chapter', ['id'=> 1]); - * - * ``` - * - * @param string|array $child The child/children to add. - * @param string $value The child text content. - * @param bool $switchContext Whether to return the current context - * or the context of the created node. - * - * @return FluidContext The context associated to the DOMNodeList. - */ - public function appendChild($child, ...$optionals); - public function prependSibling($sibling, ...$optionals); - public function appendSibling($sibling, ...$optionals); - public function setAttribute(...$arguments); - public function setText($text); - public function appendText($text); - public function setCdata($text); - public function appendCdata($text); - public function remove(...$xpath); - public function xml($strip = false); - // Aliases: - public function add($child, ...$optionals); - public function prepend($sibling, ...$optionals); - public function insertSiblingBefore($sibling, ...$optionals); - public function append($sibling, ...$optionals); - public function insertSiblingAfter($sibling, ...$optionals); - public function attr(...$arguments); - public function text($text); -} - -trait ReservedCallTrait -{ - public function __call($method, $arguments) - { - $m = "{$method}_"; - - if (\method_exists($this, $m)) { - return $this->$m(...$arguments); - } - - throw new \Exception("Method '$method' not found."); - } -} - -trait ReservedCallStaticTrait -{ - public static function __callStatic($method, $arguments) - { - $m = "{$method}_"; - - if (\method_exists(static::class, $m)) { - return static::$m(...$arguments); - } - - throw new \Exception("Method '$method' not found."); - } -} - -trait NewableTrait -{ - // This method should be called 'new', - // but for compatibility with PHP 5.6 - // it is shadowed by the __callStatic() method. - public static function new_(...$arguments) - { - return new static(...$arguments); - } -} - -class FluidDocument -{ - public $dom; - public $xpath; - public $namespaces = []; -} - class FluidXml implements FluidInterface { use NewableTrait, @@ -537,6 +437,230 @@ protected function chooseContext($help_context, $new_context) } } +class FluidNamespace +{ + const ID = 'id' ; + const URI = 'uri' ; + const MODE = 'mode'; + + const MODE_IMPLICIT = 0; + const MODE_EXPLICIT = 1; + + private $config = [ self::ID => '', + self::URI => '', + self::MODE => self::MODE_EXPLICIT ]; + + public function __construct($id, $uri, $mode = 1) + { + if (\is_array($id)) { + $args = $id; + $id = $args[self::ID]; + $uri = $args[self::URI]; + + if (isset($args[self::MODE])) { + $mode = $args[self::MODE]; + } + } + + $this->config[self::ID] = $id; + $this->config[self::URI] = $uri; + $this->config[self::MODE] = $mode; + } + + public function id() + { + return $this->config[self::ID]; + } + + public function uri() + { + return $this->config[self::URI]; + } + + public function mode() + { + return $this->config[self::MODE]; + } + + public function querify($xpath) + { + $id = $this->id(); + + if ($id) { + $id .= ':'; + } + + // An XPath query may not start with a slash ('/'). + // Relative queries are an example '../target". + $new_xpath = ''; + + $nodes = \explode('/', $xpath); + + foreach ($nodes as $node) { + // An XPath query may have multiple slashes ('/') + // example: //target + if ($node) { + $new_xpath .= "{$id}{$node}"; + } + + $new_xpath .= '/'; + } + + // Removes the last appended slash. + return \substr($new_xpath, 0, -1); + } +} + +} // END OF NAMESPACE FluidXml + +namespace FluidXml\Core +{ + +use \FluidXml\FluidXml; +use \FluidXml\FluidNamespace; + +use function \FluidXml\is_an_xml_string; +use function \FluidXml\domnodes_to_string; + +interface FluidInterface +{ + /** + * Executes an XPath query. + * + * ```php + * $xml = fluidxml(); + + * $xml->query("/doc/book[@id='123']"); + * + * // Relative queries are valid. + * $xml->query("/doc")->query("book[@id='123']"); + * ``` + * + * @param string $xpath The XPath to execute. + * + * @return FluidContext The context associated to the DOMNodeList. + */ + public function query(...$xpath); + public function times($times, callable $fn = null); + public function each(callable $fn); + + /** + * Append a new node as child of the current context. + * + * ```php + * $xml = fluidxml(); + + * $xml->appendChild('title', 'The Theory Of Everything'); + * $xml->appendChild([ 'author' => 'S. Hawking' ]); + * + * $xml->appendChild('chapters', true)->appendChild('chapter', ['id'=> 1]); + * + * ``` + * + * @param string|array $child The child/children to add. + * @param string $value The child text content. + * @param bool $switchContext Whether to return the current context + * or the context of the created node. + * + * @return FluidContext The context associated to the DOMNodeList. + */ + public function appendChild($child, ...$optionals); + public function prependSibling($sibling, ...$optionals); + public function appendSibling($sibling, ...$optionals); + public function setAttribute(...$arguments); + public function setText($text); + public function appendText($text); + public function setCdata($text); + public function appendCdata($text); + public function remove(...$xpath); + public function xml($strip = false); + // Aliases: + public function add($child, ...$optionals); + public function prepend($sibling, ...$optionals); + public function insertSiblingBefore($sibling, ...$optionals); + public function append($sibling, ...$optionals); + public function insertSiblingAfter($sibling, ...$optionals); + public function attr(...$arguments); + public function text($text); +} + +trait ReservedCallTrait +{ + public function __call($method, $arguments) + { + $m = "{$method}_"; + + if (\method_exists($this, $m)) { + return $this->$m(...$arguments); + } + + throw new \Exception("Method '$method' not found."); + } +} + +trait ReservedCallStaticTrait +{ + public static function __callStatic($method, $arguments) + { + $m = "{$method}_"; + + if (\method_exists(static::class, $m)) { + return static::$m(...$arguments); + } + + throw new \Exception("Method '$method' not found."); + } +} + +trait NewableTrait +{ + // This method should be called 'new', + // but for compatibility with PHP 5.6 + // it is shadowed by the __callStatic() method. + public static function new_(...$arguments) + { + return new static(...$arguments); + } +} + +class FluidDocument +{ + public $dom; + public $xpath; + public $namespaces = []; +} + +class FluidRepeater +{ + private $document; + private $context; + private $times; + + public function __construct($document, $context, $times) + { + $this->document = $document; + $this->context = $context; + $this->times = $times; + } + + public function __call($method, $arguments) + { + $nodes = []; + $new_context = $this->context; + + for ($i = 0, $l = $this->times; $i < $l; ++$i) { + $new_context = $this->context->$method(...$arguments); + $nodes = \array_merge($nodes, $new_context->asArray()); + } + + if ($new_context !== $this->context) { + return new FluidContext($this->document, $nodes); + } + + return $this->context; + } +} + class FluidContext implements FluidInterface, \ArrayAccess, \Iterator { use NewableTrait, @@ -1301,107 +1425,4 @@ protected function insertIntegerFluidcontext($parent, $k, $v, $fn) } } -class FluidNamespace -{ - const ID = 'id' ; - const URI = 'uri' ; - const MODE = 'mode'; - - const MODE_IMPLICIT = 0; - const MODE_EXPLICIT = 1; - - private $config = [ self::ID => '', - self::URI => '', - self::MODE => self::MODE_EXPLICIT ]; - - public function __construct($id, $uri, $mode = 1) - { - if (\is_array($id)) { - $args = $id; - $id = $args[self::ID]; - $uri = $args[self::URI]; - - if (isset($args[self::MODE])) { - $mode = $args[self::MODE]; - } - } - - $this->config[self::ID] = $id; - $this->config[self::URI] = $uri; - $this->config[self::MODE] = $mode; - } - - public function id() - { - return $this->config[self::ID]; - } - - public function uri() - { - return $this->config[self::URI]; - } - - public function mode() - { - return $this->config[self::MODE]; - } - - public function querify($xpath) - { - $id = $this->id(); - - if ($id) { - $id .= ':'; - } - - // An XPath query may not start with a slash ('/'). - // Relative queries are an example '../target". - $new_xpath = ''; - - $nodes = \explode('/', $xpath); - - foreach ($nodes as $node) { - // An XPath query may have multiple slashes ('/') - // example: //target - if ($node) { - $new_xpath .= "{$id}{$node}"; - } - - $new_xpath .= '/'; - } - - // Removes the last appended slash. - return \substr($new_xpath, 0, -1); - } -} - -class FluidRepeater -{ - private $document; - private $context; - private $times; - - public function __construct($document, $context, $times) - { - $this->document = $document; - $this->context = $context; - $this->times = $times; - } - - public function __call($method, $arguments) - { - $nodes = []; - $new_context = $this->context; - - for ($i = 0, $l = $this->times; $i < $l; ++$i) { - $new_context = $this->context->$method(...$arguments); - $nodes = \array_merge($nodes, $new_context->asArray()); - } - - if ($new_context !== $this->context) { - return new FluidContext($this->document, $nodes); - } - - return $this->context; - } -} +} // END OF NAMESPACE FluidXml\Core diff --git a/specs/.common.php b/specs/.common.php index 49867e4..ff830de 100644 --- a/specs/.common.php +++ b/specs/.common.php @@ -4,6 +4,9 @@ $source_dir = __DIR__ . "{$ds}..{$ds}source"; \set_include_path($source_dir . \PATH_SEPARATOR . \get_include_path()); +use \FluidXml\FluidXml; +use \FluidXml\Core\FluidInterface; + function __($actual, $expected) { $v = [ 'actual' => \var_export($actual, true), @@ -36,11 +39,11 @@ function assert_is_a($actual, $expected) function assert_is_fluid($method, ...$args) { - $instance = new \FluidXml\FluidXml(); + $instance = new FluidXml(); if (\method_exists($instance, $method)) { $actual = \call_user_func([$instance, $method], ...$args); - $expected = \FluidXml\FluidInterface::class; + $expected = FluidInterface::class; assert_is_a($actual, $expected); } @@ -48,7 +51,7 @@ function assert_is_fluid($method, ...$args) if (\method_exists($instance, $method)) { $actual = \call_user_func([$instance, $method], ...$args); - $expected = \FluidXml\FluidInterface::class; + $expected = FluidInterface::class; assert_is_a($actual, $expected); } } diff --git a/specs/FluidXml.php b/specs/FluidXml.php index 1ae3487..098d9c1 100644 --- a/specs/FluidXml.php +++ b/specs/FluidXml.php @@ -3,10 +3,10 @@ require_once __DIR__ . DIRECTORY_SEPARATOR . '.common.php'; use \FluidXml\FluidXml; -use \FluidXml\FluidContext; use \FluidXml\FluidNamespace; -use \FluidXml\FluidDocument; -use \FluidXml\FluidRepeater; +use \FluidXml\Core\FluidContext; +use \FluidXml\Core\FluidDocument; +use \FluidXml\Core\FluidRepeater; use function \FluidXml\fluidxml; use function \FluidXml\fluidns; use function \FluidXml\fluidify;