dokuwiki/inc/Remote/ApiCall.php

180 lines
4.2 KiB
PHP

<?php
namespace dokuwiki\Remote;
use dokuwiki\Remote\OpenApiDoc\DocBlockMethod;
use InvalidArgumentException;
use ReflectionException;
use ReflectionFunction;
use ReflectionMethod;
use RuntimeException;
class ApiCall
{
/** @var callable The method to be called for this endpoint */
protected $method;
/** @var bool Whether this call can be called without authentication */
protected bool $isPublic = false;
/** @var string The category this call belongs to */
protected string $category;
/** @var DocBlockMethod The meta data of this call as parsed from its doc block */
protected $docs;
/**
* Make the given method available as an API call
*
* @param string|array $method Either [object,'method'] or 'function'
* @param string $category The category this call belongs to
*/
public function __construct($method, $category = '')
{
if (!is_callable($method)) {
throw new InvalidArgumentException('Method is not callable');
}
$this->method = $method;
$this->category = $category;
}
/**
* Call the method
*
* Important: access/authentication checks need to be done before calling this!
*
* @param array $args
* @return mixed
*/
public function __invoke($args)
{
if (!array_is_list($args)) {
$args = $this->namedArgsToPositional($args);
}
return call_user_func_array($this->method, $args);
}
/**
* Access the method documentation
*
* This lazy loads the docs only when needed
*
* @return DocBlockMethod
*/
public function getDocs()
{
if ($this->docs === null) {
try {
if (is_array($this->method)) {
$reflect = new ReflectionMethod($this->method[0], $this->method[1]);
} else {
$reflect = new ReflectionFunction($this->method);
}
$this->docs = new DocBlockMethod($reflect);
} catch (ReflectionException $e) {
throw new RuntimeException('Failed to parse API method documentation', 0, $e);
}
}
return $this->docs;
}
/**
* Is this a public method?
*
* Public methods can be called without authentication
*
* @return bool
*/
public function isPublic()
{
return $this->isPublic;
}
/**
* Set the public flag
*
* @param bool $isPublic
* @return $this
*/
public function setPublic(bool $isPublic = true)
{
$this->isPublic = $isPublic;
return $this;
}
/**
* Get information about the argument of this call
*
* @return array
*/
public function getArgs()
{
return $this->getDocs()->getParameters();
}
/**
* Get information about the return value of this call
*
* @return array
*/
public function getReturn()
{
return $this->getDocs()->getReturn();
}
/**
* Get the summary of this call
*
* @return string
*/
public function getSummary()
{
return $this->getDocs()->getSummary();
}
/**
* Get the description of this call
*
* @return string
*/
public function getDescription()
{
return $this->getDocs()->getDescription();
}
/**
* Get the category of this call
*
* @return string
*/
public function getCategory()
{
return $this->category;
}
/**
* Converts named arguments to positional arguments
*
* @fixme with PHP 8 we can use named arguments directly using the spread operator
* @param array $params
* @return array
*/
protected function namedArgsToPositional($params)
{
$args = [];
foreach ($this->getDocs()->getParameters() as $arg => $arginfo) {
if (isset($params[$arg])) {
$args[] = $params[$arg];
} elseif ($arginfo['optional'] && array_key_exists('default', $arginfo)) {
$args[] = $arginfo['default'];
} else {
throw new InvalidArgumentException("Missing argument $arg");
}
}
return $args;
}
}