test(aot): add ThinkPHP middleware and request filter test cases

- Add test case for array_map nested closure and unpacked callable params in ThinkPHP middleware
- Add test case for array_walk_recursive by-ref filter and match throw type cast in ThinkPHP request filter
- Add test case for preg_replace_callback use ref and foreach ref match cast in ThinkPHP route parsing
- Remove XFAIL annotations from Symfony nullable first-class callable test
- Remove XFAIL annotations from Symfony uksort priority fallback test
pull/16/head
韩天峰 2 months ago
parent 78c01ca97b
commit be5e553bf7
  1. 2
      tests/aot/symfony/nullable-first-class-callable.phpt
  2. 2
      tests/aot/symfony/uksort-priority-fallback.phpt
  3. 69
      tests/aot/thinkphp/middleware-array-map-unpack-callable.phpt
  4. 144
      tests/aot/thinkphp/request-filter-walk-recursive-match-throw.phpt
  5. 83
      tests/aot/thinkphp/route-parse-rule-callback-foreach-ref.phpt

@ -1,7 +1,5 @@
--TEST--
Symfony pattern: nullable callable converted to first-class callable
--XFAIL--
Known AOT bug: first-class callable stored in a typed nullable Closure property can crash during shutdown.
--FILE--
<?php

@ -1,7 +1,5 @@
--TEST--
Symfony pattern: uksort with priority fallback comparator
--XFAIL--
Known AOT bug: uksort with captured arrays in a static arrow comparator can crash during shutdown.
--FILE--
<?php

@ -0,0 +1,69 @@
--TEST--
ThinkPHP Middleware pattern: array_map nested closure and unpacked callable params
--FILE--
<?php
class ThinkMiddlewareLike
{
private array $queue = [];
public function add(array|Closure|string $middleware): void
{
$this->queue[] = $this->buildMiddleware($middleware);
}
private function buildMiddleware(array|Closure|string $middleware): array
{
if (is_array($middleware)) {
[$middleware, $params] = $middleware;
}
if ($middleware instanceof Closure) {
return [$middleware, $params ?? []];
}
return [[$middleware, 'handle'], $params ?? []];
}
public function pipeline(): array
{
return array_map(function ($middleware) {
return function ($request, $next) use ($middleware) {
[$call, $params] = $middleware;
$response = call_user_func($call, $request, $next, ...$params);
if (!$response instanceof ThinkResponseLike) {
throw new LogicException('The middleware must return Response instance');
}
return $response;
};
}, $this->queue);
}
}
class ThinkResponseLike
{
public function __construct(public string $body)
{
}
}
function main(): void
{
$middleware = new ThinkMiddlewareLike();
$middleware->add([
function ($request, $next, string $prefix, string $suffix): ThinkResponseLike {
$response = $next($prefix . $request);
$response->body .= $suffix;
return $response;
},
['[', ']'],
]);
$pipes = $middleware->pipeline();
$response = $pipes[0]('thinkphp', fn ($request) => new ThinkResponseLike($request . ':next'));
var_dump($response->body);
}
?>
--EXPECT--
string(15) "[thinkphp:next]"

