Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
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": "^11.0 | ^10.0 | ^9.0 | ^8.0 | ^7.0",
"xp-framework/http": "^10.0 | ^9.0 | ^8.0 | ^7.0",
"xp-framework/reflection": "^2.0",
"xp-forge/web": "^3.0 | ^2.9",
"xp-forge/json": "^5.0 | ^4.0",
"php": ">=7.0.0"
Expand Down
38 changes: 20 additions & 18 deletions src/main/php/web/frontend/Delegate.class.php
Original file line number Diff line number Diff line change
@@ -1,8 +1,8 @@
<?php namespace web\frontend;

use lang\IllegalArgumentException;
use lang\reflection\Method;
use lang\{IllegalArgumentException, Reflection};
use web\frontend\View;
use lang\reflect\Method;

class Delegate {
private static $SOURCES;
Expand All @@ -25,11 +25,11 @@ static function __static() {
* Creates a new delegate
*
* @param object $instance
* @param string|lang.reflect.Method $method
* @param string|lang.reflection.Method $method
*/
public function __construct($instance, $method) {
$this->instance= $instance;
$this->method= $method instanceof Method ? $method : typeof($instance)->getMethod($method);
$this->method= $method instanceof Method ? $method : Reflection::type($instance)->method($method);
}

/** @return string */
Expand All @@ -40,7 +40,7 @@ public function group() {

/** @return string */
public function name() {
return nameof($this->instance).'::'.$this->method->getName();
return nameof($this->instance).'::'.$this->method->name();
}

/**
Expand All @@ -52,26 +52,28 @@ public function name() {
public function parameters() {
if (null === $this->parameters) {
$this->parameters= [];
foreach ($this->method->getParameters() as $param) {
if ($annotations= $param->getAnnotations()) {
foreach ($annotations as $from => $value) {
$source= self::$SOURCES[$from] ?? self::$SOURCES['default'];
}
foreach ($this->method->parameters() as $param) {

// Check for parameter annotations...
foreach ($param->annotations() as $annotation) {
$source= self::$SOURCES[$annotation->name()] ?? self::$SOURCES['default'];
$name= $annotation->argument(0) ?? $param->name();

$name= null === $value ? $param->getName() : $value;
if ($param->isOptional()) {
$default= $param->getDefaultValue();
$this->parameters[$name]= function($req, $name) use($source, $default) {
return $source($req, $name) ?? $default;
if ($param->optional()) {
$this->parameters[$name]= function($req, $name) use($source, $param) {
return $source($req, $name) ?? $param->default();
};
} else {
$this->parameters[$name]= $source;
}
} else {
$this->parameters[$param->getName()]= self::$SOURCES['segment'];
continue 2;
}

// ...falling back to selecting the parameter from the segments
$this->parameters[$param->name()]= self::$SOURCES['segment'];
}
}

return $this->parameters;
}

Expand All @@ -80,7 +82,7 @@ public function parameters() {
*
* @param var[] $args
* @return web.frontend.View
* @throws lang.reflect.TargetInvocationException
* @throws lang.reflection.TargetException
*/
public function invoke($args) {
$result= $this->method->invoke($this->instance, $args);
Expand Down
15 changes: 7 additions & 8 deletions src/main/php/web/frontend/Delegates.class.php
Original file line number Diff line number Diff line change
@@ -1,10 +1,8 @@
<?php namespace web\frontend;

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

/**
* Matches request and routes to correct delegate
*/
/** Matches request and routes to correct delegate */
class Delegates {
public $patterns= [];

Expand All @@ -22,15 +20,16 @@ public function with($instance, $base= '/') {
}

$base= rtrim($base, '/');
foreach (typeof($instance)->getMethods() as $method) {
$name= $method->getName();
foreach ($method->getAnnotations() as $verb => $segment) {
foreach (Reflection::type($instance)->methods() as $method) {
$name= $method->name();
foreach ($method->annotations() as $annotation) {
$segment= $annotation->argument(0);
$pattern= preg_replace(
['/\{([^:}]+):([^}]+)\}/', '/\{([^}]+)\}/'],
['(?<$1>$2)', '(?<$1>[^/]+)'],
$base.('/' === $segment || null === $segment ? '/?' : $segment)
);
$this->patterns['#'.$verb.$pattern.'$#']= new Delegate($instance, $method);
$this->patterns['#'.$annotation->name().$pattern.'$#']= new Delegate($instance, $method);
}
}
return $this;
Expand Down
4 changes: 2 additions & 2 deletions src/main/php/web/frontend/Frontend.class.php
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
<?php namespace web\frontend;

use lang\reflect\TargetInvocationException;
use lang\reflection\TargetException;
use web\{Error, Handler, Request};

/**
Expand Down Expand Up @@ -98,7 +98,7 @@ private function view($req, $res, $delegate, $matches= []) {
}

return $delegate->invoke($args);
} catch (TargetInvocationException $e) {
} catch (TargetException $e) {
return $this->errors()->handle($e->getCause());
}
}
Expand Down
20 changes: 11 additions & 9 deletions src/main/php/web/frontend/HandlersIn.class.php
Original file line number Diff line number Diff line change
@@ -1,26 +1,28 @@
<?php namespace web\frontend;

use lang\reflect\Package;
use lang\reflection\Package;

/**
* Creates routing based on classes annotated with `@handler` in a
* given package.
* Creates routing based on classes annotated with the `Handler` annotation
* in a given package.
*
* @test xp://web.frontend.unittest.HandlersInTest
* @test web.frontend.unittest.HandlersInTest
*/
class HandlersIn extends Delegates {

/**
* Creates this delegates instance
*
* @param lang.reflect.Package|string $package
* @param lang.reflection.Package|string $package
* @param function(lang.XPClass): object $new Optional function to create instances
*/
public function __construct($package, $new= null) {
$p= $package instanceof Package ? $package : Package::forName($package);
foreach ($p->getClasses() as $class) {
if ($class->hasAnnotation('handler')) {
$this->with($new ? $new($class) : $class->newInstance(), (string)$class->getAnnotation('handler'));
$p= $package instanceof Package ? $package : new Package($package);
foreach ($p->types() as $type) {
if ($handler= $type->annotation(Handler::class)) {
$this->with($new ? $new($type) : $type->newInstance(), (string)$handler->argument(0));
} else {
throw new \lang\IllegalStateException('Not a handler: '.$type->name());
}
}
uksort($this->patterns, function($a, $b) { return strlen($b) - strlen($a); });
Expand Down
16 changes: 9 additions & 7 deletions src/main/php/web/frontend/MethodsIn.class.php
Original file line number Diff line number Diff line change
@@ -1,20 +1,22 @@
<?php namespace web\frontend;

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

/**
* Creates routing based on a given instance
*/
/** Creates routing based on a given instance */
class MethodsIn extends Delegates {

/** @param object $instance */
public function __construct($instance) {
$type= typeof($instance);
if (!is_object($instance)) {
throw new IllegalArgumentException('Expected an object, have '.$type);
throw new IllegalArgumentException('Expected an object, have '.typeof($instance));
}

$this->with($instance, $type->hasAnnotation('handler') ? (string)$type->getAnnotation('handler') : '/');
$type= Reflection::type($instance);
if ($handler= $type->annotation(Handler::class)) {
$this->with($instance, (string)$handler->argument(0));
} else {
$this->with($instance, '/');
}
uksort($this->patterns, function($a, $b) { return strlen($b) - strlen($a); });
}
}
12 changes: 6 additions & 6 deletions src/test/php/web/frontend/unittest/HandlersInTest.class.php
Original file line number Diff line number Diff line change
@@ -1,25 +1,25 @@
<?php namespace web\frontend\unittest;

use lang\reflect\Package;
use test\Assert;
use test\{Test, TestCase};
use lang\reflection\Package;
use test\{Assert, Test};
use web\frontend\HandlersIn;

class HandlersInTest {
const PACKAGE= 'web.frontend.unittest.actions';

#[Test]
public function can_create_with_string() {
new HandlersIn('web.frontend.unittest.actions');
new HandlersIn(self::PACKAGE);
}

#[Test]
public function can_create_with_package() {
new HandlersIn(Package::forName('web.frontend.unittest.actions'));
new HandlersIn(new Package(self::PACKAGE));
}

#[Test]
public function patterns_sorted_by_length() {
$delegates= new HandlersIn('web.frontend.unittest.actions');
$delegates= new HandlersIn(self::PACKAGE);
Assert::equals(
[
'#get/blogs/(?<category>[^/]+)/(?<id>[0-9]+)$#',
Expand Down