Skip to content

Commit

Permalink
Merge pull request #4 from xp-framework/refactor/reflection
Browse files Browse the repository at this point in the history
Refactor to use new reflection library
  • Loading branch information
thekid authored Mar 29, 2024
2 parents 09c6e53 + 6afde75 commit 9d93641
Show file tree
Hide file tree
Showing 12 changed files with 159 additions and 157 deletions.
1 change: 1 addition & 0 deletions composer.json
Original file line number Diff line number Diff line change
Expand Up @@ -8,6 +8,7 @@
"require" : {
"xp-framework/core": "^12.0 | ^11.0 | ^10.0",
"xp-framework/collections": "^10.0 | ^9.0 | ^8.0",
"xp-framework/reflection": "^3.0 | ^2.0",
"php" : ">=7.0.0"
},
"require-dev" : {
Expand Down
2 changes: 1 addition & 1 deletion src/main/php/xml/DomXSLProcessor.class.php
Original file line number Diff line number Diff line change
Expand Up @@ -250,7 +250,7 @@ public function getMessages() {
* @param string name
* @param object instance
*/
function registerInstance($name, $instance) {
public function registerInstance($name, $instance) {
$this->_instances[$name]= $instance;
}

Expand Down
29 changes: 16 additions & 13 deletions src/main/php/xml/XSLCallback.class.php
Original file line number Diff line number Diff line change
@@ -1,11 +1,11 @@
<?php namespace xml;

use lang\ElementNotFoundException;
use lang\{ElementNotFoundException, IllegalArgumentException, Reflection};

/**
* XSL callback class.
*
* @ext dom
* @ext xsl
* @test xp://xml.unittest.XslCallbackTest
* @see php://xslt_registerphpfunctions
*/
Expand Down Expand Up @@ -33,12 +33,17 @@ public static function getInstance() {
* @param object instance
*/
public function registerInstance($name, $instance) {
$this->instances[$name]= $instance;
$methods= [];
foreach (Reflection::type($instance)->methods()->annotated(Xslmethod::class) as $method => $_) {
$methods[$method]= true;
}
$this->instances[$name]= [$instance, $methods];
}

/**
* Remove all registered instances
*
* @return void
*/
public function clearInstances() {
$this->instances= [];
Expand All @@ -47,24 +52,22 @@ public function clearInstances() {
/**
* Invoke method on a registered instance.
*
* @param string instancename
* @param string methodname
* @param var* method arguments
* @param string $name
* @param string $method
* @param var... $arguments
* @return var
* @throws lang.IllegalArgumentException if the instance is not known
* @throws lang.ElementNotFoundException if the given method does not exist or is not xsl-accessible
*/
public static function invoke($name, $method, ...$args) {
if (!isset(self::$instance->instances[$name])) throw new \lang\IllegalArgumentException(
'No such registered XSL callback instance: "'.$name.'"'
);
if (null === ($instance= self::$instance->instances[$name] ?? null)) {
throw new IllegalArgumentException('No such registered XSL callback instance: "'.$name.'"');
}

$instance= self::$instance->instances[$name];
if (!(typeof($instance)->getMethod($method)->hasAnnotation('xslmethod'))) {
if (!isset($instance[1][$method])) {
throw new ElementNotFoundException('Instance "'.$name.'" does not have method "'.$method.'"');
}

// Call callback method
return $instance->{$method}(...$args);
return $instance[0]->{$method}(...$args);
}
}
87 changes: 44 additions & 43 deletions src/main/php/xml/meta/Marshaller.class.php
Original file line number Diff line number Diff line change
@@ -1,6 +1,8 @@
<?php namespace xml\meta;

use xml\{QName, Tree, XMLFormatException};
use Traversable;
use lang\{Reflection, IllegalArgumentException};
use xml\{QName, Tree, Node, XMLFormatException, Xmlfactory, Xmlns};

/**
* Marshalls XML from objects by using annotations.
Expand All @@ -12,7 +14,7 @@
* $xml= Marshaller::marshal($transmission);
* ```
*
* @test xp://xml.unittest.MarshallerTest
* @test xml.unittest.MarshallerTest
* @ext dom
* @see http://castor.org/xml-mapping.html
*/
Expand All @@ -21,40 +23,40 @@ class Marshaller {
/**
* Iterate over class methods with @xmlfactory annotation
*
* @param object instance
* @param lang.XPClass class
* @param xml.Node node
* @param [:var] inject
* @param object $instance
* @param lang.reflection.Type $type
* @param xml.Node $node
* @param [:var] $inject
*/
protected static function recurse($instance, $class, $node, $inject) {
protected static function recurse($instance, $type, $node, $inject) {

// Calculate element name
if ('' == $node->getName()) {
if ($class->hasAnnotation('xmlfactory', 'element')) {
$node->setName($class->getAnnotation('xmlfactory', 'element'));
if (null === $node->getName()) {
if (($factory= $type->annotation(Xmlfactory::class)) && ($element= $factory->argument('element'))) {
$node->setName($element);
} else {
$node->setName(strtolower($class->getSimpleName()));
$node->setName(strtolower($type->class()->getSimpleName()));
}
}

// Namespace handling
if ($class->hasAnnotation('xmlns')) {
$node->setName(key($class->getAnnotation('xmlns')).':'.$node->getName());
foreach ($class->getAnnotation('xmlns') as $prefix => $url) {
if ($xmlns= $type->annotation(Xmlns::class)) {
$map= $xmlns->arguments();
$node->setName(key($map).':'.$node->getName());
foreach ($map as $prefix => $url) {
$node->setAttribute('xmlns:'.$prefix, $url);
}
}

foreach ($class->getMethods() as $method) {
if (!$method->hasAnnotation('xmlfactory', 'element')) continue;

$element= $method->getAnnotation('xmlfactory', 'element');
foreach ($type->methods()->annotated(Xmlfactory::class) as $method) {
$annotation= $method->annotation(Xmlfactory::class);
if (null === ($element= $annotation->argument('element'))) continue;

// Pass injection parameters at end of list
$arguments= [];
if ($method->hasAnnotation('xmlfactory', 'inject')) {
foreach ($method->getAnnotation('xmlfactory', 'inject') as $name) {
if (!isset($inject[$name])) throw new \lang\IllegalArgumentException(
if ($injection= $annotation->argument('inject')) {
foreach ($injection as $name) {
if (!isset($inject[$name])) throw new IllegalArgumentException(
'Injection parameter "'.$name.'" not found for '.$method->toString()
);
$arguments[]= $inject[$name];
Expand All @@ -64,14 +66,13 @@ protected static function recurse($instance, $class, $node, $inject) {
$result= $method->invoke($instance, $arguments);

// Cast result if specified
if ($method->hasAnnotation('xmlfactory', 'cast')) {
$cast= $method->getAnnotation('xmlfactory', 'cast');
if ($cast= $annotation->argument('cast')) {
switch (sscanf($cast, '%[^:]::%s', $c, $m)) {
case 1: $target= [$instance, $c]; break;
case 2: $target= [$c, $m]; break;
default: throw new \lang\IllegalArgumentException('Unparseable cast "'.$cast.'"');
default: throw new IllegalArgumentException('Unparseable cast "'.$cast.'"');
}
$result= call_user_func([$instance, $method->getAnnotation('xmlfactory', 'cast')], $result);
$result= $target($result);
}

// Attributes = "@<name>", Node content= ".", Name = "name()"
Expand Down Expand Up @@ -100,22 +101,22 @@ protected static function recurse($instance, $class, $node, $inject) {
// - For objects, add a new node and invoke the recurse() method
// on it.
if (is_scalar($result) || null === $result) {
$node->addChild(new \xml\Node($element, $result));
$node->addChild(new Node($element, $result));
} else if (is_array($result)) {
$child= $node->addChild(new \xml\Node($element));
$child= $node->addChild(new Node($element));
foreach ($result as $key => $val) {
$child->addChild(new \xml\Node($key, $val));
$child->addChild(new Node($key, $val));
}
} else if ($result instanceof \Traversable) {
} else if ($result instanceof Traversable) {
foreach ($result as $value) {
if (is_object($value)) {
self::recurse($value, typeof($value), $node->addChild(new \xml\Node($element)), $inject);
self::recurse($value, Reflection::type($value), $node->addChild(new Node($element)), $inject);
} else {
$node->addChild(new \xml\Node($element, $value));
$node->addChild(new Node($element, $value));
}
}
} else if (is_object($result)) {
self::recurse($result, typeof($result), $node->addChild(new \xml\Node($element)), $inject);
self::recurse($result, Reflection::type($result), $node->addChild(new Node($element)), $inject);
}
}
}
Expand All @@ -129,7 +130,7 @@ protected static function recurse($instance, $class, $node, $inject) {
* @deprecated Use marshalTo() instead
*/
public static function marshal($instance, $qname= null) {
$class= typeof($instance);
$type= Reflection::type($instance);

// Create XML tree and root node. Use the information provided by the
// qname argument if existant, use the class` non-qualified (and
Expand All @@ -139,31 +140,31 @@ public static function marshal($instance, $qname= null) {
$prefix= $qname->prefix ? $qname->prefix : $qname->localpart[0];
$tree->root()->setName($prefix.':'.$qname->localpart);
$tree->root()->setAttribute('xmlns:'.$prefix, $qname->namespace);
} else if ($class->hasAnnotation('xmlns')) {
$tree->root()->setName($class->getSimpleName());
} else if ($type->annotation(Xmlns::class)) {
$tree->root()->setName($type->class()->getSimpleName());
} else {
$tree->root()->setName(strtolower($class->getSimpleName()));
$tree->root()->setName(strtolower($type->class()->getSimpleName()));
}

self::recurse($instance, $class, $tree->root(), []);
self::recurse($instance, $type, $tree->root(), []);
return $tree->getSource(INDENT_DEFAULT);
}

/**
* Marshal an object to xml
*
* @param xml.Node target
* @param ?xml.Node target
* @param object $instance
* @param [:var] inject
* @return xml.Node the given target
*/
public function marshalTo(\xml\Node $target= null, $instance, $inject= []) {
$class= typeof($instance);
public function marshalTo($target= null, $instance, $inject= []) {
$type= Reflection::type($instance);

// Create node if not existant
if (null === $target) $target= new \xml\Node(null);
$target ?? $target= new Node(null);

self::recurse($instance, $class, $target, $inject);
self::recurse($instance, $type, $target, $inject);
return $target;
}
}
Loading

0 comments on commit 9d93641

Please sign in to comment.