@ -0,0 +1,144 @@
--TEST--
ThinkPHP Request pattern: array_walk_recursive by-ref filter and match throw type cast
--XFAIL--
Known AOT bug: mixed parameter narrowed to array after array_walk_recursive path and reused for scalar input.
--FILE--
<?php
class ThinkRequestFilterLike
{
private mixed $filter = null;
public function filter(mixed $filter = null): mixed
{
if ($filter === null) {
return $this->filter;
}
$this->filter = $filter;
return $this;
}
public function input(array $data, string|bool $name = '', mixed $default = null, string|array|null $filter = ''): mixed
{
if (false === $name) {
return $data;
}
$name = (string) $name;
if ('' !== $name) {
if (str_contains($name, '/')) {
[$name, $type] = explode('/', $name);
}
$data = $this->getData($data, $name);
}
return $this->filterData($data, $filter, $name, $default, $type ?? '');
}
private function filterData(mixed $data, mixed $filter, string $name, mixed $default, string $type): mixed
{
if ($data === null) {
return $default;
}
$filter = $this->getFilter($filter, $default);
if (is_array($data)) {
array_walk_recursive($data, [$this, 'filterValue'], $filter);
} else {
$this->filterValue($data, $name, $filter);
}
if ($type) {
$this->typeCast($data, $type);
}
return $data;
}
private function getData(array $data, string $name, mixed $default = null): mixed
{
foreach (explode('.', $name) as $val) {
if (isset($data[$val])) {
$data = $data[$val];
} else {
return $default;
}
}
return $data;
}
private function getFilter(mixed $filter, mixed $default): array
{
if ($filter === null) {
$filter = [];
} else {
$filter = $filter ?: $this->filter;
if (is_string($filter) && !str_contains($filter, '/')) {
$filter = explode(',', $filter);
} else {
$filter = (array) $filter;
}
}
$filter[] = $default;
return $filter;
}
public function filterValue(mixed &$value, mixed $key, array $filters): void
{
$default = array_pop($filters);
foreach ($filters as $filter) {
if (is_callable($filter)) {
if ($value === null) {
continue;
}
$value = call_user_func($filter, $value);
} elseif (is_scalar($value) && is_string($filter) && str_contains($filter, '/')) {
if (!preg_match($filter, (string) $value)) {
$value = $default;
break;
}
}
}
}
private function typeCast(mixed &$data, string $type): void
{
$data = match (strtolower($type)) {
'a' => (array) $data,
'b' => (bool) $data,
'd' => (int) $data,
'f' => (float) $data,
's' => is_scalar($data) ? (string) $data : throw new InvalidArgumentException('variable type error:' . gettype($data)),
default => $data,
};
}
}
function main(): void
{
$request = new ThinkRequestFilterLike();
$request->filter(fn ($value) => is_string($value) ? trim($value) : $value);
var_dump($request->input(['user' => ['name' => ' thinkphp ', 'role' => ' admin ']], 'user'));
var_dump($request->input(['id' => '42'], 'id/d'));
try {
$request->input(['user' => ['name' => 'thinkphp']], 'user/s');
} catch (InvalidArgumentException $e) {
echo "type error\n";
}
}
?>
--EXPECT--
array(2) {
["name"]=>
string(8) "thinkphp"
["role"]=>
string(5) "admin"
}
int(42)
type error

@ -0,0 +1,83 @@
--TEST--
ThinkPHP Route pattern: preg_replace_callback use ref and foreach ref match cast
--FILE--
<?php
class ThinkRouteRuleLike
{
private array $pattern = [
'id' => 'int',
'price' => 'float',
];
public array $vars = [];
public function parseRule(string $rule, string $route, string $url, array $matches = []): string
{
$extraParams = true;
$search = $replace = [];
$depr = '/';
foreach ($matches as $key => $value) {
$search[] = '<' . $key . '>';
$replace[] = $value;
$search[] = '{' . $key . '}';
$replace[] = $value;
$search[] = ':' . $key;
$replace[] = $value;
if (str_contains($value, $depr)) {
$extraParams = false;
}
}
$route = str_replace($search, $replace, $route);
if ($extraParams) {
$count = substr_count($rule, '/');
$extra = array_slice(explode('|', $url), $count + 1);
$this->parseUrlParams(implode('/', $extra), $matches);
}
foreach ($matches as $key => &$val) {
if (isset($this->pattern[$key]) && in_array($this->pattern[$key], ['\d+', 'int', 'float'], true)) {
$val = match ($this->pattern[$key]) {
'int', '\d+' => (int) $val,
'float' => (float) $val,
default => $val,
};
} elseif (in_array($key, ['__module__', '__controller__', '__action__'], true)) {
unset($matches[$key]);
}
}
unset($val);
$this->vars = $matches;
return $route;
}
private function parseUrlParams(string $url, array &$var = []): void
{
if ($url) {
preg_replace_callback('/(\w+)\/([^\/]+)/', function ($match) use (&$var) {
$var[$match[1]] = strip_tags($match[2]);
}, $url);
}
}
}
function main(): void
{
$rule = new ThinkRouteRuleLike();
var_dump($rule->parseRule('shop/<id>', 'product/:id', 'shop|42|price/12.5/__action__/show', ['id' => '42']));
var_dump($rule->vars);
}
?>
--EXPECT--
string(10) "product/42"
array(2) {
["id"]=>
int(42)
["price"]=>
float(12.5)
}
Loading…
Cancel
Save