diff --git a/composer.json b/composer.json index 553610c..92e62ab 100755 --- a/composer.json +++ b/composer.json @@ -7,6 +7,7 @@ "keywords": ["module", "xp"], "require" : { "xp-framework/core": "^11.0 | ^10.0 | ^9.0 | ^8.0 | ^7.0", + "xp-framework/reflection": "^2.0", "xp-forge/web": "^3.0 | ^2.0 | ^1.0", "xp-forge/marshalling": "^1.0 | ^0.3 | ^0.2", "xp-forge/json": "^5.0 | ^4.0 | ^3.1", diff --git a/src/main/php/web/rest/Delegate.class.php b/src/main/php/web/rest/Delegate.class.php index e1b5aa6..223fa0a 100755 --- a/src/main/php/web/rest/Delegate.class.php +++ b/src/main/php/web/rest/Delegate.class.php @@ -1,8 +1,8 @@ instance= $instance; - $this->method= $method; - foreach ($method->getParameters() as $param) { + $this->method= $method instanceof Method ? $method : Reflection::type($instance)->method($method); + foreach ($this->method->parameters() as $param) { // Source explicitely set by annotation - foreach ($param->getAnnotations() as $source => $name) { - if (isset(self::$SOURCES[$source])) { - $this->param($param, $name ?? $param->getName(), $source); + foreach ($param->annotations() as $annotation) { + if ($accessor= self::$SOURCES[$annotation->name()] ?? null) { + $this->param($param, $name ?? $param->name(), $accessor); continue 2; } } // Source derived from parameter type - $type= $param->getType(); - if ('var' === $type->getName()) { + $type= $param->constraint()->type(); + if (Type::$VAR === $type) { // NOOP } else if ($type->isAssignableFrom(InputStream::class)) { $source= 'stream'; } else if ($type->isAssignableFrom(Request::class)) { $source= 'request'; } - $this->param($param, $param->getName(), $source); + $this->param($param, $param->name(), self::$SOURCES[$source]); } } /** - * Adds parameter request reader for a given parameter + * Adds parameter request accessor for a given parameter * - * @param lang.reflect.Parameter $param + * @param lang.reflection.Parameter $param * @param string $name - * @param function(web.Request, web.rest.format.EntityFormat, string): var $source + * @param function(web.Request, web.rest.format.EntityFormat, string): var $accessor * @return void + * @throws lang.IllegalArgumentException */ - private function param($param, $name, $source) { - $extract= self::$SOURCES[$source]; - - if ($param->isOptional()) { - $default= $param->getDefaultValue(); - $read= function($req, $format) use($extract, $name, $default) { - return $extract($req, $format, $name) ?? $default; + private function param($param, $name, $accessor) { + if ($param->optional()) { + $default= $param->default(); + $read= function($req, $format) use($accessor, $name, $default) { + return $accessor($req, $format, $name) ?? $default; }; } else { - $read= function($req, $format) use($extract, $name) { - if (null === ($value= $extract($req, $format, $name))) { + $read= function($req, $format) use($accessor, $name) { + if (null === ($value= $accessor($req, $format, $name))) { throw new IllegalArgumentException('Missing argument '.$name); } return $value; }; } - $this->params[$name]= ['type' => $param->getType(), 'read' => $read]; + $this->params[$name]= ['type' => $param->constraint()->type(), 'read' => $read]; } /** @return string */ - public function name() { return nameof($this->instance).'::'.$this->method->getName(); } + public function name() { return nameof($this->instance).'::'.$this->method->name(); } - /** @return [:var] */ - public function annotations() { return $this->method->getAnnotations(); } + /** @return lang.reflection.Annotations */ + public function annotations() { return $this->method->annotations(); } /** @return [:var] */ public function params() { return $this->params; } @@ -106,7 +105,7 @@ public function params() { return $this->params; } public function invoke($args) { try { return $this->method->invoke($this->instance, $args); - } catch (TargetInvocationException $e) { + } catch (TargetException $e) { throw $e->getCause(); } } diff --git a/src/main/php/web/rest/Delegates.class.php b/src/main/php/web/rest/Delegates.class.php index 86b871d..5a7305d 100755 --- a/src/main/php/web/rest/Delegates.class.php +++ b/src/main/php/web/rest/Delegates.class.php @@ -1,6 +1,6 @@ getMethods() as $method) { - foreach (array_intersect_key($method->getAnnotations(), self::$METHODS) as $verb => $segment) { + foreach (Reflection::type($instance)->methods() as $method) { + + foreach ($method->annotations() as $annotation) { + $verb= $annotation->name(); + if (null === ($source= self::$METHODS[$verb] ?? null)) continue; + + $segment= $annotation->argument(0); if (null === $segment) { $pattern= $base.'(/.+)?'; } else if ('/' === $segment || '' === $segment) { @@ -40,7 +45,7 @@ public function with($instance, $base= '/') { } else { $pattern= $base.preg_replace(['/\{([^:}]+):([^}]+)\}/', '/\{([^}]+)\}/'], ['(?<$1>$2)', '(?<$1>[^/]+)'], $segment); } - $this->patterns['#^'.$verb.$pattern.'$#']= new Delegate($instance, $method, self::$METHODS[$verb]); + $this->patterns['#^'.$verb.$pattern.'$#']= new Delegate($instance, $method, $source); } } return $this; diff --git a/src/main/php/web/rest/MethodsIn.class.php b/src/main/php/web/rest/MethodsIn.class.php index 358c02c..a013242 100755 --- a/src/main/php/web/rest/MethodsIn.class.php +++ b/src/main/php/web/rest/MethodsIn.class.php @@ -1,5 +1,7 @@ with($instance, $class->hasAnnotation('resource') ? $class->getAnnotation('resource') ?? '' : '/'); + $class= Reflection::type($instance); + if ($annotation= $class->annotation(Resource::class)) { + $this->with($instance, (string)$annotation->argument(0)); + } else { + $this->with($instance, '/'); + } uksort($this->patterns, function($a, $b) { return strlen($b) - strlen($a); }); } } \ No newline at end of file diff --git a/src/main/php/web/rest/ResourcesIn.class.php b/src/main/php/web/rest/ResourcesIn.class.php index 342f58c..9eb6e4a 100755 --- a/src/main/php/web/rest/ResourcesIn.class.php +++ b/src/main/php/web/rest/ResourcesIn.class.php @@ -1,25 +1,25 @@ getClasses() as $class) { - if ($class->hasAnnotation('resource')) { - $this->with($new ? $new($class) : $class->newInstance(), $class->getAnnotation('resource') ?? ''); + $p= $package instanceof Package ? $package : new Package($package); + foreach ($p->types() as $type) { + if ($resource= $type->annotation(Resource::class)) { + $this->with($new ? $new($type->class()) : $type->newInstance(), (string)$resource->argument(0)); } } uksort($this->patterns, function($a, $b) { return strlen($b) - strlen($a); }); diff --git a/src/test/php/web/rest/unittest/InvocationsTest.class.php b/src/test/php/web/rest/unittest/InvocationsTest.class.php index 93b8106..d3ad164 100755 --- a/src/test/php/web/rest/unittest/InvocationsTest.class.php +++ b/src/test/php/web/rest/unittest/InvocationsTest.class.php @@ -2,7 +2,7 @@ use lang\{ElementNotFoundException, IllegalStateException}; use test\{Assert, Test}; -use web\rest\unittest\api\Users; +use web\rest\unittest\api\{Users, Cached}; use web\rest\{Interceptor, Response, RestApi}; class InvocationsTest extends RunTest { @@ -49,12 +49,12 @@ public function intercepting_catching_exceptions() { #[Test] public function intercepting_can_access_annotations() { $invocations= function($invocation, $args) use(&$cached) { - $cached= $invocation->target()->annotations()['cached']; + $cached= $invocation->target()->annotations()->type(Cached::class); return $invocation->proceed($args); }; $this->run((new RestApi(new Users()))->intercepting($invocations), 'GET', '/users/1549/avatar'); - Assert::equals(['ttl' => 3600], $cached); + Assert::equals(['ttl' => 3600], $cached->arguments()); } #[Test] diff --git a/src/test/php/web/rest/unittest/ResourcesInTest.class.php b/src/test/php/web/rest/unittest/ResourcesInTest.class.php index 39f225b..75d60cf 100755 --- a/src/test/php/web/rest/unittest/ResourcesInTest.class.php +++ b/src/test/php/web/rest/unittest/ResourcesInTest.class.php @@ -1,6 +1,6 @@ target('get', '/monitoring/status')); }