Skip to content

Commit

Permalink
Merge pull request #291 from koriym/InvokeRequestInterface
Browse files Browse the repository at this point in the history
Invoke request interface
  • Loading branch information
koriym authored Mar 22, 2024
2 parents 4249268 + 04a9614 commit d5e21c0
Show file tree
Hide file tree
Showing 9 changed files with 115 additions and 51 deletions.
10 changes: 8 additions & 2 deletions src/ClassParam.php
Original file line number Diff line number Diff line change
Expand Up @@ -11,6 +11,7 @@
use ReflectionEnum;
use ReflectionNamedType;
use ReflectionParameter;
use UnitEnum;

use function assert;
use function class_exists;
Expand Down Expand Up @@ -103,10 +104,15 @@ private function getProps(string $varName, array $query, InjectorInterface $inje
throw new ParameterException($varName);
}

/** @param class-string $type */
/**
* @param class-string $type
*
* @psalm-suppress MixedArgument
*/
private function enum(string $type, mixed $props, string $varName): mixed
{
$refEnum = new ReflectionEnum($type); // @phpstan-ignore-line
/** @var class-string<UnitEnum> $type */
$refEnum = new ReflectionEnum($type);
assert(enum_exists($type));

if (! $refEnum->isBacked()) {
Expand Down
2 changes: 1 addition & 1 deletion src/HalLinker.php
Original file line number Diff line number Diff line change
Expand Up @@ -55,7 +55,7 @@ private function linkAnnotation(array $body, array $methodAnnotations, Hal $hal)
$uri = uri_template($annotation->href, $body);
$reverseUri = $this->getReverseLink($uri, []);

if (isset($body['_links'][$annotation->rel])) { // @phpstan-ignore-line
if (isset($body['_links'][$annotation->rel])) {
// skip if already difined links in ResourceObject
continue;
}
Expand Down
9 changes: 8 additions & 1 deletion src/HttpResourceObject.php
Original file line number Diff line number Diff line change
Expand Up @@ -26,7 +26,7 @@
* @property-read array<string, string> $body
* @property-read string $view
*/
final class HttpResourceObject extends ResourceObject
final class HttpResourceObject extends ResourceObject implements InvokeRequestInterface
{
/** {@inheritDoc} */
public $body;
Expand Down Expand Up @@ -102,6 +102,13 @@ public function __toString(): string
return $this->response->getContent();
}

public function _invokeRequest(InvokerInterface $invoker, AbstractRequest $request): ResourceObject
{
unset($invoker);

return $this->request($request);
}

public function request(AbstractRequest $request): self
{
$uri = $request->resourceObject->uri;
Expand Down
15 changes: 15 additions & 0 deletions src/InvokeRequestInterface.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,15 @@
<?php

declare(strict_types=1);

namespace BEAR\Resource;

interface InvokeRequestInterface
{
/**
* Invokes a request using the given invoker and request objects.
*
* @return ResourceObject The resulting resource object returned from the request invocation.
*/
public function _invokeRequest(InvokerInterface $invoker, AbstractRequest $request): ResourceObject;
}
39 changes: 2 additions & 37 deletions src/Invoker.php
Original file line number Diff line number Diff line change
Expand Up @@ -3,18 +3,10 @@

namespace BEAR\Resource;

use BEAR\Resource\Exception\BadRequestException;
use Throwable;
use function call_user_func_array;
use function is_callable;
use function ucfirst;

final class Invoker implements InvokerInterface
{
public function __construct(
private readonly NamedParameterInterface $params,
private readonly ExtraMethodInvoker $extraMethod,
private readonly LoggerInterface $logger,
private PhpClassInvoker $classInvoker
) {
}

Expand All @@ -23,33 +15,6 @@ public function __construct(
*/
public function invoke(AbstractRequest $request): ResourceObject
{
if ($request->resourceObject instanceof HttpResourceObject) {
return $request->resourceObject->request($request);
}

$callable = [$request->resourceObject, 'on' . ucfirst($request->method)];
if (! is_callable($callable)) {
// OPTIONS or HEAD
return ($this->extraMethod)($request, $this);
}

$params = $this->params->getParameters($callable, $request->query);
/** @psalm-suppress MixedAssignment */
try {
$response = call_user_func_array($callable, $params);
} catch (Throwable $e) {
if ($e::class === \TypeError::class) {
throw new BadRequestException('Invalid parameter type', Code::BAD_REQUEST, $e);
}
throw $e;
}
if (! $response instanceof ResourceObject) {
$request->resourceObject->body = $response;
$response = $request->resourceObject;
}

($this->logger)($response);

return $response;
return $request->resourceObject->_invokeRequest($this->classInvoker, $request);
}
}
2 changes: 2 additions & 0 deletions src/Module/ResourceClientModule.php
Original file line number Diff line number Diff line change
Expand Up @@ -25,6 +25,7 @@
use BEAR\Resource\NullReverseLinker;
use BEAR\Resource\OptionsMethods;
use BEAR\Resource\OptionsRenderer;
use BEAR\Resource\PhpClassInvoker;
use BEAR\Resource\PrettyJsonRenderer;
use BEAR\Resource\RenderInterface;
use BEAR\Resource\Resource;
Expand Down Expand Up @@ -90,6 +91,7 @@ protected function configure(): void
$this->bind(ReverseLinkerInterface::class)->to(NullReverseLinker::class);
$this->bind(LoggerInterface::class)->to(NullLogger::class);
$this->configureDeprecatedBindings();
$this->bind(PhpClassInvoker::class);
}

/** @psalm-suppress DeprecatedClass */
Expand Down
51 changes: 51 additions & 0 deletions src/PhpClassInvoker.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,51 @@
<?php
// phpcs:ignoreFile

namespace BEAR\Resource;

use BEAR\Resource\Exception\BadRequestException;
use Throwable;
use function call_user_func_array;
use function is_callable;
use function ucfirst;

final class PhpClassInvoker implements InvokerInterface
{
public function __construct(
private NamedParameterInterface $params,
private ExtraMethodInvoker $extraMethod,
private LoggerInterface $logger,
) {
}

/**
* {@inheritdoc}
*/
public function invoke(AbstractRequest $request): ResourceObject
{
$callable = [$request->resourceObject, 'on' . ucfirst($request->method)];
if (! is_callable($callable)) {
// OPTIONS or HEAD
return ($this->extraMethod)($request, $this);
}

$params = $this->params->getParameters($callable, $request->query);
/** @psalm-suppress MixedAssignment */
try {
$response = call_user_func_array($callable, $params);
} catch (Throwable $e) {
if ($e::class === \TypeError::class) {
throw new BadRequestException('Invalid parameter type', Code::BAD_REQUEST, $e);
}
throw $e;
}
if (! $response instanceof ResourceObject) {
$request->resourceObject->body = $response;
$response = $request->resourceObject;
}

($this->logger)($response);

return $response;
}
}
18 changes: 17 additions & 1 deletion src/ResourceObject.php
Original file line number Diff line number Diff line change
Expand Up @@ -32,7 +32,7 @@
* @phpstan-implements ArrayAccess<string, mixed>
* @phpstan-implements IteratorAggregate<(int|string), mixed>
*/
abstract class ResourceObject implements AcceptTransferInterface, ArrayAccess, Countable, IteratorAggregate, JsonSerializable, Stringable
abstract class ResourceObject implements AcceptTransferInterface, ArrayAccess, Countable, IteratorAggregate, JsonSerializable, Stringable, InvokeRequestInterface
{
/**
* @var AbstractUri
Expand Down Expand Up @@ -261,4 +261,20 @@ public function __clone()
{
$this->uri = clone $this->uri;
}

/**
* Invokes a request using the specified invoker and request object.
*
* Requests from resource clients are called and executed by this method. Unconventionally,
* underscores are added to method names to avoid conflicts with method names in classes that inherit from existing ResouceObjects.
*
* @param InvokerInterface $invoker The invoker to use for invoking the request.
* @param AbstractRequest $request The request object to be invoked.
*
* @return ResourceObject The result of invoking the request.
*/
public function _invokeRequest(InvokerInterface $invoker, AbstractRequest $request): ResourceObject
{
return $invoker->invoke($request);
}
}
20 changes: 11 additions & 9 deletions tests/InvokerFactory.php
Original file line number Diff line number Diff line change
Expand Up @@ -11,18 +11,20 @@ final class InvokerFactory
public function __invoke(string $schemaDir = ''): Invoker
{
return new Invoker(
new NamedParameter(
new NamedParamMetas(),
new Injector(),
),
new ExtraMethodInvoker(
new OptionsRenderer(
new OptionsMethods(
$schemaDir,
new PhpClassInvoker(
new NamedParameter(
new NamedParamMetas(),
new Injector(),
),
new ExtraMethodInvoker(
new OptionsRenderer(
new OptionsMethods(
$schemaDir,
),
),
),
new NullLogger(),
),
new NullLogger(),
);
}
}

0 comments on commit d5e21c0

Please sign in to comment.